mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 11:54:44 -06:00
Ensure file, include and exclude specs used are strings (#40041)
* Test displaying failure when specs used are not strings * Ensure specs used are strings Fixes #38164, #39856 * Feedback
This commit is contained in:
parent
edaa8aa9c0
commit
10304555e7
@ -2308,53 +2308,48 @@ namespace ts {
|
||||
};
|
||||
|
||||
function getFileNames(): ExpandResult {
|
||||
let filesSpecs: readonly string[] | undefined;
|
||||
if (hasProperty(raw, "files") && !isNullOrUndefined(raw.files)) {
|
||||
if (isArray(raw.files)) {
|
||||
filesSpecs = <readonly string[]>raw.files;
|
||||
const hasReferences = hasProperty(raw, "references") && !isNullOrUndefined(raw.references);
|
||||
const hasZeroOrNoReferences = !hasReferences || raw.references.length === 0;
|
||||
const hasExtends = hasProperty(raw, "extends");
|
||||
if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) {
|
||||
if (sourceFile) {
|
||||
const fileName = configFileName || "tsconfig.json";
|
||||
const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty;
|
||||
const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer);
|
||||
const error = nodeValue
|
||||
? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName)
|
||||
: createCompilerDiagnostic(diagnosticMessage, fileName);
|
||||
errors.push(error);
|
||||
}
|
||||
else {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array");
|
||||
}
|
||||
|
||||
const filesSpecs = toPropValue(getSpecsFromRaw("files"));
|
||||
if (filesSpecs) {
|
||||
const hasZeroOrNoReferences = referencesOfRaw === "no-prop" || isArray(referencesOfRaw) && referencesOfRaw.length === 0;
|
||||
const hasExtends = hasProperty(raw, "extends");
|
||||
if (filesSpecs.length === 0 && hasZeroOrNoReferences && !hasExtends) {
|
||||
if (sourceFile) {
|
||||
const fileName = configFileName || "tsconfig.json";
|
||||
const diagnosticMessage = Diagnostics.The_files_list_in_config_file_0_is_empty;
|
||||
const nodeValue = firstDefined(getTsConfigPropArray(sourceFile, "files"), property => property.initializer);
|
||||
const error = nodeValue
|
||||
? createDiagnosticForNodeInSourceFile(sourceFile, nodeValue, diagnosticMessage, fileName)
|
||||
: createCompilerDiagnostic(diagnosticMessage, fileName);
|
||||
errors.push(error);
|
||||
}
|
||||
else {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let includeSpecs: readonly string[] | undefined;
|
||||
if (hasProperty(raw, "include") && !isNullOrUndefined(raw.include)) {
|
||||
if (isArray(raw.include)) {
|
||||
includeSpecs = <readonly string[]>raw.include;
|
||||
}
|
||||
else {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array");
|
||||
}
|
||||
}
|
||||
let includeSpecs = toPropValue(getSpecsFromRaw("include"));
|
||||
|
||||
let excludeSpecs: readonly string[] | undefined;
|
||||
if (hasProperty(raw, "exclude") && !isNullOrUndefined(raw.exclude)) {
|
||||
if (isArray(raw.exclude)) {
|
||||
excludeSpecs = <readonly string[]>raw.exclude;
|
||||
}
|
||||
else {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array");
|
||||
}
|
||||
}
|
||||
else if (raw.compilerOptions) {
|
||||
const excludeOfRaw = getSpecsFromRaw("exclude");
|
||||
let excludeSpecs = toPropValue(excludeOfRaw);
|
||||
if (excludeOfRaw === "no-prop" && raw.compilerOptions) {
|
||||
const outDir = raw.compilerOptions.outDir;
|
||||
const declarationDir = raw.compilerOptions.declarationDir;
|
||||
|
||||
@ -2372,28 +2367,33 @@ namespace ts {
|
||||
errors.push(getErrorForNoInputFiles(result.spec, configFileName));
|
||||
}
|
||||
|
||||
if (hasProperty(raw, "references") && !isNullOrUndefined(raw.references)) {
|
||||
if (isArray(raw.references)) {
|
||||
for (const ref of raw.references) {
|
||||
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 result;
|
||||
}
|
||||
|
||||
type PropOfRaw<T> = readonly T[] | "not-array" | "no-prop";
|
||||
function toPropValue<T>(specResult: PropOfRaw<T>) {
|
||||
return isArray(specResult) ? specResult : undefined;
|
||||
}
|
||||
|
||||
function getSpecsFromRaw(prop: "files" | "include" | "exclude"): PropOfRaw<string> {
|
||||
return getPropFromRaw(prop, isString, "string");
|
||||
}
|
||||
|
||||
function getPropFromRaw<T>(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw<T> {
|
||||
if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) {
|
||||
if (isArray(raw[prop])) {
|
||||
const result = raw[prop];
|
||||
if (!sourceFile && !every(result, validateElement)) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "references", "Array");
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, "Array");
|
||||
return "not-array";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return "no-prop";
|
||||
}
|
||||
|
||||
function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) {
|
||||
@ -2941,7 +2941,15 @@ namespace ts {
|
||||
// new entries in these paths.
|
||||
const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames);
|
||||
|
||||
const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories };
|
||||
const spec: ConfigFileSpecs = {
|
||||
filesSpecs,
|
||||
includeSpecs,
|
||||
excludeSpecs,
|
||||
validatedFilesSpec: filter(filesSpecs, isString),
|
||||
validatedIncludeSpecs,
|
||||
validatedExcludeSpecs,
|
||||
wildcardDirectories
|
||||
};
|
||||
return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions);
|
||||
}
|
||||
|
||||
@ -2980,7 +2988,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 { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
|
||||
const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
|
||||
|
||||
// Rather than requery this for each file and filespec, we query the supported extensions
|
||||
// once and store it on the expansion context.
|
||||
@ -2989,8 +2997,8 @@ namespace ts {
|
||||
|
||||
// Literal files are always included verbatim. An "include" or "exclude" specification cannot
|
||||
// remove a literal file.
|
||||
if (filesSpecs) {
|
||||
for (const fileName of filesSpecs) {
|
||||
if (validatedFilesSpec) {
|
||||
for (const fileName of validatedFilesSpec) {
|
||||
const file = getNormalizedAbsolutePath(fileName, basePath);
|
||||
literalFileMap.set(keyMapper(file), file);
|
||||
}
|
||||
@ -3056,14 +3064,14 @@ namespace ts {
|
||||
useCaseSensitiveFileNames: boolean,
|
||||
currentDirectory: string
|
||||
): boolean {
|
||||
const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs } = spec;
|
||||
const { validatedFilesSpec, validatedIncludeSpecs, validatedExcludeSpecs } = spec;
|
||||
if (!length(validatedIncludeSpecs) || !length(validatedExcludeSpecs)) return false;
|
||||
|
||||
basePath = normalizePath(basePath);
|
||||
|
||||
const keyMapper = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
if (filesSpecs) {
|
||||
for (const fileName of filesSpecs) {
|
||||
if (validatedFilesSpec) {
|
||||
for (const fileName of validatedFilesSpec) {
|
||||
if (keyMapper(getNormalizedAbsolutePath(fileName, basePath)) === pathToCheck) return false;
|
||||
}
|
||||
}
|
||||
@ -3077,6 +3085,7 @@ namespace ts {
|
||||
|
||||
function validateSpecs(specs: readonly string[], errors: Push<Diagnostic>, allowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
|
||||
return specs.filter(spec => {
|
||||
if (!isString(spec)) return false;
|
||||
const diag = specToDiagnostic(spec, allowTrailingRecursion);
|
||||
if (diag !== undefined) {
|
||||
errors.push(createDiagnostic(diag, spec));
|
||||
|
||||
@ -5880,13 +5880,14 @@ namespace ts {
|
||||
/**
|
||||
* Present to report errors (user specified specs), validatedIncludeSpecs are used for file name matching
|
||||
*/
|
||||
includeSpecs?: readonly string[];
|
||||
includeSpecs: readonly string[] | undefined;
|
||||
/**
|
||||
* Present to report errors (user specified specs), validatedExcludeSpecs are used for file name matching
|
||||
*/
|
||||
excludeSpecs?: readonly string[];
|
||||
validatedIncludeSpecs?: readonly string[];
|
||||
validatedExcludeSpecs?: readonly string[];
|
||||
excludeSpecs: readonly string[] | undefined;
|
||||
validatedFilesSpec: readonly string[] | undefined;
|
||||
validatedIncludeSpecs: readonly string[] | undefined;
|
||||
validatedExcludeSpecs: readonly string[] | undefined;
|
||||
wildcardDirectories: MapLike<WatchDirectoryFlags>;
|
||||
}
|
||||
|
||||
|
||||
@ -381,5 +381,36 @@ namespace ts {
|
||||
const parsed = getParsedCommandJsonNode(jsonText, "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"]);
|
||||
assert.isTrue(parsed.errors.length >= 0);
|
||||
});
|
||||
|
||||
it("generates errors when files is not string", () => {
|
||||
assertParseFileDiagnostics(
|
||||
JSON.stringify({
|
||||
files: [{
|
||||
compilerOptions: {
|
||||
experimentalDecorators: true,
|
||||
allowJs: true
|
||||
}
|
||||
}]
|
||||
}),
|
||||
"/apath/tsconfig.json",
|
||||
"tests/cases/unittests",
|
||||
["/apath/a.ts"],
|
||||
Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code,
|
||||
/*noLocation*/ true);
|
||||
});
|
||||
|
||||
it("generates errors when include is not string", () => {
|
||||
assertParseFileDiagnostics(
|
||||
JSON.stringify({
|
||||
include: [
|
||||
["./**/*.ts"]
|
||||
]
|
||||
}),
|
||||
"/apath/tsconfig.json",
|
||||
"tests/cases/unittests",
|
||||
["/apath/a.ts"],
|
||||
Diagnostics.Compiler_option_0_requires_a_value_of_type_1.code,
|
||||
/*noLocation*/ true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user