Skip to content

Commit 9cd7178

Browse files
author
Andy
authored
Merge pull request microsoft#12556 from Microsoft/include_ordering
Sort matched files by include order
2 parents 71d1a3f + 2960228 commit 9cd7178

File tree

7 files changed

+115
-72
lines changed

7 files changed

+115
-72
lines changed

src/compiler/commandLineParser.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1258,7 +1258,6 @@ namespace ts {
12581258

12591259
const literalFiles = arrayFrom(literalFileMap.values());
12601260
const wildcardFiles = arrayFrom(wildcardFileMap.values());
1261-
wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive);
12621261
return {
12631262
fileNames: literalFiles.concat(wildcardFiles),
12641263
wildcardDirectories

src/compiler/core.ts

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ namespace ts {
260260
return undefined;
261261
}
262262

263+
/** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */
264+
export function findIndex<T>(array: T[], predicate: (element: T, index: number) => boolean): number {
265+
for (let i = 0; i < array.length; i++) {
266+
if (predicate(array[i], i)) {
267+
return i;
268+
}
269+
}
270+
return -1;
271+
}
272+
263273
/**
264274
* Returns the first truthy result of `callback`, or else fails.
265275
* This is like `forEach`, but never returns undefined.
@@ -1724,7 +1734,19 @@ namespace ts {
17241734
const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*";
17251735
const singleAsteriskRegexFragmentOther = "[^/]*";
17261736

1727-
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
1737+
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string | undefined {
1738+
const patterns = getRegularExpressionsForWildcards(specs, basePath, usage);
1739+
if (!patterns || !patterns.length) {
1740+
return undefined;
1741+
}
1742+
1743+
const pattern = patterns.map(pattern => `(${pattern})`).join("|");
1744+
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
1745+
const terminator = usage === "exclude" ? "($|/)" : "$";
1746+
return `^(${pattern})${terminator}`;
1747+
}
1748+
1749+
function getRegularExpressionsForWildcards(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
17281750
if (specs === undefined || specs.length === 0) {
17291751
return undefined;
17301752
}
@@ -1738,33 +1760,8 @@ namespace ts {
17381760
*/
17391761
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";
17401762

1741-
let pattern = "";
1742-
let hasWrittenSubpattern = false;
1743-
for (const spec of specs) {
1744-
if (!spec) {
1745-
continue;
1746-
}
1747-
1748-
const subPattern = getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter);
1749-
if (subPattern === undefined) {
1750-
continue;
1751-
}
1752-
1753-
if (hasWrittenSubpattern) {
1754-
pattern += "|";
1755-
}
1756-
1757-
pattern += "(" + subPattern + ")";
1758-
hasWrittenSubpattern = true;
1759-
}
1760-
1761-
if (!pattern) {
1762-
return undefined;
1763-
}
1764-
1765-
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
1766-
const terminator = usage === "exclude" ? "($|/)" : "$";
1767-
return `^(${pattern})${terminator}`;
1763+
return flatMap(specs, spec =>
1764+
spec && getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter));
17681765
}
17691766

17701767
/**
@@ -1859,6 +1856,9 @@ namespace ts {
18591856
}
18601857

18611858
export interface FileMatcherPatterns {
1859+
/** One pattern for each "include" spec. */
1860+
includeFilePatterns: string[];
1861+
/** One pattern matching one of any of the "include" specs. */
18621862
includeFilePattern: string;
18631863
includeDirectoryPattern: string;
18641864
excludePattern: string;
@@ -1871,6 +1871,7 @@ namespace ts {
18711871
const absolutePath = combinePaths(currentDirectory, path);
18721872

18731873
return {
1874+
includeFilePatterns: map(getRegularExpressionsForWildcards(includes, absolutePath, "files"), pattern => `^${pattern}$`),
18741875
includeFilePattern: getRegularExpressionForWildcard(includes, absolutePath, "files"),
18751876
includeDirectoryPattern: getRegularExpressionForWildcard(includes, absolutePath, "directories"),
18761877
excludePattern: getRegularExpressionForWildcard(excludes, absolutePath, "exclude"),
@@ -1885,26 +1886,39 @@ namespace ts {
18851886
const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames, currentDirectory);
18861887

18871888
const regexFlag = useCaseSensitiveFileNames ? "" : "i";
1888-
const includeFileRegex = patterns.includeFilePattern && new RegExp(patterns.includeFilePattern, regexFlag);
1889+
const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => new RegExp(pattern, regexFlag));
18891890
const includeDirectoryRegex = patterns.includeDirectoryPattern && new RegExp(patterns.includeDirectoryPattern, regexFlag);
18901891
const excludeRegex = patterns.excludePattern && new RegExp(patterns.excludePattern, regexFlag);
18911892

1892-
const result: string[] = [];
1893+
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
1894+
// If there are no "includes", then just put everything in results[0].
1895+
const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]];
1896+
1897+
const comparer = useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive;
18931898
for (const basePath of patterns.basePaths) {
18941899
visitDirectory(basePath, combinePaths(currentDirectory, basePath));
18951900
}
1896-
return result;
1901+
1902+
return flatten(results);
18971903

