Skip to content

Commit ca179cf

Browse files
committed
Library directives WIP
1 parent 92e49db commit ca179cf

File tree

9 files changed

+163
-34
lines changed

9 files changed

+163
-34
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,10 @@
26202620
"category": "Message",
26212621
"code": 6112
26222622
},
2623+
"Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to a local typings folder to resolve this conflict.": {
2624+
"category": "Message",
2625+
"code": 6113
2626+
},
26232627

26242628
"Variable '{0}' implicitly has an '{1}' type.": {
26252629
"category": "Error",

src/compiler/program.ts

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,8 @@ namespace ts {
697697
let program: Program;
698698
let files: SourceFile[] = [];
699699
let fileProcessingDiagnostics = createDiagnosticCollection();
700+
const resolvedLibraries: Map<ResolvedLibrary> = {};
701+
const libraryRoot = options.rootDir || options.configFilePath || computeCommonSourceDirectoryOfFilenames(rootNames);
700702
const programDiagnostics = createDiagnosticCollection();
701703

702704
let commonSourceDirectory: string;
@@ -1489,11 +1491,9 @@ namespace ts {
14891491
skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib;
14901492

14911493
const basePath = getDirectoryPath(fileName);
1492-
const libraryBasePath = options.rootDir || options.configFilePath || getDirectoryPath(file.fileName);
14931494
if (!options.noResolve) {
14941495
processReferencedFiles(file, basePath);
1495-
1496-
processReferencedLibraries(file, libraryBasePath);
1496+
processReferencedLibraries(file, libraryRoot);
14971497
}
14981498

14991499
// always process imported modules to record module name resolutions
@@ -1517,41 +1517,90 @@ namespace ts {
15171517
});
15181518
}
15191519

1520+
function findLibraryDefinition(searchPath: string) {
1521+
let typingFilename = "index.d.ts";
1522+
const packageJsonPath = combinePaths(searchPath, "package.json");
1523+
if (host.fileExists(packageJsonPath)) {
1524+
let package: { typings?: string } = {};
1525+
try {
1526+
package = JSON.parse(host.readFile(packageJsonPath));
1527+
} catch (e) { }
1528+
1529+
if (package.typings) {
1530+
typingFilename = package.typings;
1531+
}
1532+
}
1533+
1534+
const combinedPath = normalizePath(combinePaths(searchPath, typingFilename));
1535+
return host.fileExists(combinedPath) ? combinedPath : undefined;
1536+
}
1537+
15201538
function processReferencedLibraries(file: SourceFile, compilationRoot: string) {
1521-
const searchPaths = getEffectiveLibrarySearchPaths();
1539+
const primarySearchPaths = map(getEffectiveLibraryPrimarySearchPaths(), path => combinePaths(compilationRoot, path));
1540+
1541+
const failedSearchPaths: string[] = [];
1542+
const moduleResolutionState: ModuleResolutionState = {
1543+
compilerOptions: options,
1544+
host: host,
1545+
skipTsx: true,
1546+
traceEnabled: false
1547+
};
1548+
15221549
for (const ref of file.referencedLibraries) {
1550+
// If we already found this library as a primary reference, or failed to find it, nothing to do
1551+
const previousResolution = resolvedLibraries[ref.fileName];
1552+
if (previousResolution && (previousResolution.primary || (previousResolution.resolvedFileName === undefined))) {
1553+
continue;
1554+
}
1555+
15231556
let foundIt = false;
15241557

1525-
for (const path of searchPaths) {
1526-
let typingFilename = "index.d.ts";
1527-
const searchPath = combinePaths(combinePaths(compilationRoot, path), ref.fileName);
1528-
const packageJsonPath = combinePaths(searchPath, "package.json");
1529-
console.log('Check for ' + packageJsonPath);
1530-
if (host.fileExists(packageJsonPath)) {
1531-
let package: { typings?: string } = {};
1532-
try {
1533-
package = JSON.parse(host.readFile(packageJsonPath));
1534-
} catch (e) { }
1535-
1536-
if (package.typings) {
1537-
typingFilename = package.typings;
1538-
}
1558+
// Check primary library paths
1559+
for (const primaryPath of primarySearchPaths) {
1560+
const searchPath = combinePaths(primaryPath, ref.fileName);
1561+
const resolvedFile = findLibraryDefinition(searchPath);
1562+
if (resolvedFile) {
1563+
processSourceFile(resolvedFile, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
1564+
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: resolvedFile };
1565+
foundIt = true;
1566+
break;
15391567
}
1568+
}
15401569

1541-
const combinedPath = normalizePath(combinePaths(compilationRoot, combinePaths(combinePaths(path, ref.fileName), typingFilename)));
1542-
if (host.fileExists(combinedPath)) {
1543-
processSourceFile(combinedPath, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
1570+
// Check secondary library paths
1571+
if (!foundIt) {
1572+
const secondaryResult = loadModuleFromNodeModules(ref.fileName, file.fileName, failedSearchPaths, moduleResolutionState);
1573+
if (secondaryResult) {
15441574
foundIt = true;
1545-
break;
1575+
// If we already resolved to this file, it must have been a secondary reference. Check file contents
1576+
// for sameness and possibly issue an error
1577+
if (previousResolution) {
1578+
const otherFileText = host.readFile(secondaryResult);
1579+
if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) {
1580+
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos,
1581+
Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_a_local_typings_folder_to_resolve_this_conflict,
1582+
ref.fileName,
1583+
secondaryResult,
1584+
previousResolution.resolvedFileName));
1585+
}
1586+
}
1587+
else {
1588+
// First resolution of this library
1589+
processSourceFile(secondaryResult, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
1590+
resolvedLibraries[ref.fileName] = { primary: false, resolvedFileName: secondaryResult };
1591+
}
15461592
}
15471593
}
1594+
15481595
if (!foundIt) {
15491596
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos, Diagnostics.Cannot_find_name_0, ref.fileName));
1597+
// Create an entry as a primary lookup result so we don't keep doing this
1598+
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: undefined };
15501599
}
15511600
}
15521601
}
15531602

