Skip to content

Commit b220831

Browse files
author
Andy Hanson
committed
Sort matched files by include order
1 parent e313fef commit b220831

File tree

7 files changed

+142
-84
lines changed

7 files changed

+142
-84
lines changed

src/compiler/commandLineParser.ts

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

12651265
const literalFiles = reduceProperties(literalFileMap, addFileToOutput, []);
12661266
const wildcardFiles = reduceProperties(wildcardFileMap, addFileToOutput, []);
1267-
wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive);
12681267
return {
12691268
fileNames: literalFiles.concat(wildcardFiles),
12701269
wildcardDirectories

src/compiler/core.ts

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,7 +1668,19 @@ namespace ts {
16681668
const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*";
16691669
const singleAsteriskRegexFragmentOther = "[^/]*";
16701670

1671-
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
1671+
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string | undefined {
1672+
const patterns = getRegularExpressionsForWildcards(specs, basePath, usage);
1673+
if (!patterns || !patterns.length) {
1674+
return undefined;
1675+
}
1676+
1677+
const pattern = patterns.map(pattern => `(${pattern})`).join("|");
1678+
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
1679+
const terminator = usage === "exclude" ? "($|/)" : "$";
1680+
return `^(${pattern})${terminator}`;
1681+
}
1682+
1683+
function getRegularExpressionsForWildcards(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
16721684
if (specs === undefined || specs.length === 0) {
16731685
return undefined;
16741686
}
@@ -1682,33 +1694,8 @@ namespace ts {
16821694
*/
16831695
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";
16841696

1685-
let pattern = "";
1686-
let hasWrittenSubpattern = false;
1687-
for (const spec of specs) {
1688-
if (!spec) {
1689-
continue;
1690-
}
1691-
1692-
const subPattern = getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter);
1693-
if (subPattern === undefined) {
1694-
continue;
1695-
}
1696-
1697-
if (hasWrittenSubpattern) {
1698-
pattern += "|";
1699-
}
1700-
1701-
pattern += "(" + subPattern + ")";
1702-
hasWrittenSubpattern = true;
1703-
}
1704-
1705-
if (!pattern) {
1706-
return undefined;
1707-
}
1708-
1709-
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
1710-
const terminator = usage === "exclude" ? "($|/)" : "$";
1711-
return `^(${pattern})${terminator}`;
1697+
return flatMap(specs, spec =>
1698+
spec && getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter));
17121699
}
17131700

17141701
/**
@@ -1803,6 +1790,9 @@ namespace ts {
18031790
}
18041791

18051792
export interface FileMatcherPatterns {
1793+
/** One pattern for each "include" spec. */
1794+
includeFilePatterns: string[];
1795+
/** One pattern matching one of any of the "include" specs. */
18061796
includeFilePattern: string;
18071797
includeDirectoryPattern: string;
18081798
excludePattern: string;
@@ -1815,6 +1805,7 @@ namespace ts {
18151805
const absolutePath = combinePaths(currentDirectory, path);
18161806

18171807
return {
1808+
includeFilePatterns: map(getRegularExpressionsForWildcards(includes, absolutePath, "files"), pattern => `^${pattern}$`),
18181809
includeFilePattern: getRegularExpressionForWildcard(includes, absolutePath, "files"),
18191810
includeDirectoryPattern: getRegularExpressionForWildcard(includes, absolutePath, "directories"),
18201811
excludePattern: getRegularExpressionForWildcard(excludes, absolutePath, "exclude"),
@@ -1829,38 +1820,76 @@ namespace ts {
18291820
const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames, currentDirectory);
18301821

18311822
const regexFlag = useCaseSensitiveFileNames ? "" : "i";
1832-
const includeFileRegex = patterns.includeFilePattern && new RegExp(patterns.includeFilePattern, regexFlag);
1823+
const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => new RegExp(pattern, regexFlag));
18331824
const includeDirectoryRegex = patterns.includeDirectoryPattern && new RegExp(patterns.includeDirectoryPattern, regexFlag);
18341825
const excludeRegex = patterns.excludePattern && new RegExp(patterns.excludePattern, regexFlag);
18351826

1836-
const result: string[] = [];
1827+
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
1828+
// If there are no "includes", then just put everything in results[0].
1829+
const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]];
1830+
18371831
for (const basePath of patterns.basePaths) {
1838-
visitDirectory(basePath, combinePaths(currentDirectory, basePath));
1832+
forEachFileInRecursiveDirectories(basePath, combinePaths(currentDirectory, basePath), { useCaseSensitiveFileNames, getFileSystemEntries, includeDirectory, visitFile });
18391833
}
1840-
return result;
18411834

