Skip to content

Commit b747c2d

Browse files
committed
exclude node_modules unless explicitly included
1 parent 1552761 commit b747c2d

File tree

6 files changed

+200
-106
lines changed

6 files changed

+200
-106
lines changed

src/compiler/commandLineParser.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,14 +1446,10 @@ namespace ts {
14461446
}
14471447
}
14481448
else {
1449-
// If no includes were specified, exclude common package folders and the outDir
1450-
const specs = includeSpecs ? [] : ["node_modules", "bower_components", "jspm_packages"];
1451-
14521449
const outDir = raw["compilerOptions"] && raw["compilerOptions"]["outDir"];
14531450
if (outDir) {
1454-
specs.push(outDir);
1451+
excludeSpecs = [outDir];
14551452
}
1456-
excludeSpecs = specs;
14571453
}
14581454

14591455
if (fileNames === undefined && includeSpecs === undefined) {

src/compiler/core.ts

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,14 +1889,54 @@ namespace ts {
18891889
const reservedCharacterPattern = /[^\w\s\/]/g;
18901890
const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question];
18911891

1892-
/**
1893-
* Matches any single directory segment unless it is the last segment and a .min.js file
1894-
* Breakdown:
1895-
* [^./] # matches everything up to the first . character (excluding directory seperators)
1896-
* (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension
1897-
*/
1898-
const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*";
1899-
const singleAsteriskRegexFragmentOther = "[^/]*";
1892+
/* @internal */
1893+
export const commonPackageFolders: ReadonlyArray<string> = ["node_modules", "bower_components", "jspm_packages"];
1894+
1895+
const implicitExcludePathRegexPattern = `(?!(${commonPackageFolders.join("|")})(/|$))`;
1896+
1897+
interface WildcardMatcher {
1898+
singleAsteriskRegexFragment: string;
1899+
doubleAsteriskRegexFragment: string;
1900+
replaceWildcardCharacter: (match: string) => string;
1901+
}
1902+
1903+
const filesMatcher: WildcardMatcher = {
1904+
/**
1905+
* Matches any single directory segment unless it is the last segment and a .min.js file
1906+
* Breakdown:
1907+
* [^./] # matches everything up to the first . character (excluding directory seperators)
1908+
* (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension
1909+
*/
1910+
singleAsteriskRegexFragment: "([^./]|(\\.(?!min\\.js$))?)*",
1911+
/**
1912+
* Regex for the ** wildcard. Matches any number of subdirectories. When used for including
1913+
* files or directories, does not match subdirectories that start with a . character
1914+
*/
1915+
doubleAsteriskRegexFragment: `(/${implicitExcludePathRegexPattern}[^/.][^/]*)*?`,
1916+
replaceWildcardCharacter: match => replaceWildcardCharacter(match, filesMatcher.singleAsteriskRegexFragment)
1917+
};
1918+
1919+
const directoriesMatcher: WildcardMatcher = {
1920+
singleAsteriskRegexFragment: "[^/]*",
1921+
/**
1922+
* Regex for the ** wildcard. Matches any number of subdirectories. When used for including
1923+
* files or directories, does not match subdirectories that start with a . character
1924+
*/
1925+
doubleAsteriskRegexFragment: `(/${implicitExcludePathRegexPattern}[^/.][^/]*)*?`,
1926+
replaceWildcardCharacter: match => replaceWildcardCharacter(match, directoriesMatcher.singleAsteriskRegexFragment)
1927+
};
1928+
1929+
const excludeMatcher: WildcardMatcher = {
1930+
singleAsteriskRegexFragment: "[^/]*",
1931+
doubleAsteriskRegexFragment: "(/.+?)?",
1932+
replaceWildcardCharacter: match => replaceWildcardCharacter(match, excludeMatcher.singleAsteriskRegexFragment)
1933+
};
1934+
1935+
const wildcardMatchers = {
1936+
files: filesMatcher,
1937+
directories: directoriesMatcher,
1938+
exclude: excludeMatcher
1939+
};
19001940

19011941
export function getRegularExpressionForWildcard(specs: ReadonlyArray<string>, basePath: string, usage: "files" | "directories" | "exclude"): string | undefined {
19021942
const patterns = getRegularExpressionsForWildcards(specs, basePath, usage);
@@ -1915,17 +1955,8 @@ namespace ts {
19151955
return undefined;
19161956
}
19171957

1918-
const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther;
1919-
const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther;
1920-
1921-
/**
1922-
* Regex for the ** wildcard. Matches any number of subdirectories. When used for including
1923-
* files or directories, does not match subdirectories that start with a . character
1924-
*/
1925-
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";
1926-
19271958
return flatMap(specs, spec =>
1928-
spec && getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter));
1959+
spec && getSubPatternFromSpec(spec, basePath, usage, wildcardMatchers[usage]));
19291960
}
19301961

