Skip to content

Commit 5919595

Browse files
committed
Add support for including dotted and .min.js files explicitly in include
Porting #9528 to release 2.0 branch
1 parent bd6d2c0 commit 5919595

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
@@ -689,9 +689,6 @@ namespace ts {
689689
return output;
690690
}
691691

692-
// Skip over any minified JavaScript files (ending in ".min.js")
693-
// Skip over dotted files and folders as well
694-
const ignoreFileNamePattern = /(\.min\.js$)|([\\/]\.[\w.])/;
695692
/**
696693
* Parse the contents of a config file (tsconfig.json).
697694
* @param json The contents of the config file to parse
@@ -1003,10 +1000,6 @@ namespace ts {
10031000
continue;
10041001
}
10051002

1006-
if (ignoreFileNamePattern.test(file)) {
1007-
continue;
1008-
}
1009-
10101003
// We may have included a wildcard path with a lower priority
10111004
// extension due to the user-defined order of entries in the
10121005
// "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
@@ -922,11 +922,29 @@ namespace ts {
922922
const reservedCharacterPattern = /[^\w\s\/]/g;
923923
const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question];
924924

925+
/**
926+
* Matches any single directory segment unless it is the last segment and a .min.js file
927+
* Breakdown:
928+
* [^./] # matches everything up to the first . character (excluding directory seperators)
929+
* (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension
930+
*/
931+
const singleAsteriskRegexFragmentFiles = "([^./]*(\\.(?!min\\.js$))?)*";
932+
const singleAsteriskRegexFragmentOther = "[^/]*";
933+
925934
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
926935
if (specs === undefined || specs.length === 0) {
927936
return undefined;
928937
}
929938

939+
const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther;
940+
const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther;
941+
942+
/**
943+
* Regex for the ** wildcard. Matches any number of subdirectories. When used for including
944+
* files or directories, does not match subdirectories that start with a . character
945+
*/
946+
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";
947+
930948
let pattern = "";
931949
let hasWrittenSubpattern = false;
932950
spec: for (const spec of specs) {
@@ -947,13 +965,13 @@ namespace ts {
947965
components[0] = removeTrailingDirectorySeparator(components[0]);
948966

949967
let optionalCount = 0;
950-
for (const component of components) {
968+
for (let component of components) {
951969
if (component === "**") {
952970
if (hasRecursiveDirectoryWildcard) {
953971
continue spec;
954972
}
955973

956-
subpattern += "(/.+?)?";
974+
subpattern += doubleAsteriskRegexFragment;
957975
hasRecursiveDirectoryWildcard = true;
958976
hasWrittenComponent = true;
959977
}
@@ -967,6 +985,20 @@ namespace ts {
967985
subpattern += directorySeparator;
968986
}
969987

988+
if (usage !== "exclude") {
989+
// The * and ? wildcards should not match directories or files that start with . if they
990+
// appear first in a component. Dotted directories and files can be included explicitly
991+
// like so: **/.*/.*
992+
if (component.charCodeAt(0) === CharacterCodes.asterisk) {
993+
subpattern += "([^./]" + singleAsteriskRegexFragment + ")?";
994+
component = component.substr(1);
995+
}
996+
else if (component.charCodeAt(0) === CharacterCodes.question) {
997+
subpattern += "[^./]";
998+
component = component.substr(1);
999+
}
1000+
}
1001+
9701002
subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
9711003
hasWrittenComponent = true;
9721004
}
@@ -992,8 +1024,16 @@ namespace ts {
9921024
return "^(" + pattern + (usage === "exclude" ? ")($|/)" : ")$");
9931025
}
9941026

995-
function replaceWildcardCharacter(match: string) {
996-
return match === "*" ? "[^/]*" : match === "?" ? "[^/]" : "\\" + match;
1027+
function replaceWildCardCharacterFiles(match: string) {
1028+
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentFiles);
1029+
}
1030+
1031+
function replaceWildCardCharacterOther(match: string) {
1032+
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentOther);
1033+
}
1034+
1035+
function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) {
1036+
return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match;
9971037
}
9981038

9991039
export interface FileSystemEntries {

tests/cases/unittests/matchFiles.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ namespace ts {
2424
"c:/dev/x/y/b.ts",
2525
"c:/dev/js/a.js",
2626
"c:/dev/js/b.js",
27+
"c:/dev/js/d.min.js",
28+
"c:/dev/js/ab.min.js",
2729
"c:/ext/ext.ts",
2830
"c:/ext/b/a..b.ts"
2931
]);
@@ -76,6 +78,17 @@ namespace ts {
7678
"c:/dev/jspm_packages/a.ts"
7779
]);
7880

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

0 commit comments

Comments
 (0)