Skip to content

Commit

Permalink
Merge pull request #9475 from Microsoft/include_relative_path
Browse files Browse the repository at this point in the history
Handle relative paths in tsconfig exclude and include globs
  • Loading branch information
riknoll committed Jul 1, 2016
2 parents a591d40 + 9eba8d7 commit 44a86b0
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 12 deletions.
20 changes: 19 additions & 1 deletion src/compiler/commandLineParser.ts
Expand Up @@ -891,6 +891,21 @@ namespace ts {
*/
const invalidMultipleRecursionPatterns = /(^|\/)\*\*\/(.*\/)?\*\*($|\/)/;

/**
* Tests for a path where .. appears after a recursive directory wildcard.
* Matches **\..\*, **\a\..\*, and **\.., but not ..\**\*
*
* NOTE: used \ in place of / above to avoid issues with multiline comments.
*
* Breakdown:
* (^|\/) # matches either the beginning of the string or a directory separator.
* \*\*\/ # matches a recursive directory wildcard "**" followed by a directory separator.
* (.*\/)? # optionally matches any number of characters followed by a directory separator.
* \.\. # matches a parent directory path component ".."
* ($|\/) # matches either the end of the string or a directory separator.
*/
const invalidDotDotAfterRecursiveWildcardPattern = /(^|\/)\*\*\/(.*\/)?\.\.($|\/)/;

/**
* Tests for a path containing a wildcard character in a directory component of the path.
* Matches \*\, \?\, and \a*b\, but not \a\ or \a\*.
Expand Down Expand Up @@ -1023,6 +1038,9 @@ namespace ts {
else if (invalidMultipleRecursionPatterns.test(spec)) {
errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, spec));
}
else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) {
errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec));
}
else {
validSpecs.push(spec);
}
Expand Down Expand Up @@ -1052,7 +1070,7 @@ namespace ts {
if (include !== undefined) {
const recursiveKeys: string[] = [];
for (const file of include) {
const name = combinePaths(path, file);
const name = normalizePath(combinePaths(path, file));
if (excludeRegex && excludeRegex.test(name)) {
continue;
}
Expand Down
20 changes: 11 additions & 9 deletions src/compiler/core.ts
Expand Up @@ -1072,15 +1072,17 @@ namespace ts {
// Storage for literal base paths amongst the include patterns.
const includeBasePaths: string[] = [];
for (const include of includes) {
if (isRootedDiskPath(include)) {
const wildcardOffset = indexOfAnyCharCode(include, wildcardCharCodes);
const includeBasePath = wildcardOffset < 0
? removeTrailingDirectorySeparator(getDirectoryPath(include))
: include.substring(0, include.lastIndexOf(directorySeparator, wildcardOffset));

// Append the literal and canonical candidate base paths.
includeBasePaths.push(includeBasePath);
}
// We also need to check the relative paths by converting them to absolute and normalizing
// in case they escape the base path (e.g "..\somedirectory")
const absolute: string = isRootedDiskPath(include) ? include : normalizePath(combinePaths(path, include));

const wildcardOffset = indexOfAnyCharCode(absolute, wildcardCharCodes);
const includeBasePath = wildcardOffset < 0
? removeTrailingDirectorySeparator(getDirectoryPath(absolute))
: absolute.substring(0, absolute.lastIndexOf(directorySeparator, wildcardOffset));

// Append the literal and canonical candidate base paths.
includeBasePaths.push(includeBasePath);
}

// Sort the offsets array using either the literal or canonical path representations.
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Expand Up @@ -2332,6 +2332,10 @@
"category": "Error",
"code": 5064
},
"File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '{0}'.": {
"category": "Error",
"code": 5065
},
"Concatenate and emit output to single file.": {
"category": "Message",
"code": 6001
Expand Down
198 changes: 196 additions & 2 deletions tests/cases/unittests/matchFiles.ts
Expand Up @@ -24,7 +24,8 @@ namespace ts {
"c:/dev/x/y/b.ts",
"c:/dev/js/a.js",
"c:/dev/js/b.js",
"c:/ext/ext.ts"
"c:/ext/ext.ts",
"c:/ext/b/a..b.ts"
]);

const caseSensitiveBasePath = "/dev/";
Expand Down Expand Up @@ -740,7 +741,7 @@ namespace ts {
"c:/dev/a.ts",
"c:/dev/b.ts",
"c:/dev/c.d.ts",
"c:/ext/ext.ts",
"c:/ext/ext.ts"
],
wildcardDirectories: {
"c:/dev": ts.WatchDirectoryFlags.None,
Expand All @@ -752,6 +753,97 @@ namespace ts {
assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories);
assert.deepEqual(actual.errors, expected.errors);
});
it("include paths outside of the project using relative paths", () => {
const json = {
include: [
"*",
"../ext/*"
],
exclude: [
"**"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/ext/ext.ts"
],
wildcardDirectories: {
"c:/ext": 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("exclude paths outside of the project using relative paths", () => {
const json = {
include: [
"c:/**/*"
],
exclude: [
"../**"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [],
wildcardDirectories: {}
};
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 files with .. in their name", () => {
const json = {
include: [
"c:/ext/b/a..b.ts"
],
exclude: [
"**"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/ext/b/a..b.ts"
],
wildcardDirectories: {}
};
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("exclude files with .. in their name", () => {
const json = {
include: [
"c:/ext/**/*"
],
exclude: [
"c:/ext/b/a..b.ts"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [],
fileNames: [
"c:/ext/ext.ts",
],
wildcardDirectories: {
"c:/ext": ts.WatchDirectoryFlags.Recursive
}
};
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("with jsx=none, allowJs=false", () => {
const json = {
compilerOptions: {
Expand Down Expand Up @@ -951,6 +1043,108 @@ namespace ts {
assert.deepEqual(actual.errors, expected.errors);
});
});

describe("with parent directory symbols after a recursive directory pattern", () => {
it("in includes immediately after", () => {
const json = {
include: [
"**/../*"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [
ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*")
],
fileNames: [],
wildcardDirectories: {}
};
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("in includes after a subdirectory", () => {
const json = {
include: [
"**/y/../*"
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [
ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*")
],
fileNames: [],
wildcardDirectories: {}
};
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("in excludes immediately after", () => {
const json = {
include: [
"**/a.ts"
],
exclude: [
"**/.."
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [
ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..")
],
fileNames: [
"c:/dev/a.ts",
"c:/dev/x/a.ts",
"c:/dev/x/y/a.ts",
"c:/dev/z/a.ts"
],
wildcardDirectories: {
"c:/dev": ts.WatchDirectoryFlags.Recursive
}
};
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("in excludes after a subdirectory", () => {
const json = {
include: [
"**/a.ts"
],
exclude: [
"**/y/.."
]
};
const expected: ts.ParsedCommandLine = {
options: {},
errors: [
ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..")
],
fileNames: [
"c:/dev/a.ts",
"c:/dev/x/a.ts",
"c:/dev/x/y/a.ts",
"c:/dev/z/a.ts"
],
wildcardDirectories: {
"c:/dev": ts.WatchDirectoryFlags.Recursive
}
};
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);
});
});
});
});
}

0 comments on commit 44a86b0

Please sign in to comment.