diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1a221cc3932d2..c03a01f424ba5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8470,7 +8470,7 @@ namespace ts { export interface ResolvedModuleSpecifierInfo { modulePaths: readonly ModulePath[] | undefined; moduleSpecifiers: readonly string[] | undefined; - isAutoImportable: boolean | undefined; + isBlockedByPackageJsonDependencies: boolean | undefined; } /* @internal */ @@ -8482,7 +8482,7 @@ namespace ts { export interface ModuleSpecifierCache { get(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions): Readonly | undefined; set(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, modulePaths: readonly ModulePath[], moduleSpecifiers: readonly string[]): void; - setIsAutoImportable(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, isAutoImportable: boolean): void; + setBlockedByPackageJsonDependencies(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, isBlockedByPackageJsonDependencies: boolean): void; setModulePaths(fromFileName: Path, toFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions, modulePaths: readonly ModulePath[]): void; clear(): void; count(): number; diff --git a/src/server/moduleSpecifierCache.ts b/src/server/moduleSpecifierCache.ts index e5114fb64460d..c4a26b3f80c4f 100644 --- a/src/server/moduleSpecifierCache.ts +++ b/src/server/moduleSpecifierCache.ts @@ -14,7 +14,7 @@ namespace ts.server { return cache.get(toFileName); }, set(fromFileName, toFileName, preferences, options, modulePaths, moduleSpecifiers) { - ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isAutoImportable*/ true)); + ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isBlockedByPackageJsonDependencies*/ false)); // If any module specifiers were generated based off paths in node_modules, // a package.json file in that package was read and is an input to the cached. @@ -43,17 +43,17 @@ namespace ts.server { info.modulePaths = modulePaths; } else { - cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isAutoImportable*/ undefined)); + cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isBlockedByPackageJsonDependencies*/ undefined)); } }, - setIsAutoImportable(fromFileName, toFileName, preferences, options, isAutoImportable) { + setBlockedByPackageJsonDependencies(fromFileName, toFileName, preferences, options, isBlockedByPackageJsonDependencies) { const cache = ensureCache(fromFileName, preferences, options); const info = cache.get(toFileName); if (info) { - info.isAutoImportable = isAutoImportable; + info.isBlockedByPackageJsonDependencies = isBlockedByPackageJsonDependencies; } else { - cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isAutoImportable)); + cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isBlockedByPackageJsonDependencies)); } }, clear() { @@ -87,9 +87,9 @@ namespace ts.server { function createInfo( modulePaths: readonly ModulePath[] | undefined, moduleSpecifiers: readonly string[] | undefined, - isAutoImportable: boolean | undefined, + isBlockedByPackageJsonDependencies: boolean | undefined, ): ResolvedModuleSpecifierInfo { - return { modulePaths, moduleSpecifiers, isAutoImportable }; + return { modulePaths, moduleSpecifiers, isBlockedByPackageJsonDependencies }; } } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index fc53ad151ed67..121b69895f947 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2403,6 +2403,7 @@ namespace ts.server.protocol { } export interface CompletionInfo { + readonly flags?: number; readonly isGlobalCompletion: boolean; readonly isMemberCompletion: boolean; readonly isNewIdentifierLocation: boolean; diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index e277a7d4e8918..94b289cd317c5 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -644,6 +644,7 @@ namespace ts.codefix { const compilerOptions = program.getCompilerOptions(); const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host); const getChecker = memoizeOne((isFromPackageJson: boolean) => isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker()); + const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(getEmitModuleResolutionKind(compilerOptions)); const getModuleSpecifiers = fromCacheOnly ? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false }) : (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences); @@ -655,19 +656,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 : // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. - !importedSymbolHasValueMeaning && isJs && position !== undefined - ? { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, position, exportInfo, isReExport: i > 0 } - : { - kind: ImportFixKind.AddNew, - moduleSpecifier, - importKind: getImportKind(sourceFile, exportInfo.exportKind, compilerOptions), - useRequire, - addAsTypeOnly, - exportInfo, - isReExport: i > 0, - } + !importedSymbolHasValueMeaning && isJs && position !== undefined ? { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, position, exportInfo, isReExport: i > 0 } : + { + kind: ImportFixKind.AddNew, + moduleSpecifier, + importKind: getImportKind(sourceFile, exportInfo.exportKind, compilerOptions), + useRequire, + addAsTypeOnly, + exportInfo, + isReExport: i > 0, + } ); }); diff --git a/src/services/completions.ts b/src/services/completions.ts index 50bfad2d5e4ed..d5f270e82e5be 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -170,14 +170,16 @@ namespace ts.Completions { const enum GlobalsSearch { Continue, Success, Fail } interface ModuleSpecifierResolutioContext { - tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult | undefined; - resolutionLimitExceeded: () => boolean; + tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult; + resolvedAny: () => boolean; + skippedAny: () => boolean; + resolvedBeyondLimit: () => boolean; } - interface ModuleSpecifierResolutionResult { + type ModuleSpecifierResolutionResult = "skipped" | "failed" | { exportInfo?: SymbolExportInfo; moduleSpecifier: string; - } + }; function resolvingModuleSpecifiers( logPrefix: string, @@ -192,45 +194,56 @@ namespace ts.Completions { ): TReturn { const start = timestamp(); const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host); - let resolutionLimitExceeded = false; + // Under `--moduleResolution nodenext`, we have to resolve module specifiers up front, because + // 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. + const needsFullResolution = isForImportStatementCompletion || moduleResolutionRespectsExports(getEmitModuleResolutionKind(program.getCompilerOptions())); + let skippedAny = false; let ambientCount = 0; let resolvedCount = 0; let resolvedFromCacheCount = 0; let cacheAttemptCount = 0; - const result = cb({ tryResolve, resolutionLimitExceeded: () => resolutionLimitExceeded }); + const result = cb({ + tryResolve, + skippedAny: () => skippedAny, + resolvedAny: () => resolvedCount > 0, + resolvedBeyondLimit: () => resolvedCount > moduleSpecifierResolutionLimit, + }); const hitRateMessage = cacheAttemptCount ? ` (${(resolvedFromCacheCount / cacheAttemptCount * 100).toFixed(1)}% hit rate)` : ""; host.log?.(`${logPrefix}: resolved ${resolvedCount} module specifiers, plus ${ambientCount} ambient and ${resolvedFromCacheCount} from cache${hitRateMessage}`); - host.log?.(`${logPrefix}: response is ${resolutionLimitExceeded ? "incomplete" : "complete"}`); + host.log?.(`${logPrefix}: response is ${skippedAny ? "incomplete" : "complete"}`); host.log?.(`${logPrefix}: ${timestamp() - start}`); return result; - function tryResolve(exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean): ModuleSpecifierResolutionResult | undefined { + function tryResolve(exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean): ModuleSpecifierResolutionResult { if (isFromAmbientModule) { const result = codefix.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite, sourceFile, program, host, preferences); if (result) { ambientCount++; } - return result; + return result || "failed"; } - const shouldResolveModuleSpecifier = isForImportStatementCompletion || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; + const shouldResolveModuleSpecifier = needsFullResolution || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit; const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache) ? codefix.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite, sourceFile, program, host, preferences, packageJsonImportFilter, shouldGetModuleSpecifierFromCache) : undefined; if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) { - resolutionLimitExceeded = true; + skippedAny = true; } resolvedCount += result?.computedWithoutCacheCount || 0; - resolvedFromCacheCount += exportInfo.length - resolvedCount; + resolvedFromCacheCount += exportInfo.length - (result?.computedWithoutCacheCount || 0); if (shouldGetModuleSpecifierFromCache) { cacheAttemptCount++; } - return result; + return result || (needsFullResolution ? "failed" : "skipped"); } } @@ -379,7 +392,11 @@ namespace ts.Completions { const info = exportMap.get(file.path, entry.data.exportMapKey); const result = info && context.tryResolve(info, entry.name, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); - if (!result) return entry; + if (result === "skipped") return entry; + if (!result || result === "failed") { + host.log?.(`Unexpected failure resolving auto import for '${entry.name}' from '${entry.source}'`); + return undefined; + } const newOrigin: SymbolOriginInfoResolvedExport = { ...origin, @@ -394,7 +411,7 @@ namespace ts.Completions { return entry; }); - if (!context.resolutionLimitExceeded()) { + if (!context.skippedAny()) { previousResponse.isIncomplete = undefined; } @@ -403,6 +420,7 @@ namespace ts.Completions { ); previousResponse.entries = newEntries; + previousResponse.flags = (previousResponse.flags || 0) | CompletionInfoFlags.IsContinuation; return previousResponse; } @@ -574,12 +592,13 @@ namespace ts.Completions { } return { + flags: completionData.flags, isGlobalCompletion: isInSnippetScope, isIncomplete: preferences.allowIncompleteCompletions && hasUnresolvedAutoImports ? true : undefined, isMemberCompletion: isMemberCompletionKind(completionKind), isNewIdentifierLocation, optionalReplacementSpan: getOptionalReplacementSpan(location), - entries + entries, }; } @@ -1841,6 +1860,7 @@ namespace ts.Completions { readonly isRightOfOpenTag: boolean; readonly importCompletionNode?: Node; readonly hasUnresolvedAutoImports?: boolean; + readonly flags: CompletionInfoFlags; } type Request = | { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } @@ -2025,6 +2045,7 @@ namespace ts.Completions { let location = getTouchingPropertyName(sourceFile, position); let keywordFilters = KeywordCompletionFilters.None; let isNewIdentifierLocation = false; + let flags = CompletionInfoFlags.None; if (contextToken) { const importStatementCompletion = getImportStatementCompletionInfo(contextToken); @@ -2045,6 +2066,7 @@ namespace ts.Completions { // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients // to opt in with the `includeCompletionsForImportStatements` user preference. importCompletionNode = importStatementCompletion.replacementNode; + flags |= CompletionInfoFlags.IsImportStatementCompletion; } // Bail out if this is a known invalid completion location if (!importCompletionNode && isCompletionListBlocker(contextToken)) { @@ -2252,6 +2274,7 @@ namespace ts.Completions { isRightOfOpenTag, importCompletionNode, hasUnresolvedAutoImports, + flags, }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag; @@ -2692,6 +2715,7 @@ namespace ts.Completions { return; } + flags |= CompletionInfoFlags.MayIncludeAutoImports; // import { type | -> token text should be blank const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken && importCompletionNode @@ -2736,14 +2760,34 @@ namespace ts.Completions { return; } - const defaultExportInfo = find(info, isImportableExportInfo); - if (!defaultExportInfo) { + // Do a relatively cheap check to bail early if all re-exports are non-importable + // due to file location or package.json dependency filtering. For non-node12+ + // module resolution modes, getting past this point guarantees that we'll be + // able to generate a suitable module specifier, so we can safely show a completion, + // even if we defer computing the module specifier. + const firstImportableExportInfo = find(info, isImportableExportInfo); + if (!firstImportableExportInfo) { return; } - // If we don't need to resolve module specifiers, we can use any re-export that is importable at all - // (We need to ensure that at least one is importable to show a completion.) - const { exportInfo = defaultExportInfo, moduleSpecifier } = context.tryResolve(info, symbolName, isFromAmbientModule) || {}; + // In node12+, module specifier resolution can fail due to modules being blocked + // by package.json `exports`. If that happens, don't show a completion item. + // N.B. in this resolution mode we always try to resolve module specifiers here, + // because we have to know now if it's going to fail so we can omit the completion + // from the list. + const result = context.tryResolve(info, symbolName, isFromAmbientModule) || {}; + if (result === "failed") return; + + // If we skipped resolving module specifiers, our selection of which ExportInfo + // to use here is arbitrary, since the info shown in the completion list derived from + // it should be identical regardless of which one is used. During the subsequent + // `CompletionEntryDetails` request, we'll get all the ExportInfos again and pick + // the best one based on the module specifier it produces. + let exportInfo = firstImportableExportInfo, moduleSpecifier; + if (result !== "skipped") { + ({ exportInfo = firstImportableExportInfo, moduleSpecifier } = result); + } + const isDefaultExport = exportInfo.exportKind === ExportKind.Default; const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol; @@ -2761,7 +2805,9 @@ namespace ts.Completions { } ); - hasUnresolvedAutoImports = context.resolutionLimitExceeded(); + hasUnresolvedAutoImports = context.skippedAny(); + flags |= context.resolvedAny() ? CompletionInfoFlags.ResolvedModuleSpecifiers : 0; + flags |= context.resolvedBeyondLimit() ? CompletionInfoFlags.ResolvedModuleSpecifiersBeyondLimit : 0; } ); @@ -2831,6 +2877,7 @@ namespace ts.Completions { return; } const origin: SymbolOriginInfoObjectLiteralMethod = { kind: SymbolOriginInfoKind.ObjectLiteralMethod, ...entryProps }; + flags |= CompletionInfoFlags.MayIncludeMethodSnippets; symbolToOriginInfoMap[symbols.length] = origin; symbols.push(member); }); diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts index 4004815a4a6de..875511fdefff2 100644 --- a/src/services/exportInfoMap.ts +++ b/src/services/exportInfoMap.ts @@ -291,8 +291,8 @@ namespace ts { ): boolean { if (from === to) return false; const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences, {}); - if (cachedResult?.isAutoImportable !== undefined) { - return cachedResult.isAutoImportable; + if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) { + return !cachedResult.isBlockedByPackageJsonDependencies; } const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); @@ -313,7 +313,7 @@ namespace ts { if (packageJsonFilter) { const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); - moduleSpecifierCache?.setIsAutoImportable(from.path, to.path, preferences, {}, isAutoImportable); + moduleSpecifierCache?.setBlockedByPackageJsonDependencies(from.path, to.path, preferences, {}, !isAutoImportable); return isAutoImportable; } diff --git a/src/services/types.ts b/src/services/types.ts index f84ee7d3ca16b..886546f5f068e 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1176,7 +1176,20 @@ namespace ts { argumentCount: number; } + // Do not change existing values, as they exist in telemetry. + export const enum CompletionInfoFlags { + None = 0, + MayIncludeAutoImports = 1 << 0, + IsImportStatementCompletion = 1 << 1, + IsContinuation = 1 << 2, + ResolvedModuleSpecifiers = 1 << 3, + ResolvedModuleSpecifiersBeyondLimit = 1 << 4, + MayIncludeMethodSnippets = 1 << 5, + } + export interface CompletionInfo { + /** For performance telemetry. */ + flags?: CompletionInfoFlags; /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 16d406f7d259d..a2f5846b82e41 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1928,6 +1928,14 @@ namespace ts { }; } + export function moduleResolutionRespectsExports(moduleResolution: ModuleResolutionKind): boolean { + return moduleResolution >= ModuleResolutionKind.Node12 && moduleResolution <= ModuleResolutionKind.NodeNext; + } + + export function moduleResolutionUsesNodeModules(moduleResolution: ModuleResolutionKind): boolean { + return moduleResolution === ModuleResolutionKind.NodeJs || moduleResolution >= ModuleResolutionKind.Node12 && moduleResolution <= ModuleResolutionKind.NodeNext; + } + export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: readonly ImportSpecifier[] | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; } diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index ab84366efc933..3da4c8fe8ca28 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -52,6 +52,7 @@ namespace ts.projectSystem { assert.isString(exportMapKey); delete (response?.entries[0].data as any).exportMapKey; assert.deepEqual(response, { + flags: CompletionInfoFlags.MayIncludeAutoImports, isGlobalCompletion: true, isIncomplete: undefined, isMemberCompletion: false, diff --git a/src/testRunner/unittests/tsserver/metadataInResponse.ts b/src/testRunner/unittests/tsserver/metadataInResponse.ts index 2ca20ff4046a9..8ea9d5ff7f742 100644 --- a/src/testRunner/unittests/tsserver/metadataInResponse.ts +++ b/src/testRunner/unittests/tsserver/metadataInResponse.ts @@ -77,6 +77,7 @@ namespace ts.projectSystem { command: protocol.CommandTypes.CompletionInfo, arguments: completionRequestArgs }, { + flags: 0, isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, diff --git a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts index 60e584882a0d2..e86cd6a694e1b 100644 --- a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts +++ b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts @@ -39,7 +39,7 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: moduleSpecifierCache", () => { it("caches importability within a file", () => { const { moduleSpecifierCache } = setup(); - assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isAutoImportable); + assert.isFalse(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isBlockedByPackageJsonDependencies); }); it("caches module specifiers within a file", () => { @@ -54,7 +54,7 @@ namespace ts.projectSystem { isRedirect: false }], moduleSpecifiers: ["mobx"], - isAutoImportable: true, + isBlockedByPackageJsonDependencies: false, }); }); @@ -72,7 +72,7 @@ namespace ts.projectSystem { const { host, moduleSpecifierCache } = setup(); host.writeFile("/src/a2.ts", aTs.content); host.runQueuedTimeoutCallbacks(); - assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isAutoImportable); + assert.isFalse(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {}, {})?.isBlockedByPackageJsonDependencies); }); it("invalidates the cache when symlinks are added or removed", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 0fd5bf9e0a933..1e389228866c0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6435,7 +6435,18 @@ declare namespace ts { argumentIndex: number; argumentCount: number; } + enum CompletionInfoFlags { + None = 0, + MayIncludeAutoImports = 1, + IsImportStatementCompletion = 2, + IsContinuation = 4, + ResolvedModuleSpecifiers = 8, + ResolvedModuleSpecifiersBeyondLimit = 16, + MayIncludeMethodSnippets = 32 + } interface CompletionInfo { + /** For performance telemetry. */ + flags?: CompletionInfoFlags; /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; @@ -8823,6 +8834,7 @@ declare namespace ts.server.protocol { body?: CompletionInfo; } interface CompletionInfo { + readonly flags?: number; readonly isGlobalCompletion: boolean; readonly isMemberCompletion: boolean; readonly isNewIdentifierLocation: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index beb29fd613a93..c1fc7000c4bef 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6435,7 +6435,18 @@ declare namespace ts { argumentIndex: number; argumentCount: number; } + enum CompletionInfoFlags { + None = 0, + MayIncludeAutoImports = 1, + IsImportStatementCompletion = 2, + IsContinuation = 4, + ResolvedModuleSpecifiers = 8, + ResolvedModuleSpecifiersBeyondLimit = 16, + MayIncludeMethodSnippets = 32 + } interface CompletionInfo { + /** For performance telemetry. */ + flags?: CompletionInfoFlags; /** Not true for all global completions. This will be true if the enclosing scope matches a few syntax kinds. See `isSnippetScope`. */ isGlobalCompletion: boolean; isMemberCompletion: boolean; diff --git a/tests/baselines/reference/completionEntryForUnionMethod.baseline b/tests/baselines/reference/completionEntryForUnionMethod.baseline index f03ee666dc0d8..b46b77f0f7641 100644 --- a/tests/baselines/reference/completionEntryForUnionMethod.baseline +++ b/tests/baselines/reference/completionEntryForUnionMethod.baseline @@ -6,6 +6,7 @@ "name": "" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionImportCallAssertion.baseline b/tests/baselines/reference/completionImportCallAssertion.baseline index 6009fb9e67f87..2e54c403997e0 100644 --- a/tests/baselines/reference/completionImportCallAssertion.baseline +++ b/tests/baselines/reference/completionImportCallAssertion.baseline @@ -6,6 +6,7 @@ "name": "0" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -73,6 +74,7 @@ "name": "1" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsCommentsClass.baseline b/tests/baselines/reference/completionsCommentsClass.baseline index 66766ee70bef1..f7144af7e1208 100644 --- a/tests/baselines/reference/completionsCommentsClass.baseline +++ b/tests/baselines/reference/completionsCommentsClass.baseline @@ -6,6 +6,7 @@ "name": "26" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsCommentsClassMembers.baseline b/tests/baselines/reference/completionsCommentsClassMembers.baseline index af89101b92aed..9a068cdbf2872 100644 --- a/tests/baselines/reference/completionsCommentsClassMembers.baseline +++ b/tests/baselines/reference/completionsCommentsClassMembers.baseline @@ -6,6 +6,7 @@ "name": "4" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -754,6 +755,7 @@ "name": "5" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -4818,6 +4820,7 @@ "name": "7" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -5566,6 +5569,7 @@ "name": "9" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -6314,6 +6318,7 @@ "name": "11" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -7062,6 +7067,7 @@ "name": "12" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -7810,6 +7816,7 @@ "name": "13" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -11874,6 +11881,7 @@ "name": "16" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -12622,6 +12630,7 @@ "name": "17" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -16686,6 +16695,7 @@ "name": "19" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -17434,6 +17444,7 @@ "name": "21" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -18182,6 +18193,7 @@ "name": "23" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -18930,6 +18942,7 @@ "name": "24" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -19678,6 +19691,7 @@ "name": "25" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -23742,6 +23756,7 @@ "name": "29" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -27806,6 +27821,7 @@ "name": "30" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -28961,6 +28977,7 @@ "name": "31" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -33025,6 +33042,7 @@ "name": "33" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -37043,6 +37061,7 @@ "name": "34" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -38198,6 +38217,7 @@ "name": "35" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -42216,6 +42236,7 @@ "name": "36" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -43371,6 +43392,7 @@ "name": "38" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -47435,6 +47457,7 @@ "name": "39" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -48590,6 +48613,7 @@ "name": "40" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -52654,6 +52678,7 @@ "name": "41" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -53809,6 +53834,7 @@ "name": "42" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -57873,6 +57899,7 @@ "name": "45" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -61932,6 +61959,7 @@ "name": "49" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -65991,6 +66019,7 @@ "name": "52" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -70050,6 +70079,7 @@ "name": "56" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -74109,6 +74139,7 @@ "name": "59" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -78168,6 +78199,7 @@ "name": "63" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -82227,6 +82259,7 @@ "name": "67" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -82610,6 +82643,7 @@ "name": "87" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -86782,6 +86816,7 @@ "name": "88" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -87937,6 +87972,7 @@ "name": "109" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -92138,6 +92174,7 @@ "name": "110" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -92362,6 +92399,7 @@ "name": "114" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, @@ -92461,6 +92499,7 @@ "name": "115" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, diff --git a/tests/baselines/reference/completionsCommentsCommentParsing.baseline b/tests/baselines/reference/completionsCommentsCommentParsing.baseline index 1ec7d5e207235..f03678262a706 100644 --- a/tests/baselines/reference/completionsCommentsCommentParsing.baseline +++ b/tests/baselines/reference/completionsCommentsCommentParsing.baseline @@ -6,6 +6,7 @@ "name": "18" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -5653,6 +5654,7 @@ "name": "15" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -12096,6 +12098,7 @@ "name": "24" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -17719,6 +17722,7 @@ "name": "27" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -23419,6 +23423,7 @@ "name": "39" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -29161,6 +29166,7 @@ "name": "44" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -35604,6 +35610,7 @@ "name": "" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline b/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline index ae41c9cc50c9e..68e1b3d6ce02e 100644 --- a/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline +++ b/tests/baselines/reference/completionsCommentsFunctionDeclaration.baseline @@ -6,6 +6,7 @@ "name": "3" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -4316,6 +4317,7 @@ "name": "7" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": true, @@ -7792,6 +7794,7 @@ "name": "9" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsCommentsFunctionExpression.baseline b/tests/baselines/reference/completionsCommentsFunctionExpression.baseline index 9c1bc3392836e..5be58374a3f48 100644 --- a/tests/baselines/reference/completionsCommentsFunctionExpression.baseline +++ b/tests/baselines/reference/completionsCommentsFunctionExpression.baseline @@ -6,6 +6,7 @@ "name": "2" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -3853,6 +3854,7 @@ "name": "4" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -8575,6 +8577,7 @@ "name": "15" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, @@ -12241,6 +12244,7 @@ "name": "17" }, "completionList": { + "flags": 0, "isGlobalCompletion": true, "isMemberCompletion": false, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsJSDocTags.baseline b/tests/baselines/reference/completionsJSDocTags.baseline index 4c876248179c9..945b84dd88ba7 100644 --- a/tests/baselines/reference/completionsJSDocTags.baseline +++ b/tests/baselines/reference/completionsJSDocTags.baseline @@ -6,6 +6,7 @@ "name": "14" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsSalsaMethodsOnAssignedFunctionExpressions.baseline b/tests/baselines/reference/completionsSalsaMethodsOnAssignedFunctionExpressions.baseline index 4c5bcf2c68d03..732ce4a31642a 100644 --- a/tests/baselines/reference/completionsSalsaMethodsOnAssignedFunctionExpressions.baseline +++ b/tests/baselines/reference/completionsSalsaMethodsOnAssignedFunctionExpressions.baseline @@ -6,6 +6,7 @@ "name": "2" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, diff --git a/tests/baselines/reference/completionsStringMethods.baseline b/tests/baselines/reference/completionsStringMethods.baseline index 327daf9f0d9a2..0d3fe39d755c9 100644 --- a/tests/baselines/reference/completionsStringMethods.baseline +++ b/tests/baselines/reference/completionsStringMethods.baseline @@ -6,6 +6,7 @@ "name": "1" }, "completionList": { + "flags": 0, "isGlobalCompletion": false, "isMemberCompletion": true, "isNewIdentifierLocation": false, diff --git a/tests/cases/fourslash/autoImportsNodeNext1.ts b/tests/cases/fourslash/autoImportsNodeNext1.ts new file mode 100644 index 0000000000000..32aeaaef28d01 --- /dev/null +++ b/tests/cases/fourslash/autoImportsNodeNext1.ts @@ -0,0 +1,35 @@ +/// + +// @module: nodenext + +// @Filename: /node_modules/pack/package.json +//// { +//// "name": "pack", +//// "version": "1.0.0", +//// "exports": { +//// ".": "./main.mjs" +//// } +//// } + +// @Filename: /node_modules/pack/main.d.mts +//// import {} from "./unreachable.mjs"; +//// export const fromMain = 0; + +// @Filename: /node_modules/pack/unreachable.d.mts +//// export const fromUnreachable = 0; + +// @Filename: /index.mts +//// import { fromMain } from "pack"; +//// fromUnreachable/**/ + +goTo.marker(""); +verify.importFixAtPosition([]); + +verify.completions({ + marker: "", + excludes: ["fromUnreachable"], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: false, + } +}); diff --git a/tests/cases/fourslash/server/autoImportProvider_exportMap3.ts b/tests/cases/fourslash/server/autoImportProvider_exportMap3.ts index e4e70c749fefa..b31bc52cb6982 100644 --- a/tests/cases/fourslash/server/autoImportProvider_exportMap3.ts +++ b/tests/cases/fourslash/server/autoImportProvider_exportMap3.ts @@ -39,16 +39,6 @@ goTo.marker(""); verify.completions({ marker: "", exact: completion.globalsPlus([{ - // TODO: We should filter this one out due to its bad module specifier, - // but we don't know it's going to be filtered out until we actually - // resolve the module specifier, which is a problem for completions - // that don't have their module specifiers eagerly resolved. - name: "fooFromIndex", - source: "../node_modules/dependency/lib/index.js", - sourceDisplay: "../node_modules/dependency/lib/index.js", - sortText: completion.SortText.AutoImportSuggestions, - hasAction: true, - }, { name: "fooFromLol", source: "dependency", sourceDisplay: "dependency", diff --git a/tests/cases/fourslash/server/autoImportProvider_exportMap8.ts b/tests/cases/fourslash/server/autoImportProvider_exportMap8.ts index b679c2184fd50..cdee1fe7cc8ad 100644 --- a/tests/cases/fourslash/server/autoImportProvider_exportMap8.ts +++ b/tests/cases/fourslash/server/autoImportProvider_exportMap8.ts @@ -49,13 +49,6 @@ goTo.marker("cts"); verify.completions({ marker: "cts", exact: completion.globalsPlus([{ - // TODO: this one will go away (see note in ./autoImportProvider_exportMap3.ts) - name: "fooFromIndex", - source: "../node_modules/dependency/lib/index", - sourceDisplay: "../node_modules/dependency/lib/index", - sortText: completion.SortText.AutoImportSuggestions, - hasAction: true, - }, { name: "fooFromLol", source: "dependency/lol", sourceDisplay: "dependency/lol", @@ -78,13 +71,6 @@ verify.completions({ sourceDisplay: "dependency/lol", sortText: completion.SortText.AutoImportSuggestions, hasAction: true, - }, { - // TODO: this one will go away (see note in ./autoImportProvider_exportMap3.ts) - name: "fooFromLol", - source: "../node_modules/dependency/lib/lol.js", - sourceDisplay: "../node_modules/dependency/lib/lol.js", - sortText: completion.SortText.AutoImportSuggestions, - hasAction: true, }]), preferences: { includeCompletionsForModuleExports: true,