Skip to content

Add support for including dotted and .min.js files explicitly in include #9528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 22, 2016
7 changes: 0 additions & 7 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,9 +693,6 @@ namespace ts {
return output;
}

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

if (ignoreFileNamePattern.test(file)) {
continue;
}

// We may have included a wildcard path with a lower priority
// extension due to the user-defined order of entries in the
// "include" array. If there is a lower priority extension in the
Expand Down
48 changes: 44 additions & 4 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -932,11 +932,29 @@ namespace ts {
const reservedCharacterPattern = /[^\w\s\/]/g;
const wildcardCharCodes = [CharacterCodes.asterisk, CharacterCodes.question];

/**
* Matches any single directory segment unless it is the last segment and a .min.js file
* Breakdown:
* [^./] # matches everything up to the first . character (excluding directory seperators)
* (\\.(?!min\\.js$))? # matches . characters but not if they are part of the .min.js file extension
*/
const singleAsteriskRegexFragmentFiles = "([^./]*(\\.(?!min\\.js$))?)*";
const singleAsteriskRegexFragmentOther = "[^/]*";

export function getRegularExpressionForWildcard(specs: string[], basePath: string, usage: "files" | "directories" | "exclude") {
if (specs === undefined || specs.length === 0) {
return undefined;
}

const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther;
const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther;

/**
* Regex for the ** wildcard. Matches any number of subdirectories. When used for including
* files or directories, does not match subdirectories that start with a . character
*/
const doubleAsteriskRegexFragment = usage === "exclude" ? "(/.+?)?" : "(/[^/.][^/]*)*?";

let pattern = "";
let hasWrittenSubpattern = false;
spec: for (const spec of specs) {
Expand All @@ -957,13 +975,13 @@ namespace ts {
components[0] = removeTrailingDirectorySeparator(components[0]);

let optionalCount = 0;
for (const component of components) {
for (let component of components) {
if (component === "**") {
if (hasRecursiveDirectoryWildcard) {
continue spec;
}

subpattern += "(/.+?)?";
subpattern += doubleAsteriskRegexFragment;
hasRecursiveDirectoryWildcard = true;
hasWrittenComponent = true;
}
Expand All @@ -977,6 +995,20 @@ namespace ts {
subpattern += directorySeparator;
}

if (usage !== "exclude") {
// The * and ? wildcards should not match directories or files that start with . if they
// appear first in a component. Dotted directories and files can be included explicitly
// like so: **/.*/.*
if (component.charCodeAt(0) === CharacterCodes.asterisk) {
subpattern += "([^./]" + singleAsteriskRegexFragment + ")?";
component = component.substr(1);
}
else if (component.charCodeAt(0) === CharacterCodes.question) {
subpattern += "[^./]";
component = component.substr(1);
}
}

subpattern += component.replace(reservedCharacterPattern, replaceWildcardCharacter);
hasWrittenComponent = true;
}
Expand All @@ -1002,8 +1034,16 @@ namespace ts {
return "^(" + pattern + (usage === "exclude" ? ")($|/)" : ")$");
}

function replaceWildcardCharacter(match: string) {
return match === "*" ? "[^/]*" : match === "?" ? "[^/]" : "\\" + match;
function replaceWildCardCharacterFiles(match: string) {
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentFiles);
}

function replaceWildCardCharacterOther(match: string) {
return replaceWildcardCharacter(match, singleAsteriskRegexFragmentOther);
}

function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: string) {
return match === "*" ? singleAsteriskRegexFragment : match === "?" ? "[^/]" : "\\" + match;
}

export interface FileSystemEntries {
Expand Down
186 changes: 186 additions & 0 deletions src/harness/unittests/matchFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ namespace ts {
"c:/dev/x/y/b.ts",
"c:/dev/js/a.js",
"c:/dev/js/b.js",
"c:/dev/js/d.min.js",
"c:/dev/js/ab.min.js",
"c:/ext/ext.ts",
"c:/ext/b/a..b.ts"
]);
Expand Down Expand Up @@ -75,6 +77,17 @@ namespace ts {
"c:/dev/jspm_packages/a.ts"
]);

const caseInsensitiveDottedFoldersHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, [
"c:/dev/x/d.ts",
"c:/dev/x/y/d.ts",
"c:/dev/x/y/.e.ts",
"c:/dev/x/.y/a.ts",
"c:/dev/.z/.b.ts",
"c:/dev/.z/c.ts",
"c:/dev/w/.u/e.ts",
"c:/dev/g.min.js/.g/g.ts"
]);

describe("matchFiles", () => {
describe("with literal file list", () => {
it("without exclusions", () => {
Expand Down Expand Up @@ -726,6 +739,33 @@ namespace ts {
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("include explicitly listed .min.js files when allowJs=true", () => {
const json = {
compilerOptions: {
allowJs: true
},
include: [
"js/*.min.js"
]
};
const expected: ts.ParsedCommandLine = {
options: {
allowJs: true
},
errors: [],
fileNames: [
"c:/dev/js/ab.min.js",
"c:/dev/js/d.min.js"
],
wildcardDirectories: {
"c:/dev/js": ts.WatchDirectoryFlags.None
}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("include paths outside of the project", () => {
const json = {
include: [
Expand Down Expand Up @@ -951,6 +991,35 @@ namespace ts {
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("exclude .min.js files using wildcards", () => {
const json = {
compilerOptions: {
allowJs: true
},
include: [
"js/*.min.js"
],
exclude: [
"js/a*"
]
};
const expected: ts.ParsedCommandLine = {
options: {
allowJs: true
},
errors: [],
fileNames: [
"c:/dev/js/d.min.js"
],
wildcardDirectories: {
"c:/dev/js": ts.WatchDirectoryFlags.None
}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
describe("with trailing recursive directory", () => {
it("in includes", () => {
const json = {
Expand Down Expand Up @@ -1145,5 +1214,122 @@ namespace ts {
});
});
});
describe("with files or folders that begin with a .", () => {
it("that are not explicitly included", () => {
const json = {
include: [
"x/**/*",
"w/*/*"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/dev/x/d.ts",
"c:/dev/x/y/d.ts",
],
wildcardDirectories: {
"c:/dev/x": ts.WatchDirectoryFlags.Recursive,
"c:/dev/w": ts.WatchDirectoryFlags.Recursive
}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
describe("that are explicitly included", () => {
it("without wildcards", () => {
const json = {
include: [
"x/.y/a.ts",
"c:/dev/.z/.b.ts"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/dev/.z/.b.ts",
"c:/dev/x/.y/a.ts"
],
wildcardDirectories: {}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("with recursive wildcards that match directories", () => {
const json = {
include: [
"**/.*/*"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/dev/.z/c.ts",
"c:/dev/g.min.js/.g/g.ts",
"c:/dev/w/.u/e.ts",
"c:/dev/x/.y/a.ts"
],
wildcardDirectories: {
"c:/dev": ts.WatchDirectoryFlags.Recursive
}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("with recursive wildcards that match nothing", () => {
const json = {
include: [
"x/**/.y/*",
".z/**/.*"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/dev/.z/.b.ts",
"c:/dev/x/.y/a.ts"
],
wildcardDirectories: {
"c:/dev/.z": ts.WatchDirectoryFlags.Recursive,
"c:/dev/x": ts.WatchDirectoryFlags.Recursive
}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("with wildcard excludes that implicitly exclude dotted files", () => {
const json = {
include: [
"**/.*/*"
],
exclude: [
"**/*"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [],
wildcardDirectories: {}
};
const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath);
assert.deepEqual(actual.fileNames, expected.fileNames);
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
});
});
});
}