Skip to content

Commit 5b3060d

Browse files
[Experiment] Some tweaking to handle project references for auto import (#55955)
Co-authored-by: Andrew Branch <[email protected]>
1 parent 207932a commit 5b3060d

File tree

93 files changed

+15574
-231
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+15574
-231
lines changed

src/compiler/utilities.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9064,6 +9064,7 @@ export interface SymlinkCache {
90649064
getSymlinkedFiles(): ReadonlyMap<Path, string> | undefined;
90659065
setSymlinkedDirectory(symlink: string, real: SymlinkedDirectory | false): void;
90669066
setSymlinkedFile(symlinkPath: Path, real: string): void;
9067+
hasAnySymlinks(): boolean;
90679068
/**
90689069
* @internal
90699070
* Uses resolvedTypeReferenceDirectives from program instead of from files, since files
@@ -9118,8 +9119,13 @@ export function createSymlinkCache(cwd: string, getCanonicalFileName: GetCanonic
91189119
typeReferenceDirectives.forEach(resolution => processResolution(this, resolution.resolvedTypeReferenceDirective));
91199120
},
91209121
hasProcessedResolutions: () => hasProcessedResolutions,
9122+
hasAnySymlinks,
91219123
};
91229124

9125+
function hasAnySymlinks() {
9126+
return !!symlinkedFiles?.size || (!!symlinkedDirectories && !!forEachEntry(symlinkedDirectories, value => !!value));
9127+
}
9128+
91239129
function processResolution(cache: SymlinkCache, resolution: ResolvedModuleFull | ResolvedTypeReferenceDirective | undefined) {
91249130
if (!resolution || !resolution.originalPath || !resolution.resolvedFileName) return;
91259131
const { resolvedFileName, originalPath } = resolution;

src/harness/tsserverLogger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export function sanitizeLog(s: string): string {
123123
s = s.replace(/getExportInfoMap: done in \d+(?:\.\d+)?/g, `getExportInfoMap: done in *`);
124124
s = s.replace(/collectAutoImports: \d+(?:\.\d+)?/g, `collectAutoImports: *`);
125125
s = s.replace(/continuePreviousIncompleteResponse: \d+(?:\.\d+)?/g, `continuePreviousIncompleteResponse: *`);
126-
s = s.replace(/dependencies in \d+(?:\.\d+)?/g, `dependencies in *`);
126+
s = s.replace(/referenced projects in \d+(?:\.\d+)?/g, `referenced projects in *`);
127127
s = s.replace(/"exportMapKey":\s*"\d+ \d+ /g, match => match.replace(/ \d+ /, ` * `));
128128
s = s.replace(/getIndentationAtPosition: getCurrentSourceFile: \d+(?:\.\d+)?/, `getIndentationAtPosition: getCurrentSourceFile: *`);
129129
s = s.replace(/getIndentationAtPosition: computeIndentation\s*: \d+(?:\.\d+)?/, `getIndentationAtPosition: computeIndentation: *`);

src/server/editorServices.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,16 @@ export class ProjectService {
17001700
const project = this.getConfiguredProjectByCanonicalConfigFilePath(projectCanonicalPath);
17011701
if (!project) return;
17021702

1703+
if (
1704+
configuredProjectForConfig !== project &&
1705+
this.getHostPreferences().includeCompletionsForModuleExports
1706+
) {
1707+
const path = this.toPath(configFileName);
1708+
if (find(project.getCurrentProgram()?.getResolvedProjectReferences(), ref => ref?.sourceFile.path === path)) {
1709+
project.markAutoImportProviderAsDirty();
1710+
}
1711+
}
1712+
17031713
// Load root file names for configured project with the config file name
17041714
// But only schedule update if project references this config file
17051715
const updateLevel = configuredProjectForConfig === project ? ProgramUpdateLevel.RootNamesAndUpdate : ProgramUpdateLevel.Update;
@@ -1765,11 +1775,19 @@ export class ProjectService {
17651775
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
17661776
project.pendingUpdateReason = loadReason;
17671777
this.delayUpdateProjectGraph(project);
1778+
project.markAutoImportProviderAsDirty();
17681779
}
17691780
else {
17701781
// Change in referenced project config file
1771-
project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(this.toPath(canonicalConfigFilePath));
1782+
const path = this.toPath(canonicalConfigFilePath);
1783+
project.resolutionCache.removeResolutionsFromProjectReferenceRedirects(path);
17721784
this.delayUpdateProjectGraph(project);
1785+
if (
1786+
this.getHostPreferences().includeCompletionsForModuleExports &&
1787+
find(project.getCurrentProgram()?.getResolvedProjectReferences(), ref => ref?.sourceFile.path === path)
1788+
) {
1789+
project.markAutoImportProviderAsDirty();
1790+
}
17731791
}
17741792
});
17751793
return scheduledAnyProjectUpdate;
@@ -3557,6 +3575,7 @@ export class ProjectService {
35573575
const {
35583576
lazyConfiguredProjectsFromExternalProject,
35593577
includePackageJsonAutoImports,
3578+
includeCompletionsForModuleExports,
35603579
} = this.hostConfiguration.preferences;
35613580

35623581
this.hostConfiguration.preferences = { ...this.hostConfiguration.preferences, ...args.preferences };
@@ -3576,7 +3595,10 @@ export class ProjectService {
35763595
})
35773596
);
35783597
}
3579-
if (includePackageJsonAutoImports !== args.preferences.includePackageJsonAutoImports) {
3598+
if (
3599+
includePackageJsonAutoImports !== args.preferences.includePackageJsonAutoImports ||
3600+
!!includeCompletionsForModuleExports !== !!args.preferences.includeCompletionsForModuleExports
3601+
) {
35803602
this.forEachProject(project => {
35813603
project.onAutoImportProviderSettingsChanged();
35823604
});

src/server/project.ts

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
arrayToMap,
88
BuilderState,
99
CachedDirectoryStructureHost,
10+
changeExtension,
1011
changesAffectModuleResolution,
1112
clearMap,
1213
cloneCompilerOptions,
@@ -16,7 +17,6 @@ import {
1617
comparePaths,
1718
CompilerHost,
1819
CompilerOptions,
19-
concatenate,
2020
containsPath,
2121
createCacheableExportInfoMap,
2222
createLanguageService,
@@ -50,6 +50,7 @@ import {
5050
getAutomaticTypeDirectiveNames,
5151
getBaseFileName,
5252
GetCanonicalFileName,
53+
getCommonSourceDirectoryOfConfig,
5354
getDeclarationEmitOutputFilePathWorker,
5455
getDefaultCompilerOptions,
5556
getDefaultLibFileName,
@@ -60,6 +61,7 @@ import {
6061
getEntrypointsFromPackageJsonInfo,
6162
getNormalizedAbsolutePath,
6263
getOrUpdate,
64+
getOutputDeclarationFileName,
6365
GetPackageJsonEntrypointsHost,
6466
getStringComparer,
6567
HasInvalidatedLibResolutions,
@@ -79,6 +81,7 @@ import {
7981
map,
8082
mapDefined,
8183
maybeBind,
84+
memoize,
8285
ModuleResolutionCache,
8386
ModuleResolutionHost,
8487
noop,
@@ -1316,6 +1319,12 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
13161319
}
13171320
}
13181321

1322+
/** @internal */
1323+
markAutoImportProviderAsDirty() {
1324+
if (!this.autoImportProviderHost) this.autoImportProviderHost = undefined;
1325+
this.autoImportProviderHost?.markAsDirty();
1326+
}
1327+
13191328
/** @internal */
13201329
onAutoImportProviderSettingsChanged() {
13211330
if (this.autoImportProviderHost === false) {
@@ -1400,8 +1409,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
14001409
this.projectProgramVersion++;
14011410
}
14021411
if (hasAddedorRemovedFiles) {
1403-
if (!this.autoImportProviderHost) this.autoImportProviderHost = undefined;
1404-
this.autoImportProviderHost?.markAsDirty();
1412+
this.markAutoImportProviderAsDirty();
14051413
}
14061414
if (isFirstProgramLoad) {
14071415
// Preload auto import provider so it's not created during completions request
@@ -2439,7 +2447,7 @@ export class AutoImportProviderProject extends Project {
24392447

24402448
const start = timestamp();
24412449
let dependencyNames: Set<string> | undefined;
2442-
let rootNames: string[] | undefined;
2450+
let rootNames: Set<string> | undefined;
24432451
const rootFileName = combinePaths(hostProject.currentDirectory, inferredTypesContainingFile);
24442452
const packageJsons = hostProject.getPackageJsonsForAutoImport(combinePaths(hostProject.currentDirectory, rootFileName));
24452453
for (const packageJson of packageJsons) {
@@ -2471,8 +2479,7 @@ export class AutoImportProviderProject extends Project {
24712479
if (packageJson) {
24722480
const entrypoints = getRootNamesFromPackageJson(packageJson, program, symlinkCache);
24732481
if (entrypoints) {
2474-
rootNames = concatenate(rootNames, entrypoints);
2475-
dependenciesAdded += entrypoints.length ? 1 : 0;
2482+
dependenciesAdded += addRootNames(entrypoints);
24762483
continue;
24772484
}
24782485
}
@@ -2490,8 +2497,7 @@ export class AutoImportProviderProject extends Project {
24902497
);
24912498
if (typesPackageJson) {
24922499
const entrypoints = getRootNamesFromPackageJson(typesPackageJson, program, symlinkCache);
2493-
rootNames = concatenate(rootNames, entrypoints);
2494-
dependenciesAdded += entrypoints?.length ? 1 : 0;
2500+
dependenciesAdded += addRootNames(entrypoints);
24952501
return true;
24962502
}
24972503
}
@@ -2504,16 +2510,56 @@ export class AutoImportProviderProject extends Project {
25042510
// package and load the JS.
25052511
if (packageJson && compilerOptions.allowJs && compilerOptions.maxNodeModuleJsDepth) {
25062512
const entrypoints = getRootNamesFromPackageJson(packageJson, program, symlinkCache, /*resolveJs*/ true);
2507-
rootNames = concatenate(rootNames, entrypoints);
2508-
dependenciesAdded += entrypoints?.length ? 1 : 0;
2513+
dependenciesAdded += addRootNames(entrypoints);
25092514
}
25102515
}
25112516
}
25122517

2513-
if (rootNames?.length) {
2514-
hostProject.log(`AutoImportProviderProject: found ${rootNames.length} root files in ${dependenciesAdded} dependencies in ${timestamp() - start} ms`);
2518+
const references = program.getResolvedProjectReferences();
2519+
let referencesAddded = 0;
2520+
if (references?.length && hostProject.projectService.getHostPreferences().includeCompletionsForModuleExports) {
2521+
// Add direct referenced projects to rootFiles names
2522+
references.forEach(ref => {
2523+
if (ref?.commandLine.options.outFile) {
2524+
referencesAddded += addRootNames(filterEntrypoints([
2525+
changeExtension(ref.commandLine.options.outFile, ".d.ts"),
2526+
]));
2527+
}
2528+
else if (ref) {
2529+
const getCommonSourceDirectory = memoize(() =>
2530+
getCommonSourceDirectoryOfConfig(
2531+
ref.commandLine,
2532+
!hostProject.useCaseSensitiveFileNames(),
2533+
)
2534+
);
2535+
referencesAddded += addRootNames(filterEntrypoints(mapDefined(
2536+
ref.commandLine.fileNames,
2537+
fileName =>
2538+
!isDeclarationFileName(fileName) &&
2539+
!fileExtensionIs(fileName, Extension.Json) &&
2540+
!program.getSourceFile(fileName) ?
2541+
getOutputDeclarationFileName(
2542+
fileName,
2543+
ref.commandLine,
2544+
!hostProject.useCaseSensitiveFileNames(),
2545+
getCommonSourceDirectory,
2546+
) : undefined,
2547+
)));
2548+
}
2549+
});
2550+
}
2551+
2552+
if (rootNames?.size) {
2553+
hostProject.log(`AutoImportProviderProject: found ${rootNames.size} root files in ${dependenciesAdded} dependencies ${referencesAddded} referenced projects in ${timestamp() - start} ms`);
2554+
}
2555+
return rootNames ? arrayFrom(rootNames.values()) : ts.emptyArray;
2556+
2557+
function addRootNames(entrypoints: readonly string[] | undefined) {
2558+
if (!entrypoints?.length) return 0;
2559+
rootNames ??= new Set();
2560+
entrypoints.forEach(entry => rootNames!.add(entry));
2561+
return 1;
25152562
}
2516-
return rootNames || ts.emptyArray;
25172563

25182564
function addDependency(dependency: string) {
25192565
if (!startsWith(dependency, "@types/")) {
@@ -2540,14 +2586,18 @@ export class AutoImportProviderProject extends Project {
25402586
});
25412587
}
25422588

2543-
return mapDefined(entrypoints, entrypoint => {
2544-
const resolvedFileName = isSymlink ? entrypoint.replace(packageJson.packageDirectory, real!) : entrypoint;
2545-
if (!program.getSourceFile(resolvedFileName) && !(isSymlink && program.getSourceFile(entrypoint))) {
2546-
return resolvedFileName;
2547-
}
2548-
});
2589+
return filterEntrypoints(entrypoints, isSymlink ? entrypoint => entrypoint.replace(packageJson.packageDirectory, real!) : undefined);
25492590
}
25502591
}
2592+
2593+
function filterEntrypoints(entrypoints: readonly string[] | undefined, symlinkName?: (entrypoint: string) => string) {
2594+
return mapDefined(entrypoints, entrypoint => {
2595+
const resolvedFileName = symlinkName ? symlinkName(entrypoint) : entrypoint;
2596+
if (!program!.getSourceFile(resolvedFileName) && !(symlinkName && program!.getSourceFile(entrypoint))) {
2597+
return resolvedFileName;
2598+
}
2599+
});
2600+
}
25512601
}
25522602

25532603
/** @internal */

src/services/completions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3948,7 +3948,9 @@ function getCompletionData(
39483948
// If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK.
39493949
if (compilerOptionsIndicateEsModules(program.getCompilerOptions())) return true;
39503950
// If some file is using ES6 modules, assume that it's OK to add more.
3951-
return programContainsModules(program);
3951+
return program.getSymlinkCache?.().hasAnySymlinks() ||
3952+
!!program.getCompilerOptions().paths ||
3953+
programContainsModules(program);
39523954
}
39533955

39543956
function isSnippetScope(scopeNode: Node): boolean {

src/testRunner/unittests/tsserver/autoImportProvider.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,15 @@ describe("unittests:: tsserver:: autoImportProvider - monorepo", () => {
393393
function setup(files: File[]) {
394394
const host = createServerHost(files);
395395
const session = new TestSession(host);
396+
session.executeCommandSeq<ts.server.protocol.ConfigureRequest>({
397+
command: ts.server.protocol.CommandTypes.Configure,
398+
arguments: {
399+
preferences: {
400+
includePackageJsonAutoImports: "auto",
401+
includeCompletionsForModuleExports: true,
402+
},
403+
},
404+
});
396405
return {
397406
host,
398407
session,
@@ -427,16 +436,12 @@ function setup(files: File[]) {
427436
}
428437

429438
function triggerCompletions(file: string, line: number, offset: number) {
430-
const requestLocation: ts.server.protocol.FileLocationRequestArgs = {
431-
file,
432-
line,
433-
offset,
434-
};
435439
session.executeCommandSeq<ts.server.protocol.CompletionsRequest>({
436440
command: ts.server.protocol.CommandTypes.CompletionInfo,
437441
arguments: {
438-
...requestLocation,
439-
includeExternalModuleExports: true,
442+
file,
443+
line,
444+
offset,
440445
},
441446
});
442447
}

0 commit comments

Comments
 (0)