1namespace ts { 2 describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", () => { 3 function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { 4 const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); 5 assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); 6 } 7 8 function assertParseErrorWithExcludesKeyword(jsonText: string) { 9 { 10 const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); 11 const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "tests/cases/unittests"); 12 assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && 13 parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); 14 } 15 { 16 const parsed = parseJsonText("/apath/tsconfig.json", jsonText); 17 const parsedCommand = parseJsonSourceFileConfigFileContent(parsed, sys, "tests/cases/unittests"); 18 assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && 19 parsedCommand.errors[0].code === Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); 20 } 21 } 22 23 function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { 24 const parsed = parseConfigFileTextToJson(configFileName, jsonText); 25 const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); 26 const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); 27 return parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); 28 } 29 30 function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { 31 const parsed = parseJsonText(configFileName, jsonText); 32 const files = allFileList.reduce((files, value) => (files[value] = "", files), {} as vfs.FileSet); 33 const host: ParseConfigHost = new fakes.ParseConfigHost(new vfs.FileSystem(/*ignoreCase*/ false, { cwd: basePath, files: { "/": {}, ...files } })); 34 return parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); 35 } 36 37 function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { 38 { 39 const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); 40 assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); 41 } 42 { 43 const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); 44 assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); 45 } 46 } 47 48 function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { 49 { 50 const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); 51 assert.isTrue(parsed.errors.length >= 0); 52 assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); 53 } 54 { 55 const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); 56 assert.isTrue(parsed.errors.length >= 0); 57 assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); 58 if (!noLocation) { 59 assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); 60 } 61 } 62 } 63 64 function assertParseFileDiagnosticsExclusion(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedExcludedDiagnosticCode: number) { 65 { 66 const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); 67 assert.isTrue(parsed.errors.length >= 0); 68 assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); 69 } 70 { 71 const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); 72 assert.isTrue(parsed.errors.length >= 0); 73 assert.isTrue(parsed.errors.findIndex(e => e.code === expectedExcludedDiagnosticCode) === -1, `Expected error code ${expectedExcludedDiagnosticCode} to not be in ${JSON.stringify(parsed.errors)}`); 74 } 75 } 76 77 it("returns empty config for file with only whitespaces", () => { 78 assertParseResult("", { config : {} }); 79 assertParseResult(" ", { config : {} }); 80 }); 81 82 it("returns empty config for file with comments only", () => { 83 assertParseResult("// Comment", { config: {} }); 84 assertParseResult("/* Comment*/", { config: {} }); 85 }); 86 87 it("returns empty config when config is empty object", () => { 88 assertParseResult("{}", { config: {} }); 89 }); 90 91 it("returns config object without comments", () => { 92 assertParseResult( 93 `{ // Excluded files 94 "exclude": [ 95 // Exclude d.ts 96 "file.d.ts" 97 ] 98 }`, { config: { exclude: ["file.d.ts"] } }); 99 100 assertParseResult( 101 `{ 102 /* Excluded 103 Files 104 */ 105 "exclude": [ 106 /* multiline comments can be in the middle of a line */"file.d.ts" 107 ] 108 }`, { config: { exclude: ["file.d.ts"] } }); 109 }); 110 111 it("keeps string content untouched", () => { 112 assertParseResult( 113 `{ 114 "exclude": [ 115 "xx//file.d.ts" 116 ] 117 }`, { config: { exclude: ["xx//file.d.ts"] } }); 118 assertParseResult( 119 `{ 120 "exclude": [ 121 "xx/*file.d.ts*/" 122 ] 123 }`, { config: { exclude: ["xx/*file.d.ts*/"] } }); 124 }); 125 126 it("handles escaped characters in strings correctly", () => { 127 assertParseResult( 128 `{ 129 "exclude": [ 130 "xx\\"//files" 131 ] 132 }`, { config: { exclude: ["xx\"//files"] } }); 133 134 assertParseResult( 135 `{ 136 "exclude": [ 137 "xx\\\\" // end of line comment 138 ] 139 }`, { config: { exclude: ["xx\\"] } }); 140 }); 141 142 it("returns object with error when json is invalid", () => { 143 const parsed = parseConfigFileTextToJson("/apath/tsconfig.json", "invalid"); 144 assert.deepEqual(parsed.config, {}); 145 const expected = createCompilerDiagnostic(Diagnostics._0_expected, "{"); 146 const error = parsed.error!; 147 assert.equal(error.messageText, expected.messageText); 148 assert.equal(error.category, expected.category); 149 assert.equal(error.code, expected.code); 150 assert.equal(error.start, 0); 151 assert.equal(error.length, "invalid".length); 152 }); 153 154 it("returns object when users correctly specify library", () => { 155 assertParseResult( 156 `{ 157 "compilerOptions": { 158 "lib": ["es5"] 159 } 160 }`, { 161 config: { compilerOptions: { lib: ["es5"] } } 162 }); 163 164 assertParseResult( 165 `{ 166 "compilerOptions": { 167 "lib": ["es5", "es6"] 168 } 169 }`, { 170 config: { compilerOptions: { lib: ["es5", "es6"] } } 171 }); 172 }); 173 174 it("returns error when tsconfig have excludes", () => { 175 assertParseErrorWithExcludesKeyword( 176 `{ 177 "compilerOptions": { 178 "lib": ["es5"] 179 }, 180 "excludes": [ 181 "foge.ts" 182 ] 183 }`); 184 }); 185 186 it("ignore dotted files and folders", () => { 187 assertParseFileList( 188 `{}`, 189 "tsconfig.json", 190 "/apath", 191 ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], 192 ["/apath/test.ts"] 193 ); 194 }); 195 196 it("allow dotted files and folders when explicitly requested", () => { 197 assertParseFileList( 198 `{ 199 "files": ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] 200 }`, 201 "tsconfig.json", 202 "/apath", 203 ["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"], 204 ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"] 205 ); 206 }); 207 208 it("exclude outDir unless overridden", () => { 209 const tsconfigWithoutExclude = 210 `{ 211 "compilerOptions": { 212 "outDir": "bin" 213 } 214 }`; 215 const tsconfigWithExclude = 216 `{ 217 "compilerOptions": { 218 "outDir": "bin" 219 }, 220 "exclude": [ "obj" ] 221 }`; 222 const rootDir = "/"; 223 const allFiles = ["/bin/a.ts", "/b.ts"]; 224 const expectedFiles = ["/b.ts"]; 225 assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); 226 assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); 227 }); 228 229 it("exclude declarationDir unless overridden", () => { 230 const tsconfigWithoutExclude = 231 `{ 232 "compilerOptions": { 233 "declarationDir": "declarations" 234 } 235 }`; 236 const tsconfigWithExclude = 237 `{ 238 "compilerOptions": { 239 "declarationDir": "declarations" 240 }, 241 "exclude": [ "types" ] 242 }`; 243 244 const rootDir = "/"; 245 const allFiles = ["/declarations/a.d.ts", "/a.ts"]; 246 const expectedFiles = ["/a.ts"]; 247 248 assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles); 249 assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles); 250 }); 251 252 it("implicitly exclude common package folders", () => { 253 assertParseFileList( 254 `{}`, 255 "tsconfig.json", 256 "/", 257 ["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"], 258 ["/d.ts", "/folder/e.ts"] 259 ); 260 }); 261 262 it("parse and re-emit tsconfig.json file with diagnostics", () => { 263 const content = `{ 264 "compilerOptions": { 265 "allowJs": true 266 // Some comments 267 "outDir": "bin" 268 } 269 "files": ["file1.ts"] 270 }`; 271 const result = parseJsonText("config.json", content); 272 const diagnostics = result.parseDiagnostics; 273 const configJsonObject = convertToObject(result, diagnostics); 274 const expectedResult = { 275 compilerOptions: { 276 allowJs: true, 277 outDir: "bin" 278 }, 279 files: ["file1.ts"] 280 }; 281 assert.isTrue(diagnostics.length === 2); 282 assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult)); 283 }); 284 285 it("generates errors for empty files list", () => { 286 const content = `{ 287 "files": [] 288 }`; 289 assertParseFileDiagnostics(content, 290 "/apath/tsconfig.json", 291 "tests/cases/unittests", 292 ["/apath/a.ts"], 293 Diagnostics.The_files_list_in_config_file_0_is_empty.code); 294 }); 295 296 it("generates errors for empty files list when no references are provided", () => { 297 const content = `{ 298 "files": [], 299 "references": [] 300 }`; 301 assertParseFileDiagnostics(content, 302 "/apath/tsconfig.json", 303 "tests/cases/unittests", 304 ["/apath/a.ts"], 305 Diagnostics.The_files_list_in_config_file_0_is_empty.code); 306 }); 307 308 it("does not generate errors for empty files list when one or more references are provided", () => { 309 const content = `{ 310 "files": [], 311 "references": [{ "path": "/apath" }] 312 }`; 313 assertParseFileDiagnosticsExclusion(content, 314 "/apath/tsconfig.json", 315 "tests/cases/unittests", 316 ["/apath/a.ts"], 317 Diagnostics.The_files_list_in_config_file_0_is_empty.code); 318 }); 319 320 it("generates errors for directory with no .ts files", () => { 321 const content = `{ 322 }`; 323 assertParseFileDiagnostics(content, 324 "/apath/tsconfig.json", 325 "tests/cases/unittests", 326 ["/apath/a.js"], 327 Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, 328 /*noLocation*/ true); 329 }); 330 331 it("generates errors for empty directory", () => { 332 const content = `{ 333 "compilerOptions": { 334 "allowJs": true 335 } 336 }`; 337 assertParseFileDiagnostics(content, 338 "/apath/tsconfig.json", 339 "tests/cases/unittests", 340 [], 341 Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, 342 /*noLocation*/ true); 343 }); 344 345 it("generates errors for empty include", () => { 346 const content = `{ 347 "include": [] 348 }`; 349 assertParseFileDiagnostics(content, 350 "/apath/tsconfig.json", 351 "tests/cases/unittests", 352 ["/apath/a.ts"], 353 Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, 354 /*noLocation*/ true); 355 }); 356 357 it("generates errors for includes with outDir", () => { 358 const content = `{ 359 "compilerOptions": { 360 "outDir": "./" 361 }, 362 "include": ["**/*"] 363 }`; 364 assertParseFileDiagnostics(content, 365 "/apath/tsconfig.json", 366 "tests/cases/unittests", 367 ["/apath/a.ts"], 368 Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, 369 /*noLocation*/ true); 370 }); 371 372 373 it("generates errors for when invalid comment type present in tsconfig", () => { 374 const jsonText = `{ 375 "compilerOptions": { 376 ## this comment does cause issues 377 "types" : [ 378 ] 379 } 380 }`; 381 const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]); 382 assert.isTrue(parsed.errors.length >= 0); 383 }); 384 385 it("generates errors when files is not string", () => { 386 assertParseFileDiagnostics( 387 JSON.stringify({ 388 files: [{ 389 compilerOptions: { 390 experimentalDecorators: true, 391 allowJs: true 392 } 393 }] 394 }), 395 "/apath/tsconfig.json", 396 "tests/cases/unittests", 397 ["/apath/a.ts"], 398 Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, 399 /*noLocation*/ true); 400 }); 401 402 it("generates errors when include is not string", () => { 403 assertParseFileDiagnostics( 404 JSON.stringify({ 405 include: [ 406 ["./**/*.ts"] 407 ] 408 }), 409 "/apath/tsconfig.json", 410 "tests/cases/unittests", 411 ["/apath/a.ts"], 412 Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code, 413 /*noLocation*/ true); 414 }); 415 416 it("parses wildcard directories even when parent directories have dots", () => { 417 const parsed = parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({ 418 include: ["src"] 419 })); 420 421 const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "/foo.bar"); 422 assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo.bar/src": WatchDirectoryFlags.Recursive }); 423 }); 424 425 it("correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", () => { 426 const parsed = parseConfigFileTextToJson("/foo.bar/tsconfig.json", JSON.stringify({ 427 include: ["./", "./**/*.json"] 428 })); 429 430 const parsedCommand = parseJsonConfigFileContent(parsed.config, sys, "/foo"); 431 assert.deepEqual(parsedCommand.wildcardDirectories, { "/foo": WatchDirectoryFlags.Recursive }); 432 }); 433 }); 434} 435