-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Eagerly resolve module specifiers for auto-import completions in --moduleResolution node12+
#48752
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
Eagerly resolve module specifiers for auto-import completions in --moduleResolution node12+
#48752
Conversation
Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @amcasey, @mjbvz, @minestarks for you. Feel free to loop in other consumers/maintainers if necessary |
@@ -8463,7 +8463,7 @@ namespace ts { | |||
export interface ResolvedModuleSpecifierInfo { | |||
modulePaths: readonly ModulePath[] | undefined; | |||
moduleSpecifiers: readonly string[] | undefined; | |||
isAutoImportable: boolean | undefined; | |||
isBlockedByPackageJsonDependencies: boolean | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a behavior change, but the property became a misnomer because in nodenext, factors that don't contribute to this property can make the module specifiers not auto-importable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Blocked" sounds like it means "not auto-importable". Is that correct and, if so, does that mean that the meaning of the flag has flipped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For other reviewers, yes, the meaning has flipped.
@@ -629,19 +630,19 @@ namespace ts.codefix { | |||
const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value); | |||
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions); | |||
computedWithoutCacheCount += computedWithoutCache ? 1 : 0; | |||
return moduleSpecifiers?.map((moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport => | |||
return mapDefined(moduleSpecifiers, (moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport | undefined => | |||
rejectNodeModulesRelativePaths && pathContainsNodeModules(moduleSpecifier) ? undefined : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is the most important change.
} | ||
const shouldResolveModuleSpecifier = isForImportStatementCompletion || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; | ||
const shouldResolveModuleSpecifier = needsFullResolution || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is the next most important change. Everything else is bookkeeping, telemetry, renaming for accuracy, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're going to have to be a little careful that this doesn't get consumed by the telemetry system as a floating point number that might get rounded.
@@ -8463,7 +8463,7 @@ namespace ts { | |||
export interface ResolvedModuleSpecifierInfo { | |||
modulePaths: readonly ModulePath[] | undefined; | |||
moduleSpecifiers: readonly string[] | undefined; | |||
isAutoImportable: boolean | undefined; | |||
isBlockedByPackageJsonDependencies: boolean | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Blocked" sounds like it means "not auto-importable". Is that correct and, if so, does that mean that the meaning of the flag has flipped?
@@ -8463,7 +8463,7 @@ namespace ts { | |||
export interface ResolvedModuleSpecifierInfo { | |||
modulePaths: readonly ModulePath[] | undefined; | |||
moduleSpecifiers: readonly string[] | undefined; | |||
isAutoImportable: boolean | undefined; | |||
isBlockedByPackageJsonDependencies: boolean | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For other reviewers, yes, the meaning has flipped.
// package.json exports can mean we *can't* resolve a module specifier (that doesn't include a | ||
// relative path into node_modules), and we want to filter those completions out entirely. | ||
// Import statement completions always need specifier resolution because the module specifier is | ||
// part of their `insertText`, not the `codeActions` creating edits away from the cursor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"away from the cursor" => "elsewhere in the file"?
interface CompletionInfo { | ||
/** For performance telemetry. */ | ||
flags?: CompletionInfoFlags; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had discussed the possibility that this might subsume one or more existing flags, but it looks like that wasn't necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a property that we could delete in the future if we’re not worried about old clients using it.
@@ -6,6 +6,7 @@ | |||
"name": "1" | |||
}, | |||
"completionList": { | |||
"flags": 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skimming, I couldn't find one that wasn't 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem like any of the baselines have a value other than 0, no, but there is a unit test that has one of the flags set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but I'm not sure if you were wanting some tests that test all of the different flag code paths or not.
I wasn’t particularly concerned with testing all the telemetry flag combinations, was satisfied to see one of them come through in that unit test. |
For auto-import completions, we used to not resolve the module specifier until the subsequent completion details request, triggered for one completion entry at a time when you focus it in the list. Later, we added a cache for module specifiers and started resolving up to 100 module specifiers per completions request, and pulling up to an additional 1000 from the cache.
Under
--moduleResolution node12
ornodenext
, we have to resolve module specifiers for auto-import completions right away, in excess of 100 if needed, because respecting package.jsonexports
means that there may be exported symbols for which we cannot generate a module specifier without writing a relative path into"../node_modules/"
. We would prefer to treat such exports as unreachable and filter them from the list, which means we need to know the module specifier when generating the list. This is unfortunate for performance reasons if the list is very big, but we have no choice if we want to avoid showing completions for unreachable exports.The introduction of the PackageJsonInfoCache in module resolution (added in the initial support for node12 and nodenext), and sharing that cache with the auto-imports logic (#47388) should mitigate some of this cost. I currently have no realistic project to measure this change with, as real-world projects using these yet-experimental resolution modes are rare or possibly nonexistent. I’ve added some bit flags to
CompletionInfo
that indicate a number of perf-impacting things that happened while computing the response, including the scenario that wasn’t possible before this PR, resolving >100 module specifiers in a single request.