fix: disallow non-numeric computed enum values
MODIFY
packages/@romejs/compiler/transforms/compile/transpile/enums.ts
packages/@romejs/compiler/transforms/compile/transpile/enums.ts
@@ -8,7 +8,13 @@ import {
jsNumericLiteral,
jsStringLiteral,
} from "@romejs/ast";
-import {LetBinding, Path, Scope, VarBinding} from "@romejs/compiler";
+import {
+ CompilerContext,
+ 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";
@@ -63,8 +69,12 @@ function buildStringAssignment(
`;
}
-function enumFill(node: TSEnumDeclaration, scope: Scope): AnyJSExpression {
- const x = translateEnumValues(node, scope);
+function enumFill(
+ node: TSEnumDeclaration,
+ scope: Scope,
+ context: CompilerContext,
+): AnyJSExpression {
+ const x = translateEnumValues(node, scope, context);
const assignments = x.map(([memberName, memberValue]) =>
buildEnumMember(
typeof memberValue !== "string" && memberValue.type === "JSStringLiteral",
@@ -79,6 +89,7 @@ function enumFill(node: TSEnumDeclaration, scope: Scope): AnyJSExpression {
function translateEnumValues(
node: TSEnumDeclaration,
scope: Scope,
+ context: CompilerContext,
): Array<[string, AnyJSExpression]> {
const seen: PreviousEnumMembers = new Map();
let prev: number | undefined = -1;
@@ -90,7 +101,26 @@ function translateEnumValues(
member.id.type === "JSIdentifier" ? member.id.name : member.id.value;
if (initializer) {
- let {value: constValue, bailed} = tryStaticEvaluation(initializer, scope);
+ let {value: constValue, bailed} = tryStaticEvaluation(
+ initializer,
+ scope,
+ {
+ isNodeValid: (node, resolvedNode) => {
+ if (
+ node.type === "JSReferenceIdentifier" &&
+ resolvedNode.type !== "JSNumericLiteral"
+ ) {
+ context.addNodeDiagnostic(
+ member,
+ descriptions.COMPILER.ENUM_COMPUTED_VALUES_UNSUPPORTED,
+ );
+ return false;
+ }
+ return true;
+ },
+ },
+ );
+
if (bailed && initializer.type === "JSReferenceIdentifier") {
constValue = seen.get(initializer.name);
}
@@ -143,7 +173,7 @@ export default {
return REDUCE_REMOVE;
}
- const fill = enumFill(node, scope);
+ const fill = enumFill(node, scope, context);
switch (path.parent.type) {
case "JSBlockStatement":

MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -18,6 +18,7 @@ export type DiagnosticCategory =
| "compile/classes"
| "compile/const-enums"
| "compile/jsx"
+ | "compile/nonnumeric-enum-values"
| "flags/invalid"
| "format/disabled"
| "internalError/httpServer"

MODIFY
packages/@romejs/diagnostics/descriptions/compiler.ts
packages/@romejs/diagnostics/descriptions/compiler.ts
@@ -14,4 +14,8 @@ export const compiler = createDiagnosticsCategory({
category: "compile/const-enums",
message: "Const enums are not supported",
},
+ ENUM_COMPUTED_VALUES_UNSUPPORTED: {
+ category: "compile/nonnumeric-enum-values",
+ message: "Only numeric enums can have computed members",
+ },
});

MODIFY
packages/@romejs/js-ast-utils/tryStaticEvaluation.ts
packages/@romejs/js-ast-utils/tryStaticEvaluation.ts
@@ -12,7 +12,9 @@ export type EvalResult = {
bailed: boolean;
};
-export type EvalOptions = {};
+export type EvalOptions = {
+ isNodeValid?: (node: AnyNode, resolvedNode: AnyNode) => boolean;
+};
const BAILED: EvalResult = {
bailed: true,
@@ -160,16 +162,23 @@ export default function tryStaticEvaluation(
let res: EvalResult = BAILED;
- ({node, scope} = resolveIndirection(node, scope));
+ const {node: resolvedNode, scope: resolvedScope} = resolveIndirection(
+ node,
+ scope,
+ );
+
+ if (opts.isNodeValid && !opts.isNodeValid(node, resolvedNode)) {
+ return res;
+ }
- switch (node.type) {
+ switch (resolvedNode.type) {
case "JSUnaryExpression": {
- res = evalUnaryExpression(node, scope, opts);
+ res = evalUnaryExpression(resolvedNode, resolvedScope, opts);
break;
}
case "JSBinaryExpression": {
- res = evalBinaryExpression(node, scope, opts);
+ res = evalBinaryExpression(resolvedNode, resolvedScope, opts);
break;
}
@@ -179,27 +188,27 @@ export default function tryStaticEvaluation(
}
case "JSBigIntLiteral": {
- res = createResult(BigInt(node.value));
+ res = createResult(BigInt(resolvedNode.value));
break;
}
case "JSStringLiteral":
case "JSBooleanLiteral":
case "JSNumericLiteral": {
- res = createResult(node.value);
+ res = createResult(resolvedNode.value);
break;
}
case "JSReferenceIdentifier": {
- const binding = scope.getBinding(node.name);
- if (binding === undefined && node.name === "undefined") {
+ const binding = resolvedScope.getBinding(resolvedNode.name);
+ if (binding === undefined && resolvedNode.name === "undefined") {
res = createResult(undefined);
}
break;
}
case "JSTemplateLiteral": {
- res = evalTemplateLiteral(node, scope, opts);
+ res = evalTemplateLiteral(resolvedNode, resolvedScope, opts);
break;
}
}

fix: disallow non-numeric computed enum values (#617)