Skip to content

Commit a089297

Browse files
committed
Add 'lib' reference support
1 parent c05923b commit a089297

32 files changed

+175
-233
lines changed

src/compiler/core.ts

-104
Original file line numberDiff line numberDiff line change
@@ -2029,110 +2029,6 @@ namespace ts {
20292029
return res > max ? undefined : res;
20302030
}
20312031

2032-
export function normalizeSlashes(path: string): string {
2033-
return path.replace(/\\/g, "/");
2034-
}
2035-
2036-
/**
2037-
* Returns length of path root (i.e. length of "/", "x:/", "//server/share/, file:///user/files")
2038-
*/
2039-
export function getRootLength(path: string): number {
2040-
if (path.charCodeAt(0) === CharacterCodes.slash) {
2041-
if (path.charCodeAt(1) !== CharacterCodes.slash) return 1;
2042-
const p1 = path.indexOf("/", 2);
2043-
if (p1 < 0) return 2;
2044-
const p2 = path.indexOf("/", p1 + 1);
2045-
if (p2 < 0) return p1 + 1;
2046-
return p2 + 1;
2047-
}
2048-
if (path.charCodeAt(1) === CharacterCodes.colon) {
2049-
if (path.charCodeAt(2) === CharacterCodes.slash || path.charCodeAt(2) === CharacterCodes.backslash) return 3;
2050-
}
2051-
// Per RFC 1738 'file' URI schema has the shape file://<host>/<path>
2052-
// if <host> is omitted then it is assumed that host value is 'localhost',
2053-
// however slash after the omitted <host> is not removed.
2054-
// file:///folder1/file1 - this is a correct URI
2055-
// file://folder2/file2 - this is an incorrect URI
2056-
if (path.lastIndexOf("file:///", 0) === 0) {
2057-
return "file:///".length;
2058-
}
2059-
const idx = path.indexOf("://");
2060-
if (idx !== -1) {
2061-
return idx + "://".length;
2062-
}
2063-
return 0;
2064-
}
2065-
2066-
/**
2067-
* Internally, we represent paths as strings with '/' as the directory separator.
2068-
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
2069-
* we expect the host to correctly handle paths in our specified format.
2070-
*/
2071-
export const directorySeparator = "/";
2072-
const directorySeparatorCharCode = CharacterCodes.slash;
2073-
function getNormalizedParts(normalizedSlashedPath: string, rootLength: number): string[] {
2074-
const parts = normalizedSlashedPath.substr(rootLength).split(directorySeparator);
2075-
const normalized: string[] = [];
2076-
for (const part of parts) {
2077-
if (part !== ".") {
2078-
if (part === ".." && normalized.length > 0 && lastOrUndefined(normalized) !== "..") {
2079-
normalized.pop();
2080-
}
2081-
else {
2082-
// A part may be an empty string (which is 'falsy') if the path had consecutive slashes,
2083-
// e.g. "path//file.ts". Drop these before re-joining the parts.
2084-
if (part) {
2085-
normalized.push(part);
2086-
}
2087-
}
2088-
}
2089-
}
2090-
2091-
return normalized;
2092-
}
2093-
2094-
export function normalizePath(path: string): string {
2095-
return normalizePathAndParts(path).path;
2096-
}
2097-
2098-
export function normalizePathAndParts(path: string): { path: string, parts: string[] } {
2099-
path = normalizeSlashes(path);
2100-
const rootLength = getRootLength(path);
2101-
const root = path.substr(0, rootLength);
2102-
const parts = getNormalizedParts(path, rootLength);
2103-
if (parts.length) {
2104-
const joinedParts = root + parts.join(directorySeparator);
2105-
return { path: pathEndsWithDirectorySeparator(path) ? joinedParts + directorySeparator : joinedParts, parts };
2106-
}
2107-
else {
2108-
return { path: root, parts };
2109-
}
2110-
}
2111-
2112-
/** A path ending with '/' refers to a directory only, never a file. */
2113-
export function pathEndsWithDirectorySeparator(path: string): boolean {
2114-
return path.charCodeAt(path.length - 1) === directorySeparatorCharCode;
2115-
}
2116-
2117-
/**
2118-
* Returns the path except for its basename. Eg:
2119-
*
2120-
* /path/to/file.ext -> /path/to
2121-
*/
2122-
export function getDirectoryPath(path: Path): Path;
2123-
export function getDirectoryPath(path: string): string;
2124-
export function getDirectoryPath(path: string): string {
2125-
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf(directorySeparator)));
2126-
}
2127-
2128-
export function isUrl(path: string) {
2129-
return path && !isRootedDiskPath(path) && stringContains(path, "://");
2130-
}
2131-
2132-
export function pathIsRelative(path: string): boolean {
2133-
return /^\.\.?($|[\\/])/.test(path);
2134-
}
2135-
21362032
export function getEmitScriptTarget(compilerOptions: CompilerOptions) {
21372033
return compilerOptions.target || ScriptTarget.ES3;
21382034
}

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,14 @@
23562356
"category": "Error",
23572357
"code": 2724
23582358
},
2359+
"Cannot find lib definition for '{0}'.": {
2360+
"category": "Error",
2361+
"code": 2725
2362+
},
2363+
"Cannot find lib definition for '{0}'. Did you mean '{1}'?": {
2364+
"category": "Error",
2365+
"code": 2726
2366+
},
23592367
"Import declaration '{0}' is using private name '{1}'.": {
23602368
"category": "Error",
23612369
"code": 4000

src/compiler/factory.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2417,12 +2417,13 @@ namespace ts {
24172417

24182418
// Top-level nodes
24192419

2420-
export function updateSourceFileNode(node: SourceFile, statements: ReadonlyArray<Statement>, isDeclarationFile?: boolean, referencedFiles?: SourceFile["referencedFiles"], typeReferences?: SourceFile["typeReferenceDirectives"], hasNoDefaultLib?: boolean) {
2420+
export function updateSourceFileNode(node: SourceFile, statements: ReadonlyArray<Statement>, isDeclarationFile?: boolean, referencedFiles?: SourceFile["referencedFiles"], typeReferences?: SourceFile["typeReferenceDirectives"], hasNoDefaultLib?: boolean, libReferences?: SourceFile["libReferenceDirectives"]) {
24212421
if (
24222422
node.statements !== statements ||
24232423
(isDeclarationFile !== undefined && node.isDeclarationFile !== isDeclarationFile) ||
24242424
(referencedFiles !== undefined && node.referencedFiles !== referencedFiles) ||
24252425
(typeReferences !== undefined && node.typeReferenceDirectives !== typeReferences) ||
2426+
(libReferences !== undefined && node.libReferenceDirectives !== libReferences) ||
24262427
(hasNoDefaultLib !== undefined && node.hasNoDefaultLib !== hasNoDefaultLib)
24272428
) {
24282429
const updated = <SourceFile>createSynthesizedNode(SyntaxKind.SourceFile);
@@ -2436,6 +2437,7 @@ namespace ts {
24362437
updated.referencedFiles = referencedFiles === undefined ? node.referencedFiles : referencedFiles;
24372438
updated.typeReferenceDirectives = typeReferences === undefined ? node.typeReferenceDirectives : typeReferences;
24382439
updated.hasNoDefaultLib = hasNoDefaultLib === undefined ? node.hasNoDefaultLib : hasNoDefaultLib;
2440+
updated.libReferenceDirectives = libReferences === undefined ? node.libReferenceDirectives : libReferences;
24392441
if (node.amdDependencies !== undefined) updated.amdDependencies = node.amdDependencies;
24402442
if (node.moduleName !== undefined) updated.moduleName = node.moduleName;
24412443
if (node.languageVariant !== undefined) updated.languageVariant = node.languageVariant;

src/compiler/parser.ts

+6
Original file line numberDiff line numberDiff line change
@@ -7592,6 +7592,7 @@ namespace ts {
75927592
checkJsDirective?: CheckJsDirective;
75937593
referencedFiles: FileReference[];
75947594
typeReferenceDirectives: FileReference[];
7595+
libReferenceDirectives: FileReference[];
75957596
amdDependencies: AmdDependency[];
75967597
hasNoDefaultLib?: boolean;
75977598
moduleName?: string;
@@ -7645,6 +7646,7 @@ namespace ts {
76457646
context.checkJsDirective = undefined;
76467647
context.referencedFiles = [];
76477648
context.typeReferenceDirectives = [];
7649+
context.libReferenceDirectives = [];
76487650
context.amdDependencies = [];
76497651
context.hasNoDefaultLib = false;
76507652
context.pragmas.forEach((entryOrList, key) => {
@@ -7654,13 +7656,17 @@ namespace ts {
76547656
case "reference": {
76557657
const referencedFiles = context.referencedFiles;
76567658
const typeReferenceDirectives = context.typeReferenceDirectives;
7659+
const libReferenceDirectives = context.libReferenceDirectives;
76577660
forEach(toArray(entryOrList), (arg: PragmaPsuedoMap["reference"]) => {
76587661
if (arg.arguments["no-default-lib"]) {
76597662
context.hasNoDefaultLib = true;
76607663
}
76617664
else if (arg.arguments.types) {
76627665
typeReferenceDirectives.push({ pos: arg.arguments.types.pos, end: arg.arguments.types.end, fileName: arg.arguments.types.value });
76637666
}
7667+
else if (arg.arguments.lib) {
7668+
libReferenceDirectives.push({ pos: arg.arguments.lib.pos, end: arg.arguments.lib.end, fileName: arg.arguments.lib.value });
7669+
}
76647670
else if (arg.arguments.path) {
76657671
referencedFiles.push({ pos: arg.arguments.path.pos, end: arg.arguments.path.end, fileName: arg.arguments.path.value });
76667672
}

src/compiler/program.ts

+37-13
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ namespace ts {
584584
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
585585
const structuralIsReused = tryReuseStructureFromOldProgram();
586586
if (structuralIsReused !== StructureIsReused.Completely) {
587-
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
587+
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false));
588588

589589
// load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders
590590
const typeReferences: string[] = getAutomaticTypeDirectiveNames(options, host);
@@ -608,11 +608,11 @@ namespace ts {
608608
// otherwise, using options specified in '--lib' instead of '--target' default library file
609609
const defaultLibraryFileName = getDefaultLibraryFileName();
610610
if (!options.lib && defaultLibraryFileName) {
611-
processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true);
611+
processRootFile(defaultLibraryFileName, /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false);
612612
}
613613
else {
614614
forEach(options.lib, libFileName => {
615-
processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true);
615+
processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false);
616616
});
617617
}
618618
}
@@ -964,6 +964,11 @@ namespace ts {
964964
if (fileChanged) {
965965
// The `newSourceFile` object was created for the new program.
966966

967+
if (!arrayIsEqualTo(oldSourceFile.libReferenceDirectives, newSourceFile.libReferenceDirectives, fileReferenceIsEqualTo)) {
968+
// 'lib' references has changed. Matches behavior in chagnesAffectModuleResolution
969+
return oldProgram.structureIsReused = StructureIsReused.Not;
970+
}
971+
967972
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
968973
// value of no-default-lib has changed
969974
// this will affect if default library is injected into the list of files
@@ -1579,8 +1584,8 @@ namespace ts {
15791584
return configFileParsingDiagnostics || emptyArray;
15801585
}
15811586

1582-
function processRootFile(fileName: string, isDefaultLib: boolean) {
1583-
processSourceFile(normalizePath(fileName), isDefaultLib, /*packageId*/ undefined);
1587+
function processRootFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean) {
1588+
processSourceFile(normalizePath(fileName), isDefaultLib, ignoreNoDefaultLib, /*packageId*/ undefined);
15841589
}
15851590

15861591
function fileReferenceIsEqualTo(a: FileReference, b: FileReference): boolean {
@@ -1747,9 +1752,9 @@ namespace ts {
17471752
}
17481753

17491754
/** This has side effects through `findSourceFile`. */
1750-
function processSourceFile(fileName: string, isDefaultLib: boolean, packageId: PackageId | undefined, refFile?: SourceFile, refPos?: number, refEnd?: number): void {
1755+
function processSourceFile(fileName: string, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, packageId: PackageId | undefined, refFile?: SourceFile, refPos?: number, refEnd?: number): void {
17511756
getSourceFileFromReferenceWorker(fileName,
1752-
fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, refFile, refPos, refEnd, packageId),
1757+
fileName => findSourceFile(fileName, toPath(fileName), isDefaultLib, ignoreNoDefaultLib, refFile, refPos, refEnd, packageId),
17531758
(diagnostic, ...args) => {
17541759
fileProcessingDiagnostics.add(refFile !== undefined && refEnd !== undefined && refPos !== undefined
17551760
? createFileDiagnostic(refFile, refPos, refEnd - refPos, diagnostic, ...args)
@@ -1787,7 +1792,7 @@ namespace ts {
17871792
}
17881793

17891794
// Get source file from normalized fileName
1790-
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, refFile: SourceFile, refPos: number, refEnd: number, packageId: PackageId | undefined): SourceFile | undefined {
1795+
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: SourceFile, refPos: number, refEnd: number, packageId: PackageId | undefined): SourceFile | undefined {
17911796
if (filesByName.has(path)) {
17921797
const file = filesByName.get(path);
17931798
// try to check if we've already seen this file but with a different casing in path
@@ -1802,6 +1807,7 @@ namespace ts {
18021807
sourceFilesFoundSearchingNodeModules.set(file.path, false);
18031808
if (!options.noResolve) {
18041809
processReferencedFiles(file, isDefaultLib);
1810+
processLibReferenceDirectives(file);
18051811
processTypeReferenceDirectives(file);
18061812
}
18071813

@@ -1867,10 +1873,11 @@ namespace ts {
18671873
}
18681874
}
18691875

1870-
skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib;
1876+
skipDefaultLib = skipDefaultLib || (file.hasNoDefaultLib && !ignoreNoDefaultLib);
18711877

18721878
if (!options.noResolve) {
18731879
processReferencedFiles(file, isDefaultLib);
1880+
processLibReferenceDirectives(file);
18741881
processTypeReferenceDirectives(file);
18751882
}
18761883

@@ -1891,7 +1898,7 @@ namespace ts {
18911898
function processReferencedFiles(file: SourceFile, isDefaultLib: boolean) {
18921899
forEach(file.referencedFiles, ref => {
18931900
const referencedFileName = resolveTripleslashReference(ref.fileName, file.fileName);
1894-
processSourceFile(referencedFileName, isDefaultLib, /*packageId*/ undefined, file, ref.pos, ref.end);
1901+
processSourceFile(referencedFileName, isDefaultLib, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined, file, ref.pos, ref.end);
18951902
});
18961903
}
18971904

@@ -1922,7 +1929,7 @@ namespace ts {
19221929
if (resolvedTypeReferenceDirective) {
19231930
if (resolvedTypeReferenceDirective.primary) {
19241931
// resolved from the primary path
1925-
processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile, refPos, refEnd);
1932+
processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile, refPos, refEnd);
19261933
}
19271934
else {
19281935
// If we already resolved to this file, it must have been a secondary reference. Check file contents
@@ -1945,7 +1952,7 @@ namespace ts {
19451952
}
19461953
else {
19471954
// First resolution of this library
1948-
processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile, refPos, refEnd);
1955+
processSourceFile(resolvedTypeReferenceDirective.resolvedFileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, resolvedTypeReferenceDirective.packageId, refFile, refPos, refEnd);
19491956
}
19501957
}
19511958
}
@@ -1958,6 +1965,23 @@ namespace ts {
19581965
}
19591966
}
19601967

1968+
function processLibReferenceDirectives(file: SourceFile) {
1969+
forEach(file.libReferenceDirectives, libReference => {
1970+
const libName = libReference.fileName.toLocaleLowerCase();
1971+
const libFileName = libMap.get(libName);
1972+
if (libFileName) {
1973+
// we ignore any 'no-default-lib' reference set on this file.
1974+
processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true);
1975+
}
1976+
else {
1977+
const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts");
1978+
const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity);
1979+
const message = suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0;
1980+
fileProcessingDiagnostics.add(createDiagnostic(file, libReference.pos, libReference.end, message, libName, suggestion));
1981+
}
1982+
});
1983+
}
1984+
19611985
function createDiagnostic(refFile: SourceFile, refPos: number, refEnd: number, message: DiagnosticMessage, ...args: any[]): Diagnostic {
19621986
if (refFile === undefined || refPos === undefined || refEnd === undefined) {
19631987
return createCompilerDiagnostic(message, ...args);
@@ -2018,7 +2042,7 @@ namespace ts {
20182042
else if (shouldAddFile) {
20192043
const path = toPath(resolvedFileName);
20202044
const pos = skipTrivia(file.text, file.imports[i].pos);
2021-
findSourceFile(resolvedFileName, path, /*isDefaultLib*/ false, file, pos, file.imports[i].end, resolution.packageId);
2045+
findSourceFile(resolvedFileName, path, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, file, pos, file.imports[i].end, resolution.packageId);
20222046
}
20232047

20242048
if (isFromNodeModulesSearch) {

src/compiler/transformers/declarations.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ namespace ts {
156156
[createModifier(SyntaxKind.DeclareKeyword)],
157157
createLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)),
158158
createModuleBlock(setTextRange(createNodeArray(filterCandidateImports(statements)), sourceFile.statements))
159-
)], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false);
159+
)], /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []);
160160
return newFile;
161161
}
162162
needsDeclare = true;
163163
const updated = visitNodes(sourceFile.statements, visitDeclarationStatements);
164-
return updateSourceFileNode(sourceFile, filterCandidateImports(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false);
164+
return updateSourceFileNode(sourceFile, filterCandidateImports(updated), /*isDeclarationFile*/ true, /*referencedFiles*/ [], /*typeReferences*/ [], /*hasNoDefaultLib*/ false, /*libReferences*/ []);
165165
}
166166
));
167167
bundle.syntheticFileReferences = [];

0 commit comments

Comments
 (0)