Skip to content

Commit 30cb987

Browse files
committed
Support reference library directives
1 parent 9094e01 commit 30cb987

39 files changed

+750
-35
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,11 @@
21552155
"category": "Error",
21562156
"code": 4082
21572157
},
2158+
"Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to a local typings folder to resolve this conflict.": {
2159+
"category": "Message",
2160+
"code": 4090
2161+
},
2162+
21582163
"The current host does not support the '{0}' option.": {
21592164
"category": "Error",
21602165
"code": 5001

src/compiler/parser.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5505,6 +5505,7 @@ namespace ts {
55055505
function processReferenceComments(sourceFile: SourceFile): void {
55065506
const triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/false, LanguageVariant.Standard, sourceText);
55075507
const referencedFiles: FileReference[] = [];
5508+
const referencedLibraries: FileReference[] = [];
55085509
const amdDependencies: { path: string; name: string }[] = [];
55095510
let amdModuleName: string;
55105511

@@ -5531,7 +5532,12 @@ namespace ts {
55315532
sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
55325533
const diagnosticMessage = referencePathMatchResult.diagnosticMessage;
55335534
if (fileReference) {
5534-
referencedFiles.push(fileReference);
5535+
if (referencePathMatchResult.isLibraryReference) {
5536+
referencedLibraries.push(fileReference);
5537+
}
5538+
else {
5539+
referencedFiles.push(fileReference);
5540+
}
55355541
}
55365542
if (diagnosticMessage) {
55375543
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
@@ -5563,6 +5569,7 @@ namespace ts {
55635569
}
55645570

55655571
sourceFile.referencedFiles = referencedFiles;
5572+
sourceFile.referencedLibraries = referencedLibraries;
55665573
sourceFile.amdDependencies = amdDependencies;
55675574
sourceFile.moduleName = amdModuleName;
55685575
}

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
@@ -1536,6 +1536,7 @@ namespace ts {
15361536
amdDependencies: AmdDependency[];
15371537
moduleName: string;
15381538
referencedFiles: FileReference[];
1539+
referencedLibraries: FileReference[];
15391540
languageVariant: LanguageVariant;
15401541
isDeclarationFile: boolean;
15411542

@@ -2415,6 +2416,7 @@ namespace ts {
24152416
jsx?: JsxEmit;
24162417
reactNamespace?: string;
24172418
listFiles?: boolean;
2419+
librarySearchPaths?: string[];
24182420
locale?: string;
24192421
mapRoot?: string;
24202422
module?: ModuleKind;
@@ -2466,8 +2468,11 @@ namespace ts {
24662468
// Do not perform validation of output file name in transpile scenarios
24672469
/* @internal */ suppressOutputPathCheck?: boolean;
24682470

2469-
list?: string[];
2471+
/* @internal */
2472+
// When options come from a config file, its path is recorded here
2473+
configFilePath?: string;
24702474

2475+
list?: string[];
24712476
[option: string]: CompilerOptionsValue;
24722477
}
24732478

@@ -2743,6 +2748,13 @@ namespace ts {
27432748
isExternalLibraryImport?: boolean;
27442749
}
27452750

2751+
export interface ResolvedLibrary {
2752+
// True if the library was found in a primary lookup location
2753+
primary: boolean;
2754+
// The location of the .d.ts file we located, or undefined if resolution failed
2755+
resolvedFileName?: string;
2756+
}
2757+
27462758
export interface ResolvedModuleWithFailedLookupLocations {
27472759
resolvedModule: ResolvedModule;
27482760
failedLookupLocations: string[];

src/compiler/utilities.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace ts {
66
fileReference?: FileReference;
77
diagnosticMessage?: DiagnosticMessage;
88
isNoDefaultLib?: boolean;
9+
isLibraryReference?: boolean;
910
}
1011

1112
export interface SynthesizedNode extends Node {
@@ -498,6 +499,7 @@ namespace ts {
498499
}
499500

500501
export let fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
502+
export let fullTripleSlashReferenceLibraryRegEx = /^(\/\/\/\s*<reference\s+library\s*=\s*)('|")(.+?)\2.*?\/>/;
501503
export let fullTripleSlashAMDReferencePathRegEx = /^(\/\/\/\s*<amd-dependency\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
502504

503505
export function isTypeNode(node: Node): boolean {
@@ -1538,25 +1540,26 @@ namespace ts {
15381540
};
15391541
}
15401542
else {
1541-
const matchResult = fullTripleSlashReferencePathRegEx.exec(comment);
1542-
if (matchResult) {
1543+
const refMatchResult = fullTripleSlashReferencePathRegEx.exec(comment);
1544+
const refLibResult = !refMatchResult && fullTripleSlashReferenceLibraryRegEx.exec(comment);
1545+
if (refMatchResult || refLibResult) {
15431546
const start = commentRange.pos;
15441547
const end = commentRange.end;
15451548
return {
15461549
fileReference: {
15471550
pos: start,
15481551
end: end,
1549-
fileName: matchResult[3]
1552+
fileName: (refMatchResult || refLibResult)[3]
15501553
},
1551-
isNoDefaultLib: false
1552-
};
1553-
}
1554-
else {
1555-
return {
1556-
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
1557-
isNoDefaultLib: false
1554+
isNoDefaultLib: false,
1555+
isLibraryReference: !!refLibResult
15581556
};
15591557
}
1558+
1559+
return {
1560+
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
1561+
isNoDefaultLib: false
1562+
};
15601563
}
15611564
}
15621565

0 commit comments

Comments
 (0)