Skip to content

Commit 869abb1

Browse files
committed
Parse excludeDirectories and excludeFiles
1 parent a3ee09d commit 869abb1

File tree

9 files changed

+265
-35
lines changed

9 files changed

+265
-35
lines changed

src/compiler/commandLineParser.ts

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,30 @@ namespace ts {
119119
category: Diagnostics.Advanced_Options,
120120
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
121121
},
122+
{
123+
name: "excludeDirectories",
124+
type: "list",
125+
element: {
126+
name: "excludeDirectories",
127+
type: "string",
128+
isFilePath: true,
129+
extraValidation: specToDiagnostic
130+
},
131+
category: Diagnostics.Advanced_Options,
132+
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
133+
},
134+
{
135+
name: "excludeFiles",
136+
type: "list",
137+
element: {
138+
name: "excludeFiles",
139+
type: "string",
140+
isFilePath: true,
141+
extraValidation: specToDiagnostic
142+
},
143+
category: Diagnostics.Advanced_Options,
144+
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
145+
},
122146
];
123147

124148
/* @internal */
@@ -1165,9 +1189,9 @@ namespace ts {
11651189
const values = value.split(",");
11661190
switch (opt.element.type) {
11671191
case "number":
1168-
return map(values, parseInt);
1192+
return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors));
11691193
case "string":
1170-
return map(values, v => v || "");
1194+
return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors));
11711195
default:
11721196
return mapDefined(values, v => parseCustomTypeOption(<CommandLineOptionOfCustomType>opt.element, v, errors));
11731197
}
@@ -1297,7 +1321,7 @@ namespace ts {
12971321
}
12981322
else if (opt.type === "boolean") {
12991323
if (optValue === "false") {
1300-
options[opt.name] = false;
1324+
options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors);
13011325
i++;
13021326
}
13031327
else {
@@ -1319,20 +1343,20 @@ namespace ts {
13191343
if (args[i] !== "null") {
13201344
switch (opt.type) {
13211345
case "number":
1322-
options[opt.name] = parseInt(args[i]);
1346+
options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors);
13231347
i++;
13241348
break;
13251349
case "boolean":
13261350
// boolean flag has optional value true, false, others
13271351
const optValue = args[i];
1328-
options[opt.name] = optValue !== "false";
1352+
options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors);
13291353
// consume next argument as boolean flag value
13301354
if (optValue === "false" || optValue === "true") {
13311355
i++;
13321356
}
13331357
break;
13341358
case "string":
1335-
options[opt.name] = args[i] || "";
1359+
options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors);
13361360
i++;
13371361
break;
13381362
case "list":
@@ -1777,28 +1801,30 @@ namespace ts {
17771801
function convertArrayLiteralExpressionToJson(
17781802
elements: NodeArray<Expression>,
17791803
elementOption: CommandLineOption | undefined
1780-
): any[] | void {
1804+
) {
17811805
if (!returnValue) {
1782-
return elements.forEach(element => convertPropertyValueToJson(element, elementOption));
1806+
elements.forEach(element => convertPropertyValueToJson(element, elementOption));
1807+
return undefined;
17831808
}
17841809

17851810
// Filter out invalid values
17861811
return filter(elements.map(element => convertPropertyValueToJson(element, elementOption)), v => v !== undefined);
17871812
}
17881813

17891814
function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any {
1815+
let invalidReported: boolean | undefined;
17901816
switch (valueExpression.kind) {
17911817
case SyntaxKind.TrueKeyword:
17921818
reportInvalidOptionValue(option && option.type !== "boolean");
1793-
return true;
1819+
return validateValueOk(/*value*/ true);
17941820

17951821
case SyntaxKind.FalseKeyword:
17961822
reportInvalidOptionValue(option && option.type !== "boolean");
1797-
return false;
1823+
return validateValueOk(/*value*/ false);
17981824

17991825
case SyntaxKind.NullKeyword:
18001826
reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for
1801-
return null; // eslint-disable-line no-null/no-null
1827+
return validateValueOk(/*value*/ null); // eslint-disable-line no-null/no-null
18021828

18031829
case SyntaxKind.StringLiteral:
18041830
if (!isDoubleQuotedString(valueExpression)) {
@@ -1816,20 +1842,21 @@ namespace ts {
18161842
(message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1)
18171843
)
18181844
);
1845+
invalidReported = true;
18191846
}
18201847
}
1821-
return text;
1848+
return validateValueOk(text);
18221849

18231850
case SyntaxKind.NumericLiteral:
18241851
reportInvalidOptionValue(option && option.type !== "number");
1825-
return Number((<NumericLiteral>valueExpression).text);
1852+
return validateValueOk(Number((<NumericLiteral>valueExpression).text));
18261853

18271854
case SyntaxKind.PrefixUnaryExpression:
18281855
if ((<PrefixUnaryExpression>valueExpression).operator !== SyntaxKind.MinusToken || (<PrefixUnaryExpression>valueExpression).operand.kind !== SyntaxKind.NumericLiteral) {
18291856
break; // not valid JSON syntax
18301857
}
18311858
reportInvalidOptionValue(option && option.type !== "number");
1832-
return -Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text);
1859+
return validateValueOk(-Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text));
18331860

