Skip to content
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

Adds support for overwriting lib references via the TSConfig #45518

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
44 changes: 35 additions & 9 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
}
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
}
Expand Down
21 changes: 16 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 7 additions & 1 deletion src/harness/harnessIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
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