Skip to content

Commit

Permalink
Support basic expressions
Browse files Browse the repository at this point in the history
Summary: Support some basic commonly used expressions.

Reviewed By: bradzacher

Differential Revision: D40782144

fbshipit-source-id: 6b4a6ce0a4cedbdf8476850f1ead4960d60113b1
  • Loading branch information
pieterv authored and facebook-github-bot committed Oct 28, 2022
1 parent 5bfdb32 commit f4b0b05
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 40 deletions.
4 changes: 2 additions & 2 deletions tools/hermes-parser/js/hermes-estree/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -1260,14 +1260,14 @@ export interface ObjectTypeAnnotation extends BaseNode {
}
export interface ObjectTypeProperty extends BaseNode {
+type: 'ObjectTypeProperty';
+key: Identifier;
+key: Identifier | StringLiteral;
+value: TypeAnnotationType;
+method: boolean;
+optional: boolean;
+static: boolean;
+proto: false; // ???
+variance: Variance | null;
+kind: 'init';
+kind: 'init' | 'get' | 'set';

+parent: ObjectTypeAnnotation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,20 @@ describe('flowToFlowDef', () => {
`declare export default class Foo {}`,
);
});
it('export default typecast', () => {
it('export default expression', () => {
expectTranslate(
`export default (1: number);`,
`declare export default number;`,
);
});
it('export default var', () => {
expectTranslate(
`function foo() {}
export default foo;`,
`declare function foo(): void;
declare export default foo;`,
);
});
});
describe('ExportAllDeclaration', () => {
it('export basic', () => {
Expand Down Expand Up @@ -463,4 +471,77 @@ describe('flowToFlowDef', () => {
);
});
});
describe('Expression', () => {
function expectTranslateExpression(
expectExprCode: string,
toBeExprCode: string,
): void {
expectTranslate(
`export const expr = ${expectExprCode};`,
`declare export var expr: ${toBeExprCode};`,
);
}
describe('Identifier', () => {
it('basic', () => {
expectTranslateExpression(`foo`, `foo`);
});
});
describe('ObjectExpression', () => {
it('empty', () => {
expectTranslateExpression(`{}`, `{}`);
});
it('methods', () => {
expectTranslateExpression(`{foo() {}}`, `{foo(): void}`);
expectTranslateExpression(`{get foo() {}}`, `{get foo(): void}`);
expectTranslateExpression(
`{set foo(bar: string) {}}`,
`{set foo(bar: string): void}`,
);
});
it('spread', () => {
expectTranslateExpression(`{...a}`, `{...a}`);
});
});
describe('Literals', () => {
it('number', () => {
expectTranslateExpression(`1`, `1`);
expectTranslateExpression(`1.99`, `1.99`);
});
it('string', () => {
expectTranslateExpression(`'s'`, `'s'`);
});
it('boolean', () => {
expectTranslateExpression(`true`, `true`);
});
it('regex', () => {
expectTranslateExpression(`/a/`, `RegExp`);
});
it('null', () => {
expectTranslateExpression(`null`, `null`);
});
});
describe('TypeCastExpression', () => {
it('basic', () => {
expectTranslateExpression(`(1: number)`, `number`);
});
});
describe('FunctionExpression', () => {
it('basic', () => {
expectTranslateExpression(`function foo() {}`, `() => void`);
expectTranslateExpression(
`function foo<T>(baz: T, bar: string) {}`,
`<T>(baz: T, bar: string) => void`,
);
});
});
describe('ArrowFunctionExpression', () => {
it('basic', () => {
expectTranslateExpression(`() => {}`, `() => void`);
expectTranslateExpression(
`<T>(baz: T, bar: string) => {}`,
`<T>(baz: T, bar: string) => void`,
);
});
});
});
});
203 changes: 166 additions & 37 deletions tools/hermes-parser/js/hermes-translate/src/flowToFlowDef.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import type {
ImportDeclaration,
InterfaceDeclaration,
InterfaceExtends,
Literal,
ModuleDeclaration,
ObjectExpression,
ObjectTypeAnnotation,
ObjectTypeProperty,
OpaqueType,
Expand Down Expand Up @@ -61,6 +63,7 @@ import {
import {createTranslationContext} from './utils/TranslationUtils';
import {asDetachedNode} from 'hermes-transform';
import {translationError, flowFixMeOrError} from './utils/ErrorUtils';
import {isExpression} from 'hermes-estree';

const EMPTY_TRANSLATION_RESULT = [null, []];

Expand Down Expand Up @@ -351,14 +354,142 @@ function convertStatement(
function convertExpressionToTypeAnnotation(
expr: Expression,
context: TranslationContext,
): TranslatedResultOrNull<TypeAnnotationType> {
): TranslatedResult<TypeAnnotationType> {
switch (expr.type) {
case 'TypeCastExpression': {
const [resultExpr, deps] = convertTypeCastExpression(expr, context);
return [resultExpr, deps];
}
case 'Identifier': {
return [
t.GenericTypeAnnotation({id: t.Identifier({name: expr.name})}),
analyzeTypeDependencies(expr, context),
];
}
case 'Literal': {
const [resultExpr, deps] = convertLiteral(expr, context);
return [resultExpr, deps];
}
case 'ObjectExpression': {
const [resultExpr, deps] = convertObjectExpression(expr, context);
return [resultExpr, deps];
}
case 'ArrowFunctionExpression':
case 'FunctionExpression': {
const [resultExpr, deps] = convertAFunction(expr, context);
return [resultExpr, deps];
}
default: {
return EMPTY_TRANSLATION_RESULT;
return [
flowFixMeOrError(
expr,
`convertExpressionToTypeAnnotation: Unsupported expression of type "${expr.type}", a type annotation is required.`,
context,
),
[],
];
}
}
}

function convertObjectExpression(
expr: ObjectExpression,
context: TranslationContext,
): TranslatedResult<TypeAnnotationType> {
const [resultProperties, deps] = convertArray(expr.properties, prop => {
switch (prop.type) {
case 'SpreadElement': {
const [resultExpr, deps] = convertExpressionToTypeAnnotation(
prop.argument,
context,
);
return [t.ObjectTypeSpreadProperty({argument: resultExpr}), deps];
}
case 'Property': {
if (
prop.key.type !== 'Identifier' &&
prop.key.type !== 'StringLiteral'
) {
throw translationError(
prop.key,
`ObjectExpression Property: Unsupported key type of "${prop.key.type}"`,
context,
);
}
const [resultExpr, deps] = convertExpressionToTypeAnnotation(
prop.value,
context,
);
return [
t.ObjectTypeProperty({
key: asDetachedNode(prop.key),
value: resultExpr,
method: prop.method,
optional: false,
static: false,
proto: false,
variance: null,
kind: prop.kind,
}),
deps,
];
}
}
});
return [
t.ObjectTypeAnnotation({
inexact: false,
exact: false,
properties: resultProperties,
indexers: [],
callProperties: [],
internalSlots: [],
}),
deps,
];
}

function convertLiteral(
expr: Literal,
context: TranslationContext,
): TranslatedResult<TypeAnnotationType> {
switch (expr.literalType) {
case 'bigint': {
return [t.BigIntLiteralTypeAnnotation({raw: expr.raw}), []];
}
case 'boolean': {
return [
t.BooleanLiteralTypeAnnotation({raw: expr.raw, value: expr.value}),
[],
];
}
case 'null': {
return [t.NullLiteralTypeAnnotation({}), []];
}
case 'numeric': {
return [
t.NumberLiteralTypeAnnotation({raw: expr.raw, value: expr.value}),
[],
];
}
case 'string': {
return [
t.StringLiteralTypeAnnotation({raw: expr.raw, value: expr.value}),
[],
];
}
case 'regexp': {
return [
t.GenericTypeAnnotation({id: t.Identifier({name: 'RegExp'})}),
[],
];
}
default: {
throw translationError(
expr,
'convertLiteral: Unsupported literal type.',
context,
);
}
}
}
Expand Down Expand Up @@ -438,25 +569,6 @@ function convertExportDeclaration(
deps,
];
}
case 'TypeCastExpression': {
if (!opts.default) {
throw translationError(
decl,
'ExportDeclaration: Non default typecast found, invalid AST.',
context,
);
}
const [declDecl, deps] = convertTypeCastExpression(decl, context);
return [
t.DeclareExportDeclaration({
specifiers: [],
declaration: declDecl,
default: true,
source: null,
}),
deps,
];
}
case 'VariableDeclaration': {
if (opts.default) {
throw translationError(
Expand Down Expand Up @@ -488,9 +600,31 @@ function convertExportDeclaration(
];
}
default: {
if (isExpression(decl)) {
if (!opts.default) {
throw translationError(
decl,
'ExportDeclaration: Non default expression found, invalid AST.',
context,
);
}
const [declDecl, deps] = convertExpressionToTypeAnnotation(
decl,
context,
);
return [
t.DeclareExportDeclaration({
specifiers: [],
declaration: declDecl,
default: true,
source: null,
}),
deps,
];
}
throw translationError(
decl,
`ExportNamedDeclaration: Unsupported declaration case of type "${decl.type}"`,
`ExportDeclaration: Unsupported declaration of type "${decl.type}".`,
context,
);
}
Expand Down Expand Up @@ -559,23 +693,18 @@ function convertVariableDeclaration(
}

const init = first.init;
const [resultInit, deps] =
init == null
? EMPTY_TRANSLATION_RESULT
: convertExpressionToTypeAnnotation(init, context);

if (resultInit != null) {
return [resultInit, deps];
if (init == null) {
return [
flowFixMeOrError(
first,
`VariableDeclaration: Type annotation missing`,
context,
),
[],
];
}

return [
flowFixMeOrError(
first,
`VariableDeclaration: Type annotation missing`,
context,
),
[],
];
return convertExpressionToTypeAnnotation(init, context);
})();

return [
Expand Down

0 comments on commit f4b0b05

Please sign in to comment.