123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- import {ContextualKeyword} from "../parser/tokenizer/keywords";
- import {TokenType as tt} from "../parser/tokenizer/types";
- import Transformer from "./Transformer";
- export default class FlowTransformer extends Transformer {
- constructor(
- rootTransformer,
- tokens,
- isImportsTransformEnabled,
- ) {
- super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.isImportsTransformEnabled = isImportsTransformEnabled;;
- }
- process() {
- if (
- this.rootTransformer.processPossibleArrowParamEnd() ||
- this.rootTransformer.processPossibleAsyncArrowWithTypeParams() ||
- this.rootTransformer.processPossibleTypeRange()
- ) {
- return true;
- }
- if (this.tokens.matches1(tt._enum)) {
- this.processEnum();
- return true;
- }
- if (this.tokens.matches2(tt._export, tt._enum)) {
- this.processNamedExportEnum();
- return true;
- }
- if (this.tokens.matches3(tt._export, tt._default, tt._enum)) {
- this.processDefaultExportEnum();
- return true;
- }
- return false;
- }
- /**
- * Handle a declaration like:
- * export enum E ...
- *
- * With this imports transform, this becomes:
- * const E = [[enum]]; exports.E = E;
- *
- * otherwise, it becomes:
- * export const E = [[enum]];
- */
- processNamedExportEnum() {
- if (this.isImportsTransformEnabled) {
- // export
- this.tokens.removeInitialToken();
- const enumName = this.tokens.identifierNameAtRelativeIndex(1);
- this.processEnum();
- this.tokens.appendCode(` exports.${enumName} = ${enumName};`);
- } else {
- this.tokens.copyToken();
- this.processEnum();
- }
- }
- /**
- * Handle a declaration like:
- * export default enum E
- *
- * With the imports transform, this becomes:
- * const E = [[enum]]; exports.default = E;
- *
- * otherwise, it becomes:
- * const E = [[enum]]; export default E;
- */
- processDefaultExportEnum() {
- // export
- this.tokens.removeInitialToken();
- // default
- this.tokens.removeToken();
- const enumName = this.tokens.identifierNameAtRelativeIndex(1);
- this.processEnum();
- if (this.isImportsTransformEnabled) {
- this.tokens.appendCode(` exports.default = ${enumName};`);
- } else {
- this.tokens.appendCode(` export default ${enumName};`);
- }
- }
- /**
- * Transpile flow enums to invoke the "flow-enums-runtime" library.
- *
- * Currently, the transpiled code always uses `require("flow-enums-runtime")`,
- * but if future flexibility is needed, we could expose a config option for
- * this string (similar to configurable JSX). Even when targeting ESM, the
- * default behavior of babel-plugin-transform-flow-enums is to use require
- * rather than injecting an import.
- *
- * Flow enums are quite a bit simpler than TS enums and have some convenient
- * constraints:
- * - Element initializers must be either always present or always absent. That
- * means that we can use fixed lookahead on the first element (if any) and
- * assume that all elements are like that.
- * - The right-hand side of an element initializer must be a literal value,
- * not a complex expression and not referencing other elements. That means
- * we can simply copy a single token.
- *
- * Enums can be broken up into three basic cases:
- *
- * Mirrored enums:
- * enum E {A, B}
- * ->
- * const E = require("flow-enums-runtime").Mirrored(["A", "B"]);
- *
- * Initializer enums:
- * enum E {A = 1, B = 2}
- * ->
- * const E = require("flow-enums-runtime")({A: 1, B: 2});
- *
- * Symbol enums:
- * enum E of symbol {A, B}
- * ->
- * const E = require("flow-enums-runtime")({A: Symbol("A"), B: Symbol("B")});
- *
- * We can statically detect which of the three cases this is by looking at the
- * "of" declaration (if any) and seeing if the first element has an initializer.
- * Since the other transform details are so similar between the three cases, we
- * use a single implementation and vary the transform within processEnumElement
- * based on case.
- */
- processEnum() {
- // enum E -> const E
- this.tokens.replaceToken("const");
- this.tokens.copyExpectedToken(tt.name);
- let isSymbolEnum = false;
- if (this.tokens.matchesContextual(ContextualKeyword._of)) {
- this.tokens.removeToken();
- isSymbolEnum = this.tokens.matchesContextual(ContextualKeyword._symbol);
- this.tokens.removeToken();
- }
- const hasInitializers = this.tokens.matches3(tt.braceL, tt.name, tt.eq);
- this.tokens.appendCode(' = require("flow-enums-runtime")');
- const isMirrored = !isSymbolEnum && !hasInitializers;
- this.tokens.replaceTokenTrimmingLeftWhitespace(isMirrored ? ".Mirrored([" : "({");
- while (!this.tokens.matches1(tt.braceR)) {
- // ... is allowed at the end and has no runtime behavior.
- if (this.tokens.matches1(tt.ellipsis)) {
- this.tokens.removeToken();
- break;
- }
- this.processEnumElement(isSymbolEnum, hasInitializers);
- if (this.tokens.matches1(tt.comma)) {
- this.tokens.copyToken();
- }
- }
- this.tokens.replaceToken(isMirrored ? "]);" : "});");
- }
- /**
- * Process an individual enum element, producing either an array element or an
- * object element based on what type of enum this is.
- */
- processEnumElement(isSymbolEnum, hasInitializers) {
- if (isSymbolEnum) {
- // Symbol enums never have initializers and are expanded to object elements.
- // A, -> A: Symbol("A"),
- const elementName = this.tokens.identifierName();
- this.tokens.copyToken();
- this.tokens.appendCode(`: Symbol("${elementName}")`);
- } else if (hasInitializers) {
- // Initializers are expanded to object elements.
- // A = 1, -> A: 1,
- this.tokens.copyToken();
- this.tokens.replaceTokenTrimmingLeftWhitespace(":");
- this.tokens.copyToken();
- } else {
- // Enum elements without initializers become string literal array elements.
- // A, -> "A",
- this.tokens.replaceToken(`"${this.tokens.identifierName()}"`);
- }
- }
- }
|