Skip to content

Commit c0537cd

Browse files
committed
Support reference library directives
1 parent b5f418f commit c0537cd

39 files changed

+751
-35
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,11 @@
21432143
"category": "Error",
21442144
"code": 4082
21452145
},
2146+
"Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to a local typings folder to resolve this conflict.": {
2147+
"category": "Message",
2148+
"code": 4090
2149+
},
2150+
21462151
"The current host does not support the '{0}' option.": {
21472152
"category": "Error",
21482153
"code": 5001
@@ -2604,6 +2609,7 @@
26042609
"category": "Message",
26052610
"code": 6112
26062611
},
2612+
26072613
"Variable '{0}' implicitly has an '{1}' type.": {
26082614
"category": "Error",
26092615
"code": 7005

src/compiler/parser.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5491,6 +5491,7 @@ namespace ts {
54915491
function processReferenceComments(sourceFile: SourceFile): void {
54925492
const triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/false, LanguageVariant.Standard, sourceText);
54935493
const referencedFiles: FileReference[] = [];
5494+
const referencedLibraries: FileReference[] = [];
54945495
const amdDependencies: { path: string; name: string }[] = [];
54955496
let amdModuleName: string;
54965497

@@ -5517,7 +5518,12 @@ namespace ts {
55175518
sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
55185519
const diagnosticMessage = referencePathMatchResult.diagnosticMessage;
55195520
if (fileReference) {
5520-
referencedFiles.push(fileReference);
5521+
if (referencePathMatchResult.isLibraryReference) {
5522+
referencedLibraries.push(fileReference);
5523+
}
5524+
else {
5525+
referencedFiles.push(fileReference);
5526+
}
55215527
}
55225528
if (diagnosticMessage) {
55235529
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
@@ -5549,6 +5555,7 @@ namespace ts {
55495555
}
55505556

55515557
sourceFile.referencedFiles = referencedFiles;
5558+
sourceFile.referencedLibraries = referencedLibraries;
55525559
sourceFile.amdDependencies = amdDependencies;
55535560
sourceFile.moduleName = amdModuleName;
55545561
}

src/compiler/program.ts

Lines changed: 131 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ namespace ts {
1212

1313
const emptyArray: any[] = [];
1414

15+
const defaultLibrarySearchPaths = <Path[]>[
16+
"typings/",
17+
"node_modules/",
18+
"node_modules/@types/",
19+
];
20+
1521
export const version = "1.9.0";
1622

1723
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string {
@@ -371,7 +377,7 @@ namespace ts {
371377
const traceEnabled = isTraceEnabled(compilerOptions, host);
372378

373379
const failedLookupLocations: string[] = [];
374-
const state = {compilerOptions, host, traceEnabled, skipTsx: false};
380+
const state = { compilerOptions, host, traceEnabled, skipTsx: false };
375381
let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName,
376382
failedLookupLocations, supportedExtensions, state);
377383

@@ -407,7 +413,7 @@ namespace ts {
407413
}
408414

409415
/* @internal */
410-
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean } ): boolean {
416+
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
411417
// if host does not support 'directoryExists' assume that directory will exist
412418
return !host.directoryExists || host.directoryExists(directoryName);
413419
}
@@ -554,7 +560,7 @@ namespace ts {
554560

555561

556562
return referencedSourceFile
557-
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
563+
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
558564
: { resolvedModule: undefined, failedLookupLocations };
559565
}
560566

@@ -649,9 +655,9 @@ namespace ts {
649655

650656
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] {
651657
let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat(
652-
program.getSyntacticDiagnostics(sourceFile, cancellationToken),
653-
program.getGlobalDiagnostics(cancellationToken),
654-
program.getSemanticDiagnostics(sourceFile, cancellationToken));
658+
program.getSyntacticDiagnostics(sourceFile, cancellationToken),
659+
program.getGlobalDiagnostics(cancellationToken),
660+
program.getSemanticDiagnostics(sourceFile, cancellationToken));
655661

656662
if (program.getCompilerOptions().declaration) {
657663
diagnostics = diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken));
@@ -690,6 +696,14 @@ namespace ts {
690696
let program: Program;
691697
let files: SourceFile[] = [];
692698
let fileProcessingDiagnostics = createDiagnosticCollection();
699+
const currentDirectory = host.getCurrentDirectory();
700+
const resolvedLibraries: Map<ResolvedLibrary> = {};
701+
let libraryRoot =
702+
(options.rootDir && ts.toPath(options.rootDir, currentDirectory, host.getCanonicalFileName)) ||
703+
(options.configFilePath && getDirectoryPath(getNormalizedAbsolutePath(options.configFilePath, currentDirectory)));
704+
if (libraryRoot === undefined) {
705+
libraryRoot = computeCommonSourceDirectoryOfFilenames(rootNames);
706+
}
693707
const programDiagnostics = createDiagnosticCollection();
694708

695709
let commonSourceDirectory: string;
@@ -706,7 +720,6 @@ namespace ts {
706720
// Map storing if there is emit blocking diagnostics for given input
707721
const hasEmitBlockingDiagnostics = createFileMap<boolean>(getCanonicalFileName);
708722

709-
const currentDirectory = host.getCurrentDirectory();
710723
const resolveModuleNamesWorker = host.resolveModuleNames
711724
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
712725
: ((moduleNames: string[], containingFile: string) => {
@@ -883,8 +896,8 @@ namespace ts {
883896
const oldResolution = getResolvedModule(oldSourceFile, moduleNames[i]);
884897
const resolutionChanged = oldResolution
885898
? !newResolution ||
886-
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
887-
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
899+
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
900+
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
888901
: newResolution;
889902

890903
if (resolutionChanged) {
@@ -1007,9 +1020,9 @@ namespace ts {
10071020
}
10081021

10091022
function getDiagnosticsHelper(
1010-
sourceFile: SourceFile,
1011-
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[],
1012-
cancellationToken: CancellationToken): Diagnostic[] {
1023+
sourceFile: SourceFile,
1024+
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[],
1025+
cancellationToken: CancellationToken): Diagnostic[] {
10131026
if (sourceFile) {
10141027
return getDiagnostics(sourceFile, cancellationToken);
10151028
}
@@ -1484,6 +1497,7 @@ namespace ts {
14841497
const basePath = getDirectoryPath(fileName);
14851498
if (!options.noResolve) {
14861499
processReferencedFiles(file, basePath);
1500+
processReferencedLibraries(file, libraryRoot);
14871501
}
14881502

14891503
// always process imported modules to record module name resolutions
@@ -1507,6 +1521,97 @@ namespace ts {
15071521
});
15081522
}
15091523

1524+
function findLibraryDefinition(searchPath: string) {
1525+
let typingFilename = "index.d.ts";
1526+
const packageJsonPath = combinePaths(searchPath, "package.json");
1527+
if (host.fileExists(packageJsonPath)) {
1528+
let package: { typings?: string } = {};
1529+
try {
1530+
package = JSON.parse(host.readFile(packageJsonPath));
1531+
}
1532+
catch (e) { }
1533+
1534+
if (package.typings) {
1535+
typingFilename = package.typings;
1536+
}
1537+
}
1538+
1539+
const combinedPath = normalizePath(combinePaths(searchPath, typingFilename));
1540+
return host.fileExists(combinedPath) ? combinedPath : undefined;
1541+
}
1542+
1543+
function processReferencedLibraries(file: SourceFile, compilationRoot: string) {
1544+
const primarySearchPaths = map(getEffectiveLibraryPrimarySearchPaths(), path => combinePaths(compilationRoot, path));
1545+
1546+
const failedSearchPaths: string[] = [];
1547+
const moduleResolutionState: ModuleResolutionState = {
1548+
compilerOptions: options,
1549+
host: host,
1550+
skipTsx: true,
1551+
traceEnabled: false
1552+
};
1553+
1554+
for (const ref of file.referencedLibraries) {
1555+
// If we already found this library as a primary reference, or failed to find it, nothing to do
1556+
const previousResolution = resolvedLibraries[ref.fileName];
1557+
if (previousResolution && (previousResolution.primary || (previousResolution.resolvedFileName === undefined))) {
1558+
continue;
1559+
}
1560+
1561+
let foundIt = false;
1562+
1563+
// Check primary library paths
1564+
for (const primaryPath of primarySearchPaths) {
1565+
const searchPath = combinePaths(primaryPath, ref.fileName);
1566+
const resolvedFile = findLibraryDefinition(searchPath);
1567+
if (resolvedFile) {
1568+
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: resolvedFile };
1569+
processSourceFile(resolvedFile, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
1570+
foundIt = true;
1571+
break;
1572+
}
1573+
}
1574+
1575+
// Check secondary library paths
1576+
if (!foundIt) {
1577+
const secondaryResult = loadModuleFromNodeModules(ref.fileName, file.fileName, failedSearchPaths, moduleResolutionState);
1578+
if (secondaryResult) {
1579+
foundIt = true;
1580+
// If we already resolved to this file, it must have been a secondary reference. Check file contents
1581+
// for sameness and possibly issue an error
1582+
if (previousResolution) {
1583+
const otherFileText = host.readFile(secondaryResult);
1584+
if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) {
1585+
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos,
1586+
Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_a_local_typings_folder_to_resolve_this_conflict,
1587+
ref.fileName,
1588+
secondaryResult,
1589+
previousResolution.resolvedFileName));
1590+
}
1591+
}
1592+
else {
1593+
// First resolution of this library
1594+
resolvedLibraries[ref.fileName] = { primary: false, resolvedFileName: secondaryResult };
1595+
processSourceFile(secondaryResult, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
1596+
}
1597+
}
1598+
}
1599+
1600+
if (!foundIt) {
1601+
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos, Diagnostics.Cannot_find_name_0, ref.fileName));
1602+
// Create an entry as a primary lookup result so we don't keep doing this
1603+
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: undefined };
1604+
}
1605+
}
1606+
}
1607+
1608+
function getEffectiveLibraryPrimarySearchPaths(): Path[] {
1609+
return <Path[]>(options.librarySearchPaths ||
1610+
(options.configFilePath ?
1611+
[options.configFilePath].concat(defaultLibrarySearchPaths) :
1612+
defaultLibrarySearchPaths));
1613+
}
1614+
15101615
function getCanonicalFileName(fileName: string): string {
15111616
return host.getCanonicalFileName(fileName);
15121617
}
@@ -1553,15 +1658,11 @@ namespace ts {
15531658
return;
15541659
}
15551660

1556-
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
1661+
function computeCommonSourceDirectoryOfFilenames(fileNames: string[]): string {
15571662
let commonPathComponents: string[];
1558-
const failed = forEach(files, sourceFile => {
1663+
const failed = forEach(fileNames, sourceFile => {
15591664
// Each file contributes into common source file path
1560-
if (isDeclarationFile(sourceFile)) {
1561-
return;
1562-
}
1563-
1564-
const sourcePathComponents = getNormalizedPathComponents(sourceFile.fileName, currentDirectory);
1665+
const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory);
15651666
sourcePathComponents.pop(); // The base file name is not part of the common directory path
15661667

15671668
if (!commonPathComponents) {
@@ -1601,6 +1702,16 @@ namespace ts {
16011702
return getNormalizedPathFromPathComponents(commonPathComponents);
16021703
}
16031704

1705+
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
1706+
const fileNames: string[] = [];
1707+
for (const file of sourceFiles) {
1708+
if (!file.isDeclarationFile) {
1709+
fileNames.push(file.fileName);
1710+
}
1711+
}
1712+
return computeCommonSourceDirectoryOfFilenames(fileNames);
1713+
}
1714+
16041715
function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
16051716
let allFilesBelongToPath = true;
16061717
if (sourceFiles) {
@@ -1742,7 +1853,7 @@ namespace ts {
17421853

17431854
// If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure
17441855
if (options.outDir && dir === "" && forEach(files, file => getRootLength(file.fileName) > 1)) {
1745-
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files));
1856+
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files));
17461857
}
17471858
}
17481859

src/compiler/tsc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ namespace ts {
390390
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* compilerHost */ undefined);
391391
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
392392
}
393+
configParseResult.options.configFilePath = configFileName as Path;
393394
return configParseResult;
394395
}
395396

src/compiler/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,7 @@ namespace ts {
15221522
amdDependencies: AmdDependency[];
15231523
moduleName: string;
15241524
referencedFiles: FileReference[];
1525+
referencedLibraries: FileReference[];
15251526
languageVariant: LanguageVariant;
15261527
isDeclarationFile: boolean;
15271528

@@ -2397,6 +2398,7 @@ namespace ts {
23972398
jsx?: JsxEmit;
23982399
reactNamespace?: string;
23992400
listFiles?: boolean;
2401+
librarySearchPaths?: string[];
24002402
locale?: string;
24012403
mapRoot?: string;
24022404
module?: ModuleKind;
@@ -2447,8 +2449,11 @@ namespace ts {
24472449
// Do not perform validation of output file name in transpile scenarios
24482450
/* @internal */ suppressOutputPathCheck?: boolean;
24492451

2450-
list?: string[];
2452+
/* @internal */
2453+
// When options come from a config file, its path is recorded here
2454+
configFilePath?: string;
24512455

2456+
list?: string[];
24522457
[option: string]: CompilerOptionsValue;
24532458
}
24542459

@@ -2724,6 +2729,13 @@ namespace ts {
27242729
isExternalLibraryImport?: boolean;
27252730
}
27262731

2732+
export interface ResolvedLibrary {
2733+
// True if the library was found in a primary lookup location
2734+
primary: boolean;
2735+
// The location of the .d.ts file we located, or undefined if resolution failed
2736+
resolvedFileName?: string;
2737+
}
2738+
27272739
export interface ResolvedModuleWithFailedLookupLocations {
27282740
resolvedModule: ResolvedModule;
27292741
failedLookupLocations: string[];

0 commit comments

Comments
 (0)