Repository: alloy-framework/alloy
Pull request #45 (2024-12-17 01:57:13)

Handle reserved words
Typescript Name Policy to handle reserved words


Patch:
diff --git a/.chronus/changes/feature-ts-namepolicy-reserved-words-2024-11-19-13-22-32.md b/.chronus/changes/feature-ts-namepolicy-reserved-words-2024-11-19-13-22-32.md
new file mode 100644
index 0000000..dbe767f
--- /dev/null
+++ b/.chronus/changes/feature-ts-namepolicy-reserved-words-2024-11-19-13-22-32.md
@@ -0,0 +1,7 @@
+---
+changeKind: feature
+packages:
+  - "@alloy-js/typescript"
+---
+
+Handle reserved words in typescript naming policy
\ No newline at end of file
diff --git a/packages/typescript/src/name-policy.ts b/packages/typescript/src/name-policy.ts
index 053d99c..ddf94af 100644
--- a/packages/typescript/src/name-policy.ts
+++ b/packages/typescript/src/name-policy.ts
@@ -16,21 +16,118 @@ export type TypeScriptElements =
   | "interface-member"
   | "type";
 
+// Reserved words
+const GLOBAL_RESERVED_WORDS = new Set([
+  "break",
+  "case",
+  "catch",
+  "class",
+  "const",
+  "continue",
+  "debugger",
+  "default",
+  "delete",
+  "do",
+  "else",
+  "enum",
+  "export",
+  "extends",
+  "false",
+  "finally",
+  "for",
+  "function",
+  "if",
+  "import",
+  "in",
+  "instanceof",
+  "new",
+  "null",
+  "return",
+  "super",
+  "switch",
+  "this",
+  "throw",
+  "true",
+  "try",
+  "typeof",
+  "var",
+  "void",
+  "while",
+  "with",
+  "as",
+  "implements",
+  "interface",
+  "let",
+  "package",
+  "private",
+  "protected",
+  "public",
+  "static",
+  "yield",
+  "await",
+]);
+
+const CONTEXT_SAFE_WORDS = new Set([
+  "delete",
+  "default",
+  "super",
+  "this",
+  "typeof",
+  "instanceof",
+]);
+
+/**
+ * Ensures a valid TypeScript identifier for the given element kind.
+ * @param name - The name to validate.
+ * @param element - The TypeScript element kind.
+ * @returns A TypeScript-safe name.
+ */
+function ensureNonReservedName(
+  name: string,
+  element: TypeScriptElements,
+): string {
+  const suffix = "_";
+
+  // Global reserved words always need handling
+  if (GLOBAL_RESERVED_WORDS.has(name)) {
+    return `${name}${suffix}`;
+  }
+
+  // Context-safe reserved words for properties
+  if (
+    CONTEXT_SAFE_WORDS.has(name) &&
+    (element.includes("member") || element.includes("object-member"))
+  ) {
+    return name; // Safe as properties
+  }
+
+  return name;
+}
+
 export function createTSNamePolicy(): NamePolicy<TypeScriptElements> {
   const caseOptions = {
     prefixCharacters: "$_",
     suffixCharacters: "$_",
   };
+
   return createNamePolicy((name, element) => {
+    let transformedName: string;
+
     switch (element) {
       case "class":
       case "type":
       case "interface":
       case "enum":
-        return pascalCase(name, caseOptions);
+      case "enum-member":
+        transformedName = pascalCase(name, caseOptions);
+        break;
+
       default:
-        return camelCase(name, caseOptions);
+        transformedName = camelCase(name, caseOptions);
+        break;
     }
+
+    return ensureNonReservedName(transformedName, element);
   });
 }
 


Test patch:
diff --git a/packages/typescript/test/name-policy.test.tsx b/packages/typescript/test/name-policy.test.tsx
index f0b6c99..00d330c 100644
--- a/packages/typescript/test/name-policy.test.tsx
+++ b/packages/typescript/test/name-policy.test.tsx
@@ -60,3 +60,31 @@ it("keeps _ and $ prefix", () => {
     $oneTwo;
   `);
 });
+
+it("appends _ to reserved words", () => {
+  const ref1 = refkey({});
+  const ref2 = refkey({});
+
+  const namePolicy = createTSNamePolicy();
+  const res = render(
+    <Output namePolicy={namePolicy}>
+      <ts.SourceFile path="test.ts">
+        <ts.FunctionDeclaration name="default" refkey={ref1} parameters={{"await": "any"}} />
+        <ts.VarDeclaration name="super" refkey={ref2}>
+          "hello"
+        </ts.VarDeclaration>
+        <ts.Reference refkey={ref1} />;
+        <ts.Reference refkey={ref2} />;
+      </ts.SourceFile>
+    </Output>,
+  );
+
+  expect(res.contents[0].contents).toEqual(d`
+    function default_(await_: any) {
+      
+    }
+    const super_ = "hello";
+    default_;
+    super_;
+  `);
+});