diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7b2aedb5d3ddf..72fd5ff6fd7a7 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -689,9 +689,6 @@ namespace ts { return output; } - // Skip over any minified JavaScript files (ending in ".min.js") - // Skip over dotted files and folders as well - const ignoreFileNamePattern = /(\.min\.js$)|([\\/]\.[\w.])/; /** * Parse the contents of a config file (tsconfig.json). * @param json The contents of the config file to parse @@ -1003,10 +1000,6 @@ namespace ts { continue; } - if (ignoreFileNamePattern.test(file)) { - continue; - } - // We may have included a wildcard path with a lower priority // extension due to the user-defined order of entries in the // "include" array. If there is a lower priority extension in the diff --git a/src/compiler/core.ts b/src/compiler/core.ts index fe4731a1c9144..98d9495b6366a 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -922,11 +922,29 @@ namespace ts { const reservedCharacterPattern = /[^\w\s\/]/g; const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question]; + /** + * Matches any single directory segment unless it is the last segment and a .min.js file + * Breakdown: + * [^./] # matches everything up to the first . character (excluding directory seperators) + * (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension + */ + const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*"; + const singleAsteriskRegexFragmentOther = "[^/]*"; + export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") { if (specs === undefined || specs.length === 0) { return undefined; } + const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther; + const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther; + + /** + * Regex for the ** wildcard. Matches any number of subdirectories. When used for including + * files or directories, does not match subdirectories that start with a . character + */ + const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?"; + let pattern = ""; let hasWrittenSubpattern = false; spec: for (const spec of specs) { @@ -947,13 +965,13 @@ namespace ts { components[0] = removeTrailingDirectorySeparator(components[0]); let optionalCount = 0; - for (const component of components) { + for (let component of components) { if (component === "**") { if (hasRecursiveDirectoryWildcard) { continue spec; } - subpattern += "(/.+?)?"; + subpattern += doubleAsteriskRegexFragment; hasRecursiveDirectoryWildcard = true; hasWrittenComponent = true; } @@ -967,6 +985,20 @@ namespace ts { subpattern += directorySeparator; } + if (usage !== "exclude") { + // The * and ? wildcards should not match directories or files that start with . if they + // appear first in a component. Dotted directories and files can be included explicitly + // like so: **/.*/.* + if (component.charCodeAt(0) === CharacterCodes.asterisk) { + subpattern += "([^./]" + singleAsteriskRegexFragment + ")?"; + component = component.substr(1); + } + else if (component.charCodeAt(0) === CharacterCodes.question) { + subpattern += "[^./]"; + component = component.substr(1); + } + } + subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter); hasWrittenComponent = true; } @@ -992,8 +1024,16 @@ namespace ts { return "^(" + pattern + (usage === "exclude" ? ")($|/)" : ")$"); } - function replaceWildcardCharacter(match: string) { - return match === "*" ? "[^/]*" : match === "?" ? "[^/]" : "\\" + match; + function replaceWildCardCharacterFiles(match: string) { + return replaceWildcardCharacter(match, singleAsteriskRegexFragmentFiles); + } + + function replaceWildCardCharacterOther(match: string) { + return replaceWildcardCharacter(match, singleAsteriskRegexFragmentOther); + } + + function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) { + return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match; } export interface FileSystemEntries { diff --git a/tests/cases/unittests/matchFiles.ts b/tests/cases/unittests/matchFiles.ts index d68fb9b2a7f24..e862cdc379ed9 100644 --- a/tests/cases/unittests/matchFiles.ts +++ b/tests/cases/unittests/matchFiles.ts @@ -24,6 +24,8 @@ namespace ts { "c:/dev/x/y/b.ts", "c:/dev/js/a.js", "c:/dev/js/b.js", + "c:/dev/js/d.min.js", + "c:/dev/js/ab.min.js", "c:/ext/ext.ts", "c:/ext/b/a..b.ts" ]); @@ -76,6 +78,17 @@ namespace ts { "c:/dev/jspm_packages/a.ts" ]); + const caseInsensitiveDottedFoldersHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [ + "c:/dev/x/d.ts", + "c:/dev/x/y/d.ts", + "c:/dev/x/y/.e.ts", + "c:/dev/x/.y/a.ts", + "c:/dev/.z/.b.ts", + "c:/dev/.z/c.ts", + "c:/dev/w/.u/e.ts", + "c:/dev/g.min.js/.g/g.ts" + ]); + describe("matchFiles", () => { describe("with literal file list", () => { it("without exclusions", () => { @@ -727,6 +740,33 @@ namespace ts { assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); assert.deepEqual(actual.errors, expected.errors); }); + it("include explicitly listed .min.js files when allowJs=true", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*.min.js" + ] + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/ab.min.js", + "c:/dev/js/d.min.js" + ], + wildcardDirectories: { + "c:/dev/js": 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("include paths outside of the project", () => { const json = { include: [ @@ -952,6 +992,35 @@ namespace ts { assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); assert.deepEqual(actual.errors, expected.errors); }); + it("exclude .min.js files using wildcards", () => { + const json = { + compilerOptions: { + allowJs: true + }, + include: [ + "js/*.min.js" + ], + exclude: [ + "js/a*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: { + allowJs: true + }, + errors: [], + fileNames: [ + "c:/dev/js/d.min.js" + ], + wildcardDirectories: { + "c:/dev/js": 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); + }); describe("with trailing recursive directory", () => { it("in includes", () => { const json = { @@ -1146,5 +1215,122 @@ namespace ts { }); }); }); + describe("with files or folders that begin with a .", () => { + it("that are not explicitly included", () => { + const json = { + include: [ + "x/**/*", + "w/*/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/x/d.ts", + "c:/dev/x/y/d.ts", + ], + wildcardDirectories: { + "c:/dev/x": ts.WatchDirectoryFlags.Recursive, + "c:/dev/w": ts.WatchDirectoryFlags.Recursive + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + describe("that are explicitly included", () => { + it("without wildcards", () => { + const json = { + include: [ + "x/.y/a.ts", + "c:/dev/.z/.b.ts" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/.z/.b.ts", + "c:/dev/x/.y/a.ts" + ], + wildcardDirectories: {} + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + it("with recursive wildcards that match directories", () => { + const json = { + include: [ + "**/.*/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/.z/c.ts", + "c:/dev/g.min.js/.g/g.ts", + "c:/dev/w/.u/e.ts", + "c:/dev/x/.y/a.ts" + ], + wildcardDirectories: { + "c:/dev": ts.WatchDirectoryFlags.Recursive + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + it("with recursive wildcards that match nothing", () => { + const json = { + include: [ + "x/**/.y/*", + ".z/**/.*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [ + "c:/dev/.z/.b.ts", + "c:/dev/x/.y/a.ts" + ], + wildcardDirectories: { + "c:/dev/.z": ts.WatchDirectoryFlags.Recursive, + "c:/dev/x": ts.WatchDirectoryFlags.Recursive + } + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + }); + it("with wildcard excludes that implicitly exclude dotted files", () => { + const json = { + include: [ + "**/.*/*" + ], + exclude: [ + "**/*" + ] + }; + const expected: ts.ParsedCommandLine = { + options: {}, + errors: [], + fileNames: [], + wildcardDirectories: {} + }; + const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, 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