Skip to content

Commit 7097889

Browse files
authored
feat: support comments in tsconfig.json files [SIM-97] (#1152)
* feat: support comments in tsconfig.json files * chore: remove unused import
1 parent 731a065 commit 7097889

File tree

13 files changed

+268
-11
lines changed

13 files changed

+268
-11
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const value = 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { value } from '@/foo1'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022", // Comment
4+
"module": "NodeNext",
5+
/**
6+
* Comment
7+
*/
8+
"moduleResolution": "nodenext",
9+
"esModuleInterop": true,
10+
"baseUrl": ".",
11+
"paths": {
12+
"@/*": [
13+
"./lib/*"
14+
]
15+
},
16+
}
17+
}

packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ describe('dependency-parser - parser()', () => {
134134
])
135135
})
136136

137+
it('should parse typescript dependencies relying on tsconfig when tsconfig has comments', async () => {
138+
const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-json-text', ...filepath)
139+
const parser = new Parser({
140+
supportedNpmModules: defaultNpmModules,
141+
})
142+
const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts'))
143+
expect(dependencies.map(d => d.filePath).sort()).toEqual([
144+
toAbsolutePath('lib', 'foo1.ts'),
145+
toAbsolutePath('tsconfig.json'),
146+
])
147+
})
148+
137149
it('should parse typescript dependencies using tsconfig', async () => {
138150
const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-sample-project', ...filepath)
139151
const parser = new Parser({
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { vi, describe, it, expect, beforeAll, afterAll } from 'vitest'
2+
3+
import { FileMeta, SourceFile } from '../source-file'
4+
import { JsonTextSourceFile } from '../json-text-source-file'
5+
6+
const plainJsonFixture = ''
7+
+ `{\n`
8+
+ ` "foo": "bar",\n`
9+
+ ` "trailing-comma": "no"\n`
10+
+ `}\n`
11+
12+
const jsonTextFixture = ''
13+
+ `{\n`
14+
+ ` "foo": "bar", // Foo bar.\n`
15+
+ ` "trailing-comma": "yes",\n`
16+
+ `}\n`
17+
18+
describe('JsonTextSourceFile', () => {
19+
describe('when typescript is NOT available', () => {
20+
beforeAll(() => {
21+
JsonTextSourceFile.reset()
22+
})
23+
24+
beforeAll(() => {
25+
vi.doMock('typescript', () => {
26+
class ModuleNotFoundError extends Error {
27+
code = 'MODULE_NOT_FOUND'
28+
29+
constructor (moduleName: string) {
30+
super(`Cannot find module '${moduleName}'`)
31+
}
32+
}
33+
34+
throw new ModuleNotFoundError('typescript')
35+
})
36+
})
37+
38+
afterAll(() => {
39+
vi.doUnmock('typescript')
40+
})
41+
42+
it('should fail to load a JSON text file', async () => {
43+
const sourceFile = new SourceFile(
44+
FileMeta.fromFilePath('foo.json'),
45+
jsonTextFixture,
46+
)
47+
48+
await expect(JsonTextSourceFile.loadFromSourceFile(sourceFile)).resolves.toBeUndefined()
49+
})
50+
51+
it('should successfully load a plain JSON file', async () => {
52+
const sourceFile = new SourceFile(
53+
FileMeta.fromFilePath('foo.json'),
54+
plainJsonFixture,
55+
)
56+
57+
await expect(JsonTextSourceFile.loadFromSourceFile(sourceFile)).resolves.toEqual(
58+
expect.objectContaining({
59+
data: {
60+
'foo': 'bar',
61+
'trailing-comma': 'no',
62+
},
63+
}),
64+
)
65+
})
66+
})
67+
68+
describe('when typescript is available', () => {
69+
beforeAll(() => {
70+
JsonTextSourceFile.reset()
71+
})
72+
73+
it('should successfully load a JSON text file', async () => {
74+
const sourceFile = new SourceFile(
75+
FileMeta.fromFilePath('foo.json'),
76+
jsonTextFixture,
77+
)
78+
79+
await expect(JsonTextSourceFile.loadFromSourceFile(sourceFile)).resolves.toEqual(
80+
expect.objectContaining({
81+
data: {
82+
'foo': 'bar',
83+
'trailing-comma': 'yes',
84+
},
85+
}),
86+
)
87+
})
88+
89+
it('should successfully load a plain JSON file', async () => {
90+
const sourceFile = new SourceFile(
91+
FileMeta.fromFilePath('foo.json'),
92+
plainJsonFixture,
93+
)
94+
95+
await expect(JsonTextSourceFile.loadFromSourceFile(sourceFile)).resolves.toEqual(
96+
expect.objectContaining({
97+
data: {
98+
'foo': 'bar',
99+
'trailing-comma': 'no',
100+
},
101+
}),
102+
)
103+
})
104+
})
105+
})

packages/cli/src/services/check-parser/package-files/jsconfig-json-file.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export class JSConfigFile extends TSConfigFile {
2121
return path.join(dirPath, JSConfigFile.FILENAME)
2222
}
2323

24-
static loadFromJsonSourceFile (jsonFile: JsonSourceFile<Schema>): JSConfigFile | undefined {
24+
// eslint-disable-next-line require-await
25+
static async loadFromJsonSourceFile (jsonFile: JsonSourceFile<Schema>): Promise<JSConfigFile | undefined> {
2526
return new JSConfigFile(jsonFile)
2627
}
2728
}

packages/cli/src/services/check-parser/package-files/json-source-file.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export class JsonSourceFile<Schema> {
1616
return this.sourceFile.meta
1717
}
1818

19-
static loadFromSourceFile<Schema> (sourceFile: SourceFile): JsonSourceFile<Schema> | undefined {
19+
// eslint-disable-next-line require-await
20+
static async loadFromSourceFile<Schema> (sourceFile: SourceFile): Promise<JsonSourceFile<Schema> | undefined> {
2021
try {
2122
const data: Schema = JSON.parse(sourceFile.contents)
2223

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { FileMeta, SourceFile } from './source-file'
2+
import { SourceFileParserFuncState, SourceFileParser } from './source-file-parser'
3+
4+
class UninitializedJsonTextSourceFileParserState extends SourceFileParser {
5+
private static init?: Promise<void>
6+
7+
async #parser (): Promise<SourceFileParser> {
8+
try {
9+
const { parseJsonText, convertToObject } = await import('typescript')
10+
11+
const parser = new SourceFileParserFuncState(<T>(sourceFile: SourceFile) => {
12+
const errors: any[] = []
13+
const parsed = parseJsonText(sourceFile.meta.filePath, sourceFile.contents)
14+
const object: T = convertToObject(parsed, errors)
15+
return object
16+
})
17+
18+
// Make sure it actually works.
19+
await parser.parseSourceFile(new SourceFile(
20+
FileMeta.fromFilePath('x.json'),
21+
'{} // Comment',
22+
))
23+
24+
return parser
25+
} catch {
26+
return new SourceFileParserFuncState(<T>(sourceFile: SourceFile) => {
27+
const data: T = JSON.parse(sourceFile.contents)
28+
return data
29+
})
30+
}
31+
}
32+
33+
async parseSourceFile<T = unknown> (sourceFile: SourceFile): Promise<T> {
34+
UninitializedJsonTextSourceFileParserState.init ??= (async () => {
35+
JsonTextSourceFileParser.state = await this.#parser()
36+
})()
37+
38+
await UninitializedJsonTextSourceFileParserState.init
39+
40+
return await JsonTextSourceFileParser.state.parseSourceFile(sourceFile)
41+
}
42+
43+
static reset () {
44+
UninitializedJsonTextSourceFileParserState.init = undefined
45+
}
46+
}
47+
48+
export class JsonTextSourceFileParser extends SourceFileParser {
49+
static state: SourceFileParser = new UninitializedJsonTextSourceFileParserState()
50+
51+
async parseSourceFile<T = unknown> (sourceFile: SourceFile): Promise<T> {
52+
return await JsonTextSourceFileParser.state.parseSourceFile<T>(sourceFile)
53+
}
54+
55+
static reset () {
56+
JsonTextSourceFileParser.state = new UninitializedJsonTextSourceFileParserState()
57+
UninitializedJsonTextSourceFileParserState.reset()
58+
}
59+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { JsonSourceFile } from './json-source-file'
2+
import { JsonTextSourceFileParser } from './json-text-source-file-parser'
3+
import { SourceFile } from './source-file'
4+
5+
export class JsonTextSourceFile<Schema> extends JsonSourceFile<Schema> {
6+
static #parser = new JsonTextSourceFileParser()
7+
8+
static async loadFromSourceFile<Schema> (sourceFile: SourceFile): Promise<JsonTextSourceFile<Schema> | undefined> {
9+
try {
10+
const data: Schema = await this.#parser.parseSourceFile(sourceFile)
11+
12+
return new JsonTextSourceFile(sourceFile, data)
13+
} catch {
14+
// Ignore.
15+
}
16+
}
17+
18+
static reset () {
19+
JsonTextSourceFileParser.reset()
20+
}
21+
}

packages/cli/src/services/check-parser/package-files/package-json-file.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ export class PackageJsonFile {
100100
return new PackageJsonFile(jsonFile)
101101
}
102102

103-
static loadFromJsonSourceFile (jsonFile: JsonSourceFile<Schema>): PackageJsonFile | undefined {
103+
// eslint-disable-next-line require-await
104+
static async loadFromJsonSourceFile (jsonFile: JsonSourceFile<Schema>): Promise<PackageJsonFile | undefined> {
104105
return new PackageJsonFile(jsonFile)
105106
}
106107

0 commit comments

Comments
 (0)