diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 8ea829eaad5b2..9c88586a5e8ba 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1379,7 +1379,32 @@ namespace ts { case "string": return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); default: - return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); + if (opt.element.type !== libMap) { + return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); + } + // Extact out potential + const result: string[] = []; + let jsonStart = ""; + let jsonParsing = false; + for (const v of values) { + const s = v.trim(); + if (s.startsWith("{")) { + Debug.assert(!jsonStart); + jsonParsing = true; + jsonStart += s + ", "; + } + else if (s.endsWith("}")) { + Debug.assert(jsonParsing, "Unexpected }"); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + append(result, JSON.parse(jsonStart + s) as unknown as string); + jsonParsing = false; + jsonStart = ""; + } + else if (!jsonParsing) { + append(result, parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); + } + } + return result; } } @@ -3093,6 +3118,8 @@ namespace ts { return value; } else if (!isString(option.type)) { + // option + // @ts-ignore !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! do not ship return option.type.get(isString(value) ? value.toLowerCase() : value); } return normalizeNonListOptionValue(option, basePath, value); @@ -3504,6 +3531,7 @@ namespace ts { const elementType = option.element; return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; default: + // @ts-ignore !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! dont ship this return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { if (optionEnumValue === value) { return optionStringValue; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 81539867f51bc..53f76ede8020e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -994,7 +994,8 @@ namespace ts { processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile }); } else { - forEach(options.lib, (libFileName, index) => { + const stringReferences = filter((options.lib || []), isString); + forEach(stringReferences, (libFileName, index) => { processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); }); } @@ -1738,7 +1739,12 @@ namespace ts { return equalityComparer(file.fileName, getDefaultLibraryFileName()); } else { - return some(options.lib, libFileName => equalityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName))); + // Handle strings in lib first as these are most + const libFiles = map(filter(options.lib, isString), (libFileName) => combinePaths(defaultLibraryPath, libFileName)); + if (some(libFiles, libFileName => equalityComparer(file.fileName, libFileName))) return true; + + // TODO: Check if this file is referenced via the types + return false; } } @@ -2403,11 +2409,31 @@ namespace ts { } } - function getLibFileFromReference(ref: FileReference) { - const libName = toFileNameLowerCase(ref.fileName); + /** Handles swapping the lib file referenes based on the users's 'lib' settings */ + function getLibFilePath(fileName: string, fromSourceFile?: SourceFile) { + const libName = toFileNameLowerCase(fileName); const libFileName = libMap.get(libName); + const swaps = filter(options.lib || [], (f) => !isString(f)) as LibReplaceReference[]; + const toSwap = find(swaps, (s) => s.replace === libName); + if (toSwap) { + const resolved = resolveModuleName(toSwap.with, fromSourceFile?.fileName ?? host.getCurrentDirectory(), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*redirectedReference*/ undefined,); + + // const newPath = actualResolveModuleNamesWorker([toSwap.with], fromSourceFile?.fileName ?? host.getCurrentDirectory())[0]; + if (resolved.resolvedModule) return resolved.resolvedModule.resolvedFileName; + else { + Debug.assert("could not resolve lib replace reference"); + } + } + if (libFileName) { - return getSourceFile(combinePaths(defaultLibraryPath, libFileName)); + return combinePaths(defaultLibraryPath, libFileName); + } + } + + function getLibFileFromReference(ref: FileReference) { + const path = getLibFilePath(ref.fileName); + if (path) { + return getSourceFile(path); } } @@ -2885,11 +2911,11 @@ namespace ts { function processLibReferenceDirectives(file: SourceFile) { forEach(file.libReferenceDirectives, (libReference, index) => { - const libName = toFileNameLowerCase(libReference.fileName); - const libFileName = libMap.get(libName); + const libName = libReference.fileName; + const libFileName = getLibFilePath(libReference.fileName, file); if (libFileName) { // we ignore any 'no-default-lib' reference set on this file. - processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); + processRootFile(libFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); } else { const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); @@ -3464,7 +3490,7 @@ namespace ts { break; case FileIncludeKind.LibFile: if (reason.index !== undefined) { - configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index]); + configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index] as string); message = Diagnostics.File_is_library_specified_here; break; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d055b91a93a5e..c91701346260f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5929,6 +5929,11 @@ namespace ts { name: string; } + export interface LibReplaceReference { + replace: string; + with: string; + } + export interface ProjectReference { /** A normalized path on disk */ path: string; @@ -5963,7 +5968,8 @@ namespace ts { FixedChunkSize, } - export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike | PluginImport[] | ProjectReference[] | null | undefined; + type LibType = (string | LibReplaceReference)[] + export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike | PluginImport[] | ProjectReference[] | LibType | null | undefined; export interface CompilerOptions { /*@internal*/ all?: boolean; @@ -6010,7 +6016,7 @@ namespace ts { isolatedModules?: boolean; jsx?: JsxEmit; keyofStringsOnly?: boolean; - lib?: string[]; + lib?: (string | LibReplaceReference)[]; /*@internal*/listEmittedFiles?: boolean; /*@internal*/listFiles?: boolean; /*@internal*/explainFiles?: boolean; @@ -6246,7 +6252,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | ESMap; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | ESMap; // a value of a primitive type, or an object literal mapping named values to actual values isFilePath?: boolean; // True if option value is a path or fileName shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help' description?: DiagnosticMessage; // The message describing what the command line switch does. @@ -6277,6 +6283,11 @@ namespace ts { type: ESMap; // an object literal mapping named values to actual values } + /* @internal */ + export interface CommandLineOptionLibType extends CommandLineOptionBase { + type: ESMap; // an object showing what to replace + } + /* @internal */ export interface AlternateModeDiagnostics { diagnostic: DiagnosticMessage; @@ -6301,11 +6312,11 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfListType extends CommandLineOptionBase { type: "list"; - element: CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption; + element: CommandLineOptionLibType | CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption; } /* @internal */ - export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption | CommandLineOptionOfListType; + export type CommandLineOption = CommandLineOptionLibType | CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption | CommandLineOptionOfListType; /* @internal */ export const enum CharacterCodes { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 3f63422a7fa16..57f06830d9513 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -278,7 +278,7 @@ namespace ts { reason.packageId && packageIdToString(reason.packageId), ); case FileIncludeKind.LibFile: - if (reason.index !== undefined) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Library_0_specified_in_compilerOptions, options.lib![reason.index]); + if (reason.index !== undefined) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Library_0_specified_in_compilerOptions, options.lib![reason.index] as string); const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === options.target ? key : undefined); return chainDiagnosticMessages( /*details*/ undefined, diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 79cd980083dfc..6d2901c144247 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -327,6 +327,7 @@ namespace FourSlash { Harness.Compiler.getDefaultLibrarySourceFile()!.text, /*isRootFile*/ false); compilationOptions.lib?.forEach(fileName => { + if (!ts.isString(fileName)) return; const libFile = Harness.Compiler.getDefaultLibrarySourceFile(fileName); ts.Debug.assertIsDefined(libFile, `Could not find lib file '${fileName}'`); if (libFile) { diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index e4730a2e9fdda..c9722a0952f36 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -1079,7 +1079,13 @@ namespace Harness { const option = ts.forEach(ts.optionDeclarations, decl => ts.equateStringsCaseInsensitive(decl.name, varyBy) ? decl : undefined); if (option) { if (typeof option.type === "object") { - return option.type; + // Array.from(option.type.values()).every(v => typeof v === "string" || typeof v === "number") + if(varyBy === "lib") { + + ts.Debug.assert("Can't vary a test which uses lib, because it can contain objects" + JSON.stringify(option)); + } + return option.type as ts.ReadonlyESMap; + } if (option.type === "boolean") { return booleanVaryByStarSettingValues || (booleanVaryByStarSettingValues = new ts.Map(ts.getEntries({ diff --git a/tests/baselines/reference/customLibReplacement.errors.txt b/tests/baselines/reference/customLibReplacement.errors.txt new file mode 100644 index 0000000000000..24989045acf92 --- /dev/null +++ b/tests/baselines/reference/customLibReplacement.errors.txt @@ -0,0 +1,14 @@ +/index.ts(5,1): error TS2304: Cannot find name 'window'. + + +==== /fake-dom.d.ts (0 errors) ==== + interface ABC {} + +==== /index.ts (1 errors) ==== + /// + const a: ABC = {} + + // This should raise ebcause 'window' is not set in the replacement for DOM + window + ~~~~~~ +!!! error TS2304: Cannot find name 'window'. \ No newline at end of file diff --git a/tests/baselines/reference/customLibReplacement.js b/tests/baselines/reference/customLibReplacement.js new file mode 100644 index 0000000000000..2b57ab8110bfa --- /dev/null +++ b/tests/baselines/reference/customLibReplacement.js @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/customLibReplacement.ts] //// + +//// [fake-dom.d.ts] +interface ABC {} + +//// [index.ts] +/// +const a: ABC = {} + +// This should raise ebcause 'window' is not set in the replacement for DOM +window + +//// [index.js] +/// +var a = {}; +// This should raise ebcause 'window' is not set in the replacement for DOM +window; diff --git a/tests/baselines/reference/customLibReplacement.symbols b/tests/baselines/reference/customLibReplacement.symbols new file mode 100644 index 0000000000000..313b88c07077b --- /dev/null +++ b/tests/baselines/reference/customLibReplacement.symbols @@ -0,0 +1,12 @@ +=== /fake-dom.d.ts === +interface ABC {} +>ABC : Symbol(ABC, Decl(fake-dom.d.ts, 0, 0)) + +=== /index.ts === +/// +const a: ABC = {} +>a : Symbol(a, Decl(index.ts, 1, 5)) +>ABC : Symbol(ABC, Decl(fake-dom.d.ts, 0, 0)) + +// This should raise ebcause 'window' is not set in the replacement for DOM +window diff --git a/tests/baselines/reference/customLibReplacement.types b/tests/baselines/reference/customLibReplacement.types new file mode 100644 index 0000000000000..5165260b9445d --- /dev/null +++ b/tests/baselines/reference/customLibReplacement.types @@ -0,0 +1,13 @@ +=== /fake-dom.d.ts === +interface ABC {} +No type information for this code. +No type information for this code.=== /index.ts === +/// +const a: ABC = {} +>a : ABC +>{} : {} + +// This should raise ebcause 'window' is not set in the replacement for DOM +window +>window : any + diff --git a/tests/cases/compiler/customLibReplacement.ts b/tests/cases/compiler/customLibReplacement.ts new file mode 100644 index 0000000000000..c795a3dd2aede --- /dev/null +++ b/tests/cases/compiler/customLibReplacement.ts @@ -0,0 +1,10 @@ +// @lib: es2015,{ "replace": "dom", "with": "./fake-dom.d.ts" } +// @Filename: /fake-dom.d.ts +interface ABC {} + +// @Filename: index.ts +/// +const a: ABC = {} + +// This should raise ebcause 'window' is not set in the replacement for DOM +window \ No newline at end of file