diff --git a/tools/hermes-parser/js/flow-api-translator/__tests__/flowToFlowDef-test.js b/tools/hermes-parser/js/flow-api-translator/__tests__/flowToFlowDef-test.js index 648bd1d36e6..afe7a89997d 100644 --- a/tools/hermes-parser/js/flow-api-translator/__tests__/flowToFlowDef-test.js +++ b/tools/hermes-parser/js/flow-api-translator/__tests__/flowToFlowDef-test.js @@ -510,6 +510,23 @@ describe('flowToFlowDef', () => { }`, ); }); + it('extends member expression', async () => { + await expectTranslateUnchanged( + `declare export class Foo extends Bar.TClass {}`, + ); + }); + it('extends type cast expression', async () => { + await expectTranslate( + `export class Foo extends (Bar: X) {}`, + `declare export class Foo extends X {}`, + ); + }); + it('extends type cast typeof expression', async () => { + await expectTranslate( + `export class Foo extends (Bar: typeof X) {}`, + `declare export class Foo extends X {}`, + ); + }); }); describe('Expression', () => { async function expectTranslateExpression( diff --git a/tools/hermes-parser/js/flow-api-translator/src/flowToFlowDef.js b/tools/hermes-parser/js/flow-api-translator/src/flowToFlowDef.js index e2e04a845e5..6d7a05d9317 100644 --- a/tools/hermes-parser/js/flow-api-translator/src/flowToFlowDef.js +++ b/tools/hermes-parser/js/flow-api-translator/src/flowToFlowDef.js @@ -49,6 +49,7 @@ import type { ObjectTypeAnnotation, ObjectTypeProperty, OpaqueType, + QualifiedTypeIdentifier, Program, RestElement, Statement, @@ -977,34 +978,116 @@ function convertClassDeclaration( ]; } -function convertSuperClass( - superClass: ?Expression, - superTypeParameters: ?TypeParameterInstantiation, +function convertExpressionToIdentifier( + node: Expression, context: TranslationContext, -): TranslatedResultOrNull { - if (superClass == null) { - return EMPTY_TRANSLATION_RESULT; +): DetachedNode | DetachedNode { + 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, + nodeForDependencies: ESNode, + superTypeParameters: ?TypeParameterInstantiation, + context: TranslationContext, +): TranslatedResultOrNull { 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 { + 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,