Skip to content

Commit 05444ea

Browse files
RyanCavanaughvladima
authored andcommitted
Support reference library directives
1 parent 68bdbe0 commit 05444ea

39 files changed

Lines changed: 750 additions & 35 deletions

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,11 @@
21592159
"category": "Error",
21602160
"code": 4082
21612161
},
2162+
"Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to a local typings folder to resolve this conflict.": {
2163+
"category": "Message",
2164+
"code": 4090
2165+
},
2166+
21622167
"The current host does not support the '{0}' option.": {
21632168
"category": "Error",
21642169
"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 {
@@ -370,7 +376,7 @@ namespace ts {
370376
const traceEnabled = isTraceEnabled(compilerOptions, host);
371377

372378
const failedLookupLocations: string[] = [];
373-
const state = {compilerOptions, host, traceEnabled, skipTsx: false};
379+
const state = { compilerOptions, host, traceEnabled, skipTsx: false };
374380
let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName,
375381
failedLookupLocations, supportedExtensions, state);
376382

@@ -406,7 +412,7 @@ namespace ts {
406412
}
407413

408414
/* @internal */
409-
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean } ): boolean {
415+
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
410416
// if host does not support 'directoryExists' assume that directory will exist
411417
return !host.directoryExists || host.directoryExists(directoryName);
412418
}
@@ -553,7 +559,7 @@ namespace ts {
553559

554560

555561
return referencedSourceFile
556-
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
562+
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
557563
: { resolvedModule: undefined, failedLookupLocations };
558564
}
559565

@@ -653,9 +659,9 @@ namespace ts {
653659

654660
export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] {
655661
let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat(
656-
program.getSyntacticDiagnostics(sourceFile, cancellationToken),
657-
program.getGlobalDiagnostics(cancellationToken),
658-
program.getSemanticDiagnostics(sourceFile, cancellationToken));
662+
program.getSyntacticDiagnostics(sourceFile, cancellationToken),
663+
program.getGlobalDiagnostics(cancellationToken),
664+
program.getSemanticDiagnostics(sourceFile, cancellationToken));
659665

660666
if (program.getCompilerOptions().declaration) {
661667
diagnostics = diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken));
@@ -694,6 +700,14 @@ namespace ts {
694700
let program: Program;
695701
let files: SourceFile[] = [];
696702
let fileProcessingDiagnostics = createDiagnosticCollection();
703+
const currentDirectory = host.getCurrentDirectory();
704+
const resolvedLibraries: Map<ResolvedLibrary> = {};
705+
let libraryRoot =
706+
(options.rootDir && ts.toPath(options.rootDir, currentDirectory, host.getCanonicalFileName)) ||
707+
(options.configFilePath && getDirectoryPath(getNormalizedAbsolutePath(options.configFilePath, currentDirectory)));
708+
if (libraryRoot === undefined) {
709+
libraryRoot = computeCommonSourceDirectoryOfFilenames(rootNames);
710+
}
697711
const programDiagnostics = createDiagnosticCollection();
698712

699713
let commonSourceDirectory: string;
@@ -710,7 +724,6 @@ namespace ts {
710724
// Map storing if there is emit blocking diagnostics for given input
711725
const hasEmitBlockingDiagnostics = createFileMap<boolean>(getCanonicalFileName);
712726

713-
const currentDirectory = host.getCurrentDirectory();
714727
const resolveModuleNamesWorker = host.resolveModuleNames
715728
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
716729
: ((moduleNames: string[], containingFile: string) => {
@@ -897,8 +910,8 @@ namespace ts {
897910
const oldResolution = getResolvedModule(oldSourceFile, moduleNames[i]);
898911
const resolutionChanged = oldResolution
899912
? !newResolution ||
900-
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
901-
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
913+
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
914+
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
902915
: newResolution;
903916

904917
if (resolutionChanged) {
@@ -1021,9 +1034,9 @@ namespace ts {
10211034
}
10221035

10231036
function getDiagnosticsHelper(
1024-
sourceFile: SourceFile,
1025-
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[],
1026-
cancellationToken: CancellationToken): Diagnostic[] {
1037+
sourceFile: SourceFile,
1038+
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[],
1039+
cancellationToken: CancellationToken): Diagnostic[] {
10271040
if (sourceFile) {
10281041
return getDiagnostics(sourceFile, cancellationToken);
10291042
}
@@ -1498,6 +1511,7 @@ namespace ts {
14981511
const basePath = getDirectoryPath(fileName);
14991512
if (!options.noResolve) {
15001513
processReferencedFiles(file, basePath, /*isDefaultLib*/ isDefaultLib);
1514+
processReferencedLibraries(file, libraryRoot);
15011515
}
15021516

15031517
// always process imported modules to record module name resolutions
@@ -1521,6 +1535,97 @@ namespace ts {
15211535
});
15221536
}
15231537

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

1570-
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
1675+
function computeCommonSourceDirectoryOfFilenames(fileNames: string[]): string {
15711676
let commonPathComponents: string[];
1572-
const failed = forEach(files, sourceFile => {
1677+
const failed = forEach(fileNames, sourceFile => {
15731678
// Each file contributes into common source file path
1574-
if (isDeclarationFile(sourceFile)) {
1575-
return;
1576-
}
1577-
1578-
const sourcePathComponents = getNormalizedPathComponents(sourceFile.fileName, currentDirectory);
1679+
const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory);
15791680
sourcePathComponents.pop(); // The base file name is not part of the common directory path
15801681

15811682
if (!commonPathComponents) {
@@ -1615,6 +1716,16 @@ namespace ts {
16151716
return getNormalizedPathFromPathComponents(commonPathComponents);
16161717
}
16171718

1719+
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
1720+
const fileNames: string[] = [];
1721+
for (const file of sourceFiles) {
1722+
if (!file.isDeclarationFile) {
1723+
fileNames.push(file.fileName);
1724+
}
1725+
}
1726+
return computeCommonSourceDirectoryOfFilenames(fileNames);
1727+
}
1728+
16181729
function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
16191730
let allFilesBelongToPath = true;
16201731
if (sourceFiles) {
@@ -1760,7 +1871,7 @@ namespace ts {
17601871

17611872
// 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
17621873
if (options.outDir && dir === "" && forEach(files, file => getRootLength(file.fileName) > 1)) {
1763-
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files));
1874+
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files));
17641875
}
17651876
}
17661877

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 {
@@ -1548,25 +1550,26 @@ namespace ts {
15481550
};
15491551
}
15501552
else {
1551-
const matchResult = fullTripleSlashReferencePathRegEx.exec(comment);
1552-
if (matchResult) {
1553+
const refMatchResult = fullTripleSlashReferencePathRegEx.exec(comment);
1554+
const refLibResult = !refMatchResult && fullTripleSlashReferenceLibraryRegEx.exec(comment);
1555+
if (refMatchResult || refLibResult) {
15531556
const start = commentRange.pos;
15541557
const end = commentRange.end;
15551558
return {
15561559
fileReference: {
15571560
pos: start,
15581561
end: end,
1559-
fileName: matchResult[3]
1562+
fileName: (refMatchResult || refLibResult)[3]
15601563
},
1561-
isNoDefaultLib: false
1562-
};
1563-
}
1564-
else {
1565-
return {
1566-
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
1567-
isNoDefaultLib: false
1564+
isNoDefaultLib: false,
1565+
isLibraryReference: !!refLibResult
15681566
};
15691567
}
1568+
1569+
return {
1570+
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
1571+
isNoDefaultLib: false
1572+
};
15701573
}
15711574
}
15721575

0 commit comments

Comments
 (0)