diff --git a/Jakefile.js b/Jakefile.js
index a602a2c459f02..db6b5f5567105 100644
--- a/Jakefile.js
+++ b/Jakefile.js
@@ -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([
diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts
index 84496fdbeb313..aaf3bf624b691 100644
--- a/src/compiler/commandLineParser.ts
+++ b/src/compiler/commandLineParser.ts
@@ -405,13 +405,41 @@ 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 output = "";
+ let scanner = createScanner(ScriptTarget.ES5, /* skipTrivia */ false, LanguageVariant.Standard, jsonText);
+ let token: SyntaxKind;
+ while ((token = scanner.scan()) !== SyntaxKind.EndOfFileToken) {
+ switch (token) {
+ case SyntaxKind.SingleLineCommentTrivia:
+ case SyntaxKind.MultiLineCommentTrivia:
+ // replace comments with whitespace to preserve original character positions
+ output += scanner.getTokenText().replace(/\S/g, " ");
+ break;
+ default:
+ output += scanner.getTokenText();
+ break;
+ }
+ }
+ return output;
+ }
+
+
/**
* Parse the contents of a config file (tsconfig.json).
* @param json The contents of the config file to parse
diff --git a/tests/cases/unittests/tsconfigParsing.ts b/tests/cases/unittests/tsconfigParsing.ts
new file mode 100644
index 0000000000000..3603d22f31457
--- /dev/null
+++ b/tests/cases/unittests/tsconfigParsing.ts
@@ -0,0 +1,86 @@
+///
+///
+
+namespace 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: {} });
+ });
+
+ 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": [
+ /* 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*/"] } });
+ });
+
+ it("handles escaped characters in strings correctly", () => {
+ assertParseResult(
+ `{
+ "exclude": [
+ "xx\\"//files"
+ ]
+ }`, { config: { exclude: ["xx\"//files"] } });
+
+ assertParseResult(
+ `{
+ "exclude": [
+ "xx\\\\" // end of line comment
+ ]
+ }`, { config: { exclude: ["xx\\"] } });
+ });
+
+ it("returns object with error when json is invalid", () => {
+ assertParseError("invalid");
+ });
+ });
+}