Skip to content

Commit b60d88f

Browse files
committed
Allow comments in tsconfig.json issue #4987
1 parent d28acec commit b60d88f

File tree

3 files changed

+179
-2
lines changed

3 files changed

+179
-2
lines changed

Jakefile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ var harnessSources = harnessCoreSources.concat([
145145
"transpile.ts",
146146
"reuseProgramStructure.ts",
147147
"cachingInServerLSHost.ts",
148-
"moduleResolution.ts"
148+
"moduleResolution.ts",
149+
"tsconfigParsing.ts"
149150
].map(function (f) {
150151
return path.join(unittestsDirectory, f);
151152
})).concat([

src/compiler/commandLineParser.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,105 @@ namespace ts {
405405
*/
406406
export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } {
407407
try {
408-
return { config: /\S/.test(jsonText) ? JSON.parse(jsonText) : {} };
408+
let jsonTextWithoutComments = removeComments(jsonText);
409+
return { config: /\S/.test(jsonTextWithoutComments) ? JSON.parse(jsonTextWithoutComments) : {} };
409410
}
410411
catch (e) {
411412
return { error: createCompilerDiagnostic(Diagnostics.Failed_to_parse_file_0_Colon_1, fileName, e.message) };
412413
}
413414
}
414415

416+
417+
/**
418+
* Remove the comments from a json like text.
419+
* Comments can be single line comments (starting with # or //) or multiline comments using / * * /
420+
*
421+
* This method replace comment content by whitespace rather than completely remove them to keep positions in json parsing error reporting accurate.
422+
*/
423+
function removeComments(jsonText: string): string {
424+
let result = "";
425+
let processingString = false;
426+
let processingSingleLineComment = false;
427+
let processingMultiLineComment = false;
428+
for (let i = 0; i < jsonText.length; i++) {
429+
let currentChar = jsonText.charAt(i);
430+
let nextChar = (i + 1 < jsonText.length) ? jsonText.charAt(i + 1) : undefined;
431+
if (processingString) {
432+
if (currentChar === "\\"
433+
&& nextChar === "\"") {
434+
// Escaped quote consume the 2 characters
435+
result += currentChar;
436+
result += nextChar;
437+
i += 1;
438+
}
439+
else if (currentChar === "\"") {
440+
// End of string
441+
result += currentChar;
442+
processingString = false;
443+
}
444+
else {
445+
// String content
446+
result += currentChar;
447+
}
448+
}
449+
else if (processingSingleLineComment) {
450+
if (currentChar === "\n") {
451+
// End of single line comment
452+
processingSingleLineComment = false;
453+
// Keep the line breaks to keep line numbers aligned
454+
result += currentChar;
455+
}
456+
else {
457+
// replace comment content by whitespaces
458+
result += " ";
459+
}
460+
}
461+
else if (processingMultiLineComment) {
462+
if (currentChar === "*" && nextChar === "/") {
463+
// End of comment
464+
result += " ";
465+
i += 1;
466+
processingMultiLineComment = false;
467+
}
468+
else if (currentChar === "\n") {
469+
// Keep the line breaks to Keep line aligned
470+
result += currentChar;
471+
}
472+
else {
473+
// replace comment content by whitespaces
474+
result += " ";
475+
}
476+
}
477+
else if (currentChar === "\"") {
478+
// String start
479+
result += currentChar;
480+
processingString = true;
481+
}
482+
else if (currentChar === "#") {
483+
// Start of # comment
484+
result += " ";
485+
processingSingleLineComment = true;
486+
}
487+
else if (currentChar === "/" && nextChar === "/") {
488+
// Start of // comment
489+
result += " ";
490+
i += 1;
491+
processingSingleLineComment = true;
492+
}
493+
else if (currentChar === "/" && nextChar === "*") {
494+
// Start of /**/ comment
495+
result += " ";
496+
i += 1;
497+
processingMultiLineComment = true;
498+
}
499+
else {
500+
// Keep other characters
501+
result += currentChar;
502+
}
503+
}
504+
return result;
505+
}
506+
415507
/**
416508
* Parse the contents of a config file (tsconfig.json).
417509
* @param json The contents of the config file to parse
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/// <reference path="..\..\..\src\harness\harness.ts" />
2+
/// <reference path="..\..\..\src\compiler\commandLineParser.ts" />
3+
4+
module ts {
5+
describe('parseConfigFileTextToJson', () => {
6+
function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic }) {
7+
let parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
8+
assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject));
9+
}
10+
11+
function assertParseError(jsonText: string) {
12+
let parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
13+
assert.isTrue(undefined === parsed.config);
14+
assert.isTrue(undefined !== parsed.error);
15+
}
16+
17+
it("returns empty config for file with only whitespaces", () => {
18+
assertParseResult("", { config : {} });
19+
assertParseResult(" ", { config : {} });
20+
});
21+
22+
it("returns empty config for file with comments only", () => {
23+
assertParseResult("// Comment", { config: {} });
24+
assertParseResult("# Comment", { config: {} });
25+
assertParseResult("/* Comment*/", { config: {} });
26+
});
27+
28+
it("returns empty config when config is empty object", () => {
29+
assertParseResult("{}", { config: {} });
30+
});
31+
32+
it("returns config object without comments", () => {
33+
assertParseResult(
34+
`{ // Excluded files
35+
"exclude": [
36+
// Exclude d.ts
37+
"file.d.ts"
38+
]
39+
}`, { config: { exclude: ["file.d.ts"] } });
40+
assertParseResult(
41+
`{
42+
# Excluded files
43+
"exclude": [
44+
# Exclude d.ts
45+
"file.d.ts"
46+
]
47+
}`, { config: { exclude: ["file.d.ts"] } });
48+
assertParseResult(
49+
`{
50+
/* Excluded
51+
Files
52+
*/
53+
"exclude": [
54+
/* multiline comments can be in the middle of a line */"file.d.ts"
55+
]
56+
}`, { config: { exclude: ["file.d.ts"] } });
57+
});
58+
59+
it("keeps string content untouched", () => {
60+
assertParseResult(
61+
`{
62+
"exclude": [
63+
"xx//file.d.ts"
64+
]
65+
}`, { config: { exclude: ["xx//file.d.ts"] } });
66+
assertParseResult(
67+
`{
68+
"exclude": [
69+
"xx#file.d.ts"
70+
]
71+
}`, { config: { exclude: ["xx#file.d.ts"] } });
72+
assertParseResult(
73+
`{
74+
"exclude": [
75+
"xx/*file.d.ts*/"
76+
]
77+
}`, { config: { exclude: ["xx/*file.d.ts*/"] } });
78+
});
79+
80+
it("returns object with error when json is invalid", () => {
81+
assertParseError("invalid");
82+
});
83+
});
84+
}

0 commit comments

Comments
 (0)