18981904
function visitDirectory(path: string, absolutePath: string) {
1899-
const { files, directories } = getFileSystemEntries(path);
1905+
let { files, directories } = getFileSystemEntries(path);
1906+
files = files.slice().sort(comparer);
1907+
directories = directories.slice().sort(comparer);
19001908

19011909
for (const current of files) {
19021910
const name = combinePaths(path, current);
19031911
const absoluteName = combinePaths(absolutePath, current);
1904-
if ((!extensions || fileExtensionIsAny(name, extensions)) &&
1905-
(!includeFileRegex || includeFileRegex.test(absoluteName)) &&
1906-
(!excludeRegex || !excludeRegex.test(absoluteName))) {
1907-
result.push(name);
1912+
if (extensions && !fileExtensionIsAny(name, extensions)) continue;
1913+
if (excludeRegex && excludeRegex.test(absoluteName)) continue;
1914+
if (!includeFileRegexes) {
1915+
results[0].push(name);
1916+
}
1917+
else {
1918+
const includeIndex = findIndex(includeFileRegexes, re => re.test(absoluteName));
1919+
if (includeIndex !== -1) {
1920+
results[includeIndex].push(name);
1921+
}
19081922
}
19091923
}
19101924

src/harness/unittests/matchFiles.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,9 @@ namespace ts {
346346
fileNames: [
347347
"c:/dev/a.ts",
348348
"c:/dev/b.ts",
349+
"c:/dev/node_modules/a.ts",
349350
"c:/dev/bower_components/a.ts",
350-
"c:/dev/jspm_packages/a.ts",
351-
"c:/dev/node_modules/a.ts"
351+
"c:/dev/jspm_packages/a.ts"
352352
],
353353
wildcardDirectories: {},
354354
};
@@ -373,9 +373,9 @@ namespace ts {
373373
options: {},
374374
errors: [],
375375
fileNames: [
376+
"c:/dev/node_modules/a.ts",
376377
"c:/dev/bower_components/a.ts",
377-
"c:/dev/jspm_packages/a.ts",
378-
"c:/dev/node_modules/a.ts"
378+
"c:/dev/jspm_packages/a.ts"
379379
],
380380
wildcardDirectories: {},
381381
};
@@ -398,9 +398,9 @@ namespace ts {
398398
fileNames: [
399399
"c:/dev/a.ts",
400400
"c:/dev/b.ts",
401+
"c:/dev/node_modules/a.ts",
401402
"c:/dev/bower_components/a.ts",
402-
"c:/dev/jspm_packages/a.ts",
403-
"c:/dev/node_modules/a.ts"
403+
"c:/dev/jspm_packages/a.ts"
404404
],
405405
wildcardDirectories: {},
406406
};
@@ -410,6 +410,36 @@ namespace ts {
410410
});
411411

412412
describe("with wildcard include list", () => {
413+
it("is sorted in include order, then in alphabetical order", () => {
414+
const json = {
415+
include: [
416+
"z/*.ts",
417+
"x/*.ts"
418+
]
419+
};
420+
const expected: ts.ParsedCommandLine = {
421+
options: {},
422+
errors: [],
423+
fileNames: [
424+
"c:/dev/z/a.ts",
425+
"c:/dev/z/aba.ts",
426+
"c:/dev/z/abz.ts",
427+
"c:/dev/z/b.ts",
428+
"c:/dev/z/bba.ts",
429+
"c:/dev/z/bbz.ts",
430+
"c:/dev/x/a.ts",
431+
"c:/dev/x/aa.ts",
432+
"c:/dev/x/b.ts"
433+
],
434+
wildcardDirectories: {
435+
"c:/dev/z": ts.WatchDirectoryFlags.None,
436+
"c:/dev/x": ts.WatchDirectoryFlags.None
437+
},
438+
};
439+
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath);
440+
assertParsed(actual, expected);
441+
});
442+
413443
it("same named declarations are excluded", () => {
414444
const json = {
415445
include: [
@@ -506,8 +536,8 @@ namespace ts {
506536
options: {},
507537
errors: [],
508538
fileNames: [
509-
"c:/dev/x/a.ts",
510539
"c:/dev/x/y/a.ts",
540+
"c:/dev/x/a.ts",
511541
"c:/dev/z/a.ts"
512542
],
513543
wildcardDirectories: {
@@ -1282,8 +1312,8 @@ namespace ts {
12821312
options: {},
12831313
errors: [],
12841314
fileNames: [
1285-
"c:/dev/.z/.b.ts",
1286-
"c:/dev/x/.y/a.ts"
1315+
"c:/dev/x/.y/a.ts",
1316+
"c:/dev/.z/.b.ts"
12871317
],
12881318
wildcardDirectories: {}
12891319
};
@@ -1323,8 +1353,8 @@ namespace ts {
13231353
options: {},
13241354
errors: [],
13251355
fileNames: [
1326-
"c:/dev/.z/.b.ts",
1327-
"c:/dev/x/.y/a.ts"
1356+
"c:/dev/x/.y/a.ts",
1357+
"c:/dev/.z/.b.ts"
13281358
],
13291359
wildcardDirectories: {
13301360
"c:/dev/.z": ts.WatchDirectoryFlags.Recursive,

tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,10 @@ maxDepthExceeded/root.ts(3,1): error TS2322: Type '"10"' is not assignable to ty
22
maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
33

44

5-
==== entry.js (0 errors) ====
6-
var m3 = require("m3");
7-
8-
module.exports = {
9-
"a": 42,
10-
"b": "hello, world",
11-
"person": m3.person
12-
};
13-
145
==== relative.js (0 errors) ====
156
exports.relativeProp = true;
167

17-
==== maxDepthExceeded/node_modules/m1/index.js (0 errors) ====
8+
==== index.js (0 errors) ====
189
var m2 = require('m2');
1910
var rel = require('./relative');
2011

@@ -40,4 +31,13 @@ maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it i
4031
!!! error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
4132

4233
m1.f2.person.age = "10"; // OK if stopped at 2 modules: person will be "any".
34+
35+
==== entry.js (0 errors) ====
36+
var m3 = require("m3");
37+
38+
module.exports = {
39+
"a": 42,
40+
"b": "hello, world",
41+
"person": m3.person
42+
};
4343

tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
"project": "maxDepthExceeded",
88
"resolvedInputFiles": [
99
"lib.d.ts",
10-
"maxDepthExceeded/node_modules/m2/entry.js",
1110
"maxDepthExceeded/node_modules/m1/relative.js",
1211
"maxDepthExceeded/node_modules/m1/index.js",
13-
"maxDepthExceeded/root.ts"
12+
"maxDepthExceeded/root.ts",
13+
"maxDepthExceeded/node_modules/m2/entry.js"
1414
],
1515
"emittedFiles": [
1616
"maxDepthExceeded/built/node_modules/m1/relative.js",

tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,10 @@ maxDepthExceeded/root.ts(3,1): error TS2322: Type '"10"' is not assignable to ty
22
maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
33

44

5-
==== entry.js (0 errors) ====
6-
var m3 = require("m3");
7-
8-
module.exports = {
9-
"a": 42,
10-
"b": "hello, world",
11-
"person": m3.person
12-
};
13-
145
==== relative.js (0 errors) ====
156
exports.relativeProp = true;
167

17-
==== maxDepthExceeded/node_modules/m1/index.js (0 errors) ====
8+
==== index.js (0 errors) ====
189
var m2 = require('m2');
1910
var rel = require('./relative');
2011

@@ -40,4 +31,13 @@ maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it i
4031
!!! error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
4132

4233
m1.f2.person.age = "10"; // OK if stopped at 2 modules: person will be "any".
34+
35+
==== entry.js (0 errors) ====
36+
var m3 = require("m3");
37+
38+
module.exports = {
39+
"a": 42,
40+
"b": "hello, world",
41+
"person": m3.person
42+
};
4343

tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
"project": "maxDepthExceeded",
88
"resolvedInputFiles": [
99
"lib.d.ts",
10-
"maxDepthExceeded/node_modules/m2/entry.js",
1110
"maxDepthExceeded/node_modules/m1/relative.js",
1211
"maxDepthExceeded/node_modules/m1/index.js",
13-
"maxDepthExceeded/root.ts"
12+
"maxDepthExceeded/root.ts",
13+
"maxDepthExceeded/node_modules/m2/entry.js"
1414
],
1515
"emittedFiles": [
1616
"maxDepthExceeded/built/node_modules/m1/relative.js",

0 commit comments

Comments
 (0)