diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts
index b397f29d74e..6eedc5b8e38 100644
--- a/src/services/codefixes/importFixes.ts
+++ b/src/services/codefixes/importFixes.ts
@@ -47,12 +47,14 @@ import {
FutureSymbolExportInfo,
getAllowSyntheticDefaultImports,
getBaseFileName,
+ getDeclarationOfKind,
getDefaultLikeExportInfo,
getDirectoryPath,
getEmitModuleKind,
getEmitModuleResolutionKind,
getEmitScriptTarget,
getExportInfoMap,
+ getIsFileExcluded,
getMeaningFromLocation,
getNameForExportedSymbol,
getOutputExtension,
@@ -277,8 +279,14 @@ function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, prog
const checker = program.getTypeChecker();
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
const exportInfo = getAllExportInfoForSymbol(sourceFile, symbol, symbolName, moduleSymbol, /*preferCapitalized*/ false, program, host, preferences, cancellationToken);
+ if (!exportInfo) {
+ // If no exportInfo is found, this means export could not be resolved when we have filtered for autoImportFileExcludePatterns,
+ // so we should not generate an import.
+ Debug.assert(preferences.autoImportFileExcludePatterns?.length);
+ return;
+ }
const useRequire = shouldUseRequire(sourceFile, program);
- let fix = getImportFixForSymbol(sourceFile, Debug.checkDefined(exportInfo), program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
+ let fix = getImportFixForSymbol(sourceFile, exportInfo, program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
if (fix) {
const localName = tryCast(referenceImport?.name, isIdentifier)?.text ?? symbolName;
if (
@@ -859,9 +867,16 @@ function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAc
function getAllExportInfoForSymbol(importingFile: SourceFile | FutureSourceFile, symbol: Symbol, symbolName: string, moduleSymbol: Symbol, preferCapitalized: boolean, program: Program, host: LanguageServiceHost, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): readonly SymbolExportInfo[] | undefined {
const getChecker = createGetChecker(program, host);
+ const isFileExcluded = preferences.autoImportFileExcludePatterns && getIsFileExcluded(host, preferences);
+ const mergedModuleSymbol = program.getTypeChecker().getMergedSymbol(moduleSymbol);
+ const moduleSourceFile = isFileExcluded && mergedModuleSymbol.declarations && getDeclarationOfKind(mergedModuleSymbol, SyntaxKind.SourceFile);
+ const moduleSymbolExcluded = moduleSourceFile && isFileExcluded(moduleSourceFile as SourceFile);
return getExportInfoMap(importingFile, host, program, preferences, cancellationToken)
.search(importingFile.path, preferCapitalized, name => name === symbolName, info => {
- if (skipAlias(info[0].symbol, getChecker(info[0].isFromPackageJson)) === symbol && info.some(i => i.moduleSymbol === moduleSymbol || i.symbol.parent === moduleSymbol)) {
+ if (
+ getChecker(info[0].isFromPackageJson).getMergedSymbol(skipAlias(info[0].symbol, getChecker(info[0].isFromPackageJson))) === symbol
+ && (moduleSymbolExcluded || info.some(i => i.moduleSymbol === moduleSymbol || i.symbol.parent === moduleSymbol))
+ ) {
return info;
}
});
diff --git a/src/services/exportInfoMap.ts b/src/services/exportInfoMap.ts
index 24d0c7b9b80..6bc8d84c4fe 100644
--- a/src/services/exportInfoMap.ts
+++ b/src/services/exportInfoMap.ts
@@ -426,12 +426,7 @@ export function forEachExternalModuleToImportFrom(
cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void,
) {
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 = getSubPatternFromSpec(spec, "", "exclude");
- return pattern ? getRegexFromPattern(pattern, useCaseSensitiveFileNames) : undefined;
- });
+ const excludePatterns = preferences.autoImportFileExcludePatterns && getIsExcludedPatterns(preferences, useCaseSensitiveFileNames);
forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), excludePatterns, host, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false));
const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.();
@@ -451,9 +446,33 @@ export function forEachExternalModuleToImportFrom(
}
}
+function getIsExcludedPatterns(preferences: UserPreferences, useCaseSensitiveFileNames: boolean) {
+ return 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 = getSubPatternFromSpec(spec, "", "exclude");
+ return pattern ? getRegexFromPattern(pattern, useCaseSensitiveFileNames) : undefined;
+ });
+}
+
function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], excludePatterns: readonly RegExp[] | undefined, host: LanguageServiceHost, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
+ const isExcluded = excludePatterns && getIsExcluded(excludePatterns, host);
+
+ for (const ambient of checker.getAmbientModules()) {
+ if (!ambient.name.includes("*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded!(d.getSourceFile())))) {
+ cb(ambient, /*sourceFile*/ undefined);
+ }
+ }
+ for (const sourceFile of allSourceFiles) {
+ if (isExternalOrCommonJsModule(sourceFile) && !isExcluded?.(sourceFile)) {
+ cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
+ }
+ }
+}
+
+function getIsExcluded(excludePatterns: readonly RegExp[], host: LanguageServiceHost) {
const realpathsWithSymlinks = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath();
- const isExcluded = excludePatterns && (({ fileName, path }: SourceFile) => {
+ return (({ fileName, path }: SourceFile) => {
if (excludePatterns.some(p => p.test(fileName))) return true;
if (realpathsWithSymlinks?.size && pathContainsNodeModules(fileName)) {
let dir = getDirectoryPath(fileName);
@@ -467,17 +486,12 @@ function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly So
}
return false;
});
+}
- for (const ambient of checker.getAmbientModules()) {
- if (!ambient.name.includes("*") && !(excludePatterns && ambient.declarations?.every(d => isExcluded!(d.getSourceFile())))) {
- cb(ambient, /*sourceFile*/ undefined);
- }
- }
- for (const sourceFile of allSourceFiles) {
- if (isExternalOrCommonJsModule(sourceFile) && !isExcluded?.(sourceFile)) {
- cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
- }
- }
+/** @internal */
+export function getIsFileExcluded(host: LanguageServiceHost, preferences: UserPreferences) {
+ if (!preferences.autoImportFileExcludePatterns) return () => false;
+ return getIsExcluded(getIsExcludedPatterns(preferences, hostUsesCaseSensitiveFileNames(host)), host);
}
/** @internal */
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns10.ts b/tests/cases/fourslash/autoImportFileExcludePatterns10.ts
new file mode 100644
index 00000000000..a592f134f05
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns10.ts
@@ -0,0 +1,44 @@
+///
+
+// @Filename: /src/vs/test.ts
+//// import { Parts } from './parts';
+//// export class /**/Extended implements Parts {
+//// }
+
+// @Filename: /src/vs/parts.ts
+//// import { Event } from '../event/event';
+////
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/thing.ts
+//// import { Event } from './event/event';
+//// export { Event };
+
+// @Filename: /src/a.ts
+//// import './thing'
+//// declare module './thing' {
+//// interface Event {
+//// c: string;
+//// }
+//// }
+
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../event/event';
+import { Parts } from './parts';
+export class Extended implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/thing.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns11.ts b/tests/cases/fourslash/autoImportFileExcludePatterns11.ts
new file mode 100644
index 00000000000..44e214d78c3
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns11.ts
@@ -0,0 +1,43 @@
+///
+
+// @Filename: /src/vs/test.ts
+//// import { Parts } from './parts';
+//// export class /**/Extended implements Parts {
+//// }
+
+// @Filename: /src/vs/parts.ts
+//// import { Event } from '../thing';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/thing.ts
+//// import { Event } from './event/event';
+//// export { Event };
+
+// @Filename: /src/a.ts
+//// import './thing'
+//// declare module './thing' {
+//// interface Event {
+//// c: string;
+//// }
+//// }
+
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../event/event';
+import { Parts } from './parts';
+export class Extended implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/thing.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns12.ts b/tests/cases/fourslash/autoImportFileExcludePatterns12.ts
new file mode 100644
index 00000000000..0c259e805ab
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns12.ts
@@ -0,0 +1,42 @@
+///
+
+// @Filename: /src/vs/test.ts
+//// import { Parts } from './parts';
+//// export class /**/Extended implements Parts {
+//// }
+
+// @Filename: /src/vs/parts.ts
+//// import { Event } from '../thing';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/thing.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+// @Filename: /src/a.ts
+//// import './thing'
+//// declare module './thing' {
+//// interface Event {
+//// c: string;
+//// }
+//// }
+
+// In this test, `Event` is incorrectly imported in `thing.ts`
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Parts } from './parts';
+export class Extended implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/thing.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns13.ts b/tests/cases/fourslash/autoImportFileExcludePatterns13.ts
new file mode 100644
index 00000000000..544262eb62a
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns13.ts
@@ -0,0 +1,44 @@
+///
+
+// @Filename: /src/vs/test.ts
+//// import { Parts } from './parts';
+//// export class /**/Extended implements Parts {
+//// }
+
+// @Filename: /src/vs/parts.ts
+//// import { Event } from '../event/event';
+////
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/thing.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+// @Filename: /src/a.ts
+//// import './thing'
+//// declare module './thing' {
+//// interface Event {
+//// c: string;
+//// }
+//// }
+
+// In this test, `Event` is incorrectly imported in `thing.ts`
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../event/event';
+import { Parts } from './parts';
+export class Extended implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/thing.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns4.ts b/tests/cases/fourslash/autoImportFileExcludePatterns4.ts
new file mode 100644
index 00000000000..c01c65932d9
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns4.ts
@@ -0,0 +1,33 @@
+///
+
+// @Filename: /src/vs/workbench/test.ts
+//// import { Parts } from './parts';
+//// export class /**/EditorParts implements Parts { }
+
+// @Filename: /src/vs/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/vs/workbench/parts.ts
+//// import { Event } from '../event/event';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/vs/workbench/workbench.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../event/event';
+import { Parts } from './parts';
+export class EditorParts implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/vs/workbench/workbench.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns5.ts b/tests/cases/fourslash/autoImportFileExcludePatterns5.ts
new file mode 100644
index 00000000000..54e1b7a9dcf
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns5.ts
@@ -0,0 +1,37 @@
+///
+
+// @Filename: /src/vs/workbench/test.ts
+//// import { Parts } from './parts';
+//// export class /**/EditorParts implements Parts { }
+
+// @Filename: /src/vs/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/vs/workbench/parts.ts
+//// import { Event } from '../event/event';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/vs/workbench/workbench.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+// @Filename: /src/vs/workbench/canImport.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from './canImport';
+import { Parts } from './parts';
+export class EditorParts implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/vs/workbench/workbench.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns6.ts b/tests/cases/fourslash/autoImportFileExcludePatterns6.ts
new file mode 100644
index 00000000000..0dd2ba7ccdf
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns6.ts
@@ -0,0 +1,37 @@
+///
+
+// @Filename: /src/vs/workbench/test.ts
+//// import { Parts } from './parts';
+//// export class /**/EditorParts implements Parts { }
+
+// @Filename: /src/vs/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/vs/workbench/parts.ts
+//// import { Event } from '../event/event';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/vs/workbench/workbench.ts
+//// import { Event } from './canImport';
+//// export { Event };
+
+// @Filename: /src/vs/workbench/canImport.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from './canImport';
+import { Parts } from './parts';
+export class EditorParts implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/vs/workbench/workbench.ts"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns7.ts b/tests/cases/fourslash/autoImportFileExcludePatterns7.ts
new file mode 100644
index 00000000000..1782dc9721d
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns7.ts
@@ -0,0 +1,37 @@
+///
+
+// @Filename: /src/vs/workbench/test.ts
+//// import { Parts } from './parts';
+//// export class /**/EditorParts implements Parts { }
+
+// @Filename: /src/vs/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/vs/workbench/parts.ts
+//// import { Event } from '../event/event';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/vs/workbench/workbench.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+// @Filename: /src/vs/workbench/workbench2.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../event/event';
+import { Parts } from './parts';
+export class EditorParts implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/vs/workbench/workbench*"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns8.ts b/tests/cases/fourslash/autoImportFileExcludePatterns8.ts
new file mode 100644
index 00000000000..81a93ebde1b
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns8.ts
@@ -0,0 +1,37 @@
+///
+
+// @Filename: /src/vs/workbench/test.ts
+//// import { Parts } from './parts';
+//// export class /**/EditorParts implements Parts { }
+
+// @Filename: /src/vs/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/vs/workbench/parts.ts
+//// import { Event } from '../event/event';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/vs/workbench/workbench.ts
+//// import { Event } from './workbench2';
+//// export { Event };
+
+// @Filename: /src/vs/workbench/workbench2.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../event/event';
+import { Parts } from './parts';
+export class EditorParts implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/vs/workbench/workbench*"],
+ }
+});
diff --git a/tests/cases/fourslash/autoImportFileExcludePatterns9.ts b/tests/cases/fourslash/autoImportFileExcludePatterns9.ts
new file mode 100644
index 00000000000..bb0def4d5ad
--- /dev/null
+++ b/tests/cases/fourslash/autoImportFileExcludePatterns9.ts
@@ -0,0 +1,37 @@
+///
+
+// @Filename: /src/vs/workbench/test.ts
+//// import { Parts } from './parts';
+//// export class /**/EditorParts implements Parts { }
+
+// @Filename: /src/vs/event/event.ts
+//// export interface Event {
+//// (): string;
+//// }
+
+// @Filename: /src/vs/workbench/parts.ts
+//// import { Event } from '../event/event';
+//// export interface Parts {
+//// readonly options: Event;
+//// }
+
+// @Filename: /src/vs/workbench/workbench.ts
+//// import { Event } from '../event/event';
+//// export { Event };
+
+// @Filename: /src/vs/test.ts
+//// import { Event } from './event/event';
+//// export { Event };
+
+verify.codeFix({
+ description: "Implement interface 'Parts'",
+ newFileContent:
+`import { Event } from '../test';
+import { Parts } from './parts';
+export class EditorParts implements Parts {
+ options: Event;
+}`,
+ preferences: {
+ autoImportFileExcludePatterns: ["src/vs/workbench/workbench*"],
+ }
+});