Skip to content

Support reference library directives in tsc #7263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,10 @@
"category": "Message",
"code": 6112
},
"Conflicting library definitions for '{0}' found at '{1}' and '{2}'. Copy the correct file to a local typings folder to resolve this conflict.": {
"category": "Message",
"code": 6113
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
Expand Down
9 changes: 8 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5491,6 +5491,7 @@ namespace ts {
function processReferenceComments(sourceFile: SourceFile): void {
const triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/false, LanguageVariant.Standard, sourceText);
const referencedFiles: FileReference[] = [];
const referencedLibraries: FileReference[] = [];
const amdDependencies: { path: string; name: string }[] = [];
let amdModuleName: string;

Expand All @@ -5517,7 +5518,12 @@ namespace ts {
sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
const diagnosticMessage = referencePathMatchResult.diagnosticMessage;
if (fileReference) {
referencedFiles.push(fileReference);
if (referencePathMatchResult.isLibraryReference) {
referencedLibraries.push(fileReference);
}
else {
referencedFiles.push(fileReference);
}
}
if (diagnosticMessage) {
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
Expand Down Expand Up @@ -5549,6 +5555,7 @@ namespace ts {
}

sourceFile.referencedFiles = referencedFiles;
sourceFile.referencedLibraries = referencedLibraries;
sourceFile.amdDependencies = amdDependencies;
sourceFile.moduleName = amdModuleName;
}
Expand Down
147 changes: 127 additions & 20 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ namespace ts {

const emptyArray: any[] = [];

const defaultLibrarySearchPaths = [
"",
"typings/",
"node_modules/",
"node_modules/@types/",
];

export const version = "1.9.0";

export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string {
Expand Down Expand Up @@ -371,7 +378,7 @@ namespace ts {
const traceEnabled = isTraceEnabled(compilerOptions, host);

const failedLookupLocations: string[] = [];
const state = {compilerOptions, host, traceEnabled, skipTsx: false};
const state = { compilerOptions, host, traceEnabled, skipTsx: false };
let resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, nodeLoadModuleByRelativeName,
failedLookupLocations, supportedExtensions, state);

Expand Down Expand Up @@ -407,7 +414,7 @@ namespace ts {
}

/* @internal */
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean } ): boolean {
export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean {
// if host does not support 'directoryExists' assume that directory will exist
return !host.directoryExists || host.directoryExists(directoryName);
}
Expand Down Expand Up @@ -554,7 +561,7 @@ namespace ts {


return referencedSourceFile
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
: { resolvedModule: undefined, failedLookupLocations };
}

Expand Down Expand Up @@ -649,9 +656,9 @@ namespace ts {

export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[] {
let diagnostics = program.getOptionsDiagnostics(cancellationToken).concat(
program.getSyntacticDiagnostics(sourceFile, cancellationToken),
program.getGlobalDiagnostics(cancellationToken),
program.getSemanticDiagnostics(sourceFile, cancellationToken));
program.getSyntacticDiagnostics(sourceFile, cancellationToken),
program.getGlobalDiagnostics(cancellationToken),
program.getSemanticDiagnostics(sourceFile, cancellationToken));

if (program.getCompilerOptions().declaration) {
diagnostics = diagnostics.concat(program.getDeclarationDiagnostics(sourceFile, cancellationToken));
Expand Down Expand Up @@ -690,6 +697,9 @@ namespace ts {
let program: Program;
let files: SourceFile[] = [];
let fileProcessingDiagnostics = createDiagnosticCollection();
const currentDirectory = host.getCurrentDirectory();
const resolvedLibraries: Map<ResolvedLibrary> = {};
const libraryRoot = options.rootDir || options.configFilePath || computeCommonSourceDirectoryOfFilenames(rootNames);
const programDiagnostics = createDiagnosticCollection();

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

const currentDirectory = host.getCurrentDirectory();
const resolveModuleNamesWorker = host.resolveModuleNames
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
: ((moduleNames: string[], containingFile: string) => {
Expand Down Expand Up @@ -883,8 +892,8 @@ namespace ts {
const oldResolution = getResolvedModule(oldSourceFile, moduleNames[i]);
const resolutionChanged = oldResolution
? !newResolution ||
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
oldResolution.resolvedFileName !== newResolution.resolvedFileName ||
!!oldResolution.isExternalLibraryImport !== !!newResolution.isExternalLibraryImport
: newResolution;

if (resolutionChanged) {
Expand Down Expand Up @@ -1007,9 +1016,9 @@ namespace ts {
}

function getDiagnosticsHelper(
sourceFile: SourceFile,
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[],
cancellationToken: CancellationToken): Diagnostic[] {
sourceFile: SourceFile,
getDiagnostics: (sourceFile: SourceFile, cancellationToken: CancellationToken) => Diagnostic[],
cancellationToken: CancellationToken): Diagnostic[] {
if (sourceFile) {
return getDiagnostics(sourceFile, cancellationToken);
}
Expand Down Expand Up @@ -1484,6 +1493,7 @@ namespace ts {
const basePath = getDirectoryPath(fileName);
if (!options.noResolve) {
processReferencedFiles(file, basePath);
processReferencedLibraries(file, libraryRoot);
}

// always process imported modules to record module name resolutions
Expand All @@ -1507,6 +1517,97 @@ namespace ts {
});
}

function findLibraryDefinition(searchPath: string) {
let typingFilename = "index.d.ts";
const packageJsonPath = combinePaths(searchPath, "package.json");
if (host.fileExists(packageJsonPath)) {
let package: { typings?: string } = {};
try {
package = JSON.parse(host.readFile(packageJsonPath));
}
catch (e) { }

if (package.typings) {
typingFilename = package.typings;
}
}

const combinedPath = normalizePath(combinePaths(searchPath, typingFilename));
return host.fileExists(combinedPath) ? combinedPath : undefined;
}

function processReferencedLibraries(file: SourceFile, compilationRoot: string) {
const primarySearchPaths = map(getEffectiveLibraryPrimarySearchPaths(), path => combinePaths(compilationRoot, path));

const failedSearchPaths: string[] = [];
const moduleResolutionState: ModuleResolutionState = {
compilerOptions: options,
host: host,
skipTsx: true,
traceEnabled: false
};

for (const ref of file.referencedLibraries) {
// If we already found this library as a primary reference, or failed to find it, nothing to do
const previousResolution = resolvedLibraries[ref.fileName];
if (previousResolution && (previousResolution.primary || (previousResolution.resolvedFileName === undefined))) {
continue;
}

let foundIt = false;

// Check primary library paths
for (const primaryPath of primarySearchPaths) {
const searchPath = combinePaths(primaryPath, ref.fileName);
const resolvedFile = findLibraryDefinition(searchPath);
if (resolvedFile) {
processSourceFile(resolvedFile, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: resolvedFile };
foundIt = true;
break;
}
}

// Check secondary library paths
if (!foundIt) {
const secondaryResult = loadModuleFromNodeModules(ref.fileName, file.fileName, failedSearchPaths, moduleResolutionState);
if (secondaryResult) {
foundIt = true;
// If we already resolved to this file, it must have been a secondary reference. Check file contents
// for sameness and possibly issue an error
if (previousResolution) {
const otherFileText = host.readFile(secondaryResult);
if (otherFileText !== getSourceFile(previousResolution.resolvedFileName).text) {
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos,
Diagnostics.Conflicting_library_definitions_for_0_found_at_1_and_2_Copy_the_correct_file_to_a_local_typings_folder_to_resolve_this_conflict,
ref.fileName,
secondaryResult,
previousResolution.resolvedFileName));
}
}
else {
// First resolution of this library
processSourceFile(secondaryResult, /*isDefaultLib*/ false, /*isReference*/ true, file, ref.pos, ref.end);
resolvedLibraries[ref.fileName] = { primary: false, resolvedFileName: secondaryResult };
}
}
}

if (!foundIt) {
fileProcessingDiagnostics.add(createFileDiagnostic(file, ref.pos, ref.end - ref.pos, Diagnostics.Cannot_find_name_0, ref.fileName));
// Create an entry as a primary lookup result so we don't keep doing this
resolvedLibraries[ref.fileName] = { primary: true, resolvedFileName: undefined };
}
}
}

function getEffectiveLibraryPrimarySearchPaths() {
return options.librarySearchPaths ||
(options.configFilePath ?
[options.configFilePath].concat(defaultLibrarySearchPaths) :
defaultLibrarySearchPaths);
}

function getCanonicalFileName(fileName: string): string {
return host.getCanonicalFileName(fileName);
}
Expand Down Expand Up @@ -1553,15 +1654,11 @@ namespace ts {
return;
}

function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
function computeCommonSourceDirectoryOfFilenames(fileNames: string[]): string {
let commonPathComponents: string[];
const failed = forEach(files, sourceFile => {
const failed = forEach(fileNames, sourceFile => {
// Each file contributes into common source file path
if (isDeclarationFile(sourceFile)) {
return;
}

const sourcePathComponents = getNormalizedPathComponents(sourceFile.fileName, currentDirectory);
const sourcePathComponents = getNormalizedPathComponents(sourceFile, currentDirectory);
sourcePathComponents.pop(); // The base file name is not part of the common directory path

if (!commonPathComponents) {
Expand Down Expand Up @@ -1601,6 +1698,16 @@ namespace ts {
return getNormalizedPathFromPathComponents(commonPathComponents);
}

function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
const fileNames: string[] = [];
for (const file of sourceFiles) {
if (!file.isDeclarationFile) {
fileNames.push(file.fileName);
}
}
return computeCommonSourceDirectoryOfFilenames(fileNames);
}

function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
let allFilesBelongToPath = true;
if (sourceFiles) {
Expand Down Expand Up @@ -1742,7 +1849,7 @@ namespace ts {

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

Expand Down
1 change: 1 addition & 0 deletions src/compiler/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ namespace ts {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* compilerHost */ undefined);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
configParseResult.options.configFilePath = configFileName;
return configParseResult;
}

Expand Down
13 changes: 13 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,7 @@ namespace ts {
amdDependencies: AmdDependency[];
moduleName: string;
referencedFiles: FileReference[];
referencedLibraries: FileReference[];
languageVariant: LanguageVariant;
isDeclarationFile: boolean;

Expand Down Expand Up @@ -2395,6 +2396,7 @@ namespace ts {
jsx?: JsxEmit;
reactNamespace?: string;
listFiles?: boolean;
librarySearchPaths?: string[];
locale?: string;
mapRoot?: string;
module?: ModuleKind;
Expand Down Expand Up @@ -2444,6 +2446,10 @@ namespace ts {
// Do not perform validation of output file name in transpile scenarios
/* @internal */ suppressOutputPathCheck?: boolean;

/* @internal */
// When options come from a config file, its path is recorded here
configFilePath?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this just placed on the Program instance?


[option: string]: string | number | boolean | TsConfigOnlyOptions;
}

Expand Down Expand Up @@ -2714,6 +2720,13 @@ namespace ts {
isExternalLibraryImport?: boolean;
}

export interface ResolvedLibrary {
// True if the library was found in a primary lookup location
primary: boolean;
// The location of the .d.ts file we located, or undefined if resolution failed
resolvedFileName?: string;
}

export interface ResolvedModuleWithFailedLookupLocations {
resolvedModule: ResolvedModule;
failedLookupLocations: string[];
Expand Down
23 changes: 13 additions & 10 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ts {
fileReference?: FileReference;
diagnosticMessage?: DiagnosticMessage;
isNoDefaultLib?: boolean;
isLibraryReference?: boolean;
}

export interface SynthesizedNode extends Node {
Expand Down Expand Up @@ -498,6 +499,7 @@ namespace ts {
}

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

export function isTypeNode(node: Node): boolean {
Expand Down Expand Up @@ -1536,25 +1538,26 @@ namespace ts {
};
}
else {
const matchResult = fullTripleSlashReferencePathRegEx.exec(comment);
if (matchResult) {
const refMatchResult = fullTripleSlashReferencePathRegEx.exec(comment);
const refLibResult = !refMatchResult && fullTripleSlashReferenceLibraryRegEx.exec(comment);
if (refMatchResult || refLibResult) {
const start = commentRange.pos;
const end = commentRange.end;
return {
fileReference: {
pos: start,
end: end,
fileName: matchResult[3]
fileName: (refMatchResult || refLibResult)[3]
},
isNoDefaultLib: false
};
}
else {
return {
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
isNoDefaultLib: false
isNoDefaultLib: false,
isLibraryReference: !!refLibResult
};
}

return {
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
isNoDefaultLib: false
};
}
}

Expand Down
Loading