Skip to content

Commit

Permalink
Parse Member and TypeCast expressions in super class types (#1408)
Browse files Browse the repository at this point in the history
Summary:
Previously, member expressions in parent classes like `class A extends Foo.Bar`, and type casts such as `class A extends (Foo: Bar)`, were not parseable by flow-api-translator. This change adds support.

Pull Request resolved: #1408

Test Plan: Previously unparseable files in `react-native` are now parseable

Reviewed By: pieterv

Differential Revision: D58577282

Pulled By: huntie

fbshipit-source-id: 437f43841c2bd7eed7f9ca412984203e598ae1a9
  • Loading branch information
xleoooo authored and facebook-github-bot committed Jun 25, 2024
1 parent 3153f50 commit 14d3201
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,23 @@ describe('flowToFlowDef', () => {
}`,
);
});
it('extends member expression', async () => {
await expectTranslateUnchanged(
`declare export class Foo<T> extends Bar.TClass<T> {}`,
);
});
it('extends type cast expression', async () => {
await expectTranslate(
`export class Foo<T> extends (Bar: X) {}`,
`declare export class Foo<T> extends X {}`,
);
});
it('extends type cast typeof expression', async () => {
await expectTranslate(
`export class Foo<T> extends (Bar: typeof X) {}`,
`declare export class Foo<T> extends X {}`,
);
});
});
describe('Expression', () => {
async function expectTranslateExpression(
Expand Down
111 changes: 97 additions & 14 deletions tools/hermes-parser/js/flow-api-translator/src/flowToFlowDef.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import type {
ObjectTypeAnnotation,
ObjectTypeProperty,
OpaqueType,
QualifiedTypeIdentifier,
Program,
RestElement,
Statement,
Expand Down Expand Up @@ -977,34 +978,116 @@ function convertClassDeclaration(
];
}

function convertSuperClass(
superClass: ?Expression,
superTypeParameters: ?TypeParameterInstantiation,
function convertExpressionToIdentifier(
node: Expression,
context: TranslationContext,
): TranslatedResultOrNull<InterfaceExtends> {
if (superClass == null) {
return EMPTY_TRANSLATION_RESULT;
): DetachedNode<Identifier> | DetachedNode<QualifiedTypeIdentifier> {
if (node.type === 'Identifier') {
return t.Identifier({name: node.name});
}

if (superClass.type !== 'Identifier') {
throw translationError(
superClass,
`SuperClass: Non identifier super type of "${superClass.type}" not supported`,
context,
);
if (node.type === 'MemberExpression') {
const {property, object} = node;
if (property.type === 'Identifier' && object.type !== 'Super') {
return t.QualifiedTypeIdentifier({
qualification: convertExpressionToIdentifier(object, context),
id: t.Identifier({name: property.name}),
});
}
}

throw translationError(
node,
`Expected ${node.type} to be an Identifier or Member with Identifier property, non-Super object.`,
context,
);
}

function convertSuperClassHelper(
detachedId: DetachedNode<Identifier | QualifiedTypeIdentifier>,
nodeForDependencies: ESNode,
superTypeParameters: ?TypeParameterInstantiation,
context: TranslationContext,
): TranslatedResultOrNull<InterfaceExtends> {
const [resultTypeParams, typeParamsDeps] =
convertTypeParameterInstantiationOrNull(superTypeParameters, context);
const superDeps = analyzeTypeDependencies(superClass, context);
const superDeps = analyzeTypeDependencies(nodeForDependencies, context);
return [
t.InterfaceExtends({
id: asDetachedNode(superClass),
id: detachedId,
typeParameters: resultTypeParams,
}),
[...typeParamsDeps, ...superDeps],
];
}

function convertSuperClass(
superClass: ?Expression,
superTypeParameters: ?TypeParameterInstantiation,
context: TranslationContext,
): TranslatedResultOrNull<InterfaceExtends> {
if (superClass == null) {
return EMPTY_TRANSLATION_RESULT;
}

switch (superClass.type) {
case 'Identifier': {
return convertSuperClassHelper(
asDetachedNode(superClass),
superClass,
superTypeParameters,
context,
);
}
case 'MemberExpression': {
return convertSuperClassHelper(
convertExpressionToIdentifier(superClass, context),
superClass,
superTypeParameters,
context,
);
}
case 'TypeCastExpression': {
const typeAnnotation = superClass.typeAnnotation.typeAnnotation;

if (typeAnnotation.type === 'GenericTypeAnnotation') {
return convertSuperClassHelper(
asDetachedNode(typeAnnotation.id),
typeAnnotation,
superTypeParameters,
context,
);
}

if (typeAnnotation.type === 'TypeofTypeAnnotation') {
const typeofArg = typeAnnotation.argument;

if (typeofArg.type === 'Identifier') {
return convertSuperClassHelper(
asDetachedNode(typeofArg),
typeofArg,
typeAnnotation.typeArguments,
context,
);
}
}

throw translationError(
superClass,
`SuperClass: Typecast super type of "${typeAnnotation.type}" not supported`,
context,
);
}
default: {
throw translationError(
superClass,
`SuperClass: Non identifier super type of "${superClass.type}" not supported`,
context,
);
}
}
}

function convertClassBody(
body: ClassBody,
context: TranslationContext,
Expand Down

0 comments on commit 14d3201

Please sign in to comment.