Skip to content

Commit

Permalink
Merge pull request #24909 from weswigham/port-import-type-declaration…
Browse files Browse the repository at this point in the history
…-updates

Port import type declaration updates
  • Loading branch information
mhegazy committed Jun 12, 2018
2 parents 0db07e5 + 97c3c60 commit f98ba80
Show file tree
Hide file tree
Showing 27 changed files with 583 additions and 58 deletions.
10 changes: 9 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4092,7 +4092,15 @@ namespace ts {
// ambient module, just use declaration/symbol name (fallthrough)
}
else {
return `"${getResolvedExternalModuleName(context.tracker.moduleResolverHost, file, getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)))}"`;
const contextFile = getSourceFileOfNode(getOriginalNode(context!.enclosingDeclaration))!;
return `"${file.moduleName || moduleSpecifiers.getModuleSpecifiers(
symbol,
compilerOptions,
contextFile,
context!.tracker.moduleResolverHost!,
context!.tracker.moduleResolverHost!.getSourceFiles!(),
{ importModuleSpecifierPreference: "non-relative" }
)[0]}"`;
}
}
const declaration = symbol.declarations[0];
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,6 @@ namespace ts {
}
}

export interface GetEffectiveTypeRootsHost {
directoryExists?(directoryName: string): boolean;
getCurrentDirectory?(): string;
}
export function getEffectiveTypeRoots(options: CompilerOptions, host: GetEffectiveTypeRootsHost): string[] | undefined {
if (options.typeRoots) {
return options.typeRoots;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
// Used by importFixes to synthesize import module specifiers.
/* @internal */
namespace ts.moduleSpecifiers {
export interface ModuleSpecifierPreferences {
importModuleSpecifierPreference?: "relative" | "non-relative";
}

// Note: fromSourceFile is just for usesJsExtensionOnImports
export function getModuleSpecifier(program: Program, fromSourceFile: SourceFile, fromSourceFileName: string, toFileName: string, host: LanguageServiceHost, preferences: UserPreferences) {
const info = getInfo(program.getCompilerOptions(), fromSourceFile, fromSourceFileName, host);
const compilerOptions = program.getCompilerOptions();
export function getModuleSpecifier(compilerOptions: CompilerOptions, fromSourceFile: SourceFile, fromSourceFileName: string, toFileName: string, host: ModuleSpecifierResolutionHost, preferences: ModuleSpecifierPreferences = {}) {
const info = getInfo(compilerOptions, fromSourceFile, fromSourceFileName, host);
return getGlobalModuleSpecifier(toFileName, info, host, compilerOptions) ||
first(getLocalModuleSpecifiers(toFileName, info, compilerOptions, preferences));
}

// For each symlink/original for a module, returns a list of ways to import that file.
export function getModuleSpecifiers(
moduleSymbol: Symbol,
program: Program,
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
host: LanguageServiceHost,
preferences: UserPreferences,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences,
): ReadonlyArray<ReadonlyArray<string>> {
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
if (ambient) return [[ambient]];

const compilerOptions = program.getCompilerOptions();
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.fileName, host);
const modulePaths = getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile());
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.path, host);
if (!files) {
return Debug.fail("Files list must be present to resolve symlinks in specifier resolution");
}
const modulePaths = getAllModulePaths(files, getSourceFileOfNode(moduleSymbol.valueDeclaration), info.getCanonicalFileName, host);

const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions));
return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName =>
Expand All @@ -36,18 +42,18 @@ namespace ts.moduleSpecifiers {
readonly sourceDirectory: string;
}
// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
function getInfo(compilerOptions: CompilerOptions, importingSourceFile: SourceFile, importingSourceFileName: string, host: LanguageServiceHost): Info {
function getInfo(compilerOptions: CompilerOptions, importingSourceFile: SourceFile, importingSourceFileName: string, host: ModuleSpecifierResolutionHost): Info {
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const addJsExtension = usesJsExtensionOnImports(importingSourceFile);
const getCanonicalFileName = hostGetCanonicalFileName(host);
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true);
const sourceDirectory = getDirectoryPath(importingSourceFileName);
return { moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory };
}

