From c999d49cc2802a2977412073c92c3fe68bf18bf6 Mon Sep 17 00:00:00 2001 From: Leon Xu Date: Tue, 11 Jun 2024 02:29:22 +0000 Subject: [PATCH] Parse Member and TypeCast expressions in super class types 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. --- .../__tests__/flowToFlowDef-test.js | 11 +++ .../flow-api-translator/src/flowToFlowDef.js | 95 ++++++++++++++++--- 2 files changed, 91 insertions(+), 15 deletions(-) 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..61214a0a484 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,17 @@ 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 {}`, + ); + }); }); 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..cbe02759076 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,6 +978,35 @@ function convertClassDeclaration( ]; } +function convertExpressionToIdentifier( + node: Expression, + context: TranslationContext, +): DetachedNode | DetachedNode { + if (node.type === 'Identifier') { + return t.Identifier({name: node.name}); + } else if ( + node.type === 'MemberExpression' && + node.property.type === 'Identifier' && + node.object.type !== 'Super' + ) { + const id = t.Identifier({name: node.property.name}); + return t.QualifiedTypeIdentifier({ + qualification: convertExpressionToIdentifier( + // $FlowIssue[incompatible-call] conditional already checks for non-Super type + node.object, + context, + ), + id, + }); + } + + throw translationError( + node, + `Expected ${node.type} to be an Identifier or Member with Identifier property, non-Super object.`, + context, + ); +} + function convertSuperClass( superClass: ?Expression, superTypeParameters: ?TypeParameterInstantiation, @@ -986,23 +1016,58 @@ function convertSuperClass( return EMPTY_TRANSLATION_RESULT; } - if (superClass.type !== 'Identifier') { - throw translationError( - superClass, - `SuperClass: Non identifier super type of "${superClass.type}" not supported`, - context, - ); - } const [resultTypeParams, typeParamsDeps] = convertTypeParameterInstantiationOrNull(superTypeParameters, context); - const superDeps = analyzeTypeDependencies(superClass, context); - return [ - t.InterfaceExtends({ - id: asDetachedNode(superClass), - typeParameters: resultTypeParams, - }), - [...typeParamsDeps, ...superDeps], - ]; + + switch (superClass.type) { + case 'Identifier': { + const superDeps = analyzeTypeDependencies(superClass, context); + return [ + t.InterfaceExtends({ + id: asDetachedNode(superClass), + typeParameters: resultTypeParams, + }), + [...typeParamsDeps, ...superDeps], + ]; + } + case 'MemberExpression': { + const superDeps = analyzeTypeDependencies(superClass, context); + return [ + t.InterfaceExtends({ + id: convertExpressionToIdentifier(superClass, context), + typeParameters: resultTypeParams, + }), + [...typeParamsDeps, ...superDeps], + ]; + } + case 'TypeCastExpression': { + const typeAnnotation = superClass.typeAnnotation.typeAnnotation; + const superDeps = analyzeTypeDependencies(typeAnnotation, context); + + if (typeAnnotation.type === 'GenericTypeAnnotation') { + return [ + t.InterfaceExtends({ + id: asDetachedNode(typeAnnotation.id), + typeParameters: resultTypeParams, + }), + [...typeParamsDeps, ...superDeps], + ]; + } + + throw translationError( + superClass, + `SuperClass: Non-identifier 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(