Skip to content

Adds support for overwriting lib references via the TSConfig #45518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
@@ -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;
44 changes: 35 additions & 9 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
@@ -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;
}
21 changes: 16 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
@@ -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<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
type LibType = (string | LibReplaceReference)[]
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | 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<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
type: "string" | "number" | "boolean" | "object" | "list" | ESMap<string, number | string | { replace: string, with: string } >; // 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<string, number | string>; // an object literal mapping named values to actual values
}

/* @internal */
export interface CommandLineOptionLibType extends CommandLineOptionBase {
type: ESMap<string, number | string | { replace: string, with: string } >; // 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 {
2 changes: 1 addition & 1 deletion src/compiler/watch.ts
Original file line number Diff line number Diff line change
@@ -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,
1 change: 1 addition & 0 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
@@ -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) {
8 changes: 7 additions & 1 deletion src/harness/harnessIO.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | number>;

}
if (option.type === "boolean") {
return booleanVaryByStarSettingValues || (booleanVaryByStarSettingValues = new ts.Map(ts.getEntries({
14 changes: 14 additions & 0 deletions tests/baselines/reference/customLibReplacement.errors.txt
Original file line number Diff line number Diff line change
@@ -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) ====
/// <reference lib="dom" />
const a: ABC = {}

// This should raise ebcause 'window' is not set in the replacement for DOM
window
~~~~~~
!!! error TS2304: Cannot find name 'window'.
17 changes: 17 additions & 0 deletions tests/baselines/reference/customLibReplacement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//// [tests/cases/compiler/customLibReplacement.ts] ////

//// [fake-dom.d.ts]
interface ABC {}

//// [index.ts]
/// <reference lib="dom" />
const a: ABC = {}

// This should raise ebcause 'window' is not set in the replacement for DOM
window

//// [index.js]
/// <reference lib="dom" />
var a = {};
// This should raise ebcause 'window' is not set in the replacement for DOM
window;
12 changes: 12 additions & 0 deletions tests/baselines/reference/customLibReplacement.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=== /fake-dom.d.ts ===
interface ABC {}
>ABC : Symbol(ABC, Decl(fake-dom.d.ts, 0, 0))

=== /index.ts ===
/// <reference lib="dom" />
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
13 changes: 13 additions & 0 deletions tests/baselines/reference/customLibReplacement.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
=== /fake-dom.d.ts ===
interface ABC {}
No type information for this code.
No type information for this code.=== /index.ts ===
/// <reference lib="dom" />
const a: ABC = {}
>a : ABC
>{} : {}

// This should raise ebcause 'window' is not set in the replacement for DOM
window
>window : any

10 changes: 10 additions & 0 deletions tests/cases/compiler/customLibReplacement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @lib: es2015,{ "replace": "dom", "with": "./fake-dom.d.ts" }
// @Filename: /fake-dom.d.ts
interface ABC {}

// @Filename: index.ts
/// <reference lib="dom" />
const a: ABC = {}

// This should raise ebcause 'window' is not set in the replacement for DOM
window