function getGlobalModuleSpecifier(
moduleFileName: string,
{ addJsExtension, getCanonicalFileName, sourceDirectory }: Info,
host: LanguageServiceHost,
host: ModuleSpecifierResolutionHost,
compilerOptions: CompilerOptions,
) {
return tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension)
Expand All @@ -59,7 +65,7 @@ namespace ts.moduleSpecifiers {
moduleFileName: string,
{ moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory }: Info,
compilerOptions: CompilerOptions,
preferences: UserPreferences,
preferences: ModuleSpecifierPreferences,
) {
const { baseUrl, paths } = compilerOptions;

Expand Down Expand Up @@ -127,15 +133,57 @@ namespace ts.moduleSpecifiers {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
}

function discoverProbableSymlinks(files: ReadonlyArray<SourceFile>) {
const symlinks = mapDefined(files, sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined));
const result = createMap<string>();
if (symlinks) {
for (const [resolvedPath, originalPath] of symlinks) {
const resolvedParts = getPathComponents(resolvedPath);
const originalParts = getPathComponents(originalPath);
while (resolvedParts[resolvedParts.length - 1] === originalParts[originalParts.length - 1]) {
resolvedParts.pop();
originalParts.pop();
}
result.set(getPathFromPathComponents(originalParts), getPathFromPathComponents(resolvedParts));
}
}
return result;
}

function getAllModulePathsUsingIndirectSymlinks(files: ReadonlyArray<SourceFile>, target: string, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost) {
const links = discoverProbableSymlinks(files);
const paths = arrayFrom(links.keys());
let options: string[] | undefined;
for (const path of paths) {
const resolved = links.get(path)!;
if (startsWith(target, resolved + "/")) {
const relative = getRelativePathFromDirectory(resolved, target, getCanonicalFileName);
const option = resolvePath(path, relative);
if (!host.fileExists || host.fileExists(option)) {
if (!options) options = [];
options.push(option);
}
}
}
const resolvedtarget = host.getCurrentDirectory ? resolvePath(host.getCurrentDirectory(), target) : target;
if (options) {
options.push(resolvedtarget); // Since these are speculative, we also include the original resolved name as a possibility
return options;
}
return [resolvedtarget];
}

/**
* Looks for a existing imports that use symlinks to this module.
* Only if no symlink is available, the real path will be used.
*/
function getAllModulePaths(program: Program, { fileName }: SourceFile): ReadonlyArray<string> {
const symlinks = mapDefined(program.getSourceFiles(), sf =>
function getAllModulePaths(files: ReadonlyArray<SourceFile>, { fileName }: SourceFile, getCanonicalFileName: (file: string) => string, host: ModuleSpecifierResolutionHost): ReadonlyArray<string> {
const symlinks = mapDefined(files, sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.resolvedFileName === fileName ? res.originalPath : undefined));
return symlinks.length === 0 ? [fileName] : symlinks;
return symlinks.length === 0 ? getAllModulePathsUsingIndirectSymlinks(files, fileName, getCanonicalFileName, host) : symlinks;
}

function getRelativePathNParents(relativePath: string): number {
Expand Down Expand Up @@ -210,7 +258,7 @@ namespace ts.moduleSpecifiers {
function tryGetModuleNameAsNodeModule(
options: CompilerOptions,
moduleFileName: string,
host: LanguageServiceHost,
host: ModuleSpecifierResolutionHost,
getCanonicalFileName: (file: string) => string,
sourceDirectory: string,
): string | undefined {
Expand Down Expand Up @@ -255,7 +303,8 @@ namespace ts.moduleSpecifiers {
const fullModulePathWithoutExtension = removeFileExtension(path);

// If the file is /index, it can be imported by its directory name
if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index") {
// IFF there is not _also_ a file by the same name
if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) {
return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex);
}

Expand All @@ -274,6 +323,17 @@ namespace ts.moduleSpecifiers {
}
}

function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) {
// We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory
const extensions = getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]);
for (const e of extensions) {
const fullPath = path + e;
if (host.fileExists!(fullPath)) { // TODO: GH#18217
return fullPath;
}
}
}

