Keep track of why files are in the program (#40011)

* --explainFiles currently hardcoded

* Move configFileSpecs to configFile so it can be used in program later

* Explain root file inclusion reason and explain include files in the log

* Baseline explainFiles

* Fix incorrectly reporting of file list two times in --b mode

* Fix unnecessary new lines in output represented incorretly in the baseline

* More tests

* More cleaning up

* Keep listing files in same order as list files, just add explaination

* Fix double listing of file names when the program has errors

* Make diagnostic chains for file include reason

* Add explaination for the file include to diagnostics for program

* Harness ls incorrectly adding tsconfig as the root file

* Fix incorrect use of path for calculating absolute path

* Fix the root file in fourslash

* Test project service options merge

* Add config file name to matched by include explaination

* Add test for when the file changes and program is reused completely but related file information is reattached to correct location

* Handle file preprocessing diagnostics updates when program is reused and related information location changes

* Moved types to types.ts

* Refactoring and cleanup

* More cleanup

* More refatoring

* Handle synthetic imports

* Baselines after merge
This commit is contained in:
Sheetal Nandi
2020-12-08 16:10:05 -08:00
committed by GitHub
parent 49d7de17d6
commit 2eca17d7c1
84 changed files with 7013 additions and 723 deletions

View File

@@ -196,6 +196,11 @@ namespace ts {
description: Diagnostics.Print_names_of_files_part_of_the_compilation
},
{
name: "explainFiles",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Print_names_of_files_and_the_reason_they_are_part_of_the_compilation
}, {
name: "listEmittedFiles",
type: "boolean",
category: Diagnostics.Advanced_Options,
@@ -268,6 +273,31 @@ namespace ts {
},
];
/* @internal */
export const targetOptionDeclaration: CommandLineOptionOfCustomType = {
name: "target",
shortName: "t",
type: new Map(getEntries({
es3: ScriptTarget.ES3,
es5: ScriptTarget.ES5,
es6: ScriptTarget.ES2015,
es2015: ScriptTarget.ES2015,
es2016: ScriptTarget.ES2016,
es2017: ScriptTarget.ES2017,
es2018: ScriptTarget.ES2018,
es2019: ScriptTarget.ES2019,
es2020: ScriptTarget.ES2020,
esnext: ScriptTarget.ESNext,
})),
affectsSourceFile: true,
affectsModuleResolution: true,
affectsEmit: true,
paramType: Diagnostics.VERSION,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_ES2015_ES2016_ES2017_ES2018_ES2019_ES2020_or_ESNEXT,
};
/* @internal */
export const optionDeclarations: CommandLineOption[] = [
// CommandLine only options
@@ -330,29 +360,7 @@ namespace ts {
},
// Basic
{
name: "target",
shortName: "t",
type: new Map(getEntries({
es3: ScriptTarget.ES3,
es5: ScriptTarget.ES5,
es6: ScriptTarget.ES2015,
es2015: ScriptTarget.ES2015,
es2016: ScriptTarget.ES2016,
es2017: ScriptTarget.ES2017,
es2018: ScriptTarget.ES2018,
es2019: ScriptTarget.ES2019,
es2020: ScriptTarget.ES2020,
esnext: ScriptTarget.ESNext,
})),
affectsSourceFile: true,
affectsModuleResolution: true,
affectsEmit: true,
paramType: Diagnostics.VERSION,
showInSimplifiedHelpView: true,
category: Diagnostics.Basic_Options,
description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_ES2015_ES2016_ES2017_ES2018_ES2019_ES2020_or_ESNEXT,
},
targetOptionDeclaration,
{
name: "module",
shortName: "m",
@@ -2030,10 +2038,10 @@ namespace ts {
const files = map(
filter(
configParseResult.fileNames,
(!configParseResult.configFileSpecs || !configParseResult.configFileSpecs.validatedIncludeSpecs) ? _ => true : matchesSpecs(
!configParseResult.options.configFile?.configFileSpecs?.validatedIncludeSpecs ? returnTrue : matchesSpecs(
configFileName,
configParseResult.configFileSpecs.validatedIncludeSpecs,
configParseResult.configFileSpecs.validatedExcludeSpecs,
configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs,
configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs,
host,
)
),
@@ -2058,9 +2066,9 @@ namespace ts {
watchOptions: watchOptionMap && optionMapToObject(watchOptionMap),
references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })),
files: length(files) ? files : undefined,
...(configParseResult.configFileSpecs ? {
include: filterSameAsDefaultInclude(configParseResult.configFileSpecs.validatedIncludeSpecs),
exclude: configParseResult.configFileSpecs.validatedExcludeSpecs
...(configParseResult.options.configFile?.configFileSpecs ? {
include: filterSameAsDefaultInclude(configParseResult.options.configFile.configFileSpecs.validatedIncludeSpecs),
exclude: configParseResult.options.configFile.configFileSpecs.validatedExcludeSpecs
} : {}),
compileOnSave: !!configParseResult.compileOnSave ? true : undefined
};
@@ -2081,7 +2089,7 @@ namespace ts {
}
function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, excludeSpecs: readonly string[] | undefined, host: ConvertToTSConfigHost): (path: string) => boolean {
if (!includeSpecs) return _ => true;
if (!includeSpecs) return returnTrue;
const patterns = getFileMatcherPatterns(path, excludeSpecs, includeSpecs, host.useCaseSensitiveFileNames, host.getCurrentDirectory());
const excludeRe = patterns.excludePattern && getRegexFromPattern(patterns.excludePattern, host.useCaseSensitiveFileNames);
const includeRe = patterns.includeFilePattern && getRegexFromPattern(patterns.includeFilePattern, host.useCaseSensitiveFileNames);
@@ -2094,7 +2102,7 @@ namespace ts {
if (excludeRe) {
return path => excludeRe.test(path);
}
return _ => true;
return returnTrue;
}
function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): ESMap<string, string | number> | undefined {
@@ -2387,40 +2395,29 @@ namespace ts {
parsedConfig.watchOptions || existingWatchOptions;
options.configFilePath = configFileName && normalizeSlashes(configFileName);
const configFileSpecs = getConfigFileSpecs();
if (sourceFile) sourceFile.configFileSpecs = configFileSpecs;
setConfigFileInOptions(options, sourceFile);
let projectReferences: ProjectReference[] | undefined;
const { fileNames, wildcardDirectories, spec } = getFileNames();
const basePathForFileNames = normalizePath(configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath);
return {
options,
watchOptions,
fileNames,
projectReferences,
fileNames: getFileNames(basePathForFileNames),
projectReferences: getProjectReferences(basePathForFileNames),
typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(),
raw,
errors,
wildcardDirectories,
// Wildcard directories (provided as part of a wildcard path) are stored in a
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token),
// or a recursive directory. This information is used by filesystem watchers to monitor for
// new entries in these paths.
wildcardDirectories: getWildcardDirectories(configFileSpecs, basePathForFileNames, host.useCaseSensitiveFileNames),
compileOnSave: !!raw.compileOnSave,
configFileSpecs: spec
};
function getFileNames(): ExpandResult {
function getConfigFileSpecs(): ConfigFileSpecs {
const referencesOfRaw = getPropFromRaw<ProjectReference>("references", element => typeof element === "object", "object");
if (isArray(referencesOfRaw)) {
for (const ref of referencesOfRaw) {
if (typeof ref.path !== "string") {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string");
}
else {
(projectReferences || (projectReferences = [])).push({
path: getNormalizedAbsolutePath(ref.path, basePath),
originalPath: ref.path,
prepend: ref.prepend,
circular: ref.circular
});
}
}
}
const filesSpecs = toPropValue(getSpecsFromRaw("files"));
if (filesSpecs) {
const hasZeroOrNoReferences = referencesOfRaw === "no-prop" || isArray(referencesOfRaw) && referencesOfRaw.length === 0;
@@ -2457,13 +2454,57 @@ namespace ts {
if (filesSpecs === undefined && includeSpecs === undefined) {
includeSpecs = ["**/*"];
}
let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined;
const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile);
if (shouldReportNoInputFiles(result, canJsonReportNoInputFiles(raw), resolutionStack)) {
errors.push(getErrorForNoInputFiles(result.spec, configFileName));
// The exclude spec list is converted into a regular expression, which allows us to quickly
// test whether a file or directory should be excluded before recursively traversing the
// file system.
if (includeSpecs) {
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, sourceFile, "include");
}
return result;
if (excludeSpecs) {
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, sourceFile, "exclude");
}
return {
filesSpecs,
includeSpecs,
excludeSpecs,
validatedFilesSpec: filter(filesSpecs, isString),
validatedIncludeSpecs,
validatedExcludeSpecs,
};
}
function getFileNames(basePath: string): string[] {
const fileNames = getFileNamesFromConfigSpecs(configFileSpecs, basePath, options, host, extraFileExtensions);
if (shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(raw), resolutionStack)) {
errors.push(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
return fileNames;
}
function getProjectReferences(basePath: string): readonly ProjectReference[] | undefined {
let projectReferences: ProjectReference[] | undefined;
const referencesOfRaw = getPropFromRaw<ProjectReference>("references", element => typeof element === "object", "object");
if (isArray(referencesOfRaw)) {
for (const ref of referencesOfRaw) {
if (typeof ref.path !== "string") {
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "reference.path", "string");
}
else {
(projectReferences || (projectReferences = [])).push({
path: getNormalizedAbsolutePath(ref.path, basePath),
originalPath: ref.path,
prepend: ref.prepend,
circular: ref.circular
});
}
}
}
return projectReferences;
}
type PropOfRaw<T> = readonly T[] | "not-array" | "no-prop";
@@ -2511,8 +2552,8 @@ namespace ts {
JSON.stringify(excludeSpecs || []));
}
function shouldReportNoInputFiles(result: ExpandResult, canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) {
return result.fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0);
function shouldReportNoInputFiles(fileNames: string[], canJsonReportNoInutFiles: boolean, resolutionStack?: Path[]) {
return fileNames.length === 0 && canJsonReportNoInutFiles && (!resolutionStack || resolutionStack.length === 0);
}
/*@internal*/
@@ -2521,9 +2562,9 @@ namespace ts {
}
/*@internal*/
export function updateErrorForNoInputFiles(result: ExpandResult, configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) {
export function updateErrorForNoInputFiles(fileNames: string[], configFileName: string, configFileSpecs: ConfigFileSpecs, configParseDiagnostics: Diagnostic[], canJsonReportNoInutFiles: boolean) {
const existingErrors = configParseDiagnostics.length;
if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles)) {
if (shouldReportNoInputFiles(fileNames, canJsonReportNoInutFiles)) {
configParseDiagnostics.push(getErrorForNoInputFiles(configFileSpecs, configFileName));
}
else {
@@ -3011,65 +3052,10 @@ namespace ts {
*/
const wildcardDirectoryPattern = /^[^*?]*(?=\/[^/]*[*?])/;
/**
* Expands an array of file specifications.
*
* @param filesSpecs The literal file names to include.
* @param includeSpecs The wildcard file specifications to include.
* @param excludeSpecs The wildcard file specifications to exclude.
* @param basePath The base path for any relative file specifications.
* @param options Compiler options.
* @param host The host used to resolve files and directories.
* @param errors An array for diagnostic reporting.
*/
function matchFileNames(
filesSpecs: readonly string[] | undefined,
includeSpecs: readonly string[] | undefined,
excludeSpecs: readonly string[] | undefined,
basePath: string,
options: CompilerOptions,
host: ParseConfigHost,
errors: Push<Diagnostic>,
extraFileExtensions: readonly FileExtensionInfo[],
jsonSourceFile: TsConfigSourceFile | undefined
): ExpandResult {
basePath = normalizePath(basePath);
let validatedIncludeSpecs: readonly string[] | undefined, validatedExcludeSpecs: readonly string[] | undefined;
// The exclude spec list is converted into a regular expression, which allows us to quickly
// test whether a file or directory should be excluded before recursively traversing the
// file system.
if (includeSpecs) {
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*disallowTrailingRecursion*/ true, jsonSourceFile, "include");
}
if (excludeSpecs) {
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*disallowTrailingRecursion*/ false, jsonSourceFile, "exclude");
}
// Wildcard directories (provided as part of a wildcard path) are stored in a
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token),
// or a recursive directory. This information is used by filesystem watchers to monitor for
// new entries in these paths.
const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames);
const spec: ConfigFileSpecs = {
filesSpecs,
includeSpecs,
excludeSpecs,
validatedFilesSpec: filter(filesSpecs, isString),
validatedIncludeSpecs,
validatedExcludeSpecs,
wildcardDirectories
};
return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions);
}
/**
* Gets the file names from the provided config file specs that contain, files, include, exclude and
* other properties needed to resolve the file names
* @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details
* @param configFileSpecs The config file specs extracted with file names to include, wildcards to include/exclude and other details
* @param basePath The base path for any relative file specifications.
* @param options Compiler options.
* @param host The host used to resolve files and directories.
@@ -3077,12 +3063,12 @@ namespace ts {
*/
/* @internal */
export function getFileNamesFromConfigSpecs(
spec: ConfigFileSpecs,
configFileSpecs: ConfigFileSpecs,
basePath: string,
options: CompilerOptions,
host: ParseConfigHost,
extraFileExtensions: readonly FileExtensionInfo[] = emptyArray
): ExpandResult {
): string[] {
basePath = normalizePath(basePath);
const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
@@ -3101,7 +3087,7 @@ namespace ts {
// file map with a possibly case insensitive key. We use this map to store paths matched
// via wildcard of *.json kind
const wildCardJsonFileMap = new Map<string, string>();
const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = configFileSpecs;
// Rather than requery this for each file and filespec, we query the supported extensions
// once and store it on the expansion context.
@@ -3162,11 +3148,7 @@ namespace ts {
const literalFiles = arrayFrom(literalFileMap.values());
const wildcardFiles = arrayFrom(wildcardFileMap.values());
return {
fileNames: literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values())),
wildcardDirectories,
spec
};
return literalFiles.concat(wildcardFiles, arrayFrom(wildCardJsonFileMap.values()));
}
/* @internal */
@@ -3251,7 +3233,7 @@ namespace ts {
/**
* Gets directories in a set of include patterns that should be watched for changes.
*/
function getWildcardDirectories(include: readonly string[] | undefined, exclude: readonly string[] | undefined, path: string, useCaseSensitiveFileNames: boolean): MapLike<WatchDirectoryFlags> {
function getWildcardDirectories({ validatedIncludeSpecs: include, validatedExcludeSpecs: exclude }: ConfigFileSpecs, path: string, useCaseSensitiveFileNames: boolean): MapLike<WatchDirectoryFlags> {
// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
// of the pattern:
//