Skip to content

Commit

Permalink
Support TypeScript const enums (#13324)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Aug 3, 2021
1 parent 0542f0d commit b707842
Show file tree
Hide file tree
Showing 35 changed files with 280 additions and 21 deletions.
89 changes: 89 additions & 0 deletions packages/babel-plugin-transform-typescript/src/const-enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type * as t from "@babel/types";
import type { NodePath } from "@babel/traverse";

import { translateEnumValues } from "./enum";

export default function transpileConstEnum(
path: NodePath<t.TSEnumDeclaration & { const: true }>,
t: typeof import("@babel/types"),
) {
const { name } = path.node.id;

const parentIsExport = path.parentPath.isExportNamedDeclaration();
let isExported = parentIsExport;
if (!isExported && t.isProgram(path.parent)) {
isExported = path.parent.body.some(
stmt =>
t.isExportNamedDeclaration(stmt) &&
!stmt.source &&
stmt.specifiers.some(
spec => t.isExportSpecifier(spec) && spec.local.name === name,
),
);
}

const entries = translateEnumValues(path, t);

if (isExported) {
const obj = t.objectExpression(
entries.map(([name, value]) =>
t.objectProperty(
t.isValidIdentifier(name)
? t.identifier(name)
: t.stringLiteral(name),
value,
),
),
);

if (path.scope.hasOwnBinding(name)) {
(parentIsExport ? path.parentPath : path).replaceWith(
t.expressionStatement(
t.callExpression(
t.memberExpression(t.identifier("Object"), t.identifier("assign")),
[path.node.id, obj],
),
),
);
} else {
path.replaceWith(
t.variableDeclaration("var", [t.variableDeclarator(path.node.id, obj)]),
);
path.scope.registerDeclaration(path);
}

return;
}

const entriesMap = new Map(entries);

// TODO: After fixing https://github.com/babel/babel/pull/11065, we can
// use path.scope.getBinding(name).referencePaths rather than doing
// a full traversal.
path.scope.path.traverse({
Scope(path) {
if (path.scope.hasOwnBinding(name)) path.skip();
},
MemberExpression(path) {
if (!t.isIdentifier(path.node.object, { name })) return;

let key: string;
if (path.node.computed) {
if (t.isStringLiteral(path.node.property)) {
key = path.node.property.value;
} else {
return;
}
} else if (t.isIdentifier(path.node.property)) {
key = path.node.property.name;
} else {
return;
}
if (!entriesMap.has(key)) return;

path.replaceWith(t.cloneNode(entriesMap.get(key)));
},
});

path.remove();
}
8 changes: 4 additions & 4 deletions packages/babel-plugin-transform-typescript/src/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import type { NodePath } from "@babel/traverse";

export default function transpileEnum(path, t) {
const { node } = path;
if (node.const) {
throw path.buildCodeFrameError("'const' enums are not supported.");
}

if (node.declare) {
path.remove();
Expand Down Expand Up @@ -105,7 +102,10 @@ type PreviousEnumMembers = {
[name: string]: number | string;
};

function translateEnumValues(path, t) {
export function translateEnumValues(
path: NodePath<t.TSEnumDeclaration>,
t: typeof import("@babel/types"),
): Array<[name: string, value: t.Expression]> {
const seen: PreviousEnumMembers = Object.create(null);
// Start at -1 so the first enum member is its increment, 0.
let prev: number | typeof undefined = -1;
Expand Down
8 changes: 7 additions & 1 deletion packages/babel-plugin-transform-typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import syntaxTypeScript from "@babel/plugin-syntax-typescript";
import { types as t, template } from "@babel/core";
import { injectInitialization } from "@babel/helper-create-class-features-plugin";

import transpileConstEnum from "./const-enum";
import transpileEnum from "./enum";
import transpileNamespace from "./namespace";
import type { NodePath } from "@babel/traverse";
Expand Down Expand Up @@ -60,6 +61,7 @@ export default declare((api, opts) => {
jsxPragma = "React.createElement",
jsxPragmaFrag = "React.Fragment",
onlyRemoveTypeImports = false,
optimizeConstEnums = false,
} = opts;

if (!process.env.BABEL_8_BREAKING) {
Expand Down Expand Up @@ -462,7 +464,11 @@ export default declare((api, opts) => {
},

TSEnumDeclaration(path) {
transpileEnum(path, t);
if (optimizeConstEnums && path.node.const) {
transpileConstEnum(path, t);
} else {
transpileEnum(path, t);
}
},

TSImportEqualsDeclaration(path: NodePath<t.TSImportEqualsDeclaration>) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export declare enum A {}

;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"sourceType": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
const enum E {}
// With --isolatedModules, TSC ignores the "const" modifier when compiling enums
const enum E {}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// With --isolatedModules, TSC ignores the "const" modifier when compiling enums
var E;

(function (E) {})(E || (E = {}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const enum A {
x = 3,
y = "f",
z = 4 << 2,
w = y
}

A.x;
A.y;
A.z;
A.w;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export var A = {
x: 3,
y: "f",
z: 16,
w: "f"
};
A.x;
A.y;
A.z;
A.w;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const enum A {
x = 3,
y = "f",
z = 4 << 2,
w = y
}

A.x;
A.y;
A.z;
A.w;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
3;
"f";
16;
"f";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare const enum A { x }

A.x;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const enum A { y }

let x = A.y;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export var A = {
y: 0
};
let x = A.y;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const enum A { y }

let x = A.y;

export { A };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
var A = {
y: 0
};
let x = A.y;
export { A };
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const enum A { x }

{
let A = {};
A.x;
}

A.x;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
let A = {};
A.x;
}
0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const enum A {
x, y
}

A.x;
A["y"];
A.z;
A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
0;
1;
A.z;
A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const enum A {
x, y
}

export const enum A {
z
}

A.x;
A["y"];
A.z;
A.w;
A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export var A = {
x: 0,
y: 1
};
Object.assign(A, {
z: 0
});
A.x;
A["y"];
A.z;
A.w;
A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const enum A {
x, y
}

const enum A {
z
}

A.x;
A["y"];
A.z;
A.w;
A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0;
1;
0;
A.w;
A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"plugins": [["transform-typescript", { "optimizeConstEnums": true }]],
"sourceType": "module"
}
3 changes: 3 additions & 0 deletions packages/babel-preset-typescript/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default declare((api, opts) => {
jsxPragma,
jsxPragmaFrag,
onlyRemoveTypeImports,
optimizeConstEnums,
} = normalizeOptions(opts);

const pluginOptions = process.env.BABEL_8_BREAKING
Expand All @@ -21,6 +22,7 @@ export default declare((api, opts) => {
jsxPragma,
jsxPragmaFrag,
onlyRemoveTypeImports,
optimizeConstEnums,
})
: isTSX => ({
allowDeclareFields: opts.allowDeclareFields,
Expand All @@ -29,6 +31,7 @@ export default declare((api, opts) => {
jsxPragma,
jsxPragmaFrag,
onlyRemoveTypeImports,
optimizeConstEnums,
});

return {
Expand Down

0 comments on commit b707842

Please sign in to comment.