Merge pull request #12556 from Microsoft/include_ordering

Sort matched files by include order
This commit is contained in:
Andy 2017-01-26 12:25:58 -08:00 committed by GitHub
commit 9cd7178434
7 changed files with 115 additions and 72 deletions

View File

@ -1258,7 +1258,6 @@ namespace ts {
const literalFiles = arrayFrom(literalFileMap.values());
const wildcardFiles = arrayFrom(wildcardFileMap.values());
wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive);
return {
fileNames: literalFiles.concat(wildcardFiles),
wildcardDirectories

View File

@ -260,6 +260,16 @@ namespace ts {
return undefined;
}
/** Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found. */
export function findIndex<T>(array: T[], predicate: (element: T, index: number) => boolean): number {
for (let i = 0; i < array.length; i++) {
if (predicate(array[i], i)) {
return i;
}
}
return -1;
}
/**
* Returns the first truthy result of `callback`, or else fails.
* This is like `forEach`, but never returns undefined.
@ -1724,7 +1734,19 @@ namespace ts {
const singleAsteriskRegexFragmentFiles = "([^./]|(\\.(?!min\\.js$))?)*";
const singleAsteriskRegexFragmentOther = "[^/]*";
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string | undefined {
const patterns = getRegularExpressionsForWildcards(specs, basePath, usage);
if (!patterns || !patterns.length) {
return undefined;
}
const pattern = patterns.map(pattern => `(${pattern})`).join("|");
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
const terminator = usage === "exclude" ? "($|/)" : "$";
return `^(${pattern})${terminator}`;
}
function getRegularExpressionsForWildcards(specs: string[], basePath: string, usage: "files" | "directories" | "exclude"): string[] | undefined {
if (specs === undefined || specs.length === 0) {
return undefined;
}
@ -1738,33 +1760,8 @@ namespace ts {
*/
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";
let pattern = "";
let hasWrittenSubpattern = false;
for (const spec of specs) {
if (!spec) {
continue;
}
const subPattern = getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter);
if (subPattern === undefined) {
continue;
}
if (hasWrittenSubpattern) {
pattern += "|";
}
pattern += "(" + subPattern + ")";
hasWrittenSubpattern = true;
}
if (!pattern) {
return undefined;
}
// If excluding, match "foo/bar/baz...", but if including, only allow "foo".
const terminator = usage === "exclude" ? "($|/)" : "$";
return `^(${pattern})${terminator}`;
return flatMap(specs, spec =>
spec && getSubPatternFromSpec(spec, basePath, usage, singleAsteriskRegexFragment, doubleAsteriskRegexFragment, replaceWildcardCharacter));
}
/**
@ -1859,6 +1856,9 @@ namespace ts {
}
export interface FileMatcherPatterns {
/** One pattern for each "include" spec. */
includeFilePatterns: string[];
/** One pattern matching one of any of the "include" specs. */
includeFilePattern: string;
includeDirectoryPattern: string;
excludePattern: string;
@ -1871,6 +1871,7 @@ namespace ts {
const absolutePath = combinePaths(currentDirectory, path);
return {
includeFilePatterns: map(getRegularExpressionsForWildcards(includes, absolutePath, "files"), pattern => `^${pattern}$`),
includeFilePattern: getRegularExpressionForWildcard(includes, absolutePath, "files"),
includeDirectoryPattern: getRegularExpressionForWildcard(includes, absolutePath, "directories"),
excludePattern: getRegularExpressionForWildcard(excludes, absolutePath, "exclude"),
@ -1885,26 +1886,39 @@ namespace ts {
const patterns = getFileMatcherPatterns(path, excludes, includes, useCaseSensitiveFileNames, currentDirectory);
const regexFlag = useCaseSensitiveFileNames ? "" : "i";
const includeFileRegex = patterns.includeFilePattern && new RegExp(patterns.includeFilePattern, regexFlag);
const includeFileRegexes = patterns.includeFilePatterns && patterns.includeFilePatterns.map(pattern => new RegExp(pattern, regexFlag));
const includeDirectoryRegex = patterns.includeDirectoryPattern && new RegExp(patterns.includeDirectoryPattern, regexFlag);
const excludeRegex = patterns.excludePattern && new RegExp(patterns.excludePattern, regexFlag);
const result: string[] = [];
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
// If there are no "includes", then just put everything in results[0].
const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]];
const comparer = useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive;
for (const basePath of patterns.basePaths) {
visitDirectory(basePath, combinePaths(currentDirectory, basePath));
}
return result;
return flatten(results);
function visitDirectory(path: string, absolutePath: string) {
const { files, directories } = getFileSystemEntries(path);
let { files, directories } = getFileSystemEntries(path);
files = files.slice().sort(comparer);
directories = directories.slice().sort(comparer);
for (const current of files) {
const name = combinePaths(path, current);
const absoluteName = combinePaths(absolutePath, current);
if ((!extensions || fileExtensionIsAny(name, extensions)) &&
(!includeFileRegex || includeFileRegex.test(absoluteName)) &&
(!excludeRegex || !excludeRegex.test(absoluteName))) {
result.push(name);
if (extensions && !fileExtensionIsAny(name, extensions)) continue;
if (excludeRegex && excludeRegex.test(absoluteName)) continue;
if (!includeFileRegexes) {
results[0].push(name);
}
else {
const includeIndex = findIndex(includeFileRegexes, re => re.test(absoluteName));
if (includeIndex !== -1) {
results[includeIndex].push(name);
}
}
}

View File

@ -346,9 +346,9 @@ namespace ts {
fileNames: [
"c:/dev/a.ts",
"c:/dev/b.ts",
"c:/dev/node_modules/a.ts",
"c:/dev/bower_components/a.ts",
"c:/dev/jspm_packages/a.ts",
"c:/dev/node_modules/a.ts"
"c:/dev/jspm_packages/a.ts"
],
wildcardDirectories: {},
};
@ -373,9 +373,9 @@ namespace ts {
options: {},
errors: [],
fileNames: [
"c:/dev/node_modules/a.ts",
"c:/dev/bower_components/a.ts",
"c:/dev/jspm_packages/a.ts",
"c:/dev/node_modules/a.ts"
"c:/dev/jspm_packages/a.ts"
],
wildcardDirectories: {},
};
@ -398,9 +398,9 @@ namespace ts {
fileNames: [
"c:/dev/a.ts",
"c:/dev/b.ts",
"c:/dev/node_modules/a.ts",
"c:/dev/bower_components/a.ts",
"c:/dev/jspm_packages/a.ts",
"c:/dev/node_modules/a.ts"
"c:/dev/jspm_packages/a.ts"
],
wildcardDirectories: {},
};
@ -410,6 +410,36 @@ namespace ts {
});
describe("with wildcard include list", () => {
it("is sorted in include order, then in alphabetical order", () => {
const json = {
include: [
"z/*.ts",
"x/*.ts"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/dev/z/a.ts",
"c:/dev/z/aba.ts",
"c:/dev/z/abz.ts",
"c:/dev/z/b.ts",
"c:/dev/z/bba.ts",
"c:/dev/z/bbz.ts",
"c:/dev/x/a.ts",
"c:/dev/x/aa.ts",
"c:/dev/x/b.ts"
],
wildcardDirectories: {
"c:/dev/z": ts.WatchDirectoryFlags.None,
"c:/dev/x": ts.WatchDirectoryFlags.None
},
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath);
assertParsed(actual, expected);
});
it("same named declarations are excluded", () => {
const json = {
include: [
@ -506,8 +536,8 @@ namespace ts {
options: {},
errors: [],
fileNames: [
"c:/dev/x/a.ts",
"c:/dev/x/y/a.ts",
"c:/dev/x/a.ts",
"c:/dev/z/a.ts"
],
wildcardDirectories: {
@ -1282,8 +1312,8 @@ namespace ts {
options: {},
errors: [],
fileNames: [
"c:/dev/.z/.b.ts",
"c:/dev/x/.y/a.ts"
"c:/dev/x/.y/a.ts",
"c:/dev/.z/.b.ts"
],
wildcardDirectories: {}
};
@ -1323,8 +1353,8 @@ namespace ts {
options: {},
errors: [],
fileNames: [
"c:/dev/.z/.b.ts",
"c:/dev/x/.y/a.ts"
"c:/dev/x/.y/a.ts",
"c:/dev/.z/.b.ts"
],
wildcardDirectories: {
"c:/dev/.z": ts.WatchDirectoryFlags.Recursive,

View File

@ -2,19 +2,10 @@ maxDepthExceeded/root.ts(3,1): error TS2322: Type '"10"' is not assignable to ty
maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
==== entry.js (0 errors) ====
var m3 = require("m3");
module.exports = {
"a": 42,
"b": "hello, world",
"person": m3.person
};
==== relative.js (0 errors) ====
exports.relativeProp = true;
==== maxDepthExceeded/node_modules/m1/index.js (0 errors) ====
==== index.js (0 errors) ====
var m2 = require('m2');
var rel = require('./relative');
@ -40,4 +31,13 @@ maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it i
!!! error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
m1.f2.person.age = "10"; // OK if stopped at 2 modules: person will be "any".
==== entry.js (0 errors) ====
var m3 = require("m3");
module.exports = {
"a": 42,
"b": "hello, world",
"person": m3.person
};

View File

@ -7,10 +7,10 @@
"project": "maxDepthExceeded",
"resolvedInputFiles": [
"lib.d.ts",
"maxDepthExceeded/node_modules/m2/entry.js",
"maxDepthExceeded/node_modules/m1/relative.js",
"maxDepthExceeded/node_modules/m1/index.js",
"maxDepthExceeded/root.ts"
"maxDepthExceeded/root.ts",
"maxDepthExceeded/node_modules/m2/entry.js"
],
"emittedFiles": [
"maxDepthExceeded/built/node_modules/m1/relative.js",

View File

@ -2,19 +2,10 @@ maxDepthExceeded/root.ts(3,1): error TS2322: Type '"10"' is not assignable to ty
maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
==== entry.js (0 errors) ====
var m3 = require("m3");
module.exports = {
"a": 42,
"b": "hello, world",
"person": m3.person
};
==== relative.js (0 errors) ====
exports.relativeProp = true;
==== maxDepthExceeded/node_modules/m1/index.js (0 errors) ====
==== index.js (0 errors) ====
var m2 = require('m2');
var rel = require('./relative');
@ -40,4 +31,13 @@ maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it i
!!! error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property.
m1.f2.person.age = "10"; // OK if stopped at 2 modules: person will be "any".
==== entry.js (0 errors) ====
var m3 = require("m3");
module.exports = {
"a": 42,
"b": "hello, world",
"person": m3.person
};

View File

@ -7,10 +7,10 @@
"project": "maxDepthExceeded",
"resolvedInputFiles": [
"lib.d.ts",
"maxDepthExceeded/node_modules/m2/entry.js",
"maxDepthExceeded/node_modules/m1/relative.js",
"maxDepthExceeded/node_modules/m1/index.js",
"maxDepthExceeded/root.ts"
"maxDepthExceeded/root.ts",
"maxDepthExceeded/node_modules/m2/entry.js"
],
"emittedFiles": [
"maxDepthExceeded/built/node_modules/m1/relative.js",