Repository: fabien0102/ts-to-zod (ts)

Issue:
Generated schema doesn’t match types
## Bug description

```
npm i -D openapi-typescript ts-to-zod
```

In `package.json`:
```json
{
  "scripts": {
    "fetch-openapi-types": "openapi-typescript https://developers.zoom.us/api-hub/video-sdk/events/webhooks.json --alphabetize --export-type --output ./webhooks-types.ts",
    "convert-types-to-zod": "ts-to-zod ./webhooks-types.ts ./webhooks-schema.ts",
  }
}
```

```
npm run fetch-openapi-types
npm run convert-types-to-zod
```

Output:

```
✖ Validating generated types
 ›   Error: 'webhooksSchema' is not compatible with 
 ›   'webhooks':
 ›   Argument of type '{ "session.alert": { [x: string]: any;
 ›    parameters?: unknown; get?: unknown; put?: unknown; 
 ›   post?: unknown; "session.alert"?: unknown; delete?: 
 ›   unknown; options?: unknown; head?: unknown; patch?: 
 ›   unknown; trace?: unknown; }; "session.ended": { [x: 
 ›   string]: any; parameters?: unknown; get?: unknown; put?:
 ›    unknown; post...' is not assignable to parameter of 
 ›   type 'webhooks'.
 ›     The types of '"session.alert".parameters' are 
 ›   incompatible between these types.
 ›       Type 'unknown' is not assignable to type '{ query?: 
 ›   undefined; header?: undefined; path?: undefined; 
 ›   cookie?: undefined; }'.
```


**Versions**

```json
{
  "devDependencies": {
    "ts-to-zod": "^3.13.0",
    "typescript": "^5.6.3",
    "zod": "^3.23.8"
  }
}
```

Pull request description:
fix: handling non-identifier index access type
# Why

Fixes #273 
Non-identifier access type such as `Superman["a.b"]`, `Villain["0bla-test"]` are now handled

Also adds support for single quotes in index access type


Patch:
diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts
index a2a27df..73fde1e 100644
--- a/src/core/generateZodSchema.ts
+++ b/src/core/generateZodSchema.ts
@@ -1390,9 +1390,11 @@ function buildSchemaReference(
     getDependencyName: Required<GenerateZodSchemaProps>["getDependencyName"];
   },
   path = ""
-): ts.PropertyAccessExpression | ts.Identifier {
+): ts.PropertyAccessExpression | ts.Identifier | ts.ElementAccessExpression {
   const indexTypeText = node.indexType.getText(sourceFile);
-  const { indexTypeName, type: indexTypeType } = /^"\w+"$/.exec(indexTypeText)
+  const { indexTypeName, type: indexTypeType } = /^['"]([^'"]+)['"]$/.exec(
+    indexTypeText
+  )
     ? { type: "string" as const, indexTypeName: indexTypeText.slice(1, -1) }
     : { type: "number" as const, indexTypeName: indexTypeText };
 
@@ -1496,9 +1498,19 @@ function buildSchemaReference(
       node.objectType.typeName.getText(sourceFile)
     );
     dependencies.push(dependencyName);
-    return f.createPropertyAccessExpression(
-      f.createIdentifier(dependencyName),
-      f.createIdentifier(`shape.${indexTypeName}.${path}`.slice(0, -1))
+
+    if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(indexTypeName)) {
+      return f.createPropertyAccessExpression(
+        f.createIdentifier(dependencyName),
+        f.createIdentifier(`shape.${indexTypeName}.${path}`.slice(0, -1))
+      );
+    }
+    return f.createElementAccessExpression(
+      f.createPropertyAccessExpression(
+        f.createIdentifier(dependencyName),
+        f.createIdentifier("shape")
+      ),
+      f.createStringLiteral(indexTypeName)
     );
   }
 


Test patch:
diff --git a/src/core/generateZodSchema.test.ts b/src/core/generateZodSchema.test.ts
index 94d24ea..de49ba7 100644
--- a/src/core/generateZodSchema.test.ts
+++ b/src/core/generateZodSchema.test.ts
@@ -583,6 +583,32 @@ describe("generateZodSchema", () => {
       `);
   });
 
+  it("should deal with index access type using single quote (1st level)", () => {
+    const source = `export type SupermanName = {
+      name?: Superman['name']
+    }`;
+
+    expect(generate(source)).toMatchInlineSnapshot(`
+      "export const supermanNameSchema = z.object({
+          name: supermanSchema.shape.name.optional()
+      });"
+      `);
+  });
+
+  it("should deal with index access type with element access expression (1st level)", () => {
+    const source = `export type SupermanName = {
+      firstName: Superman["name.firstName"]
+      lastName: Superman["name-lastName"]
+    }`;
+
+    expect(generate(source)).toMatchInlineSnapshot(`
+      "export const supermanNameSchema = z.object({
+          firstName: supermanSchema.shape["name.firstName"],
+          lastName: supermanSchema.shape["name-lastName"]
+      });"
+      `);
+  });
+
   it("should deal with record with a union as key", () => {
     const source = `
     export type AvailablePower = Record<Power, boolean>;