mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 02:33:53 -06:00
Add TS Server option to exclude files from auto-imports (#49578)
* Basic functionality * Add tests * Add test for ambient modules * Add to protocol
This commit is contained in:
parent
1213c35d57
commit
7e7c53961a
@ -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 */
|
||||
|
||||
@ -3469,6 +3469,7 @@ namespace ts.server.protocol {
|
||||
readonly includeInlayPropertyDeclarationTypeHints?: boolean;
|
||||
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
|
||||
readonly includeInlayEnumMemberValueHints?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
}
|
||||
|
||||
export interface CompilerOptions {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
22
tests/cases/fourslash/autoImportFileExcludePatterns1.ts
Normal file
22
tests/cases/fourslash/autoImportFileExcludePatterns1.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/// <reference path="fourslash.ts"/>
|
||||
|
||||
// @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 });
|
||||
39
tests/cases/fourslash/autoImportFileExcludePatterns2.ts
Normal file
39
tests/cases/fourslash/autoImportFileExcludePatterns2.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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.*"] });
|
||||
52
tests/cases/fourslash/autoImportFileExcludePatterns3.ts
Normal file
52
tests/cases/fourslash/autoImportFileExcludePatterns3.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @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*"],
|
||||
}
|
||||
});
|
||||
@ -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";
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
/// <reference path="../fourslash.ts"/>
|
||||
|
||||
// @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 });
|
||||
Loading…
x
Reference in New Issue
Block a user