Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TS Server option to exclude files from auto-imports #49578

Merged
merged 5 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3469,6 +3469,7 @@ namespace ts.server.protocol {
readonly includeInlayPropertyDeclarationTypeHints?: boolean;
readonly includeInlayFunctionLikeReturnTypeHints?: boolean;
readonly includeInlayEnumMemberValueHints?: boolean;
readonly autoImportFileExcludePatterns?: string[];
}

export interface CompilerOptions {
Expand Down
4 changes: 2 additions & 2 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))) {
Expand Down Expand Up @@ -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();

Expand Down
4 changes: 2 additions & 2 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
24 changes: 17 additions & 7 deletions src/services/exportInfoMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 22 additions & 0 deletions tests/cases/fourslash/autoImportFileExcludePatterns1.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions tests/cases/fourslash/autoImportFileExcludePatterns2.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 52 additions & 0 deletions tests/cases/fourslash/autoImportFileExcludePatterns3.ts
Original file line number Diff line number Diff line change
@@ -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*"],
}
});
1 change: 1 addition & 0 deletions tests/cases/fourslash/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
31 changes: 31 additions & 0 deletions tests/cases/fourslash/server/autoImportFileExcludePatterns1.ts
Original file line number Diff line number Diff line change
@@ -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 });