18341861
case SyntaxKind.ObjectLiteralExpression:
18351862
reportInvalidOptionValue(option && option.type !== "object");
@@ -1843,20 +1870,20 @@ namespace ts {
18431870
// If need arises, we can modify this interface and callbacks as needed
18441871
if (option) {
18451872
const { elementOptions, extraKeyDiagnostics, name: optionName } = <TsConfigOnlyOption>option;
1846-
return convertObjectLiteralExpressionToJson(objectLiteralExpression,
1847-
elementOptions, extraKeyDiagnostics, optionName);
1873+
return validateValueOk(convertObjectLiteralExpressionToJson(objectLiteralExpression,
1874+
elementOptions, extraKeyDiagnostics, optionName));
18481875
}
18491876
else {
1850-
return convertObjectLiteralExpressionToJson(
1877+
return validateValueOk(convertObjectLiteralExpressionToJson(
18511878
objectLiteralExpression, /* knownOptions*/ undefined,
1852-
/*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined);
1879+
/*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined));
18531880
}
18541881

18551882
case SyntaxKind.ArrayLiteralExpression:
18561883
reportInvalidOptionValue(option && option.type !== "list");
1857-
return convertArrayLiteralExpressionToJson(
1884+
return validateValueOk(convertArrayLiteralExpressionToJson(
18581885
(<ArrayLiteralExpression>valueExpression).elements,
1859-
option && (<CommandLineOptionOfListType>option).element);
1886+
option && (<CommandLineOptionOfListType>option).element));
18601887
}
18611888

18621889
// Not in expected format
@@ -1869,9 +1896,21 @@ namespace ts {
18691896

18701897
return undefined;
18711898

1899+
function validateValueOk(value: CompilerOptionsValue) {
1900+
if (!invalidReported) {
1901+
const diagnostic = option?.extraValidation?.(value);
1902+
if (diagnostic) {
1903+
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic));
1904+
return undefined;
1905+
}
1906+
}
1907+
return value;
1908+
}
1909+
18721910
function reportInvalidOptionValue(isError: boolean | undefined) {
18731911
if (isError) {
18741912
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!)));
1913+
invalidReported = true;
18751914
}
18761915
}
18771916
}
@@ -2782,7 +2821,8 @@ namespace ts {
27822821
else if (!isString(optType)) {
27832822
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
27842823
}
2785-
return normalizeNonListOptionValue(opt, basePath, value);
2824+
const validatedValue = validateJsonOptionValue(opt, value, errors);
2825+
return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue);
27862826
}
27872827
else {
27882828
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt)));
@@ -2814,12 +2854,20 @@ namespace ts {
28142854
return value;
28152855
}
28162856

2857+
function validateJsonOptionValue<T extends CompilerOptionsValue>(opt: CommandLineOption, value: T, errors: Push<Diagnostic>): T | undefined {
2858+
if (isNullOrUndefined(value)) return undefined;
2859+
const d = opt.extraValidation?.(value);
2860+
if (!d) return value;
2861+
errors.push(createCompilerDiagnostic(...d));
2862+
return undefined;
2863+
}
2864+
28172865
function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push<Diagnostic>) {
28182866
if (isNullOrUndefined(value)) return undefined;
28192867
const key = value.toLowerCase();
28202868
const val = opt.type.get(key);
28212869
if (val !== undefined) {
2822-
return val;
2870+
return validateJsonOptionValue(opt, val, errors);
28232871
}
28242872
else {
28252873
errors.push(createCompilerDiagnosticForInvalidCustomType(opt));
@@ -2921,11 +2969,11 @@ namespace ts {
29212969
// file system.
29222970

29232971
if (includeSpecs) {
2924-
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
2972+
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, jsonSourceFile, "include");
29252973
}
29262974

29272975
if (excludeSpecs) {
2928-
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
2976+
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, jsonSourceFile, "exclude");
29292977
}
29302978

29312979
// Wildcard directories (provided as part of a wildcard path) are stored in a
@@ -3068,11 +3116,11 @@ namespace ts {
30683116
return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck));
30693117
}
30703118

3071-
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
3119+
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
30723120
return specs.filter(spec => {
3073-
const diag = specToDiagnostic(spec, allowTrailingRecursion);
3121+
const diag = specToDiagnostic(spec, disallowTrailingRecursion);
30743122
if (diag !== undefined) {
3075-
errors.push(createDiagnostic(diag, spec));
3123+
errors.push(createDiagnostic(...diag));
30763124
}
30773125
return diag === undefined;
30783126
});
@@ -3085,12 +3133,12 @@ namespace ts {
30853133
}
30863134
}
30873135

3088-
function specToDiagnostic(spec: string, allowTrailingRecursion: boolean): DiagnosticMessage | undefined {
3089-
if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
3090-
return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0;
3136+
function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined {
3137+
if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
3138+
return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
30913139
}
30923140
else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) {
3093-
return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0;
3141+
return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
30943142
}
30953143
}
30963144

src/compiler/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5749,6 +5749,8 @@ namespace ts {
57495749
watchDirectory?: WatchDirectoryKind;
57505750
fallbackPolling?: PollingWatchKind;
57515751
synchronousWatchDirectory?: boolean;
5752+
excludeDirectories?: string[];
5753+
excludeFiles?: string[];
57525754

57535755
[option: string]: CompilerOptionsValue | undefined;
57545756
}
@@ -5915,6 +5917,7 @@ namespace ts {
59155917
affectsSemanticDiagnostics?: true; // true if option affects semantic diagnostics
59165918
affectsEmit?: true; // true if the options affects emit
59175919
transpileOptionValue?: boolean | undefined; // If set this means that the option should be set to this value when transpiling
5920+
extraValidation?: (value: CompilerOptionsValue) => [DiagnosticMessage, ...string[]] | undefined; // Additional validation to be performed for the value to be valid
59185921
}
59195922

59205923
/* @internal */

src/server/protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,8 @@ namespace ts.server.protocol {
14921492
watchDirectory?: WatchDirectoryKind | ts.WatchDirectoryKind;
14931493
fallbackPolling?: PollingWatchKind | ts.PollingWatchKind;
14941494
synchronousWatchDirectory?: boolean;
1495+
excludeDirectories?: string[];
1496+
excludeFiles?: string[];
14951497
[option: string]: CompilerOptionsValue | undefined;
14961498
}
14971499

0 commit comments

Comments
 (0)