Skip to content

Commit

Permalink
Parse Member and TypeCast expressions in super class types
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: typeof Bar)`,
were not parseable by flow-api-translator. This change adds support.
  • Loading branch information
xleoooo committed Jun 19, 2024
1 parent f93242b commit e26c61b
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 16 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
120 changes: 104 additions & 16 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,6 +978,31 @@ function convertClassDeclaration(
];
}

function convertExpressionToIdentifier(
node: Expression,
context: TranslationContext,
): DetachedNode<Identifier> | DetachedNode<QualifiedTypeIdentifier> {
if (node.type === 'Identifier') {
return t.Identifier({name: node.name});
}

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 convertSuperClass(
superClass: ?Expression,
superTypeParameters: ?TypeParameterInstantiation,
Expand All @@ -986,23 +1012,85 @@ function convertSuperClass(
return EMPTY_TRANSLATION_RESULT;
}

if (superClass.type !== 'Identifier') {
throw translationError(
superClass,
`SuperClass: Non identifier super type of "${superClass.type}" not supported`,
context,
);
switch (superClass.type) {
case 'Identifier': {
const [resultTypeParams, typeParamsDeps] =
convertTypeParameterInstantiationOrNull(superTypeParameters, context);

const superDeps = analyzeTypeDependencies(superClass, context);
return [
t.InterfaceExtends({
id: asDetachedNode(superClass),
typeParameters: resultTypeParams,
}),
[...typeParamsDeps, ...superDeps],
];
}
case 'MemberExpression': {
const [resultTypeParams, typeParamsDeps] =
convertTypeParameterInstantiationOrNull(superTypeParameters, context);

const superDeps = analyzeTypeDependencies(superClass, context);
return [
t.InterfaceExtends({
id: convertExpressionToIdentifier(superClass, context),
typeParameters: resultTypeParams,
}),
[...typeParamsDeps, ...superDeps],
];
}
case 'TypeCastExpression': {
const typeAnnotation = superClass.typeAnnotation.typeAnnotation;

if (typeAnnotation.type === 'GenericTypeAnnotation') {
const superDeps = analyzeTypeDependencies(typeAnnotation, context);
const [resultTypeParams, typeParamsDeps] =
convertTypeParameterInstantiationOrNull(superTypeParameters, context);

return [
t.InterfaceExtends({
id: asDetachedNode(typeAnnotation.id),
typeParameters: resultTypeParams,
}),
[...typeParamsDeps, ...superDeps],
];
}

if (typeAnnotation.type === 'TypeofTypeAnnotation') {
const typeofArg = typeAnnotation.argument;
const [resultTypeParams, typeParamsDeps] =
convertTypeParameterInstantiationOrNull(
typeAnnotation.typeArguments,
context,
);

const superDeps = analyzeTypeDependencies(typeofArg, context);

if (typeofArg.type === 'Identifier') {
return [
t.InterfaceExtends({
id: typeofArg,
typeParameters: resultTypeParams,
}),
[...typeParamsDeps, ...superDeps],
];
}
}

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,
);
}
}
const [resultTypeParams, typeParamsDeps] =
convertTypeParameterInstantiationOrNull(superTypeParameters, context);
const superDeps = analyzeTypeDependencies(superClass, context);
return [
t.InterfaceExtends({
id: asDetachedNode(superClass),
typeParameters: resultTypeParams,
}),
[...typeParamsDeps, ...superDeps],
];
}

function convertClassBody(
Expand Down

0 comments on commit e26c61b

Please sign in to comment.