mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Add template variable ${configDir} for substitution of config files directory path (#58042)
This commit is contained in:
@@ -53,6 +53,7 @@ import {
|
||||
getFileMatcherPatterns,
|
||||
getLocaleSpecificMessage,
|
||||
getNormalizedAbsolutePath,
|
||||
getOwnKeys,
|
||||
getRegexFromPattern,
|
||||
getRegularExpressionForWildcard,
|
||||
getRegularExpressionsForWildcards,
|
||||
@@ -313,6 +314,7 @@ export const optionsForWatch: CommandLineOption[] = [
|
||||
isFilePath: true,
|
||||
extraValidation: specToDiagnostic,
|
||||
},
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
category: Diagnostics.Watch_and_Build_Modes,
|
||||
description: Diagnostics.Remove_a_list_of_directories_from_the_watch_process,
|
||||
},
|
||||
@@ -325,6 +327,7 @@ export const optionsForWatch: CommandLineOption[] = [
|
||||
isFilePath: true,
|
||||
extraValidation: specToDiagnostic,
|
||||
},
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
category: Diagnostics.Watch_and_Build_Modes,
|
||||
description: Diagnostics.Remove_a_list_of_files_from_the_watch_mode_s_processing,
|
||||
},
|
||||
@@ -1034,6 +1037,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
|
||||
name: "paths",
|
||||
type: "object",
|
||||
affectsModuleResolution: true,
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
isTSConfigOnly: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Specify_a_set_of_entries_that_re_map_imports_to_additional_lookup_locations,
|
||||
@@ -1051,6 +1055,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
|
||||
isFilePath: true,
|
||||
},
|
||||
affectsModuleResolution: true,
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Allow_multiple_folders_to_be_treated_as_one_when_resolving_modules,
|
||||
transpileOptionValue: undefined,
|
||||
@@ -1065,6 +1070,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
|
||||
isFilePath: true,
|
||||
},
|
||||
affectsModuleResolution: true,
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
category: Diagnostics.Modules,
|
||||
description: Diagnostics.Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types,
|
||||
},
|
||||
@@ -1600,6 +1606,15 @@ export const optionsAffectingProgramStructure: readonly CommandLineOption[] = op
|
||||
/** @internal */
|
||||
export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => hasProperty(option, "transpileOptionValue"));
|
||||
|
||||
/** @internal */
|
||||
export const configDirTemplateSubstitutionOptions: readonly CommandLineOption[] = optionDeclarations.filter(
|
||||
option => option.allowConfigDirTemplateSubstitution || (!option.isCommandLineOnly && option.isFilePath),
|
||||
);
|
||||
/** @internal */
|
||||
export const configDirTemplateSubstitutionWatchOptions: readonly CommandLineOption[] = optionsForWatch.filter(
|
||||
option => option.allowConfigDirTemplateSubstitution || (!option.isCommandLineOnly && option.isFilePath),
|
||||
);
|
||||
|
||||
// Build related options
|
||||
/** @internal */
|
||||
export const optionsForBuild: CommandLineOption[] = [
|
||||
@@ -2628,6 +2643,9 @@ function serializeOptionBaseObject(
|
||||
if (pathOptions && optionDefinition.isFilePath) {
|
||||
result.set(name, getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(value as string, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!));
|
||||
}
|
||||
else if (pathOptions && optionDefinition.type === "list" && optionDefinition.element.isFilePath) {
|
||||
result.set(name, (value as string[]).map(v => getRelativePathFromFile(pathOptions.configFilePath, getNormalizedAbsolutePath(v, getDirectoryPath(pathOptions.configFilePath)), getCanonicalFileName!)));
|
||||
}
|
||||
else {
|
||||
result.set(name, value);
|
||||
}
|
||||
@@ -2890,17 +2908,23 @@ function parseJsonConfigFileContentWorker(
|
||||
|
||||
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache);
|
||||
const { raw } = parsedConfig;
|
||||
const options = extend(existingOptions, parsedConfig.options || {});
|
||||
const watchOptions = existingWatchOptions && parsedConfig.watchOptions ?
|
||||
extend(existingWatchOptions, parsedConfig.watchOptions) :
|
||||
parsedConfig.watchOptions || existingWatchOptions;
|
||||
|
||||
const options = handleOptionConfigDirTemplateSubstitution(
|
||||
extend(existingOptions, parsedConfig.options || {}),
|
||||
configDirTemplateSubstitutionOptions,
|
||||
basePath,
|
||||
) as CompilerOptions;
|
||||
const watchOptions = handleWatchOptionsConfigDirTemplateSubstitution(
|
||||
existingWatchOptions && parsedConfig.watchOptions ?
|
||||
extend(existingWatchOptions, parsedConfig.watchOptions) :
|
||||
parsedConfig.watchOptions || existingWatchOptions,
|
||||
basePath,
|
||||
);
|
||||
options.configFilePath = configFileName && normalizeSlashes(configFileName);
|
||||
const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath);
|
||||
const configFileSpecs = getConfigFileSpecs();
|
||||
if (sourceFile) sourceFile.configFileSpecs = configFileSpecs;
|
||||
setConfigFileInOptions(options, sourceFile);
|
||||
|
||||
const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath);
|
||||
return {
|
||||
options,
|
||||
watchOptions,
|
||||
@@ -2955,6 +2979,7 @@ function parseJsonConfigFileContentWorker(
|
||||
includeSpecs = [defaultIncludeSpec];
|
||||
isDefaultIncludeSpec = true;
|
||||
}
|
||||
let validatedIncludeSpecsBeforeSubstitution: readonly string[] | undefined, validatedExcludeSpecsBeforeSubstitution: readonly string[] | undefined;
|
||||
let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined;
|
||||
|
||||
// The exclude spec list is converted into a regular expression, which allows us to quickly
|
||||
@@ -2962,20 +2987,37 @@ function parseJsonConfigFileContentWorker(
|
||||
// file system.
|
||||
|
||||
if (includeSpecs) {
|
||||
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include");
|
||||
validatedIncludeSpecsBeforeSubstitution = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include");
|
||||
validatedIncludeSpecs = getSubstitutedStringArrayWithConfigDirTemplate(
|
||||
validatedIncludeSpecsBeforeSubstitution,
|
||||
basePathForFileNames,
|
||||
) || validatedIncludeSpecsBeforeSubstitution;
|
||||
}
|
||||
|
||||
if (excludeSpecs) {
|
||||
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude");
|
||||
validatedExcludeSpecsBeforeSubstitution = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude");
|
||||
validatedExcludeSpecs = getSubstitutedStringArrayWithConfigDirTemplate(
|
||||
validatedExcludeSpecsBeforeSubstitution,
|
||||
basePathForFileNames,
|
||||
) || validatedExcludeSpecsBeforeSubstitution;
|
||||
}
|
||||
|
||||
const validatedFilesSpecBeforeSubstitution = filter(filesSpecs, isString);
|
||||
const validatedFilesSpec = getSubstitutedStringArrayWithConfigDirTemplate(
|
||||
validatedFilesSpecBeforeSubstitution,
|
||||
basePathForFileNames,
|
||||
) || validatedFilesSpecBeforeSubstitution;
|
||||
|
||||
return {
|
||||
filesSpecs,
|
||||
includeSpecs,
|
||||
excludeSpecs,
|
||||
validatedFilesSpec: filter(filesSpecs, isString),
|
||||
validatedFilesSpec,
|
||||
validatedIncludeSpecs,
|
||||
validatedExcludeSpecs,
|
||||
validatedFilesSpecBeforeSubstitution,
|
||||
validatedIncludeSpecsBeforeSubstitution,
|
||||
validatedExcludeSpecsBeforeSubstitution,
|
||||
pathPatterns: undefined, // Initialized on first use
|
||||
isDefaultIncludeSpec,
|
||||
};
|
||||
@@ -3043,6 +3085,84 @@ function parseJsonConfigFileContentWorker(
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function handleWatchOptionsConfigDirTemplateSubstitution(
|
||||
watchOptions: WatchOptions | undefined,
|
||||
basePath: string,
|
||||
) {
|
||||
return handleOptionConfigDirTemplateSubstitution(watchOptions, configDirTemplateSubstitutionWatchOptions, basePath) as WatchOptions | undefined;
|
||||
}
|
||||
|
||||
function handleOptionConfigDirTemplateSubstitution(
|
||||
options: OptionsBase | undefined,
|
||||
optionDeclarations: readonly CommandLineOption[],
|
||||
basePath: string,
|
||||
) {
|
||||
if (!options) return options;
|
||||
let result: OptionsBase | undefined;
|
||||
for (const option of optionDeclarations) {
|
||||
if (options[option.name] !== undefined) {
|
||||
const value = options[option.name];
|
||||
switch (option.type) {
|
||||
case "string":
|
||||
Debug.assert(option.isFilePath);
|
||||
if (startsWithConfigDirTemplate(value)) {
|
||||
setOptionValue(option, getSubstitutedPathWithConfigDirTemplate(value, basePath));
|
||||
}
|
||||
break;
|
||||
case "list":
|
||||
Debug.assert(option.element.isFilePath);
|
||||
const listResult = getSubstitutedStringArrayWithConfigDirTemplate(value as string[], basePath);
|
||||
if (listResult) setOptionValue(option, listResult);
|
||||
break;
|
||||
case "object":
|
||||
Debug.assert(option.name === "paths");
|
||||
const objectResult = getSubstitutedMapLikeOfStringArrayWithConfigDirTemplate(value as MapLike<string[]>, basePath);
|
||||
if (objectResult) setOptionValue(option, objectResult);
|
||||
break;
|
||||
default:
|
||||
Debug.fail("option type not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
return result || options;
|
||||
|
||||
function setOptionValue(option: CommandLineOption, value: CompilerOptionsValue) {
|
||||
(result ??= assign({}, options))[option.name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
const configDirTemplate = `\${configDir}`;
|
||||
function startsWithConfigDirTemplate(value: any): value is string {
|
||||
return isString(value) && startsWith(value, configDirTemplate, /*ignoreCase*/ true);
|
||||
}
|
||||
|
||||
function getSubstitutedPathWithConfigDirTemplate(value: string, basePath: string) {
|
||||
return getNormalizedAbsolutePath(value.replace(configDirTemplate, "./"), basePath);
|
||||
}
|
||||
|
||||
function getSubstitutedStringArrayWithConfigDirTemplate(list: readonly string[] | undefined, basePath: string) {
|
||||
if (!list) return list;
|
||||
let result: string[] | undefined;
|
||||
list.forEach((element, index) => {
|
||||
if (!startsWithConfigDirTemplate(element)) return;
|
||||
(result ??= list.slice())[index] = getSubstitutedPathWithConfigDirTemplate(element, basePath);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function getSubstitutedMapLikeOfStringArrayWithConfigDirTemplate(mapLike: MapLike<string[]>, basePath: string) {
|
||||
let result: MapLike<string[]> | undefined;
|
||||
const ownKeys = getOwnKeys(mapLike);
|
||||
ownKeys.forEach(key => {
|
||||
if (!isArray(mapLike[key])) return;
|
||||
const subStitution = getSubstitutedStringArrayWithConfigDirTemplate(mapLike[key], basePath);
|
||||
if (!subStitution) return;
|
||||
(result ??= assign({}, mapLike))[key] = subStitution;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isErrorNoInputFiles(error: Diagnostic) {
|
||||
return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code;
|
||||
}
|
||||
@@ -3144,9 +3264,10 @@ function parseConfig(
|
||||
else {
|
||||
ownConfig.extendedConfigPath.forEach(extendedConfigPath => applyExtendedConfig(result, extendedConfigPath));
|
||||
}
|
||||
if (!ownConfig.raw.include && result.include) ownConfig.raw.include = result.include;
|
||||
if (!ownConfig.raw.exclude && result.exclude) ownConfig.raw.exclude = result.exclude;
|
||||
if (!ownConfig.raw.files && result.files) ownConfig.raw.files = result.files;
|
||||
if (result.include) ownConfig.raw.include = result.include;
|
||||
if (result.exclude) ownConfig.raw.exclude = result.exclude;
|
||||
if (result.files) ownConfig.raw.files = result.files;
|
||||
|
||||
if (ownConfig.raw.compileOnSave === undefined && result.compileOnSave) ownConfig.raw.compileOnSave = result.compileOnSave;
|
||||
if (sourceFile && result.extendedSourceFiles) sourceFile.extendedSourceFiles = arrayFrom(result.extendedSourceFiles.keys());
|
||||
|
||||
@@ -3163,12 +3284,15 @@ function parseConfig(
|
||||
const extendsRaw = extendedConfig.raw;
|
||||
let relativeDifference: string | undefined;
|
||||
const setPropertyInResultIfNotUndefined = (propertyName: "include" | "exclude" | "files") => {
|
||||
if (ownConfig.raw[propertyName]) return; // No need to calculate if already set in own config
|
||||
if (extendsRaw[propertyName]) {
|
||||
result[propertyName] = map(extendsRaw[propertyName], (path: string) =>
|
||||
isRootedDiskPath(path) ? path : combinePaths(
|
||||
relativeDifference ||= convertToRelativePath(getDirectoryPath(extendedConfigPath), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)),
|
||||
path,
|
||||
));
|
||||
startsWithConfigDirTemplate(path) || isRootedDiskPath(path) ?
|
||||
path :
|
||||
combinePaths(
|
||||
relativeDifference ||= convertToRelativePath(getDirectoryPath(extendedConfigPath), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)),
|
||||
path,
|
||||
));
|
||||
}
|
||||
};
|
||||
setPropertyInResultIfNotUndefined("include");
|
||||
@@ -3527,7 +3651,8 @@ export function convertJsonOption(
|
||||
|
||||
function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
|
||||
if (option.isFilePath) {
|
||||
value = getNormalizedAbsolutePath(value, basePath);
|
||||
value = normalizeSlashes(value);
|
||||
value = !startsWithConfigDirTemplate(value) ? getNormalizedAbsolutePath(value, basePath) : value;
|
||||
if (value === "") {
|
||||
value = ".";
|
||||
}
|
||||
|
||||
@@ -7471,6 +7471,9 @@ export interface ConfigFileSpecs {
|
||||
validatedFilesSpec: readonly string[] | undefined;
|
||||
validatedIncludeSpecs: readonly string[] | undefined;
|
||||
validatedExcludeSpecs: readonly string[] | undefined;
|
||||
validatedFilesSpecBeforeSubstitution: readonly string[] | undefined;
|
||||
validatedIncludeSpecsBeforeSubstitution: readonly string[] | undefined;
|
||||
validatedExcludeSpecsBeforeSubstitution: readonly string[] | undefined;
|
||||
pathPatterns: readonly (string | Pattern)[] | undefined;
|
||||
isDefaultIncludeSpec: boolean;
|
||||
}
|
||||
@@ -7517,7 +7520,8 @@ export interface CommandLineOptionBase {
|
||||
affectsBuildInfo?: true; // true if this options should be emitted in buildInfo
|
||||
transpileOptionValue?: boolean | undefined; // If set this means that the option should be set to this value when transpiling
|
||||
extraValidation?: (value: CompilerOptionsValue) => [DiagnosticMessage, ...string[]] | undefined; // Additional validation to be performed for the value to be valid
|
||||
disallowNullOrUndefined?: true; // If set option does not allow setting null
|
||||
disallowNullOrUndefined?: true; // If set option does not allow setting null
|
||||
allowConfigDirTemplateSubstitution?: true; // If set option allows substitution of `${configDir}` in the value
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
FileWatcher,
|
||||
filter,
|
||||
find,
|
||||
findIndex,
|
||||
flattenDiagnosticMessageText,
|
||||
forEach,
|
||||
forEachEntry,
|
||||
@@ -418,7 +419,8 @@ export function getMatchedFileSpec(program: Program, fileName: string) {
|
||||
|
||||
const filePath = program.getCanonicalFileName(fileName);
|
||||
const basePath = getDirectoryPath(getNormalizedAbsolutePath(configFile.fileName, program.getCurrentDirectory()));
|
||||
return find(configFile.configFileSpecs.validatedFilesSpec, fileSpec => program.getCanonicalFileName(getNormalizedAbsolutePath(fileSpec, basePath)) === filePath);
|
||||
const index = findIndex(configFile.configFileSpecs.validatedFilesSpec, fileSpec => program.getCanonicalFileName(getNormalizedAbsolutePath(fileSpec, basePath)) === filePath);
|
||||
return index !== -1 ? configFile.configFileSpecs.validatedFilesSpecBeforeSubstitution![index] : undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -432,11 +434,12 @@ export function getMatchedIncludeSpec(program: Program, fileName: string) {
|
||||
const isJsonFile = fileExtensionIs(fileName, Extension.Json);
|
||||
const basePath = getDirectoryPath(getNormalizedAbsolutePath(configFile.fileName, program.getCurrentDirectory()));
|
||||
const useCaseSensitiveFileNames = program.useCaseSensitiveFileNames();
|
||||
return find(configFile?.configFileSpecs?.validatedIncludeSpecs, includeSpec => {
|
||||
const index = findIndex(configFile?.configFileSpecs?.validatedIncludeSpecs, includeSpec => {
|
||||
if (isJsonFile && !endsWith(includeSpec, Extension.Json)) return false;
|
||||
const pattern = getPatternFromSpec(includeSpec, basePath, "files");
|
||||
return !!pattern && getRegexFromPattern(`(${pattern})$`, useCaseSensitiveFileNames).test(fileName);
|
||||
});
|
||||
return index !== -1 ? configFile.configFileSpecs.validatedIncludeSpecsBeforeSubstitution![index] : undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
getPathComponents,
|
||||
getSnapshotText,
|
||||
getWatchFactory,
|
||||
handleWatchOptionsConfigDirTemplateSubstitution,
|
||||
hasExtension,
|
||||
hasProperty,
|
||||
hasTSFileExtension,
|
||||
@@ -516,6 +517,7 @@ export interface HostConfiguration {
|
||||
hostInfo: string;
|
||||
extraFileExtensions?: FileExtensionInfo[];
|
||||
watchOptions?: WatchOptions;
|
||||
/** @internal */ beforeSubstitution?: WatchOptions;
|
||||
}
|
||||
|
||||
export interface OpenConfiguredProjectResult {
|
||||
@@ -1722,7 +1724,7 @@ export class ProjectService {
|
||||
});
|
||||
},
|
||||
flags,
|
||||
this.getWatchOptionsFromProjectWatchOptions(config.parsedCommandLine!.watchOptions),
|
||||
this.getWatchOptionsFromProjectWatchOptions(config.parsedCommandLine!.watchOptions, getDirectoryPath(configFileName)),
|
||||
WatchType.WildcardDirectory,
|
||||
configFileName,
|
||||
);
|
||||
@@ -2100,7 +2102,7 @@ export class ProjectService {
|
||||
configFileName,
|
||||
(_fileName, eventKind) => this.onConfigFileChanged(canonicalConfigFilePath, eventKind),
|
||||
PollingInterval.High,
|
||||
this.getWatchOptionsFromProjectWatchOptions(configFileExistenceInfo?.config?.parsedCommandLine?.watchOptions),
|
||||
this.getWatchOptionsFromProjectWatchOptions(configFileExistenceInfo?.config?.parsedCommandLine?.watchOptions, getDirectoryPath(configFileName)),
|
||||
WatchType.ConfigFile,
|
||||
forProject,
|
||||
);
|
||||
@@ -2626,13 +2628,14 @@ export class ProjectService {
|
||||
const configFile = parseJsonText(configFilename, isString(configFileContent) ? configFileContent : "") as TsConfigSourceFile;
|
||||
const configFileErrors = configFile.parseDiagnostics as Diagnostic[];
|
||||
if (!isString(configFileContent)) configFileErrors.push(configFileContent);
|
||||
const configDir = getDirectoryPath(configFilename);
|
||||
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
|
||||
configFile,
|
||||
cachedDirectoryStructureHost,
|
||||
getDirectoryPath(configFilename),
|
||||
/*existingOptions*/ {},
|
||||
configDir,
|
||||
/*existingOptions*/ undefined,
|
||||
configFilename,
|
||||
/*resolutionStack*/ [],
|
||||
/*resolutionStack*/ undefined,
|
||||
this.hostConfiguration.extraFileExtensions,
|
||||
this.extendedConfigCache,
|
||||
);
|
||||
@@ -2668,9 +2671,9 @@ export class ProjectService {
|
||||
if (
|
||||
!oldCommandLine && !isJsonEqual(
|
||||
// Old options
|
||||
this.getWatchOptionsFromProjectWatchOptions(/*projectOptions*/ undefined),
|
||||
this.getWatchOptionsFromProjectWatchOptions(/*projectOptions*/ undefined, configDir),
|
||||
// New options
|
||||
this.getWatchOptionsFromProjectWatchOptions(parsedCommandLine.watchOptions),
|
||||
this.getWatchOptionsFromProjectWatchOptions(parsedCommandLine.watchOptions, configDir),
|
||||
)
|
||||
) {
|
||||
// Reset the config file watcher
|
||||
@@ -3588,7 +3591,10 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
if (args.watchOptions) {
|
||||
this.hostConfiguration.watchOptions = convertWatchOptions(args.watchOptions)?.watchOptions;
|
||||
const watchOptions = convertWatchOptions(args.watchOptions)?.watchOptions;
|
||||
const substitution = handleWatchOptionsConfigDirTemplateSubstitution(watchOptions, this.currentDirectory);
|
||||
this.hostConfiguration.watchOptions = substitution;
|
||||
this.hostConfiguration.beforeSubstitution = substitution === watchOptions ? undefined : watchOptions;
|
||||
this.logger.info(`Host watch options changed to ${JSON.stringify(this.hostConfiguration.watchOptions)}, it will be take effect for next watches.`);
|
||||
}
|
||||
}
|
||||
@@ -3596,14 +3602,19 @@ export class ProjectService {
|
||||
|
||||
/** @internal */
|
||||
getWatchOptions(project: Project) {
|
||||
return this.getWatchOptionsFromProjectWatchOptions(project.getWatchOptions());
|
||||
return this.getWatchOptionsFromProjectWatchOptions(project.getWatchOptions(), project.getCurrentDirectory());
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private getWatchOptionsFromProjectWatchOptions(projectOptions: WatchOptions | undefined) {
|
||||
return projectOptions && this.hostConfiguration.watchOptions ?
|
||||
{ ...this.hostConfiguration.watchOptions, ...projectOptions } :
|
||||
projectOptions || this.hostConfiguration.watchOptions;
|
||||
private getWatchOptionsFromProjectWatchOptions(projectOptions: WatchOptions | undefined, basePath: string) {
|
||||
const hostWatchOptions = !this.hostConfiguration.beforeSubstitution ? this.hostConfiguration.watchOptions :
|
||||
handleWatchOptionsConfigDirTemplateSubstitution(
|
||||
this.hostConfiguration.beforeSubstitution,
|
||||
basePath,
|
||||
);
|
||||
return projectOptions && hostWatchOptions ?
|
||||
{ ...hostWatchOptions, ...projectOptions } :
|
||||
projectOptions || hostWatchOptions;
|
||||
}
|
||||
|
||||
closeLog() {
|
||||
|
||||
@@ -98,6 +98,7 @@ import "./unittests/tsbuild/sample";
|
||||
import "./unittests/tsbuild/transitiveReferences";
|
||||
import "./unittests/tsbuildWatch/configFileErrors";
|
||||
import "./unittests/tsbuildWatch/demo";
|
||||
import "./unittests/tsbuildWatch/extends";
|
||||
import "./unittests/tsbuildWatch/libraryResolution";
|
||||
import "./unittests/tsbuildWatch/moduleResolution";
|
||||
import "./unittests/tsbuildWatch/noEmit";
|
||||
|
||||
@@ -253,6 +253,46 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
},
|
||||
}),
|
||||
"dev/extendsArrayFails2.json": jsonToReadableText({ extends: [42] }),
|
||||
"dev/configs/template.json": jsonToReadableText({
|
||||
include: ["${configDir}/../supplemental.*"], // eslint-disable-line no-template-curly-in-string
|
||||
files: ["${configDir}/main.ts"], // eslint-disable-line no-template-curly-in-string,
|
||||
compilerOptions: {
|
||||
declarationDir: "${configDir}/decls", // eslint-disable-line no-template-curly-in-string
|
||||
rootDirs: ["root1", "${configDir}/root2", "root3"], // eslint-disable-line no-template-curly-in-string
|
||||
paths: {
|
||||
"something": ["${configDir}/something"], // eslint-disable-line no-template-curly-in-string
|
||||
"something/*": ["${configDir}/something/*"], // eslint-disable-line no-template-curly-in-string
|
||||
"other/*": ["./other/*"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
"dev/configs/templateandextends.json": jsonToReadableText({
|
||||
extends: "./first/templateextends.json",
|
||||
compilerOptions: {
|
||||
strict: true,
|
||||
baseUrl: "./src",
|
||||
},
|
||||
}),
|
||||
"dev/configs/first/templateextends.json": jsonToReadableText({
|
||||
extends: "../second/templateextends.json",
|
||||
include: ["${configDir}/../supplemental.*"], // eslint-disable-line no-template-curly-in-string
|
||||
compilerOptions: {
|
||||
rootDirs: ["root1", "${configDir}/root2", "root3"], // eslint-disable-line no-template-curly-in-string
|
||||
},
|
||||
}),
|
||||
"dev/configs/second/templateextends.json": jsonToReadableText({
|
||||
files: ["${configDir}/main.ts"], // eslint-disable-line no-template-curly-in-string,
|
||||
compilerOptions: {
|
||||
outDir: "./insecond",
|
||||
declarationDir: "${configDir}/decls", // eslint-disable-line no-template-curly-in-string
|
||||
paths: {
|
||||
"something": ["${configDir}/something"], // eslint-disable-line no-template-curly-in-string
|
||||
"something/*": ["${configDir}/something/*"], // eslint-disable-line no-template-curly-in-string
|
||||
"other/*": ["./other/*"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -302,6 +342,9 @@ describe("unittests:: config:: configurationExtension", () => {
|
||||
baselineParsedCommandLine("can report missing configurations", "extendsArrayFails.json");
|
||||
baselineParsedCommandLine("can error when 'extends' is not a string or Array2", "extendsArrayFails2.json");
|
||||
|
||||
baselineParsedCommandLine("handle configDir template", "configs/template.json");
|
||||
baselineParsedCommandLine("handle configDir template", "configs/templateandextends.json");
|
||||
|
||||
baselineParseConfig({
|
||||
scenario: "configurationExtension",
|
||||
subScenario: testName,
|
||||
@@ -317,7 +360,7 @@ describe("unittests:: config:: configurationExtension", () => {
|
||||
baseline.push("CompilerOptions::");
|
||||
baseline.push(jsonToReadableText(parsed.options));
|
||||
baseline.push("FileNames::");
|
||||
baseline.push(parsed.fileNames.join());
|
||||
baseline.push(...parsed.fileNames);
|
||||
},
|
||||
})),
|
||||
skipFs: true,
|
||||
|
||||
@@ -15,9 +15,9 @@ function getParsedCommandJson(
|
||||
return ts.parseJsonConfigFileContent(
|
||||
parsed.config,
|
||||
host,
|
||||
basePath ?? host.sys.getCurrentDirectory(),
|
||||
basePath ?? ts.getNormalizedAbsolutePath(ts.getDirectoryPath(configFileName), host.sys.getCurrentDirectory()),
|
||||
existingOptions,
|
||||
configFileName,
|
||||
ts.getNormalizedAbsolutePath(configFileName, host.sys.getCurrentDirectory()),
|
||||
/*resolutionStack*/ undefined,
|
||||
/*extraFileExtensions*/ undefined,
|
||||
/*extendedConfigCache*/ undefined,
|
||||
@@ -37,9 +37,9 @@ function getParsedCommandJsonSourceFile(
|
||||
return ts.parseJsonSourceFileConfigFileContent(
|
||||
parsed,
|
||||
host,
|
||||
basePath ?? host.sys.getCurrentDirectory(),
|
||||
basePath ?? ts.getNormalizedAbsolutePath(ts.getDirectoryPath(configFileName), host.sys.getCurrentDirectory()),
|
||||
existingOptions,
|
||||
configFileName,
|
||||
ts.getNormalizedAbsolutePath(configFileName, host.sys.getCurrentDirectory()),
|
||||
/*resolutionStack*/ undefined,
|
||||
/*extraFileExtensions*/ undefined,
|
||||
/*extendedConfigCache*/ undefined,
|
||||
|
||||
@@ -115,6 +115,21 @@ describe("unittests:: config:: showConfig", () => {
|
||||
],
|
||||
});
|
||||
|
||||
showTSConfigCorrectly("Show TSConfig with configDir template template", ["-p", "tsconfig.json"], {
|
||||
compilerOptions: {
|
||||
outDir: "${configDir}/outDir", // eslint-disable-line no-template-curly-in-string
|
||||
typeRoots: ["root1", "${configDir}/root2", "root3"], // eslint-disable-line no-template-curly-in-string
|
||||
paths: {
|
||||
"@myscope/*": ["${configDir}/types/*"], // eslint-disable-line no-template-curly-in-string
|
||||
"other/*": ["other/*"],
|
||||
},
|
||||
},
|
||||
include: [
|
||||
"${configDir}/src/**/*", // eslint-disable-line no-template-curly-in-string
|
||||
],
|
||||
files: ["${configDir}/main.ts"], // eslint-disable-line no-template-curly-in-string
|
||||
});
|
||||
|
||||
// Bulk validation of all option declarations
|
||||
for (const option of ts.optionDeclarations) {
|
||||
baselineOption(option, /*isCompilerOptions*/ true);
|
||||
|
||||
@@ -97,6 +97,18 @@ describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileText
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
json: {
|
||||
extends: "./base/tsconfig.json",
|
||||
},
|
||||
additionalFiles: {
|
||||
"/base/tsconfig.json": jsonToReadableText({
|
||||
watchOptions: {
|
||||
excludeFiles: ["${configDir}/temp/*.ts"], // eslint-disable-line no-template-curly-in-string
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
verifyWatchOptions("different options", () => [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { dedent } from "../../_namespaces/Utils";
|
||||
import { jsonToReadableText } from "../helpers";
|
||||
import { FsContents } from "./contents";
|
||||
import {
|
||||
createServerHost,
|
||||
createWatchedSystem,
|
||||
@@ -31,3 +32,70 @@ export function getSymlinkedExtendsSys(forTsserver?: true): TestServerHost {
|
||||
[libFile.path]: libFile.content,
|
||||
}, { currentDirectory: "/users/user/projects/myproject" });
|
||||
}
|
||||
|
||||
export function getConfigDirExtendsSys(): FsContents {
|
||||
return {
|
||||
"/home/src/projects/configs/first/tsconfig.json": jsonToReadableText({
|
||||
extends: "../second/tsconfig.json",
|
||||
include: ["${configDir}/src"], // eslint-disable-line no-template-curly-in-string
|
||||
compilerOptions: {
|
||||
typeRoots: ["root1", "${configDir}/root2", "root3"], // eslint-disable-line no-template-curly-in-string
|
||||
types: [],
|
||||
},
|
||||
}),
|
||||
"/home/src/projects/configs/second/tsconfig.json": jsonToReadableText({
|
||||
files: ["${configDir}/main.ts"], // eslint-disable-line no-template-curly-in-string,
|
||||
compilerOptions: {
|
||||
declarationDir: "${configDir}/decls", // eslint-disable-line no-template-curly-in-string
|
||||
paths: {
|
||||
"@myscope/*": ["${configDir}/types/*"], // eslint-disable-line no-template-curly-in-string
|
||||
"other/*": ["other/*"],
|
||||
},
|
||||
baseUrl: "${configDir}", // eslint-disable-line no-template-curly-in-string
|
||||
},
|
||||
watchOptions: {
|
||||
excludeFiles: ["${configDir}/main.ts"], // eslint-disable-line no-template-curly-in-string
|
||||
},
|
||||
}),
|
||||
"/home/src/projects/myproject/tsconfig.json": jsonToReadableText({
|
||||
extends: "../configs/first/tsconfig.json",
|
||||
compilerOptions: {
|
||||
declaration: true,
|
||||
outDir: "outDir",
|
||||
traceResolution: true,
|
||||
},
|
||||
}),
|
||||
|
||||
"/home/src/projects/myproject/main.ts": dedent`
|
||||
// some comment
|
||||
export const y = 10;
|
||||
import { x } from "@myscope/sometype";
|
||||
`,
|
||||
"/home/src/projects/myproject/src/secondary.ts": dedent`
|
||||
// some comment
|
||||
export const z = 10;
|
||||
import { k } from "other/sometype2";
|
||||
`,
|
||||
"/home/src/projects/myproject/types/sometype.ts": dedent`
|
||||
export const x = 10;
|
||||
`,
|
||||
"/home/src/projects/myproject/root2/other/sometype2/index.d.ts": dedent`
|
||||
export const k = 10;
|
||||
`,
|
||||
[libFile.path]: libFile.content,
|
||||
};
|
||||
}
|
||||
|
||||
export function modifyFirstExtendedConfigOfConfigDirExtendsSys(sys: TestServerHost) {
|
||||
sys.modifyFile(
|
||||
"/home/src/projects/configs/first/tsconfig.json",
|
||||
jsonToReadableText({
|
||||
extends: "../second/tsconfig.json",
|
||||
include: ["${configDir}/src"], // eslint-disable-line no-template-curly-in-string
|
||||
compilerOptions: {
|
||||
typeRoots: ["${configDir}/root2"], // eslint-disable-line no-template-curly-in-string
|
||||
types: [],
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { getSymlinkedExtendsSys } from "../helpers/extends";
|
||||
import {
|
||||
getConfigDirExtendsSys,
|
||||
getSymlinkedExtendsSys,
|
||||
} from "../helpers/extends";
|
||||
import { verifyTsc } from "../helpers/tsc";
|
||||
import { verifyTscWatch } from "../helpers/tscWatch";
|
||||
import { loadProjectFromFiles } from "../helpers/vfs";
|
||||
|
||||
describe("unittests:: tsbuild:: extends::", () => {
|
||||
verifyTscWatch({
|
||||
@@ -8,4 +13,11 @@ describe("unittests:: tsbuild:: extends::", () => {
|
||||
sys: getSymlinkedExtendsSys,
|
||||
commandLineArgs: ["-b", "src", "--extendedDiagnostics"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "extends",
|
||||
subScenario: "configDir template",
|
||||
fs: () => loadProjectFromFiles(getConfigDirExtendsSys(), { cwd: "/home/src/projects/myproject" }),
|
||||
commandLineArgs: ["-b", "/home/src/projects/myproject", "--explainFiles", "--v"],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
getConfigDirExtendsSys,
|
||||
modifyFirstExtendedConfigOfConfigDirExtendsSys,
|
||||
} from "../helpers/extends";
|
||||
import { verifyTscWatch } from "../helpers/tscWatch";
|
||||
import { createWatchedSystem } from "../helpers/virtualFileSystemWithWatch";
|
||||
|
||||
describe("unittests:: tsbuildWatch:: watchMode:: extends::", () => {
|
||||
verifyTscWatch({
|
||||
scenario: "extends",
|
||||
subScenario: "configDir template",
|
||||
sys: () => createWatchedSystem(getConfigDirExtendsSys(), { currentDirectory: "/home/src/projects/myproject" }),
|
||||
commandLineArgs: ["-b", "-w", "--extendedDiagnostics", "--explainFiles", "-v"],
|
||||
edits: [{
|
||||
caption: "edit extended config file",
|
||||
edit: modifyFirstExtendedConfigOfConfigDirExtendsSys,
|
||||
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
|
||||
}],
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,10 @@
|
||||
import { getSymlinkedExtendsSys } from "../helpers/extends";
|
||||
import {
|
||||
getConfigDirExtendsSys,
|
||||
getSymlinkedExtendsSys,
|
||||
} from "../helpers/extends";
|
||||
import { verifyTsc } from "../helpers/tsc";
|
||||
import { verifyTscWatch } from "../helpers/tscWatch";
|
||||
import { loadProjectFromFiles } from "../helpers/vfs";
|
||||
|
||||
describe("unittests:: tsc:: extends::", () => {
|
||||
verifyTscWatch({
|
||||
@@ -8,4 +13,25 @@ describe("unittests:: tsc:: extends::", () => {
|
||||
sys: getSymlinkedExtendsSys,
|
||||
commandLineArgs: ["-p", "src", "--extendedDiagnostics"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "extends",
|
||||
subScenario: "configDir template",
|
||||
fs: () => loadProjectFromFiles(getConfigDirExtendsSys(), { cwd: "/home/src/projects/myproject" }),
|
||||
commandLineArgs: ["-p", "/home/src/projects/myproject", "--explainFiles"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "extends",
|
||||
subScenario: "configDir template showConfig",
|
||||
fs: () => loadProjectFromFiles(getConfigDirExtendsSys(), { cwd: "/home/src/projects/myproject" }),
|
||||
commandLineArgs: ["-p", "/home/src/projects/myproject", "--showConfig"],
|
||||
});
|
||||
|
||||
verifyTsc({
|
||||
scenario: "extends",
|
||||
subScenario: "configDir template with commandline",
|
||||
fs: () => loadProjectFromFiles(getConfigDirExtendsSys(), { cwd: "/home/src/projects/myproject" }),
|
||||
commandLineArgs: ["-p", "/home/src/projects/myproject", "--explainFiles", "--outDir", "${configDir}/outDir"], // eslint-disable-line no-template-curly-in-string
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { getSymlinkedExtendsSys } from "../helpers/extends";
|
||||
import {
|
||||
getConfigDirExtendsSys,
|
||||
getSymlinkedExtendsSys,
|
||||
modifyFirstExtendedConfigOfConfigDirExtendsSys,
|
||||
} from "../helpers/extends";
|
||||
import { verifyTscWatch } from "../helpers/tscWatch";
|
||||
import { createWatchedSystem } from "../helpers/virtualFileSystemWithWatch";
|
||||
|
||||
describe("unittests:: tsc-watch:: extends::", () => {
|
||||
verifyTscWatch({
|
||||
@@ -8,4 +13,16 @@ describe("unittests:: tsc-watch:: extends::", () => {
|
||||
sys: getSymlinkedExtendsSys,
|
||||
commandLineArgs: ["-w", "-p", "src", "--extendedDiagnostics"],
|
||||
});
|
||||
|
||||
verifyTscWatch({
|
||||
scenario: "extends",
|
||||
subScenario: "configDir template",
|
||||
sys: () => createWatchedSystem(getConfigDirExtendsSys(), { currentDirectory: "/home/src/projects/myproject" }),
|
||||
commandLineArgs: ["-w", "--extendedDiagnostics", "--explainFiles"],
|
||||
edits: [{
|
||||
caption: "edit extended config file",
|
||||
edit: modifyFirstExtendedConfigOfConfigDirExtendsSys,
|
||||
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
|
||||
system: sys,
|
||||
cb,
|
||||
});
|
||||
const parsedCommandResult = ts.parseJsonConfigFileContent(configFileJson, sys, config.path);
|
||||
const parsedCommandResult = ts.parseJsonConfigFileContent(configFileJson, sys, ts.getDirectoryPath(config.path));
|
||||
host.resolveModuleNames = (moduleNames, containingFile) =>
|
||||
moduleNames.map(m => {
|
||||
const result = ts.resolveModuleName(m, containingFile, parsedCommandResult.options, host);
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { getSymlinkedExtendsSys } from "../helpers/extends";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
getConfigDirExtendsSys,
|
||||
getSymlinkedExtendsSys,
|
||||
modifyFirstExtendedConfigOfConfigDirExtendsSys,
|
||||
} from "../helpers/extends";
|
||||
import {
|
||||
baselineTsserverLogs,
|
||||
openFilesForSession,
|
||||
TestSession,
|
||||
} from "../helpers/tsserver";
|
||||
import { createServerHost } from "../helpers/virtualFileSystemWithWatch";
|
||||
|
||||
describe("unittests:: tsserver:: extends::", () => {
|
||||
it("resolves the symlink path", () => {
|
||||
@@ -12,4 +18,21 @@ describe("unittests:: tsserver:: extends::", () => {
|
||||
openFilesForSession(["/users/user/projects/myproject/src/index.ts"], session);
|
||||
baselineTsserverLogs("tsserver", "resolves the symlink path", session);
|
||||
});
|
||||
|
||||
it("configDir template", () => {
|
||||
const host = createServerHost(getConfigDirExtendsSys(), { currentDirectory: "/home/src/projects/myproject" });
|
||||
const session = new TestSession(host);
|
||||
session.executeCommandSeq<ts.server.protocol.ConfigureRequest>({
|
||||
command: ts.server.protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
watchOptions: {
|
||||
excludeDirectories: ["${configDir}/node_modules"], // eslint-disable-line no-template-curly-in-string
|
||||
},
|
||||
},
|
||||
});
|
||||
openFilesForSession(["/home/src/projects/myproject/src/secondary.ts"], session);
|
||||
modifyFirstExtendedConfigOfConfigDirExtendsSys(host);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
baselineTsserverLogs("tsserver", "configDir template", session);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user