Refactor tryStaticEvaluation
MODIFY
packages/@romejs/compiler/transforms/compile/transpile/enums.ts
packages/@romejs/compiler/transforms/compile/transpile/enums.ts
@@ -8,14 +8,13 @@ import {
jsNumericLiteral,
jsStringLiteral,
} from "@romejs/ast";
-import {LetBinding, Path, VarBinding} from "@romejs/compiler";
+import {LetBinding, Path, Scope, VarBinding} from "@romejs/compiler";
import {REDUCE_REMOVE} from "@romejs/compiler/constants";
import {descriptions} from "@romejs/diagnostics";
import {template, tryStaticEvaluation} from "@romejs/js-ast-utils";
+import {EvalResult} from "@romejs/js-ast-utils/tryStaticEvaluation";
-interface PreviousEnumMembers {
- [name: string]: number | string;
-}
+type PreviousEnumMembers = Map<string, EvalResult["value"]>;
function buildEnumWrapper(
id: JSBindingIdentifier,
@@ -64,8 +63,8 @@ function buildStringAssignment(
`;
}
-function enumFill(node: TSEnumDeclaration): AnyJSExpression {
- const x = translateEnumValues(node);
+function enumFill(node: TSEnumDeclaration, scope: Scope): AnyJSExpression {
+ const x = translateEnumValues(node, scope);
const assignments = x.map(([memberName, memberValue]) =>
buildEnumMember(
typeof memberValue !== "string" && memberValue.type === "JSStringLiteral",
@@ -79,9 +78,11 @@ function enumFill(node: TSEnumDeclaration): AnyJSExpression {
function translateEnumValues(
node: TSEnumDeclaration,
+ scope: Scope,
): Array<[string, AnyJSExpression]> {
- const seen: PreviousEnumMembers = Object.create(null);
+ const seen: PreviousEnumMembers = new Map();
let prev: number | undefined = -1;
+
return node.members.map((member) => {
let value: AnyJSExpression;
const initializer = member.initializer;
@@ -89,17 +90,18 @@ function translateEnumValues(
member.id.type === "JSIdentifier" ? member.id.name : member.id.value;
if (initializer) {
- let {value: constValue, bailed} = tryStaticEvaluation(initializer);
+ let {value: constValue, bailed} = tryStaticEvaluation(initializer, scope);
if (bailed && initializer.type === "JSReferenceIdentifier") {
- constValue = seen[initializer.name];
+ constValue = seen.get(initializer.name);
}
if (constValue !== undefined) {
- seen[name] = constValue;
+ seen.set(name, constValue);
+
if (typeof constValue === "number") {
value = jsNumericLiteral.create({value: constValue});
prev = constValue;
} else {
- value = jsStringLiteral.create({value: constValue});
+ value = jsStringLiteral.create({value: String(constValue)});
prev = undefined;
}
} else {
@@ -110,7 +112,7 @@ function translateEnumValues(
if (prev !== undefined) {
prev++;
value = jsNumericLiteral.create({value: prev});
- seen[name] = prev;
+ seen.set(name, prev);
} else {
throw new Error("Enum member must have initializer");
}
@@ -123,7 +125,7 @@ function translateEnumValues(
export default {
name: "enums",
enter(path: Path) {
- const {context, node} = path;
+ const {context, node, scope} = path;
if (node.type !== "TSEnumDeclaration") {
return node;
@@ -141,7 +143,7 @@ export default {
return REDUCE_REMOVE;
}
- const fill = enumFill(node);
+ const fill = enumFill(node, scope);
switch (path.parent.type) {
case "JSBlockStatement":

MODIFY
packages/@romejs/js-ast-utils/index.ts
packages/@romejs/js-ast-utils/index.ts
@@ -42,3 +42,4 @@ export {default as hasJSXAttribute} from "./hasJSXAttribute";
export {default as getJSXAttribute} from "./getJSXAttribute";
export {default as isJSXElement} from "./isJSXElement";
export {default as tryStaticEvaluation} from "./tryStaticEvaluation";
+export {default as tryStaticEvaluationPath} from "./tryStaticEvaluationPath";

MODIFY
packages/@romejs/js-ast-utils/tryStaticEvaluation.ts
packages/@romejs/js-ast-utils/tryStaticEvaluation.ts
import {
- AnyJSExpression,
+ AnyNode,
JSBinaryExpression,
+ JSTemplateLiteral,
JSUnaryExpression,
} from "@romejs/ast";
+import {ConstBinding, Scope} from "@romejs/compiler";
-function evalConstant(expr: AnyJSExpression) {
- switch (expr.type) {
- case "JSStringLiteral":
- return expr.value;
- case "JSUnaryExpression":
- return evalUnaryExpression(expr);
- case "JSBinaryExpression":
- return evalBinaryExpression(expr);
- case "JSNumericLiteral":
- return expr.value;
- case "JSTemplateLiteral": {
- if (expr.quasis.length === 1) {
- return expr.quasis[0].cooked;
- }
- return undefined;
- }
- default:
- return undefined;
- }
+export type EvalResult = {
+ value: undefined | null | string | number | boolean;
+ bailed: boolean;
+};
+
+export type EvalOptions = {};
+
+const BAILED: EvalResult = {
+ bailed: true,
+ value: undefined,
+};
+
+function createResult(value: EvalResult["value"]): EvalResult {
+ return {
+ bailed: false,
+ value,
+ };
}
function evalUnaryExpression(
expr: JSUnaryExpression,
-): number | string | undefined {
- const value = evalConstant(expr.argument);
- if (value === undefined) {
- return;
- }
+ scope: Scope,
+ opts: EvalOptions,
+): EvalResult {
+ const res = tryStaticEvaluation(expr.argument, scope, opts);
+
+ if (!res.bailed) {
+ // We do not care about TS protections
+ // rome-ignore lint/js/noExplicitAny
+ const value = (res.value as any);
switch (expr.operator) {
case "+":
- return value;
+ return createResult(+value);
+
case "-":
- return -value;
+ return createResult(-value);
+
case "~":
- return ~value;
- default:
- return undefined;
+ return createResult(~value);
}
}
-function evalBinaryExpression(expr: JSBinaryExpression): number | undefined {
- const left = Number(evalConstant(expr.left));
- if (left === undefined) {
- return;
+ return BAILED;
}
- const right = Number(evalConstant(expr.right));
- if (right === undefined) {
- return;
+
+function evalBinaryExpression(
+ expr: JSBinaryExpression,
+ scope: Scope,
+ opts: EvalOptions,
+): EvalResult {
+ const left = tryStaticEvaluation(expr.left, scope, opts);
+ if (left.bailed) {
+ return BAILED;
}
+ const right = tryStaticEvaluation(expr.right, scope, opts);
+ if (right.bailed) {
+ return BAILED;
+ }
+
+ // We do not care about TS protections
+ // rome-ignore lint/js/noExplicitAny
+ const l = (left.value as any);
+ // rome-ignore lint/js/noExplicitAny
+ const r = (right.value as any);
+
switch (expr.operator) {
case "|":
- return left | right;
+ return createResult(l | r);
+
case "&":
- return left & right;
+ return createResult(l & r);
+
case ">>":
- return left >> right;
+ return createResult(l >> r);
+
case ">>>":
- return left >>> right;
+ return createResult(l >>> r);
+
case "<<":
- return left << right;
+ return createResult(l << r);
+
case "^":
- return left ^ right;
+ return createResult(l ^ r);
+
case "*":
- return left * right;
+ return createResult(l * r);
+
case "/":
- return left / right;
+ return createResult(l / r);
+
case "+":
- return left + right;
+ return createResult(l + r);
+
case "-":
- return left - right;
+ return createResult(l - r);
+
case "%":
- return left % right;
+ return createResult(l % r);
+
default:
- return undefined;
+ return BAILED;
+ }
+}
+
+function evalTemplateLiteral(
+ expr: JSTemplateLiteral,
+ scope: Scope,
+ opts: EvalOptions,
+): EvalResult {
+ const {expressions, quasis} = expr;
+
+ let str = "";
+ let bailed = false;
+ let index = 0;
+
+ for (const elem of quasis) {
+ str += elem.cooked;
+
+ if (index < expressions.length) {
+ const res = tryStaticEvaluation(expressions[index++], scope, opts);
+
+ if (res.bailed) {
+ bailed = true;
+ break;
+ }
+
+ str += res.value;
+ }
+ }
+
+ if (bailed) {
+ return BAILED;
+ } else {
+ return createResult(str);
}
}
+const cache: WeakMap<AnyNode, EvalResult> = new WeakMap();
+
export default function tryStaticEvaluation(
- expr: AnyJSExpression,
-): {
- value?: string | number;
- bailed?: boolean;
-} {
- const value = evalConstant(expr);
- return {
- value,
- bailed: !value,
- };
+ node: AnyNode,
+ scope: Scope,
+ opts: EvalOptions = {},
+): EvalResult {
+ const cached = cache.get(node);
+ if (cached !== undefined) {
+ return cached;
+ }
+
+ let res: EvalResult = BAILED;
+
+ switch (node.type) {
+ case "JSUnaryExpression": {
+ res = evalUnaryExpression(node, scope, opts);
+ break;
+ }
+
+ case "JSBinaryExpression": {
+ res = evalBinaryExpression(node, scope, opts);
+ break;
+ }
+
+ case "JSNullLiteral": {
+ res = createResult(null);
+ break;
+ }
+
+ case "JSStringLiteral":
+ case "JSBooleanLiteral":
+ case "JSNumericLiteral": {
+ res = createResult(node.value);
+ break;
+ }
+
+ case "JSReferenceIdentifier": {
+ const binding = scope.getBinding(node.name);
+ if (binding === undefined && node.name === "undefined") {
+ res = createResult(undefined);
+ } else {
+ if (binding instanceof ConstBinding && binding.value !== undefined) {
+ res = tryStaticEvaluation(binding.value, binding.scope, opts);
+ }
+ }
+ break;
+ }
+
+ case "JSTemplateLiteral": {
+ res = evalTemplateLiteral(node, scope, opts);
+ break;
+ }
+ }
+
+ cache.set(node, res);
+ return res;
}

ADD
packages/@romejs/js-ast-utils/tryStaticEvaluationPath.ts
+import {Path} from "@romejs/compiler";
+import tryStaticEvaluation, {
+ EvalOptions,
+ EvalResult,
+} from "./tryStaticEvaluation";
+
+export default function tryStaticEvaluationPath(
+ path: Path,
+ opts?: EvalOptions,
+): EvalResult {
+ return tryStaticEvaluation(path.node, path.scope, opts);
+}

Refactor tryStaticEvaluation (#552)