Skip to content

Commit cac042e

Browse files
authored
fix: tsconfig loading when no compilerOptions (#382)
previously the zod schema would refuse to parse a `tsconfig.json` if it didn't define any `compilerOptions`. This was problematic when configurations extend each other, as you might have intermediate configuration files that don't define this.
1 parent 206e789 commit cac042e

File tree

6 files changed

+119
-4
lines changed

6 files changed

+119
-4
lines changed

packages/openapi-code-generator/src/core/file-system/fs-adaptor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ export interface IFsAdaptor {
88
existsSync(path: string): boolean
99

1010
mkDir(path: string, recursive: boolean): Promise<void>
11+
12+
resolve(request: string, fromDir: string): string
1113
}

packages/openapi-code-generator/src/core/file-system/node-fs-adaptor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {existsSync} from "node:fs"
2-
32
import fs from "node:fs/promises"
43
import type {IFsAdaptor} from "./fs-adaptor"
54

@@ -35,4 +34,8 @@ export class NodeFsAdaptor implements IFsAdaptor {
3534
async mkDir(path: string, recursive = true) {
3635
await fs.mkdir(path, {recursive})
3736
}
37+
38+
resolve(request: string, fromDir: string): string {
39+
return require.resolve(request, {paths: [fromDir]})
40+
}
3841
}

packages/openapi-code-generator/src/core/file-system/web-fs-adaptor.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pathModule from "node:path"
12
import type {IFsAdaptor} from "./fs-adaptor"
23

34
export class WebFsAdaptor implements IFsAdaptor {
@@ -30,4 +31,8 @@ export class WebFsAdaptor implements IFsAdaptor {
3031
async mkDir() {
3132
/*noop*/
3233
}
34+
35+
resolve(request: string, fromDir: string): string {
36+
return pathModule.normalize(pathModule.resolve(fromDir, request))
37+
}
3338
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {describe, expect, it} from "@jest/globals"
2+
import {WebFsAdaptor} from "../file-system/web-fs-adaptor"
3+
import {loadTsConfigCompilerOptions} from "./tsconfig.loader"
4+
5+
function fsAdaptor(files: Record<string, string>) {
6+
return new WebFsAdaptor(new Map(Object.entries(files)))
7+
}
8+
9+
describe("core/loaders/tsconfig.loader", () => {
10+
it("returns defaults when no tsconfig.json is found", async () => {
11+
const fs = fsAdaptor({})
12+
13+
const actual = await loadTsConfigCompilerOptions(
14+
"/virtual/workspace/src",
15+
fs,
16+
)
17+
18+
expect(actual).toEqual({
19+
exactOptionalPropertyTypes: false,
20+
rewriteRelativeImportExtensions: false,
21+
})
22+
})
23+
24+
it("parses tsconfig.json without compilerOptions and applies defaults", async () => {
25+
const fs = fsAdaptor({
26+
"/virtual/workspace/tsconfig.json": "{}",
27+
})
28+
29+
const actual = await loadTsConfigCompilerOptions("/virtual/workspace", fs)
30+
31+
expect(actual).toEqual({
32+
exactOptionalPropertyTypes: false,
33+
rewriteRelativeImportExtensions: false,
34+
})
35+
})
36+
37+
it("reads and returns specified compilerOptions overriding defaults", async () => {
38+
const fs = fsAdaptor({
39+
"/virtual/ws/tsconfig.json": JSON.stringify({
40+
compilerOptions: {
41+
exactOptionalPropertyTypes: true,
42+
},
43+
}),
44+
})
45+
46+
const actual = await loadTsConfigCompilerOptions(
47+
"/virtual/ws/packages/pkg",
48+
fs,
49+
)
50+
51+
expect(actual).toEqual({
52+
exactOptionalPropertyTypes: true,
53+
rewriteRelativeImportExtensions: false,
54+
})
55+
})
56+
57+
it("supports both options when set", async () => {
58+
const fs = fsAdaptor({
59+
"/v/tsconfig.json": JSON.stringify({
60+
compilerOptions: {
61+
exactOptionalPropertyTypes: true,
62+
rewriteRelativeImportExtensions: true,
63+
},
64+
}),
65+
})
66+
67+
const actual = await loadTsConfigCompilerOptions("/v/src", fs)
68+
69+
expect(actual).toEqual({
70+
exactOptionalPropertyTypes: true,
71+
rewriteRelativeImportExtensions: true,
72+
})
73+
})
74+
75+
it("merges compilerOptions from an extends chain", async () => {
76+
const basePath = "/virt/base/tsconfig.base.json"
77+
const childPath = "/virt/proj/tsconfig.json"
78+
79+
const fs = fsAdaptor({
80+
[basePath]: JSON.stringify({
81+
compilerOptions: {
82+
exactOptionalPropertyTypes: true,
83+
rewriteRelativeImportExtensions: false,
84+
},
85+
}),
86+
[childPath]: JSON.stringify({
87+
extends: "./../base/tsconfig.base.json",
88+
compilerOptions: {
89+
rewriteRelativeImportExtensions: true,
90+
},
91+
}),
92+
})
93+
94+
const actual = await loadTsConfigCompilerOptions("/virt/proj/src", fs)
95+
96+
expect(actual).toEqual({
97+
exactOptionalPropertyTypes: true,
98+
rewriteRelativeImportExtensions: true,
99+
})
100+
})
101+
})

packages/openapi-code-generator/src/core/loaders/tsconfig.loader.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export async function loadTsConfigCompilerOptions(
2727
const path = ts.findConfigFile(searchPath, (it) => fsAdaptor.existsSync(it))
2828

2929
if (path) {
30-
return (await loadTsConfig(path, fsAdaptor)).compilerOptions
30+
const compilerOptions = (await loadTsConfig(path, fsAdaptor))
31+
.compilerOptions
32+
return {...defaults, ...compilerOptions}
3133
}
3234

3335
logger.warn(`no tsconfig.json found for ${searchPath}, using defaults`, {
@@ -59,7 +61,7 @@ async function loadTsConfig(
5961
? [config.extends]
6062
: (config.extends ?? [])
6163
)
62-
.map((it) => require.resolve(it, {paths: [path.dirname(configPath)]}))
64+
.map((it) => fsAdaptor.resolve(it, path.dirname(configPath)))
6365
.map((it) => loadTsConfig(it, fsAdaptor)),
6466
)
6567

packages/openapi-code-generator/src/core/schemas/tsconfig.schema.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ export const tsconfigSchema = z.object({
3838
rewriteRelativeImportExtensions: z.boolean(),
3939
verbatimModuleSyntax: z.boolean(),
4040
})
41-
.partial(),
41+
.partial()
42+
.optional()
43+
.default({}),
4244
})

0 commit comments

Comments
 (0)