Skip to content

Commit 8b2ea39

Browse files
authored
Merge pull request #9528 from Microsoft/explicitly_included_globs
Add support for including dotted and .min.js files explicitly in include
2 parents 046e9ea + bd48e55 commit 8b2ea39

File tree

3 files changed

+230
-11
lines changed

3 files changed

+230
-11
lines changed

src/compiler/commandLineParser.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -693,9 +693,6 @@ namespace ts {
693693
return output;
694694
}
695695

696-
// Skip over any minified JavaScript files (ending in ".min.js")
697-
// Skip over dotted files and folders as well
698-
const ignoreFileNamePattern = /(\.min\.js$)|([\\/]\.[\w.])/;
699696
/**
700697
* Parse the contents of a config file (tsconfig.json).
701698
* @param json The contents of the config file to parse
@@ -1007,10 +1004,6 @@ namespace ts {
10071004
continue;
10081005
}
10091006

1010-
if (ignoreFileNamePattern.test(file)) {
1011-
continue;
1012-
}
1013-
10141007
// We may have included a wildcard path with a lower priority
10151008
// extension due to the user-defined order of entries in the
10161009
// "include" array. If there is a lower priority extension in the

src/compiler/core.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -932,11 +932,29 @@ namespace ts {
932932
const reservedCharacterPattern = /[^\w\s\/]/g;
933933
const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question];
934934

935+
/**
936+
* Matches any single directory segment unless it is the last segment and a .min.js file
937+
* Breakdown:
938+
* [^./] # matches everything up to the first . character (excluding directory seperators)
939+
* (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension
940+
*/
941+
const singleAsteriskRegexFragmentFiles = "([^./]*(\\.(?!min\\.js$))?)*";
942+
const singleAsteriskRegexFragmentOther = "[^/]*";
943+
935944
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
936945
if (specs === undefined || specs.length === 0) {
937946
return undefined;
938947
}
939948

949+
const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther;
950+
const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther;
951+
952+
/**
953+
* Regex for the ** wildcard. Matches any number of subdirectories. When used for including
954+
* files or directories, does not match subdirectories that start with a . character
955+
*/
956+
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";
957+
940958
let pattern = "";
941959
let hasWrittenSubpattern = false;
942960
spec: for (const spec of specs) {
@@ -957,13 +975,13 @@ namespace ts {
957975
components[0] = removeTrailingDirectorySeparator(components[0]);
958976

959977
let optionalCount = 0;
960-
for (const component of components) {
978+
for (let component of components) {
961979
if (component === "**") {
962980
if (hasRecursiveDirectoryWildcard) {
963981
continue spec;
964982
}
965983

966-
subpattern += "(/.+?)?";
984+
subpattern += doubleAsteriskRegexFragment;
967985
hasRecursiveDirectoryWildcard = true;
968986
hasWrittenComponent = true;
969987
}
@@ -977,6 +995,20 @@ namespace ts {
977995
subpattern += directorySeparator;
978996
}
979997

998+
if (usage !== "exclude") {
999+
// The * and ? wildcards should not match directories or files that start with . if they
1000+
// appear first in a component. Dotted directories and files can be included explicitly
1001+
// like so: **/.*/.*
1002+
if (component.charCodeAt(0) === CharacterCodes.asterisk) {
1003+
subpattern += "([^./]" + singleAsteriskRegexFragment + ")?";
1004+
component = component.substr(1);
1005+
}
1006+
else if (component.charCodeAt(0) === CharacterCodes.question) {
1007+
subpattern += "[^./]";
1008+
component = component.substr(1);
1009+
}
1010+
}
1011+
9801012
subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
9811013
hasWrittenComponent = true;
9821014
}
@@ -1002,8 +1034,16 @@ namespace ts {
10021034
return "^(" + pattern + (usage === "exclude" ? ")($|/)" : ")$");
10031035
}
10041036

1005-
function replaceWildcardCharacter(match: string) {
1006-
return match === "*" ? "[^/]*" : match === "?" ? "[^/]" : "\\" + match;
1037+
function replaceWildCardCharacterFiles(match: string) {
1038+
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentFiles);
1039+
}
1040+
1041+
function replaceWildCardCharacterOther(match: string) {
1042+
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentOther);
1043+
}
1044+
1045+
function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) {
1046+
return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match;
10071047
}
10081048

10091049
export interface FileSystemEntries {

src/harness/unittests/matchFiles.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ namespace ts {
2323
"c:/dev/x/y/b.ts",
2424
"c:/dev/js/a.js",
2525
"c:/dev/js/b.js",
26+
"c:/dev/js/d.min.js",
27+
"c:/dev/js/ab.min.js",
2628
"c:/ext/ext.ts",
2729
"c:/ext/b/a..b.ts"
2830
]);
@@ -75,6 +77,17 @@ namespace ts {
7577
"c:/dev/jspm_packages/a.ts"
7678
]);
7779