1554-
function getEffectiveLibrarySearchPaths() {
1603+
function getEffectiveLibraryPrimarySearchPaths() {
15551604
return options.librarySearchPaths ||
15561605
(options.configFilePath ?
15571606
[options.configFilePath].concat(defaultLibrarySearchPaths) :
@@ -1604,15 +1653,11 @@ namespace ts {
16041653
return;
16051654
}
16061655

1607-
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
1656+
function computeCommonSourceDirectoryOfFilenames(fileNames: string[]): string {
16081657
let commonPathComponents: string[];
1609-
const failed = forEach(files, sourceFile => {
1658+
const failed = forEach(fileNames, sourceFile => {
16101659
// Each file contributes into common source file path
1611-
if (isDeclarationFile(sourceFile)) {
1612-
return;
1613-
}
1614-
1615-
const sourcePathComponents = getNormalizedPathComponents(sourceFile.fileName, currentDirectory);
1660+
const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory);
16161661
sourcePathComponents.pop(); // The base file name is not part of the common directory path
16171662

16181663
if (!commonPathComponents) {
@@ -1652,6 +1697,16 @@ namespace ts {
16521697
return getNormalizedPathFromPathComponents(commonPathComponents);
16531698
}
16541699

1700+
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
1701+
const fileNames: string[] = [];
1702+
for(const file of sourceFiles) {
1703+
if (!file.isDeclarationFile) {
1704+
fileNames.push(file.fileName);
1705+
}
1706+
}
1707+
return computeCommonSourceDirectoryOfFilenames(fileNames);
1708+
}
1709+
16551710
function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
16561711
let allFilesBelongToPath = true;
16571712
if (sourceFiles) {

src/compiler/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2720,6 +2720,13 @@ namespace ts {
27202720
isExternalLibraryImport?: boolean;
27212721
}
27222722

2723+
export interface ResolvedLibrary {
2724+
// True if the library was found in a primary lookup location
2725+
primary: boolean;
2726+
// The location of the .d.ts file we located, or undefined if resolution failed
2727+
resolvedFileName?: string;
2728+
}
2729+
27232730
export interface ResolvedModuleWithFailedLookupLocations {
27242731
resolvedModule: ResolvedModule;
27252732
failedLookupLocations: string[];

src/harness/harness.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ namespace Harness {
817817
export let fourslashSourceFile: ts.SourceFile;
818818

819819
export function getCanonicalFileName(fileName: string): string {
820-
return Harness.IO.useCaseSensitiveFileNames() ? fileName : fileName.toLowerCase();
820+
return fileName;
821821
}
822822

823823
export function createCompilerHost(
@@ -832,14 +832,16 @@ namespace Harness {
832832
// Local get canonical file name function, that depends on passed in parameter for useCaseSensitiveFileNames
833833
const getCanonicalFileName = ts.createGetCanonicalFileName(useCaseSensitiveFileNames);
834834

835+
const harnessNormalizePath = (f: string) => <ts.Path>ts.normalizePath(getCanonicalFileName(f));
836+
835837
const fileMap: ts.FileMap<ts.SourceFile> = ts.createFileMap<ts.SourceFile>();
836838
for (const file of inputFiles) {
837839
if (file.content !== undefined) {
838840
const fileName = ts.normalizePath(file.unitName);
839841
const sourceFile = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget);
840842
const path = ts.toPath(file.unitName, currentDirectory, getCanonicalFileName);
841843
fileMap.set(path, sourceFile);
842-
fileMap.set(<ts.Path>fileName, sourceFile);
844+
fileMap.set(harnessNormalizePath(path), sourceFile);
843845
}
844846
}
845847

@@ -868,6 +870,7 @@ namespace Harness {
868870
newLineKind === ts.NewLineKind.LineFeed ? lineFeed :
869871
Harness.IO.newLine();
870872

873+
871874
return {
872875
getCurrentDirectory: () => currentDirectory,
873876
getSourceFile,
@@ -876,8 +879,12 @@ namespace Harness {
876879
getCanonicalFileName,
877880
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
878881
getNewLine: () => newLine,
879-
fileExists: fileName => fileMap.contains(<ts.Path>ts.normalizePath(fileName)),
880-
readFile: (fileName: string): string => fileMap.get(<ts.Path>ts.normalizePath(fileName)).getText()
882+
fileExists: fileName => {
883+
return fileMap.contains(harnessNormalizePath(fileName))
884+
},
885+
readFile: (fileName: string): string => {
886+
return fileMap.get(harnessNormalizePath(fileName)).getText();
887+
}
881888
};
882889
}
883890

tests/cases/conformance/references/library-reference-1.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @noImplicitReferences: true
22

3+
// We can find typings in the ./typings folder
4+
35
// @filename: typings/jquery/index.d.ts
46
declare var $: { foo(): void };
57

tests/cases/conformance/references/library-reference-2.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @noImplicitReferences: true
22

3+
// package.json in a primary reference can refer to another file
4+
35
// @filename: typings/jquery/package.json
46
{
57
"typings": "jquery.d.ts"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @noImplicitReferences: true
2+
3+
// Secondary references are possible
4+
5+
// @filename: src/node_modules/jquery/index.d.ts
6+
declare var $: { foo(): void };
7+
8+
// @filename: src/consumer.ts
9+
/// <reference library="jquery" />
10+
$.foo();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @noImplicitReferences: true
2+
3+
// Secondary references may be duplicated if they agree in content
4+
5+
// @filename: node_modules/foo/index.d.ts
6+
/// <reference library="alpha" />
7+
declare var foo: any;
8+
9+
// @filename: node_modules/foo/node_modules/alpha/index.d.ts
10+
declare var alpha: any;
11+
12+
// @filename: node_modules/bar/index.d.ts
13+
/// <reference library="alpha" />
14+
declare var bar: any;
15+
16+
// @filename: node_modules/bar/node_modules/alpha/index.d.ts
17+
declare var alpha: any;
18+
19+
// @filename: src/root.ts
20+
/// <reference library="foo" />
21+
/// <reference library="bar" />
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @noImplicitReferences: true
2+
3+
// Secondary references may not be duplicated if they disagree in content
4+
5+
// @filename: node_modules/foo/index.d.ts
6+
/// <reference library="alpha" />
7+
declare var foo: any;
8+
9+
// @filename: node_modules/foo/node_modules/alpha/index.d.ts
10+
declare var alpha: any;
11+
12+
// @filename: node_modules/bar/index.d.ts
13+
/// <reference library="alpha" />
14+
declare var bar: any;
15+
16+
// @filename: node_modules/bar/node_modules/alpha/index.d.ts
17+
declare var alpha: {};
18+
19+
// @filename: src/root.ts
20+
/// <reference library="foo" />
21+
/// <reference library="bar" />

0 commit comments

Comments
 (0)