Skip to content

Port import type declaration updates #24909

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

Merged
Merged
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
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