fix55816: exclude files with re-exports if excluded by preferences.autoImportFileExcludePatterns (#58537)

Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
This commit is contained in:
Isabel Duan 2024-06-18 14:03:18 -07:00 committed by GitHub
parent fb88f027b0
commit f999951015
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 439 additions and 19 deletions

View File

@ -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;
}
});

View File

@ -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 */

View File

@ -0,0 +1,44 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,43 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,42 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,44 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,33 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,37 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,37 @@
/// <reference path="fourslash.ts" />
// @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"],
}
});

View File

@ -0,0 +1,37 @@
/// <reference path="fourslash.ts" />
// @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*"],
}
});

View File

@ -0,0 +1,37 @@
/// <reference path="fourslash.ts" />
// @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*"],
}
});

View File

@ -0,0 +1,37 @@
/// <reference path="fourslash.ts" />
// @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*"],
}
});