function getNodeModulePathParts(fullPath: string) {
// If fullPath can't be valid module file within node_modules, returns undefined.
// Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,9 @@ namespace ts {
writeFile: writeFileCallback || (
(fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)),
isEmitBlocked,
readFile: f => host.readFile(f),
fileExists: f => host.fileExists(f),
...(host.directoryExists ? { directoryExists: f => host.directoryExists!(f) } : {}),
};
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"builderState.ts",
"builder.ts",
"resolutionCache.ts",
"moduleSpecifiers.ts",
"watch.ts",
"commandLineParser.ts",
"tsc.ts"
Expand Down
20 changes: 13 additions & 7 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5011,8 +5011,9 @@ namespace ts {
}

/* @internal */
export interface EmitHost extends ScriptReferenceHost {
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost {
getSourceFiles(): ReadonlyArray<SourceFile>;
getCurrentDirectory(): string;

/* @internal */
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
Expand Down Expand Up @@ -5274,11 +5275,16 @@ namespace ts {
isAtStartOfLine(): boolean;
}

/* @internal */
export interface ModuleNameResolverHost {
getCanonicalFileName(f: string): string;
getCommonSourceDirectory(): string;
getCurrentDirectory(): string;
export interface GetEffectiveTypeRootsHost {
directoryExists?(directoryName: string): boolean;
getCurrentDirectory?(): string;
}
/** @internal */
export interface ModuleSpecifierResolutionHost extends GetEffectiveTypeRootsHost {
useCaseSensitiveFileNames?(): boolean;
fileExists?(path: string): boolean;
readFile?(path: string): string | undefined;
getSourceFiles?(): ReadonlyArray<SourceFile>; // Used for cached resolutions to find symlinks without traversing the fs (again)
}

/** @deprecated See comment on SymbolWriter */
Expand All @@ -5292,7 +5298,7 @@ namespace ts {
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
reportInaccessibleUniqueSymbolError?(): void;
/* @internal */
moduleResolverHost?: ModuleNameResolverHost;
moduleResolverHost?: ModuleSpecifierResolutionHost;
/* @internal */
trackReferencedAmbientModule?(decl: ModuleDeclaration): void;
}
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2893,11 +2893,11 @@ namespace ts {
};
}

export function getResolvedExternalModuleName(host: ModuleNameResolverHost, file: SourceFile, referenceFile?: SourceFile): string {
export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile, referenceFile?: SourceFile): string {
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName, referenceFile && referenceFile.fileName);
}

export function getExternalModuleNameFromDeclaration(host: ModuleNameResolverHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string {
export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode): string | undefined {
const file = resolver.getExternalModuleFileFromDeclaration(declaration);
if (!file || file.isDeclarationFile) {
return undefined;
Expand All @@ -2908,7 +2908,7 @@ namespace ts {
/**
* Resolves a local path to a path which is absolute to the base of the emit
*/
export function getExternalModuleNameFromPath(host: ModuleNameResolverHost, fileName: string, referencePath?: string): string {
export function getExternalModuleNameFromPath(host: EmitHost, fileName: string, referencePath?: string): string {
const getCanonicalFileName = (f: string) => host.getCanonicalFileName(f);
const dir = toPath(referencePath ? getDirectoryPath(referencePath) : host.getCommonSourceDirectory(), host.getCurrentDirectory(), getCanonicalFileName);
const filePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
Expand Down
4 changes: 3 additions & 1 deletion src/harness/compilerRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ class CompilerTest {
this.otherFiles,
this.harnessSettings,
/*options*/ tsConfigOptions,
/*currentDirectory*/ this.harnessSettings.currentDirectory);
/*currentDirectory*/ this.harnessSettings.currentDirectory,
testCaseContent.symlinks
);

this.options = this.result.options;
}
Expand Down
Loading

0 comments on commit f98ba80

Please sign in to comment.