Add template variable ${configDir} for substitution of config files directory path (#58042)

This commit is contained in:
Sheetal Nandi
2024-04-16 16:25:07 -07:00
committed by GitHub
parent 3d523923f5
commit cbae6cf9f4
88 changed files with 4167 additions and 233 deletions
+142 -17
View File
@@ -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 = ".";
}
+5 -1
View File
@@ -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 */
+5 -2
View File
@@ -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 */
+24 -13
View File
@@ -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() {
+1
View File
@@ -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,
+4 -4
View File
@@ -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: [],
},
}),
);
}
+13 -1
View File
@@ -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(),
}],
});
});
+27 -1
View File
@@ -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
});
});
+18 -1
View File
@@ -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);
+24 -1
View File
@@ -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);
});
});