1842-
function visitDirectory(path: string, absolutePath: string) {
1843-
const { files, directories } = getFileSystemEntries(path);
1844-
1845-
for (const current of files) {
1846-
const name = combinePaths(path, current);
1847-
const absoluteName = combinePaths(absolutePath, current);
1848-
if ((!extensions || fileExtensionIsAny(name, extensions)) &&
1849-
(!includeFileRegex || includeFileRegex.test(absoluteName)) &&
1850-
(!excludeRegex || !excludeRegex.test(absoluteName))) {
1851-
result.push(name);
1835+
return flatten(results);
1836+
1837+
function includeDirectory(absoluteDirectoryName: string): boolean {
1838+
return (!includeDirectoryRegex || includeDirectoryRegex.test(absoluteDirectoryName)) &&
1839+
(!excludeRegex || !excludeRegex.test(absoluteDirectoryName));
1840+
}
1841+
1842+
function visitFile(fileName: string, absoluteFileName: string): void {
1843+
if (extensions && !fileExtensionIsAny(fileName, extensions) ||
1844+
excludeRegex && excludeRegex.test(absoluteFileName)) {
1845+
return;
1846+
}
1847+
1848+
if (!includeFileRegexes) {
1849+
results[0].push(fileName);
1850+
}
1851+
else {
1852+
for (let i = 0; i < includeFileRegexes.length; i++) {
1853+
if (includeFileRegexes[i].test(absoluteFileName)) {
1854+
results[i].push(fileName);
1855+
// Only include a file once.
1856+
break;
1857+
}
18521858
}
18531859
}
1860+
}
1861+
}
1862+
1863+
interface RecursiveDirectoryVisitor {
1864+
useCaseSensitiveFileNames: boolean;
1865+
getFileSystemEntries: (path: string) => FileSystemEntries;
1866+
includeDirectory: (absoluteDirectoryName: string) => boolean;
1867+
visitFile: (fileName: string, absoluteFileName: string) => void;
1868+
}
1869+
1870+
function forEachFileInRecursiveDirectories(start: string, absoluteStart: string, visitor: RecursiveDirectoryVisitor): void {
1871+
visitDirectory(start, absoluteStart);
18541872

1855-
for (const current of directories) {
1856-
const name = combinePaths(path, current);
1857-
const absoluteName = combinePaths(absolutePath, current);
1858-
if ((!includeDirectoryRegex || includeDirectoryRegex.test(absoluteName)) &&
1859-
(!excludeRegex || !excludeRegex.test(absoluteName))) {
1860-
visitDirectory(name, absoluteName);
1873+
function visitDirectory(path: string, absolutePath: string) {
1874+
let { files, directories } = visitor.getFileSystemEntries(path);
1875+
files = sorted(files);
1876+
directories = sorted(directories);
1877+
1878+
for (const file of files) {
1879+
visitor.visitFile(combinePaths(path, file), combinePaths(absolutePath, file));
1880+
}
1881+
1882+
for (const dir of directories) {
1883+
const absoluteName = combinePaths(absolutePath, dir);
1884+
if (visitor.includeDirectory(absoluteName)) {
1885+
visitDirectory(combinePaths(path, dir), absoluteName);
18611886
}
18621887
}
18631888
}
1889+
1890+
function sorted(names: string[]): string[] {
1891+
return names.slice().sort(visitor.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive);
1892+
}
18641893
}
18651894

18661895
/**

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: {
@@ -1230,8 +1260,8 @@ namespace ts {
12301260
options: {},
12311261
errors: [],
12321262
fileNames: [
1233-
"c:/dev/.z/.b.ts",
1234-
"c:/dev/x/.y/a.ts"
1263+
"c:/dev/x/.y/a.ts",
1264+
"c:/dev/.z/.b.ts"
12351265
],
12361266
wildcardDirectories: {}
12371267
};
@@ -1271,8 +1301,8 @@ namespace ts {
12711301
options: {},
12721302
errors: [],
12731303
fileNames: [
1274-
"c:/dev/.z/.b.ts",
1275-
"c:/dev/x/.y/a.ts"
1304+
"c:/dev/x/.y/a.ts",
1305+
"c:/dev/.z/.b.ts"
12761306
],
12771307
wildcardDirectories: {
12781308
"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)