mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-13 22:00:59 -05:00
Add way to exclude files and directories to watch (#39243)
* Parse excludeDirectories and excludeFiles * Use watch factory in typings installer * Some refactoring for watchFactory * Create Noop watcher if file or directory being watched is excluded * Baselines without using exclude watch options * Baselines including exclude option * Handle exclude options in the system watches * Add test without exclude option for recursive directory watching * Test baselines with exclude option * Always set sysLog * Test for exclude option in server * Add exclude options in the config file and fix the test * Fix host configuration for server * Handle host configuration for watch options * Fix sysLog time log so baselines can be clean * Handle reloadProjects to reload the project from scratch * Ensure that file updates are reflected * Feedback * Feedback
This commit is contained in:
@@ -133,6 +133,30 @@ namespace ts {
|
||||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
|
||||
},
|
||||
{
|
||||
name: "excludeDirectories",
|
||||
type: "list",
|
||||
element: {
|
||||
name: "excludeDirectory",
|
||||
type: "string",
|
||||
isFilePath: true,
|
||||
extraValidation: specToDiagnostic
|
||||
},
|
||||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
|
||||
},
|
||||
{
|
||||
name: "excludeFiles",
|
||||
type: "list",
|
||||
element: {
|
||||
name: "excludeFile",
|
||||
type: "string",
|
||||
isFilePath: true,
|
||||
extraValidation: specToDiagnostic
|
||||
},
|
||||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
|
||||
},
|
||||
];
|
||||
|
||||
/* @internal */
|
||||
@@ -1231,9 +1255,9 @@ namespace ts {
|
||||
const values = value.split(",");
|
||||
switch (opt.element.type) {
|
||||
case "number":
|
||||
return map(values, parseInt);
|
||||
return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors));
|
||||
case "string":
|
||||
return map(values, v => v || "");
|
||||
return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors));
|
||||
default:
|
||||
return mapDefined(values, v => parseCustomTypeOption(<CommandLineOptionOfCustomType>opt.element, v, errors));
|
||||
}
|
||||
@@ -1363,7 +1387,7 @@ namespace ts {
|
||||
}
|
||||
else if (opt.type === "boolean") {
|
||||
if (optValue === "false") {
|
||||
options[opt.name] = false;
|
||||
options[opt.name] = validateJsonOptionValue(opt, /*value*/ false, errors);
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
@@ -1385,20 +1409,20 @@ namespace ts {
|
||||
if (args[i] !== "null") {
|
||||
switch (opt.type) {
|
||||
case "number":
|
||||
options[opt.name] = parseInt(args[i]);
|
||||
options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors);
|
||||
i++;
|
||||
break;
|
||||
case "boolean":
|
||||
// boolean flag has optional value true, false, others
|
||||
const optValue = args[i];
|
||||
options[opt.name] = optValue !== "false";
|
||||
options[opt.name] = validateJsonOptionValue(opt, optValue !== "false", errors);
|
||||
// consume next argument as boolean flag value
|
||||
if (optValue === "false" || optValue === "true") {
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
options[opt.name] = args[i] || "";
|
||||
options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors);
|
||||
i++;
|
||||
break;
|
||||
case "list":
|
||||
@@ -1843,9 +1867,10 @@ namespace ts {
|
||||
function convertArrayLiteralExpressionToJson(
|
||||
elements: NodeArray<Expression>,
|
||||
elementOption: CommandLineOption | undefined
|
||||
): any[] | void {
|
||||
) {
|
||||
if (!returnValue) {
|
||||
return elements.forEach(element => convertPropertyValueToJson(element, elementOption));
|
||||
elements.forEach(element => convertPropertyValueToJson(element, elementOption));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Filter out invalid values
|
||||
@@ -1853,18 +1878,19 @@ namespace ts {
|
||||
}
|
||||
|
||||
function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption | undefined): any {
|
||||
let invalidReported: boolean | undefined;
|
||||
switch (valueExpression.kind) {
|
||||
case SyntaxKind.TrueKeyword:
|
||||
reportInvalidOptionValue(option && option.type !== "boolean");
|
||||
return true;
|
||||
return validateValue(/*value*/ true);
|
||||
|
||||
case SyntaxKind.FalseKeyword:
|
||||
reportInvalidOptionValue(option && option.type !== "boolean");
|
||||
return false;
|
||||
return validateValue(/*value*/ false);
|
||||
|
||||
case SyntaxKind.NullKeyword:
|
||||
reportInvalidOptionValue(option && option.name === "extends"); // "extends" is the only option we don't allow null/undefined for
|
||||
return null; // eslint-disable-line no-null/no-null
|
||||
return validateValue(/*value*/ null); // eslint-disable-line no-null/no-null
|
||||
|
||||
case SyntaxKind.StringLiteral:
|
||||
if (!isDoubleQuotedString(valueExpression)) {
|
||||
@@ -1882,20 +1908,21 @@ namespace ts {
|
||||
(message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1)
|
||||
)
|
||||
);
|
||||
invalidReported = true;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
return validateValue(text);
|
||||
|
||||
case SyntaxKind.NumericLiteral:
|
||||
reportInvalidOptionValue(option && option.type !== "number");
|
||||
return Number((<NumericLiteral>valueExpression).text);
|
||||
return validateValue(Number((<NumericLiteral>valueExpression).text));
|
||||
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
if ((<PrefixUnaryExpression>valueExpression).operator !== SyntaxKind.MinusToken || (<PrefixUnaryExpression>valueExpression).operand.kind !== SyntaxKind.NumericLiteral) {
|
||||
break; // not valid JSON syntax
|
||||
}
|
||||
reportInvalidOptionValue(option && option.type !== "number");
|
||||
return -Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text);
|
||||
return validateValue(-Number((<NumericLiteral>(<PrefixUnaryExpression>valueExpression).operand).text));
|
||||
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
reportInvalidOptionValue(option && option.type !== "object");
|
||||
@@ -1909,20 +1936,20 @@ namespace ts {
|
||||
// If need arises, we can modify this interface and callbacks as needed
|
||||
if (option) {
|
||||
const { elementOptions, extraKeyDiagnostics, name: optionName } = <TsConfigOnlyOption>option;
|
||||
return convertObjectLiteralExpressionToJson(objectLiteralExpression,
|
||||
elementOptions, extraKeyDiagnostics, optionName);
|
||||
return validateValue(convertObjectLiteralExpressionToJson(objectLiteralExpression,
|
||||
elementOptions, extraKeyDiagnostics, optionName));
|
||||
}
|
||||
else {
|
||||
return convertObjectLiteralExpressionToJson(
|
||||
return validateValue(convertObjectLiteralExpressionToJson(
|
||||
objectLiteralExpression, /* knownOptions*/ undefined,
|
||||
/*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined);
|
||||
/*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined));
|
||||
}
|
||||
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
reportInvalidOptionValue(option && option.type !== "list");
|
||||
return convertArrayLiteralExpressionToJson(
|
||||
return validateValue(convertArrayLiteralExpressionToJson(
|
||||
(<ArrayLiteralExpression>valueExpression).elements,
|
||||
option && (<CommandLineOptionOfListType>option).element);
|
||||
option && (<CommandLineOptionOfListType>option).element));
|
||||
}
|
||||
|
||||
// Not in expected format
|
||||
@@ -1935,9 +1962,21 @@ namespace ts {
|
||||
|
||||
return undefined;
|
||||
|
||||
function validateValue(value: CompilerOptionsValue) {
|
||||
if (!invalidReported) {
|
||||
const diagnostic = option?.extraValidation?.(value);
|
||||
if (diagnostic) {
|
||||
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, ...diagnostic));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function reportInvalidOptionValue(isError: boolean | undefined) {
|
||||
if (isError) {
|
||||
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option!.name, getCompilerOptionValueTypeString(option!)));
|
||||
invalidReported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2846,7 +2885,8 @@ namespace ts {
|
||||
return defaultOptions;
|
||||
}
|
||||
|
||||
function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push<Diagnostic>): CompilerOptionsValue {
|
||||
/*@internal*/
|
||||
export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push<Diagnostic>): CompilerOptionsValue {
|
||||
if (isCompilerOptionsValue(opt, value)) {
|
||||
const optType = opt.type;
|
||||
if (optType === "list" && isArray(value)) {
|
||||
@@ -2855,7 +2895,8 @@ namespace ts {
|
||||
else if (!isString(optType)) {
|
||||
return convertJsonOptionOfCustomType(<CommandLineOptionOfCustomType>opt, <string>value, errors);
|
||||
}
|
||||
return normalizeNonListOptionValue(opt, basePath, value);
|
||||
const validatedValue = validateJsonOptionValue(opt, value, errors);
|
||||
return isNullOrUndefined(validatedValue) ? validatedValue : normalizeNonListOptionValue(opt, basePath, validatedValue);
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt)));
|
||||
@@ -2887,12 +2928,20 @@ namespace ts {
|
||||
return value;
|
||||
}
|
||||
|
||||
function validateJsonOptionValue<T extends CompilerOptionsValue>(opt: CommandLineOption, value: T, errors: Push<Diagnostic>): T | undefined {
|
||||
if (isNullOrUndefined(value)) return undefined;
|
||||
const d = opt.extraValidation?.(value);
|
||||
if (!d) return value;
|
||||
errors.push(createCompilerDiagnostic(...d));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Push<Diagnostic>) {
|
||||
if (isNullOrUndefined(value)) return undefined;
|
||||
const key = value.toLowerCase();
|
||||
const val = opt.type.get(key);
|
||||
if (val !== undefined) {
|
||||
return val;
|
||||
return validateJsonOptionValue(opt, val, errors);
|
||||
}
|
||||
else {
|
||||
errors.push(createCompilerDiagnosticForInvalidCustomType(opt));
|
||||
@@ -2994,11 +3043,11 @@ namespace ts {
|
||||
// file system.
|
||||
|
||||
if (includeSpecs) {
|
||||
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
|
||||
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, jsonSourceFile, "include");
|
||||
}
|
||||
|
||||
if (excludeSpecs) {
|
||||
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
|
||||
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, jsonSourceFile, "exclude");
|
||||
}
|
||||
|
||||
// Wildcard directories (provided as part of a wildcard path) are stored in a
|
||||
@@ -3142,19 +3191,44 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const excludePattern = getRegularExpressionForWildcard(validatedExcludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude");
|
||||
return matchesExcludeWorker(pathToCheck, validatedExcludeSpecs, useCaseSensitiveFileNames, currentDirectory, basePath);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function matchesExclude(
|
||||
pathToCheck: string,
|
||||
excludeSpecs: readonly string[] | undefined,
|
||||
useCaseSensitiveFileNames: boolean,
|
||||
currentDirectory: string
|
||||
) {
|
||||
return matchesExcludeWorker(
|
||||
pathToCheck,
|
||||
filter(excludeSpecs, spec => !invalidDotDotAfterRecursiveWildcardPattern.test(spec)),
|
||||
useCaseSensitiveFileNames,
|
||||
currentDirectory
|
||||
);
|
||||
}
|
||||
|
||||
function matchesExcludeWorker(
|
||||
pathToCheck: string,
|
||||
excludeSpecs: readonly string[] | undefined,
|
||||
useCaseSensitiveFileNames: boolean,
|
||||
currentDirectory: string,
|
||||
basePath?: string
|
||||
) {
|
||||
const excludePattern = getRegularExpressionForWildcard(excludeSpecs, combinePaths(normalizePath(currentDirectory), basePath), "exclude");
|
||||
const excludeRegex = excludePattern && getRegexFromPattern(excludePattern, useCaseSensitiveFileNames);
|
||||
if (!excludeRegex) return false;
|
||||
if (excludeRegex.test(pathToCheck)) return true;
|
||||
return !hasExtension(pathToCheck) && excludeRegex.test(ensureTrailingDirectorySeparator(pathToCheck));
|
||||
}
|
||||
|
||||
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
|
||||
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
|
||||
return specs.filter(spec => {
|
||||
if (!isString(spec)) return false;
|
||||
const diag = specToDiagnostic(spec, allowTrailingRecursion);
|
||||
const diag = specToDiagnostic(spec, disallowTrailingRecursion);
|
||||
if (diag !== undefined) {
|
||||
errors.push(createDiagnostic(diag, spec));
|
||||
errors.push(createDiagnostic(...diag));
|
||||
}
|
||||
return diag === undefined;
|
||||
});
|
||||
@@ -3167,12 +3241,12 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function specToDiagnostic(spec: string, allowTrailingRecursion: boolean): DiagnosticMessage | undefined {
|
||||
if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
|
||||
return Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0;
|
||||
function specToDiagnostic(spec: string, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined {
|
||||
if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
|
||||
return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
|
||||
}
|
||||
else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) {
|
||||
return Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0;
|
||||
return [Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user