diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index f505da17dd1a3..caaad5fb9ece0 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -8990,6 +8990,7 @@ namespace ts {
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
readonly allowRenameOfImportPath?: boolean;
+ readonly autoImportFileExcludePatterns?: string[];
}
/** Represents a bigint literal value without requiring bigint support */
diff --git a/src/server/protocol.ts b/src/server/protocol.ts
index 623699de435d6..e78d2262b5bca 100644
--- a/src/server/protocol.ts
+++ b/src/server/protocol.ts
@@ -3469,6 +3469,7 @@ namespace ts.server.protocol {
readonly includeInlayPropertyDeclarationTypeHints?: boolean;
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
+ readonly autoImportFileExcludePatterns?: string[];
}
export interface CompilerOptions {
diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts
index d1ea8927fc18a..1507689f6103c 100644
--- a/src/services/codefixes/importFixes.ts
+++ b/src/services/codefixes/importFixes.ts
@@ -413,7 +413,7 @@ namespace ts.codefix {
return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host);
});
- forEachExternalModuleToImportFrom(program, host, useAutoImportProvider, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
+ forEachExternalModuleToImportFrom(program, host, preferences, useAutoImportProvider, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
const checker = program.getTypeChecker();
// Don't import from a re-export when looking "up" like to `./index` or `../index`.
if (moduleFile && moduleSymbol !== exportingModuleSymbol && startsWith(importingFile.fileName, getDirectoryPath(moduleFile.fileName))) {
@@ -979,7 +979,7 @@ namespace ts.codefix {
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { symbol: exportedSymbol, moduleSymbol, moduleFileName: toFile?.fileName, exportKind, targetFlags: skipAlias(exportedSymbol, checker).flags, isFromPackageJson });
}
}
- forEachExternalModuleToImportFrom(program, host, useAutoImportProvider, (moduleSymbol, sourceFile, program, isFromPackageJson) => {
+ forEachExternalModuleToImportFrom(program, host, preferences, useAutoImportProvider, (moduleSymbol, sourceFile, program, isFromPackageJson) => {
const checker = program.getTypeChecker();
cancellationToken.throwIfCancellationRequested();
diff --git a/src/services/completions.ts b/src/services/completions.ts
index 9e5ae92726972..5d20343fff45e 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -366,7 +366,7 @@ namespace ts.Completions {
if (!previousResponse) return undefined;
const lowerCaseTokenText = location.text.toLowerCase();
- const exportMap = getExportInfoMap(file, host, program, cancellationToken);
+ const exportMap = getExportInfoMap(file, host, program, preferences, cancellationToken);
const newEntries = resolvingModuleSpecifiers(
"continuePreviousIncompleteResponse",
host,
@@ -2725,7 +2725,7 @@ namespace ts.Completions {
"";
const moduleSpecifierCache = host.getModuleSpecifierCache?.();
- const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken);
+ const exportInfo = getExportInfoMap(sourceFile, host, program, preferences, cancellationToken);
const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.();
const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host);
resolvingModuleSpecifiers(
diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts
index e8b52d48122ab..964079a8604fe 100644
--- a/src/services/exportInfoMap.ts
+++ b/src/services/exportInfoMap.ts
@@ -336,32 +336,42 @@ namespace ts {
export function forEachExternalModuleToImportFrom(
program: Program,
host: LanguageServiceHost,
+ preferences: UserPreferences,
useAutoImportProvider: boolean,
cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void,
) {
- forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), (module, file) => cb(module, file, program, /*isFromPackageJson*/ false));
+ const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
+ const excludePatterns = preferences.autoImportFileExcludePatterns && mapDefined(preferences.autoImportFileExcludePatterns, spec => {
+ // The client is expected to send rooted path specs since we don't know
+ // what directory a relative path is relative to.
+ const pattern = getPatternFromSpec(spec, "", "exclude");
+ return pattern ? getRegexFromPattern(pattern, useCaseSensitiveFileNames) : undefined;
+ });
+
+ forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false));
const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.();
if (autoImportProvider) {
const start = timestamp();
- forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true));
+ forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true));
host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`);
}
}
- function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
+ function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], excludePatterns: readonly RegExp[] | undefined, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
+ const isExcluded = (fileName: string) => excludePatterns?.some(p => p.test(fileName));
for (const ambient of checker.getAmbientModules()) {
- if (!stringContains(ambient.name, "*")) {
+ if (!stringContains(ambient.name, "*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded(d.getSourceFile().fileName)))) {
cb(ambient, /*sourceFile*/ undefined);
}
}
for (const sourceFile of allSourceFiles) {
- if (isExternalOrCommonJsModule(sourceFile)) {
+ if (isExternalOrCommonJsModule(sourceFile) && !isExcluded(sourceFile.fileName)) {
cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
}
}
}
- export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, cancellationToken: CancellationToken | undefined): ExportInfoMap {
+ export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): ExportInfoMap {
const start = timestamp();
// Pulling the AutoImportProvider project will trigger its updateGraph if pending,
// which will invalidate the export map cache if things change, so pull it before
@@ -382,7 +392,7 @@ namespace ts {
const compilerOptions = program.getCompilerOptions();
let moduleCount = 0;
try {
- forEachExternalModuleToImportFrom(program, host, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
+ forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested();
const seenExports = new Map<__String, true>();
const checker = program.getTypeChecker();
diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts
index 264708cf29fb9..fb15ec4f0113b 100644
--- a/tests/baselines/reference/api/tsserverlibrary.d.ts
+++ b/tests/baselines/reference/api/tsserverlibrary.d.ts
@@ -4138,6 +4138,7 @@ declare namespace ts {
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
readonly allowRenameOfImportPath?: boolean;
+ readonly autoImportFileExcludePatterns?: string[];
}
/** Represents a bigint literal value without requiring bigint support */
export interface PseudoBigInt {
@@ -9769,6 +9770,7 @@ declare namespace ts.server.protocol {
readonly includeInlayPropertyDeclarationTypeHints?: boolean;
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
+ readonly autoImportFileExcludePatterns?: string[];
}
interface CompilerOptions {
allowJs?: boolean;
diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts
index c8cf181dff4ac..ed87f7ef8913b 100644
--- a/tests/baselines/reference/api/typescript.d.ts
+++ b/tests/baselines/reference/api/typescript.d.ts
@@ -4138,6 +4138,7 @@ declare namespace ts {
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
readonly allowRenameOfImportPath?: boolean;
+ readonly autoImportFileExcludePatterns?: string[];
}
/** Represents a bigint literal value without requiring bigint support */
export interface PseudoBigInt {
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns1.ts b/tests/cases/fourslash/autoImportFileExcludePatterns1.ts
new file mode 100644
index 0000000000000..d4e7cb65cde88
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns1.ts
@@ -0,0 +1,22 @@
+///
+
+// @module: commonjs
+
+// @Filename: /project/node_modules/aws-sdk/clients/s3.d.ts
+//// export declare class S3 {}
+
+// @Filename: /project/index.ts
+//// S3/**/
+
+const autoImportFileExcludePatterns = ["/**/node_modules/aws-sdk"];
+
+verify.completions({
+ marker: "",
+ excludes: "S3",
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ autoImportFileExcludePatterns,
+ }
+});
+
+verify.importFixAtPosition([], /*errorCode*/ undefined, { autoImportFileExcludePatterns });
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns2.ts b/tests/cases/fourslash/autoImportFileExcludePatterns2.ts
new file mode 100644
index 0000000000000..8c5296189a2cd
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns2.ts
@@ -0,0 +1,39 @@
+///
+
+// @Filename: /lib/components/button/Button.ts
+//// export function Button() {}
+
+// @Filename: /lib/components/button/index.ts
+//// export * from "./Button";
+
+// @Filename: /lib/components/index.ts
+//// export * from "./button";
+
+// @Filename: /lib/main.ts
+//// export { Button } from "./components";
+
+// @Filename: /lib/index.ts
+//// export * from "./main";
+
+// @Filename: /i-hate-index-files.ts
+//// Button/**/
+
+verify.completions({
+ marker: "",
+ exact: completion.globalsPlus([{
+ name: "Button",
+ source: "./lib/main",
+ sourceDisplay: "./lib/main",
+ hasAction: true,
+ sortText: completion.SortText.AutoImportSuggestions,
+ }]),
+ preferences: {
+ allowIncompleteCompletions: true,
+ includeCompletionsForModuleExports: true,
+ autoImportFileExcludePatterns: ["/**/index.*"],
+ },
+});
+
+verify.importFixModuleSpecifiers("",
+ ["./lib/main", "./lib/components/button/Button"],
+ { autoImportFileExcludePatterns: ["/**/index.*"] });
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns3.ts b/tests/cases/fourslash/autoImportFileExcludePatterns3.ts
new file mode 100644
index 0000000000000..8f8ff5d4a1ed7
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns3.ts
@@ -0,0 +1,52 @@
+///
+
+// @module: commonjs
+
+// @Filename: /ambient1.d.ts
+//// declare module "foo" {
+//// export const x = 1;
+//// }
+
+// @Filename: /ambient2.d.ts
+//// declare module "foo" {
+//// export const y = 2;
+//// }
+
+// @Filename: /index.ts
+//// /**/
+
+verify.completions({
+ marker: "",
+ exact: completion.globalsPlus([{
+ // We don't look at what file each individual export came from; we
+ // only include or exclude modules wholesale, so excluding part of
+ // an ambient module or a module augmentation isn't supported.
+ name: "x",
+ source: "foo",
+ sourceDisplay: "foo",
+ hasAction: true,
+ sortText: completion.SortText.AutoImportSuggestions,
+ }, {
+ name: "y",
+ source: "foo",
+ sourceDisplay: "foo",
+ hasAction: true,
+ sortText: completion.SortText.AutoImportSuggestions,
+ }]),
+ preferences: {
+ allowIncompleteCompletions: true,
+ includeCompletionsForModuleExports: true,
+ autoImportFileExcludePatterns: ["/**/ambient1.d.ts"],
+ }
+});
+
+// Here, *every* file that declared "foo" is excluded.
+verify.completions({
+ marker: "",
+ exact: completion.globals,
+ preferences: {
+ allowIncompleteCompletions: true,
+ includeCompletionsForModuleExports: true,
+ autoImportFileExcludePatterns: ["/**/ambient*"],
+ }
+});
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index 7f66f77644cb9..a6c1804affb4a 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -662,6 +662,7 @@ declare namespace FourSlashInterface {
readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none";
readonly providePrefixAndSuffixTextForRename?: boolean;
readonly allowRenameOfImportPath?: boolean;
+ readonly autoImportFileExcludePatterns?: readonly string[];
}
interface InlayHintsOptions extends UserPreferences {
readonly includeInlayParameterNameHints?: "none" | "literals" | "all";
diff --git a/tests/cases/fourslash/server/autoImportFileExcludePatterns1.ts b/tests/cases/fourslash/server/autoImportFileExcludePatterns1.ts
new file mode 100644
index 0000000000000..2b2a7c9b9632f
--- /dev/null
+++ b/tests/cases/fourslash/server/autoImportFileExcludePatterns1.ts
@@ -0,0 +1,31 @@
+///
+
+// @module: commonjs
+
+// @Filename: /project/node_modules/aws-sdk/package.json
+//// { "name": "aws-sdk", "version": "2.0.0", "main": "index.js" }
+
+// @Filename: /project/node_modules/aws-sdk/index.d.ts
+//// export * from "./clients/s3";
+
+// @Filename: /project/node_modules/aws-sdk/clients/s3.d.ts
+//// export declare class S3 {}
+
+// @Filename: /project/package.json
+//// { "dependencies": "aws-sdk" }
+
+// @Filename: /project/index.ts
+//// S3/**/
+
+const autoImportFileExcludePatterns = ["/**/node_modules/aws-sdk"];
+
+verify.completions({
+ marker: "",
+ excludes: "S3",
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ autoImportFileExcludePatterns,
+ }
+});
+
+verify.importFixAtPosition([], /*errorCode*/ undefined, { autoImportFileExcludePatterns });