19311962
/**
@@ -1936,7 +1967,7 @@ namespace ts {
19361967
return !/[.*?]/.test(lastPathComponent);
19371968
}
19381969

1939-
function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", singleAsteriskRegexFragment: string, doubleAsteriskRegexFragment: string, replaceWildcardCharacter: (match: string) => string): string | undefined {
1970+
function getSubPatternFromSpec(spec: string, basePath: string, usage: "files" | "directories" | "exclude", { singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter }: WildcardMatcher): string | undefined {
19401971
let subpattern = "";
19411972
let hasRecursiveDirectoryWildcard = false;
19421973
let hasWrittenComponent = false;
@@ -1975,20 +2006,36 @@ namespace ts {
19752006
}
19762007

19772008
if (usage !== "exclude") {
2009+
let componentPattern = "";
19782010
// The * and ? wildcards should not match directories or files that start with . if they
19792011
// appear first in a component. Dotted directories and files can be included explicitly
19802012
// like so: **/.*/.*
19812013
if (component.charCodeAt(0) === CharacterCodes.asterisk) {
1982-
subpattern += "([^./]" + singleAsteriskRegexFragment + ")?";
2014+
componentPattern += "([^./]" + singleAsteriskRegexFragment + ")?";
19832015
component = component.substr(1);
19842016
}
19852017
else if (component.charCodeAt(0) === CharacterCodes.question) {
1986-
subpattern += "[^./]";
2018+
componentPattern += "[^./]";
19872019
component = component.substr(1);
19882020
}
1989-
}
19902021

1991-
subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
2022+
componentPattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
2023+
2024+
// Patterns should not include subfolders like node_modules unless they are
2025+
// explicitly included as part of the path.
2026+
//
2027+
// As an optimization, if the component pattern is the same as the component,
2028+
// then there definitely were no wildcard characters and we do not need to
2029+
// add the exclusion pattern.
2030+
if (componentPattern !== component) {
2031+
subpattern += implicitExcludePathRegexPattern;
2032+
}
2033+
2034+
subpattern += componentPattern;
2035+
}
2036+
else {
2037+
subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
2038+
}
19922039
}
19932040

19942041
hasWrittenComponent = true;
@@ -2002,14 +2049,6 @@ namespace ts {
20022049
return subpattern;
20032050
}
20042051

2005-
function replaceWildCardCharacterFiles(match: string) {
2006-
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentFiles);
2007-
}
2008-
2009-
function replaceWildCardCharacterOther(match: string) {
2010-
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentOther);
2011-
}
2012-
20132052
function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) {
20142053
return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match;
20152054
}

src/harness/unittests/matchFiles.ts

