Skip to content

Commit

Permalink
Allow comments in tsconfig.json issue microsoft#4987
Browse files Browse the repository at this point in the history
  • Loading branch information
sarod committed Oct 29, 2015
1 parent d28acec commit b60d88f
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 2 deletions.
3 changes: 2 additions & 1 deletion Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ var harnessSources = harnessCoreSources.concat([
"transpile.ts",
"reuseProgramStructure.ts",
"cachingInServerLSHost.ts",
"moduleResolution.ts"
"moduleResolution.ts",
"tsconfigParsing.ts"
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
Expand Down
94 changes: 93 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,13 +405,105 @@ namespace ts {
*/
export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } {
try {
return { config: /\S/.test(jsonText) ? JSON.parse(jsonText) : {} };
let jsonTextWithoutComments = removeComments(jsonText);
return { config: /\S/.test(jsonTextWithoutComments) ? JSON.parse(jsonTextWithoutComments) : {} };
}
catch (e) {
return { error: createCompilerDiagnostic(Diagnostics.Failed_to_parse_file_0_Colon_1, fileName, e.message) };
}
}


/**
* Remove the comments from a json like text.
* Comments can be single line comments (starting with # or //) or multiline comments using / * * /
*
* This method replace comment content by whitespace rather than completely remove them to keep positions in json parsing error reporting accurate.
*/
function removeComments(jsonText: string): string {
let result = "";
let processingString = false;
let processingSingleLineComment = false;
let processingMultiLineComment = false;
for (let i = 0; i < jsonText.length; i++) {
let currentChar = jsonText.charAt(i);
let nextChar = (i + 1 < jsonText.length) ? jsonText.charAt(i + 1) : undefined;
if (processingString) {
if (currentChar === "\\"
&& nextChar === "\"") {
// Escaped quote consume the 2 characters
result += currentChar;
result += nextChar;
i += 1;
}
else if (currentChar === "\"") {
// End of string
result += currentChar;
processingString = false;
}
else {
// String content
result += currentChar;
}
}
else if (processingSingleLineComment) {
if (currentChar === "\n") {
// End of single line comment
processingSingleLineComment = false;
// Keep the line breaks to keep line numbers aligned
result += currentChar;
}
else {
// replace comment content by whitespaces
result += " ";
}
}
else if (processingMultiLineComment) {
if (currentChar === "*" && nextChar === "/") {
// End of comment
result += " ";
i += 1;
processingMultiLineComment = false;
}
else if (currentChar === "\n") {
// Keep the line breaks to Keep line aligned
result += currentChar;
}
else {
// replace comment content by whitespaces
result += " ";
}
}
else if (currentChar === "\"") {
// String start
result += currentChar;
processingString = true;
}
else if (currentChar === "#") {
// Start of # comment
result += " ";
processingSingleLineComment = true;
}
else if (currentChar === "/" && nextChar === "/") {
// Start of // comment
result += " ";
i += 1;
processingSingleLineComment = true;
}
else if (currentChar === "/" && nextChar === "*") {
// Start of /**/ comment
result += " ";
i += 1;
processingMultiLineComment = true;
}
else {
// Keep other characters
result += currentChar;
}
}
return result;
}

/**
* Parse the contents of a config file (tsconfig.json).
* @param json The contents of the config file to parse
Expand Down
84 changes: 84 additions & 0 deletions tests/cases/unittests/tsconfigParsing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/// <reference path="..\..\..\src\harness\harness.ts" />
/// <reference path="..\..\..\src\compiler\commandLineParser.ts" />

module ts {
describe('parseConfigFileTextToJson', () => {
function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic }) {
let parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject));
}

function assertParseError(jsonText: string) {
let parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
assert.isTrue(undefined === parsed.config);
assert.isTrue(undefined !== parsed.error);
}

it("returns empty config for file with only whitespaces", () => {
assertParseResult("", { config : {} });
assertParseResult(" ", { config : {} });
});

it("returns empty config for file with comments only", () => {
assertParseResult("// Comment", { config: {} });
assertParseResult("# Comment", { config: {} });
assertParseResult("/* Comment*/", { config: {} });
});

it("returns empty config when config is empty object", () => {
assertParseResult("{}", { config: {} });
});

it("returns config object without comments", () => {
assertParseResult(
`{ // Excluded files
"exclude": [
// Exclude d.ts
"file.d.ts"
]
}`, { config: { exclude: ["file.d.ts"] } });
assertParseResult(
`{
# Excluded files
"exclude": [
# Exclude d.ts
"file.d.ts"
]
}`, { config: { exclude: ["file.d.ts"] } });
assertParseResult(
`{
/* Excluded
Files
*/
"exclude": [
/* multiline comments can be in the middle of a line */"file.d.ts"
]
}`, { config: { exclude: ["file.d.ts"] } });
});

it("keeps string content untouched", () => {
assertParseResult(
`{
"exclude": [
"xx//file.d.ts"
]
}`, { config: { exclude: ["xx//file.d.ts"] } });
assertParseResult(
`{
"exclude": [
"xx#file.d.ts"
]
}`, { config: { exclude: ["xx#file.d.ts"] } });
assertParseResult(
`{
"exclude": [
"xx/*file.d.ts*/"
]
}`, { config: { exclude: ["xx/*file.d.ts*/"] } });
});

it("returns object with error when json is invalid", () => {
assertParseError("invalid");
});
});
}

0 comments on commit b60d88f

Please sign in to comment.