From b6607f125f8e0098975b57a473df0f8aee7f6c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 21 Oct 2025 23:37:11 +0200 Subject: [PATCH] Fix completions at type-only `export` specifiers --- src/compiler/utilitiesPublic.ts | 2 +- src/services/completions.ts | 6 +++-- .../completionListInExportClause04.ts | 26 +++++++++++++++++++ .../completionListInImportClause07.ts | 26 +++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/completionListInExportClause04.ts create mode 100644 tests/cases/fourslash/completionListInImportClause07.ts diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 36eba18db07ba..ad57485502eea 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1550,7 +1550,7 @@ export function isTypeOnlyExportDeclaration(node: Node): node is TypeOnlyExportD case SyntaxKind.ExportSpecifier: return (node as ExportSpecifier).isTypeOnly || (node as ExportSpecifier).parent.parent.isTypeOnly; case SyntaxKind.ExportDeclaration: - return (node as ExportDeclaration).isTypeOnly && !!(node as ExportDeclaration).moduleSpecifier && !(node as ExportDeclaration).exportClause; + return (node as ExportDeclaration).isTypeOnly; case SyntaxKind.NamespaceExport: return (node as NamespaceExport).parent.isTypeOnly; } diff --git a/src/services/completions.ts b/src/services/completions.ts index 96f01f824bd57..3b603ba962a52 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -179,6 +179,7 @@ import { isImportDeclaration, isImportEqualsDeclaration, isImportKeyword, + isImportOrExportSpecifier, isImportSpecifier, isInComment, isIndexSignatureDeclaration, @@ -4651,7 +4652,7 @@ function getCompletionData( if (!namedImportsOrExports) return GlobalsSearch.Continue; // We can at least offer `type` at `import { |` - if (!isTypeKeywordTokenOrIdentifier(contextToken)) { + if (!isTypeKeywordTokenOrIdentifier(contextToken) && !isTypeOnlyImportOrExportDeclaration(contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? contextToken.parent.parent : contextToken.parent)) { keywordFilters = KeywordCompletionFilters.TypeKeyword; } @@ -5007,7 +5008,8 @@ function getCompletionData( case SyntaxKind.TypeKeyword: // import { type foo| } - return containingNodeKind !== SyntaxKind.ImportSpecifier; + // export { type foo| } + return !isImportOrExportSpecifier(parent); case SyntaxKind.AsteriskToken: return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); diff --git a/tests/cases/fourslash/completionListInExportClause04.ts b/tests/cases/fourslash/completionListInExportClause04.ts new file mode 100644 index 0000000000000..9d229bdc944be --- /dev/null +++ b/tests/cases/fourslash/completionListInExportClause04.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: m1.ts +//// export var foo: number = 1; +//// export var jkl: number = 2; +//// export type MyType = string; + +// @Filename: m2.ts +//// export { /*1*/ } from "./m1" +//// export type { /*2*/ } from "./m1" +//// export { type /*3*/ } from "./m1" +//// export { foo as foo1, /*4*/ } from "./m1" +//// export type { foo as foo2, /*5*/ } from "./m1" +//// +//// export { M/*6*/ } from "./m1" +//// export type { M/*7*/ } from "./m1" +//// export { type M/*8*/ } from "./m1" + +const type = { name: "type", sortText: completion.SortText.GlobalsOrKeywords }; + +verify.completions( + { marker: ["1", "6"], exact: ["foo", "jkl", "MyType", type] }, + { marker: ["2", "3", "7", "8"], exact: ["foo", "jkl", "MyType"] }, + { marker: "4", exact: ["jkl", "MyType", type] }, + { marker: "5", exact: ["jkl", "MyType"] }, +); diff --git a/tests/cases/fourslash/completionListInImportClause07.ts b/tests/cases/fourslash/completionListInImportClause07.ts new file mode 100644 index 0000000000000..0dfc3a10f174f --- /dev/null +++ b/tests/cases/fourslash/completionListInImportClause07.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: m1.ts +//// export var foo: number = 1; +//// export var jkl: number = 2; +//// export type MyType = string; + +// @Filename: m2.ts +//// import { /*1*/ } from "./m1" +//// import type { /*2*/ } from "./m1" +//// import { type /*3*/ } from "./m1" +//// import { foo as foo1, /*4*/ } from "./m1" +//// import type { foo as foo2, /*5*/ } from "./m1" +//// +//// import { M/*6*/ } from "./m1" +//// import type { M/*7*/ } from "./m1" +//// import { type M/*8*/ } from "./m1" + +const type = { name: "type", sortText: completion.SortText.GlobalsOrKeywords }; + +verify.completions( + { marker: ["1", "6"], exact: ["foo", "jkl", "MyType", type] }, + { marker: ["2", "3", "7", "8"], exact: ["foo", "jkl", "MyType"] }, + { marker: "4", exact: ["jkl", "MyType", type] }, + { marker: "5", exact: ["jkl", "MyType"] }, +);