Skip to content

Commit

Permalink
fix: add support for implements within classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Yohanan Radchenko committed Jun 7, 2022
1 parent 98604dc commit 9e38d80
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 29 deletions.
38 changes: 38 additions & 0 deletions src/__tests__/__snapshots__/classes.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should handle class extends 1`] = `
"declare class extension {
getString(): string;
}
declare class extender mixins extension {
getNumber(): number;
}
"
`;

exports[`should handle class implements 1`] = `
"declare interface implementation {
getString(): string;
}
declare class implementor implements implementation {
getString(): string;
}
"
`;

exports[`should handle class implements and extends 1`] = `
"declare interface implementation1 {
getString(): string;
}
declare interface implementation2 {
getNumber(): number;
}
declare class extension {}
declare class implementor
mixins extension
implements implementation1, implementation2
{
getString(): string;
getNumber(): number;
}
"
`;

exports[`should handle static methods ES6 classes 1`] = `
"declare class Subscribable<T> {}
declare class Operator<T, R> {}
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/__snapshots__/namespaces.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ declare class A$B$D<S> {}
declare var npm$namespace$A$B$C: {|
N: typeof A$B$C$N,
|};
declare class A$B$C$N<A> mixins A$B$D<A>, A$B$S<A> {
declare class A$B$C$N<A> mixins A$B$D<A> implements A$B$S<A> {
a: string;
}
"
Expand Down
47 changes: 47 additions & 0 deletions src/__tests__/classes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,50 @@ it("should handle static methods ES6 classes", () => {
expect(beautify(result)).toMatchSnapshot();
expect(result).toBeValidFlowTypeDeclarations();
});

it("should handle class extends", () => {
const ts = `
class extension {
getString(): string
}
class extender extends extension {
getNumber(): number
}
`;
const result = compiler.compileDefinitionString(ts, { quiet: true });
expect(beautify(result)).toMatchSnapshot();
expect(result).toBeValidFlowTypeDeclarations();
});

it("should handle class implements", () => {
const ts = `
interface implementation {
getString(): string
}
class implementor implements implementation {
getString(): string
}
`;
const result = compiler.compileDefinitionString(ts, { quiet: true });
expect(beautify(result)).toMatchSnapshot();
expect(result).toBeValidFlowTypeDeclarations();
});

it("should handle class implements and extends", () => {
const ts = `
interface implementation1 {
getString(): string
}
interface implementation2 {
getNumber(): number
}
class extension {}
class implementor extends extension implements implementation1, implementation2 {
getString(): string
getNumber(): number
}
`;
const result = compiler.compileDefinitionString(ts, { quiet: true });
expect(beautify(result)).toMatchSnapshot();
expect(result).toBeValidFlowTypeDeclarations();
});
73 changes: 45 additions & 28 deletions src/printers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,28 +129,38 @@ const interfaceRecordType = (
}
};

const classHeritageClause = withEnv<
{ classHeritage?: boolean },
[ts.ExpressionWithTypeArguments],
string
>((env, type) => {
let ret: string;
env.classHeritage = true;
// TODO: refactor this
const symbol = checker.current.getSymbolAtLocation(type.expression);
printers.node.fixDefaultTypeArguments(symbol, type);
if (ts.isIdentifier(type.expression) && symbol) {
ret =
printers.node.getFullyQualifiedPropertyAccessExpression(
symbol,
type.expression,
) + printers.common.generics(type.typeArguments);
} else {
ret = printers.node.printType(type);
}
env.classHeritage = false;
return ret;
});
const classHeritageClause = (
classMixins: string[],
classImplements: string[],
) =>
withEnv<{ classHeritage?: boolean }, [ts.ExpressionWithTypeArguments], void>(
(env, type) => {
env.classHeritage = true;
// TODO: refactor this
const symbol = checker.current.getSymbolAtLocation(type.expression);
printers.node.fixDefaultTypeArguments(symbol, type);
if (ts.isIdentifier(type.expression) && symbol) {
const value =
printers.node.getFullyQualifiedPropertyAccessExpression(
symbol,
type.expression,
) + printers.common.generics(type.typeArguments);
if (
symbol.declarations.some(
declaration =>
declaration.kind === ts.SyntaxKind.InterfaceDeclaration,
)
) {
classImplements.push(value);
} else {
classMixins.push(value);
}
} else {
classMixins.push(printers.node.printType(type));
}
env.classHeritage = false;
},
);

const interfaceHeritageClause = (type: ts.ExpressionWithTypeArguments) => {
// TODO: refactor this
Expand Down Expand Up @@ -305,12 +315,19 @@ export const classDeclaration = <T>(

// If the class is extending something
if (node.heritageClauses) {
heritage = node.heritageClauses
.map(clause => {
return clause.types.map(classHeritageClause).join(", ");
})
.join(", ");
heritage = heritage.length > 0 ? `mixins ${heritage}` : "";
const classMixins = [];
const classImplements = [];
node.heritageClauses.forEach(clause => {
clause.types.forEach(classHeritageClause(classMixins, classImplements));
});
const mixinsMessage =
classMixins.length > 0 ? `mixins ${classMixins.join(",")}` : "";
const classImplementsMessage =
classImplements.length > 0
? ` implements ${classImplements.join(",")}`
: "";
heritage += mixinsMessage;
heritage += classImplementsMessage;
}

const str = `declare ${printers.relationships.exporter(
Expand Down

0 comments on commit 9e38d80

Please sign in to comment.