80+
const caseInsensitiveDottedFoldersHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
81+
"c:/dev/x/d.ts",
82+
"c:/dev/x/y/d.ts",
83+
"c:/dev/x/y/.e.ts",
84+
"c:/dev/x/.y/a.ts",
85+
"c:/dev/.z/.b.ts",
86+
"c:/dev/.z/c.ts",
87+
"c:/dev/w/.u/e.ts",
88+
"c:/dev/g.min.js/.g/g.ts"
89+
]);
90+
7891
describe("matchFiles", () => {
7992
describe("with literal file list", () => {
8093
it("without exclusions", () => {
@@ -726,6 +739,33 @@ namespace ts {
726739
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
727740
assert.deepEqual(actual.errors, expected.errors);
728741
});
742+
it("include explicitly listed .min.js files when allowJs=true", () => {
743+
const json = {
744+
compilerOptions: {
745+
allowJs: true
746+
},
747+
include: [
748+
"js/*.min.js"
749+
]
750+
};
751+
const expected: ts.ParsedCommandLine = {
752+
options: {
753+
allowJs: true
754+
},
755+
errors: [],
756+
fileNames: [
757+
"c:/dev/js/ab.min.js",
758+
"c:/dev/js/d.min.js"
759+
],
760+
wildcardDirectories: {
761+
"c:/dev/js": ts.WatchDirectoryFlags.None
762+
}
763+
};
764+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath);
765+
assert.deepEqual(actual.fileNames, expected.fileNames);
766+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
767+
assert.deepEqual(actual.errors, expected.errors);
768+
});
729769
it("include paths outside of the project", () => {
730770
const json = {
731771
include: [
@@ -951,6 +991,35 @@ namespace ts {
951991
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
952992
assert.deepEqual(actual.errors, expected.errors);
953993
});
994+
it("exclude .min.js files using wildcards", () => {
995+
const json = {
996+
compilerOptions: {
997+
allowJs: true
998+
},
999+
include: [
1000+
"js/*.min.js"
1001+
],
1002+
exclude: [
1003+
"js/a*"
1004+
]
1005+
};
1006+
const expected: ts.ParsedCommandLine = {
1007+
options: {
1008+
allowJs: true
1009+
},
1010+
errors: [],
1011+
fileNames: [
1012+
"c:/dev/js/d.min.js"
1013+
],
1014+
wildcardDirectories: {
1015+
"c:/dev/js": ts.WatchDirectoryFlags.None
1016+
}
1017+
};
1018+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath);
1019+
assert.deepEqual(actual.fileNames, expected.fileNames);
1020+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
1021+
assert.deepEqual(actual.errors, expected.errors);
1022+
});
9541023
describe("with trailing recursive directory", () => {
9551024
it("in includes", () => {
9561025
const json = {
@@ -1145,5 +1214,122 @@ namespace ts {
11451214
});
11461215
});
11471216
});
1217+
describe("with files or folders that begin with a .", () => {
1218+
it("that are not explicitly included", () => {
1219+
const json = {
1220+
include: [
1221+
"x/**/*",
1222+
"w/*/*"
1223+
]
1224+
};
1225+
const expected: ts.ParsedCommandLine = {
1226+
options: {},
1227+
errors: [],
1228+
fileNames: [
1229+
"c:/dev/x/d.ts",
1230+
"c:/dev/x/y/d.ts",
1231+
],
1232+
wildcardDirectories: {
1233+
"c:/dev/x": ts.WatchDirectoryFlags.Recursive,
1234+
"c:/dev/w": ts.WatchDirectoryFlags.Recursive
1235+
}
1236+
};
1237+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
1238+
assert.deepEqual(actual.fileNames, expected.fileNames);
1239+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
1240+
assert.deepEqual(actual.errors, expected.errors);
1241+
});
1242+
describe("that are explicitly included", () => {
1243+
it("without wildcards", () => {
1244+
const json = {
1245+
include: [
1246+
"x/.y/a.ts",
1247+
"c:/dev/.z/.b.ts"
1248+
]
1249+
};
1250+
const expected: ts.ParsedCommandLine = {
1251+
options: {},
1252+
errors: [],
1253+
fileNames: [
1254+
"c:/dev/.z/.b.ts",
1255+
"c:/dev/x/.y/a.ts"
1256+
],
1257+
wildcardDirectories: {}
1258+
};
1259+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
1260+
assert.deepEqual(actual.fileNames, expected.fileNames);
1261+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
1262+
assert.deepEqual(actual.errors, expected.errors);
1263+
});
1264+
it("with recursive wildcards that match directories", () => {
1265+
const json = {
1266+
include: [
1267+
"**/.*/*"
1268+
]
1269+
};
1270+
const expected: ts.ParsedCommandLine = {
1271+
options: {},
1272+
errors: [],
1273+
fileNames: [
1274+
"c:/dev/.z/c.ts",
1275+
"c:/dev/g.min.js/.g/g.ts",
1276+
"c:/dev/w/.u/e.ts",
1277+
"c:/dev/x/.y/a.ts"
1278+
],
1279+
wildcardDirectories: {
1280+
"c:/dev": ts.WatchDirectoryFlags.Recursive
1281+
}
1282+
};
1283+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
1284+
assert.deepEqual(actual.fileNames, expected.fileNames);
1285+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
1286+
assert.deepEqual(actual.errors, expected.errors);
1287+
});
1288+
it("with recursive wildcards that match nothing", () => {
1289+
const json = {
1290+
include: [
1291+
"x/**/.y/*",
1292+
".z/**/.*"
1293+
]
1294+
};
1295+
const expected: ts.ParsedCommandLine = {
1296+
options: {},
1297+
errors: [],
1298+
fileNames: [
1299+
"c:/dev/.z/.b.ts",
1300+
"c:/dev/x/.y/a.ts"
1301+
],
1302+
wildcardDirectories: {
1303+
"c:/dev/.z": ts.WatchDirectoryFlags.Recursive,
1304+
"c:/dev/x": ts.WatchDirectoryFlags.Recursive
1305+
}
1306+
};
1307+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
1308+
assert.deepEqual(actual.fileNames, expected.fileNames);
1309+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
1310+
assert.deepEqual(actual.errors, expected.errors);
1311+
});
1312+
it("with wildcard excludes that implicitly exclude dotted files", () => {
1313+
const json = {
1314+
include: [
1315+
"**/.*/*"
1316+
],
1317+
exclude: [
1318+
"**/*"
1319+
]
1320+
};
1321+
const expected: ts.ParsedCommandLine = {
1322+
options: {},
1323+
errors: [],
1324+
fileNames: [],
1325+
wildcardDirectories: {}
1326+
};
1327+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
1328+
assert.deepEqual(actual.fileNames, expected.fileNames);
1329+
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
1330+
assert.deepEqual(actual.errors, expected.errors);
1331+
});
1332+
});
1333+
});
11481334
});
11491335
}

0 commit comments

Comments
 (0)