Lines changed: 126 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ namespace ts {
7373
"c:/dev/a.d.ts",
7474
"c:/dev/a.js",
7575
"c:/dev/b.ts",
76+
"c:/dev/x/a.ts",
7677
"c:/dev/node_modules/a.ts",
7778
"c:/dev/bower_components/a.ts",
7879
"c:/dev/jspm_packages/a.ts"
@@ -141,7 +142,8 @@ namespace ts {
141142
errors: [],
142143
fileNames: [
143144
"c:/dev/a.ts",
144-
"c:/dev/b.ts"
145+
"c:/dev/b.ts",
146+
"c:/dev/x/a.ts"
145147
],
146148
wildcardDirectories: {
147149
"c:/dev": ts.WatchDirectoryFlags.Recursive
@@ -462,7 +464,6 @@ namespace ts {
462464
};
463465
validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath);
464466
});
465-
466467
it("same named declarations are excluded", () => {
467468
const json = {
468469
include: [
@@ -651,71 +652,127 @@ namespace ts {
651652
};
652653
validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath);
653654
});
654-
it("with common package folders and no exclusions", () => {
655-
const json = {
656-
include: [
657-
"**/a.ts"
658-
]
659-
};
660-
const expected: ts.ParsedCommandLine = {
661-
options: {},
662-
errors: [],
663-
fileNames: [
664-
"c:/dev/a.ts",
665-
"c:/dev/bower_components/a.ts",
666-
"c:/dev/jspm_packages/a.ts",
667-
"c:/dev/node_modules/a.ts"
668-
],
669-
wildcardDirectories: {
670-
"c:/dev": ts.WatchDirectoryFlags.Recursive
671-
},
672-
};
673-
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
674-
});
675-
it("with common package folders and exclusions", () => {
676-
const json = {
677-
include: [
678-
"**/a.ts"
679-
],
680-
exclude: [
681-
"a.ts"
682-
]
683-
};
684-
const expected: ts.ParsedCommandLine = {
685-
options: {},
686-
errors: [],
687-
fileNames: [
688-
"c:/dev/bower_components/a.ts",
689-
"c:/dev/jspm_packages/a.ts",
690-
"c:/dev/node_modules/a.ts"
691-
],
692-
wildcardDirectories: {
693-
"c:/dev": ts.WatchDirectoryFlags.Recursive
694-
},
695-
};
696-
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
697-
});
698-
it("with common package folders and empty exclude", () => {
699-
const json = {
700-
include: [
701-
"**/a.ts"
702-
],
703-
exclude: <string[]>[]
704-
};
705-
const expected: ts.ParsedCommandLine = {
706-
options: {},
707-
errors: [],
708-
fileNames: [
709-
"c:/dev/a.ts",
710-
"c:/dev/bower_components/a.ts",
711-
"c:/dev/jspm_packages/a.ts",
712-
"c:/dev/node_modules/a.ts"
713-
],
714-
wildcardDirectories: {
715-
"c:/dev": ts.WatchDirectoryFlags.Recursive
716-
},
717-
};
718-
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
655+
describe("with common package folders", () => {
656+
it("and no exclusions", () => {
657+
const json = {
658+
include: [
659+
"**/a.ts"
660+
]
661+
};
662+
const expected: ts.ParsedCommandLine = {
663+
options: {},
664+
errors: [],
665+
fileNames: [
666+
"c:/dev/a.ts",
667+
"c:/dev/x/a.ts"
668+
],
669+
wildcardDirectories: {
670+
"c:/dev": ts.WatchDirectoryFlags.Recursive
671+
},
672+
};
673+
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
674+
});
675+
it("and exclusions", () => {
676+
const json = {
677+
include: [
678+
"**/?.ts"
679+
],
680+
exclude: [
681+
"a.ts"
682+
]
683+
};
684+
const expected: ts.ParsedCommandLine = {
685+
options: {},
686+
errors: [],
687+
fileNames: [
688+
"c:/dev/b.ts",
689+
"c:/dev/x/a.ts"
690+
],
691+
wildcardDirectories: {
692+
"c:/dev": ts.WatchDirectoryFlags.Recursive
693+
},
694+
};
695+
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
696+
});
697+
it("and empty exclude", () => {
698+
const json = {
699+
include: [
700+
"**/a.ts"
701+
],
702+
exclude: <string[]>[]
703+
};
704+
const expected: ts.ParsedCommandLine = {
705+
options: {},
706+
errors: [],
707+
fileNames: [
708+
"c:/dev/a.ts",
709+
"c:/dev/x/a.ts"
710+
],
711+
wildcardDirectories: {
712+
"c:/dev": ts.WatchDirectoryFlags.Recursive
713+
},
714+
};
715+
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
716+
});
717+
it("and explicit recursive include", () => {
718+
const json = {
719+
include: [
720+
"**/a.ts",
721+
"**/node_modules/a.ts"
722+
]
723+
};
724+
const expected: ts.ParsedCommandLine = {
725+
options: {},
726+
errors: [],
727+
fileNames: [
728+
"c:/dev/a.ts",
729+
"c:/dev/x/a.ts",
730+
"c:/dev/node_modules/a.ts"
731+
],
732+
wildcardDirectories: {
733+
"c:/dev": ts.WatchDirectoryFlags.Recursive
734+
},
735+
};
736+
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
737+
});
738+
it("and wildcard include", () => {
739+
const json = {
740+
include: [
741+
"*/a.ts"
742+
]
743+
};
744+
const expected: ts.ParsedCommandLine = {
745+
options: {},
746+
errors: [],
747+
fileNames: [
748+
"c:/dev/x/a.ts"
749+
],
750+
wildcardDirectories: {
751+
"c:/dev": ts.WatchDirectoryFlags.Recursive
752+
},
753+
};
754+
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
755+
});
756+
it("and explicit wildcard include", () => {
757+
const json = {
758+
include: [
759+
"*/a.ts",
760+
"node_modules/a.ts"
761+
]
762+
};
763+
const expected: ts.ParsedCommandLine = {
764+
options: {},
765+
errors: [],
766+
fileNames: [
767+
"c:/dev/x/a.ts",
768+
"c:/dev/node_modules/a.ts"
769+
],
770+
wildcardDirectories: {
771+
"c:/dev": ts.WatchDirectoryFlags.Recursive
772+
},
773+
};
774+
validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath);
775+
});
719776
});
720777
it("exclude .js files when allowJs=false", () => {
721778
const json = {
@@ -1066,6 +1123,7 @@ namespace ts {
10661123
};
10671124
validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath);
10681125
});
1126+
10691127
describe("with trailing recursive directory", () => {
10701128
it("in includes", () => {
10711129
const json = {
@@ -1264,6 +1322,7 @@ namespace ts {
12641322
});
12651323
});
12661324
});
1325+
12671326
describe("with files or folders that begin with a .", () => {
12681327
it("that are not explicitly included", () => {
12691328
const json = {

0 commit comments

Comments
 (0)