diff --git a/Gulpfile.ts b/Gulpfile.ts index 74113b4b0c7..3f9b2a66caa 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -650,7 +650,7 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: } if (tests && tests.toLocaleLowerCase() === "rwc") { - testTimeout = 100000; + testTimeout = 400000; } const colors = cmdLineOptions["colors"]; diff --git a/Jakefile.js b/Jakefile.js index a40fa4215e9..432c8c637c8 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -776,7 +776,7 @@ function runConsoleTests(defaultReporter, runInParallel) { } if (tests && tests.toLocaleLowerCase() === "rwc") { - testTimeout = 100000; + testTimeout = 400000; } colors = process.env.colors || process.env.color; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0181c8c78f6..6427b05c37a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13294,8 +13294,8 @@ namespace ts { } } else { - const static = forEach(member.modifiers, m => m.kind === SyntaxKind.StaticKeyword); - const names = static ? staticNames : instanceNames; + const isStatic = forEach(member.modifiers, m => m.kind === SyntaxKind.StaticKeyword); + const names = isStatic ? staticNames : instanceNames; const memberName = member.name && getPropertyNameForPropertyNameNode(member.name); if (memberName) { @@ -14560,7 +14560,10 @@ namespace ts { const local = node.locals[key]; if (!local.isReferenced) { if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { - if (compilerOptions.noUnusedParameters && !isParameterPropertyDeclaration(local.valueDeclaration)) { + const parameter = local.valueDeclaration; + if (compilerOptions.noUnusedParameters && + !isParameterPropertyDeclaration(parameter) && + !parameterNameStartsWithUnderscore(parameter)) { error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } @@ -14573,6 +14576,10 @@ namespace ts { } } + function parameterNameStartsWithUnderscore(parameter: ParameterDeclaration) { + return parameter.name && parameter.name.kind === SyntaxKind.Identifier && (parameter.name).text.charCodeAt(0) === CharacterCodes._; + } + function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression): void { if (compilerOptions.noUnusedLocals && !isInAmbientContext(node)) { if (node.members) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index aa85c32f6cd..7b2aedb5d3d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -891,6 +891,21 @@ namespace ts { */ const invalidMultipleRecursionPatterns = /(^|\/)\*\*\/(.*\/)?\*\*($|\/)/; + /** + * Tests for a path where .. appears after a recursive directory wildcard. + * Matches **\..\*, **\a\..\*, and **\.., but not ..\**\* + * + * NOTE: used \ in place of / above to avoid issues with multiline comments. + * + * Breakdown: + * (^|\/) # matches either the beginning of the string or a directory separator. + * \*\*\/ # matches a recursive directory wildcard "**" followed by a directory separator. + * (.*\/)? # optionally matches any number of characters followed by a directory separator. + * \.\. # matches a parent directory path component ".." + * ($|\/) # matches either the end of the string or a directory separator. + */ + const invalidDotDotAfterRecursiveWildcardPattern = /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/; + /** * Tests for a path containing a wildcard character in a directory component of the path. * Matches \*\, \?\, and \a*b\, but not \a\ or \a\*. @@ -1023,6 +1038,9 @@ namespace ts { else if (invalidMultipleRecursionPatterns.test(spec)) { errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, spec)); } + else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { + errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); + } else { validSpecs.push(spec); } @@ -1052,7 +1070,7 @@ namespace ts { if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { - const name = combinePaths(path, file); + const name = normalizePath(combinePaths(path, file)); if (excludeRegex && excludeRegex.test(name)) { continue; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index ffed136fb09..fc4a0964625 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1073,15 +1073,17 @@ namespace ts { // Storage for literal base paths amongst the include patterns. const includeBasePaths: string[] = []; for (const include of includes) { - if (isRootedDiskPath(include)) { - const wildcardOffset = indexOfAnyCharCode(include, wildcardCharCodes); - const includeBasePath = wildcardOffset < 0 - ? removeTrailingDirectorySeparator(getDirectoryPath(include)) - : include.substring(0, include.lastIndexOf(directorySeparator, wildcardOffset)); + // We also need to check the relative paths by converting them to absolute and normalizing + // in case they escape the base path (e.g "..\somedirectory") + const absolute: string = isRootedDiskPath(include) ? include : normalizePath(combinePaths(path, include)); - // Append the literal and canonical candidate base paths. - includeBasePaths.push(includeBasePath); - } + const wildcardOffset = indexOfAnyCharCode(absolute, wildcardCharCodes); + const includeBasePath = wildcardOffset < 0 + ? removeTrailingDirectorySeparator(getDirectoryPath(absolute)) + : absolute.substring(0, absolute.lastIndexOf(directorySeparator, wildcardOffset)); + + // Append the literal and canonical candidate base paths. + includeBasePaths.push(includeBasePath); } // Sort the offsets array using either the literal or canonical path representations. diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0ee1818ee57..7996f80bd91 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2332,6 +2332,10 @@ "category": "Error", "code": 5064 }, + "File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '{0}'.": { + "category": "Error", + "code": 5065 + }, "Concatenate and emit output to single file.": { "category": "Message", "code": 6001 diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index 81b5e4a672d..25e982cf680 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -8,7 +8,12 @@ interface FileInformation { } interface FindFileResult { +} +interface IOLogFile { + path: string; + codepage: number; + result?: FileInformation; } interface IOLog { @@ -17,11 +22,7 @@ interface IOLog { executingPath: string; currentDirectory: string; useCustomLibraryFile?: boolean; - filesRead: { - path: string; - codepage: number; - result?: FileInformation; - }[]; + filesRead: IOLogFile[]; filesWritten: { path: string; contents: string; @@ -61,7 +62,7 @@ interface IOLog { }[]; directoriesRead: { path: string, - extension: string[], + extensions: string[], exclude: string[], include: string[], result: string[] @@ -170,8 +171,7 @@ namespace Playback { path => callAndRecord(underlying.fileExists(path), recordLog.fileExists, { path }), memoize(path => { // If we read from the file, it must exist - const noResult = {}; - if (findResultByPath(wrapper, replayLog.filesRead, path, noResult) !== noResult) { + if (findFileByPath(wrapper, replayLog.filesRead, path, /*throwFileNotFoundError*/ false)) { return true; } else { @@ -215,16 +215,30 @@ namespace Playback { recordLog.filesRead.push(logEntry); return result; }, - memoize(path => findResultByPath(wrapper, replayLog.filesRead, path).contents)); + memoize(path => findFileByPath(wrapper, replayLog.filesRead, path, /*throwFileNotFoundError*/ true).contents)); wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)( - (path, extension, exclude, include) => { - const result = (underlying).readDirectory(path, extension, exclude, include); - const logEntry = { path, extension, exclude, include, result }; + (path, extensions, exclude, include) => { + const result = (underlying).readDirectory(path, extensions, exclude, include); + const logEntry = { path, extensions, exclude, include, result }; recordLog.directoriesRead.push(logEntry); return result; }, - (path, extension, exclude) => findResultByPath(wrapper, replayLog.directoriesRead, path)); + (path, extensions, exclude) => { + // Because extensions is an array of all allowed extension, we will want to merge each of the replayLog.directoriesRead into one + // if each of the directoriesRead has matched path with the given path (directory with same path but different extension will considered + // different entry). + // TODO (yuisu): We can certainly remove these once we recapture the RWC using new API + const normalizedPath = ts.normalizePath(path).toLowerCase(); + const result: string[] = []; + for (const directory of replayLog.directoriesRead) { + if (ts.normalizeSlashes(directory.path).toLowerCase() === normalizedPath) { + result.push(...directory.result); + } + } + + return result; + }); wrapper.writeFile = recordReplay(wrapper.writeFile, underlying)( (path: string, contents: string) => callAndRecord(underlying.writeFile(path, contents), recordLog.filesWritten, { path, contents, bom: false }), @@ -279,30 +293,22 @@ namespace Playback { return results[0].result; } - function findResultByPath(wrapper: { resolvePath(s: string): string }, logArray: { path: string; result?: T }[], expectedPath: string, defaultValue?: T): T { + function findFileByPath(wrapper: { resolvePath(s: string): string }, logArray: IOLogFile[], + expectedPath: string, throwFileNotFoundError: boolean): FileInformation { const normalizedName = ts.normalizePath(expectedPath).toLowerCase(); // Try to find the result through normal fileName - for (let i = 0; i < logArray.length; i++) { - if (ts.normalizeSlashes(logArray[i].path).toLowerCase() === normalizedName) { - return logArray[i].result; - } - } - // Fallback, try to resolve the target paths as well - if (replayLog.pathsResolved.length > 0) { - const normalizedResolvedName = wrapper.resolvePath(expectedPath).toLowerCase(); - for (let i = 0; i < logArray.length; i++) { - if (wrapper.resolvePath(logArray[i].path).toLowerCase() === normalizedResolvedName) { - return logArray[i].result; - } + for (const log of logArray) { + if (ts.normalizeSlashes(log.path).toLowerCase() === normalizedName) { + return log.result; } } // If we got here, we didn't find a match - if (defaultValue === undefined) { + if (throwFileNotFoundError) { throw new Error("No matching result in log array for path: " + expectedPath); } else { - return defaultValue; + return undefined; } } diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 4f657d4b52b..54ae1209cb3 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1255,33 +1255,13 @@ declare type PromiseConstructorLike = new (executor: (resolve: (value?: T | P interface PromiseLike { /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled: (value: T) => TResult1 | PromiseLike, onrejected: (reason: any) => TResult2 | PromiseLike): PromiseLike; - - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled: (value: T) => TResult | PromiseLike, onrejected: (reason: any) => TResult | PromiseLike): PromiseLike; - - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled: (value: T) => TResult | PromiseLike): PromiseLike; - - /** - * Creates a new Promise with the same internal state of this Promise. - * @returns A Promise. - */ - then(): PromiseLike; + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike; + then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): PromiseLike; } interface ArrayLike { diff --git a/tests/baselines/reference/unusedParametersWithUnderscore.errors.txt b/tests/baselines/reference/unusedParametersWithUnderscore.errors.txt new file mode 100644 index 00000000000..adfe7bb9bc1 --- /dev/null +++ b/tests/baselines/reference/unusedParametersWithUnderscore.errors.txt @@ -0,0 +1,56 @@ +tests/cases/compiler/unusedParametersWithUnderscore.ts(2,12): error TS6133: 'a' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(2,19): error TS6133: 'c' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(2,27): error TS6133: 'd' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(2,29): error TS6133: 'e___' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(6,14): error TS6133: '_a' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(6,18): error TS6133: '___b' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(9,14): error TS6133: '_a' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(9,19): error TS6133: '___b' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(12,16): error TS6133: 'arg' is declared but never used. +tests/cases/compiler/unusedParametersWithUnderscore.ts(18,13): error TS6133: 'arg' is declared but never used. + + +==== tests/cases/compiler/unusedParametersWithUnderscore.ts (10 errors) ==== + + function f(a, _b, c, ___, d,e___, _f) { + ~ +!!! error TS6133: 'a' is declared but never used. + ~ +!!! error TS6133: 'c' is declared but never used. + ~ +!!! error TS6133: 'd' is declared but never used. + ~~~~ +!!! error TS6133: 'e___' is declared but never used. + } + + + function f2({_a, __b}) { + ~~ +!!! error TS6133: '_a' is declared but never used. + ~~~ +!!! error TS6133: '___b' is declared but never used. + } + + function f3([_a, ,__b]) { + ~~ +!!! error TS6133: '_a' is declared but never used. + ~~~ +!!! error TS6133: '___b' is declared but never used. + } + + function f4(...arg) { + ~~~ +!!! error TS6133: 'arg' is declared but never used. + } + + function f5(..._arg) { + } + + function f6(arg?, _arg?) { + ~~~ +!!! error TS6133: 'arg' is declared but never used. + } + + var f7 = _ => undefined; + + var f8 = function (_) { }; \ No newline at end of file diff --git a/tests/baselines/reference/unusedParametersWithUnderscore.js b/tests/baselines/reference/unusedParametersWithUnderscore.js new file mode 100644 index 00000000000..2899d347c66 --- /dev/null +++ b/tests/baselines/reference/unusedParametersWithUnderscore.js @@ -0,0 +1,50 @@ +//// [unusedParametersWithUnderscore.ts] + +function f(a, _b, c, ___, d,e___, _f) { +} + + +function f2({_a, __b}) { +} + +function f3([_a, ,__b]) { +} + +function f4(...arg) { +} + +function f5(..._arg) { +} + +function f6(arg?, _arg?) { +} + +var f7 = _ => undefined; + +var f8 = function (_) { }; + +//// [unusedParametersWithUnderscore.js] +function f(a, _b, c, ___, d, e___, _f) { +} +function f2(_c) { + var _a = _c._a, __b = _c.__b; +} +function f3(_c) { + var _a = _c[0], __b = _c[2]; +} +function f4() { + var arg = []; + for (var _i = 0; _i < arguments.length; _i++) { + arg[_i - 0] = arguments[_i]; + } +} +function f5() { + var _arg = []; + for (var _i = 0; _i < arguments.length; _i++) { + _arg[_i - 0] = arguments[_i]; + } +} +function f6(arg, _arg) { +} +var f7 = function (_) { return undefined; }; +var f8 = function (_) { }; diff --git a/tests/cases/compiler/unusedParametersWithUnderscore.ts b/tests/cases/compiler/unusedParametersWithUnderscore.ts new file mode 100644 index 00000000000..e7382e5c2af --- /dev/null +++ b/tests/cases/compiler/unusedParametersWithUnderscore.ts @@ -0,0 +1,25 @@ +//@noUnusedLocals:true +//@noUnusedParameters:true + +function f(a, _b, c, ___, d,e___, _f) { +} + + +function f2({_a, __b}) { +} + +function f3([_a, ,__b]) { +} + +function f4(...arg) { +} + +function f5(..._arg) { +} + +function f6(arg?, _arg?) { +} + +var f7 = _ => undefined; + +var f8 = function (_) { }; \ No newline at end of file diff --git a/tests/cases/unittests/matchFiles.ts b/tests/cases/unittests/matchFiles.ts index b9c538a9e14..d68fb9b2a7f 100644 --- a/tests/cases/unittests/matchFiles.ts +++ b/tests/cases/unittests/matchFiles.ts @@ -24,7 +24,8 @@ namespace ts { "c:/dev/x/y/b.ts", "c:/dev/js/a.js", "c:/dev/js/b.js", - "c:/ext/ext.ts" + "c:/ext/ext.ts", + "c:/ext/b/a..b.ts" ]); const caseSensitiveBasePath = "/dev/"; @@ -740,7 +741,7 @@ namespace ts { "c:/dev/a.ts", "c:/dev/b.ts", "c:/dev/c.d.ts", - "c:/ext/ext.ts", + "c:/ext/ext.ts" ], wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.None, @@ -752,6 +753,97 @@ namespace ts { assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); assert.deepEqual(actual.errors, expected.errors); }); + it("include paths outside of the project using relative paths", () => { + const json = { + include: [ + "*", + "../ext/*" + ], + exclude: [ + "**" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/ext.ts" + ], + wildcardDirectories: { + "c:/ext": ts.WatchDirectoryFlags.None + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + it("exclude paths outside of the project using relative paths", () => { + const json = { + include: [ + "c:/**/*" + ], + exclude: [ + "../**" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [], + wildcardDirectories: {} + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + it("include files with .. in their name", () => { + const json = { + include: [ + "c:/ext/b/a..b.ts" + ], + exclude: [ + "**" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/b/a..b.ts" + ], + wildcardDirectories: {} + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + it("exclude files with .. in their name", () => { + const json = { + include: [ + "c:/ext/**/*" + ], + exclude: [ + "c:/ext/b/a..b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/ext/ext.ts", + ], + wildcardDirectories: { + "c:/ext": ts.WatchDirectoryFlags.Recursive + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); it("with jsx=none, allowJs=false", () => { const json = { compilerOptions: { @@ -951,6 +1043,108 @@ namespace ts { assert.deepEqual(actual.errors, expected.errors); }); }); + + describe("with parent directory symbols after a recursive directory pattern", () => { + it("in includes immediately after", () => { + const json = { + include: [ + "**/../*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*") + ], + fileNames: [], + wildcardDirectories: {} + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + + it("in includes after a subdirectory", () => { + const json = { + include: [ + "**/y/../*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*") + ], + fileNames: [], + wildcardDirectories: {} + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + + it("in excludes immediately after", () => { + const json = { + include: [ + "**/a.ts" + ], + exclude: [ + "**/.." + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") + ], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + + it("in excludes after a subdirectory", () => { + const json = { + include: [ + "**/a.ts" + ], + exclude: [ + "**/y/.." + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [ + ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") + ], + fileNames: [ + "c:/dev/a.ts", + "c:/dev/x/a.ts", + "c:/dev/x/y/a.ts", + "c:/dev/z/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + }); }); }); } \ No newline at end of file