Skip to content

Commit 1b6c8fd

Browse files
authored
fix(45607): add snippet for type only import statements (#45873)
1 parent 61d2939 commit 1b6c8fd

8 files changed

+195
-6
lines changed

src/services/completions.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ namespace ts.Completions {
602602
propertyAccessToConvert: PropertyAccessExpression | undefined,
603603
isJsxInitializer: IsJsxInitializer | undefined,
604604
importCompletionNode: Node | undefined,
605+
isTypeOnlyImport: boolean,
605606
useSemicolons: boolean,
606607
options: CompilerOptions,
607608
preferences: UserPreferences,
@@ -661,7 +662,7 @@ namespace ts.Completions {
661662
if (originIsResolvedExport(origin)) {
662663
sourceDisplay = [textPart(origin.moduleSpecifier)];
663664
if (importCompletionNode) {
664-
({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, origin, useSemicolons, options, preferences));
665+
({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, isTypeOnlyImport, origin, useSemicolons, options, preferences));
665666
isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined;
666667
}
667668
}
@@ -746,7 +747,7 @@ namespace ts.Completions {
746747
};
747748
}
748749

749-
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
750+
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, isTypeOnly: boolean | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
750751
const sourceFile = importCompletionNode.getSourceFile();
751752
const replacementSpan = createTextSpanFromNode(importCompletionNode, sourceFile);
752753
const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier);
@@ -756,12 +757,13 @@ namespace ts.Completions {
756757
ExportKind.Named;
757758
const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : "";
758759
const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true);
760+
const typeOnlyPrefix = isTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " ";
759761
const suffix = useSemicolons ? ";" : "";
760762
switch (importKind) {
761-
case ImportKind.CommonJS: return { replacementSpan, insertText: `import ${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
762-
case ImportKind.Default: return { replacementSpan, insertText: `import ${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
763-
case ImportKind.Namespace: return { replacementSpan, insertText: `import * as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
764-
case ImportKind.Named: return { replacementSpan, insertText: `import { ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
763+
case ImportKind.CommonJS: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
764+
case ImportKind.Default: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
765+
case ImportKind.Namespace: return { replacementSpan, insertText: `import${typeOnlyPrefix}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
766+
case ImportKind.Named: return { replacementSpan, insertText: `import${typeOnlyPrefix}{ ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
765767
}
766768
}
767769

@@ -814,6 +816,7 @@ namespace ts.Completions {
814816
const start = timestamp();
815817
const variableDeclaration = getVariableDeclaration(location);
816818
const useSemicolons = probablyUsesSemicolons(sourceFile);
819+
const isTypeOnlyImport = !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent);
817820
// Tracks unique names.
818821
// Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
819822
// true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
@@ -844,6 +847,7 @@ namespace ts.Completions {
844847
propertyAccessToConvert,
845848
isJsxInitializer,
846849
importCompletionNode,
850+
isTypeOnlyImport,
847851
useSemicolons,
848852
compilerOptions,
849853
preferences
@@ -1916,6 +1920,7 @@ namespace ts.Completions {
19161920

19171921
function isTypeOnlyCompletion(): boolean {
19181922
return insideJsDocTagTypeExpression
1923+
|| !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent)
19191924
|| !isContextTokenValueLocation(contextToken) &&
19201925
(isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker)
19211926
|| isPartOfTypeNode(location)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference path="fourslash.ts" />
2+
// @target: esnext
3+
4+
// @filename: /foo.ts
5+
////export interface Foo {}
6+
7+
// @filename: /bar.ts
8+
////[|import type F/**/|]
9+
10+
goTo.file("/bar.ts")
11+
verify.completions({
12+
marker: "",
13+
exact: [{
14+
name: "Foo",
15+
sourceDisplay: "./foo",
16+
source: "./foo",
17+
insertText: "import type { Foo } from \"./foo\";",
18+
replacementSpan: test.ranges()[0]
19+
}],
20+
isNewIdentifierLocation: true,
21+
preferences: {
22+
includeCompletionsForModuleExports: true,
23+
includeCompletionsForImportStatements: true,
24+
includeCompletionsWithInsertText: true
25+
}
26+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference path="fourslash.ts" />
2+
// @target: esnext
3+
4+
// @filename: /foo.ts
5+
////export const Foo = {};
6+
7+
// @filename: /bar.ts
8+
////[|import type F/**/|]
9+
10+
goTo.file("/bar.ts")
11+
verify.completions({
12+
marker: "",
13+
exact: [],
14+
isNewIdentifierLocation: true,
15+
preferences: {
16+
includeCompletionsForModuleExports: true,
17+
includeCompletionsForImportStatements: true,
18+
includeCompletionsWithInsertText: true
19+
}
20+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// <reference path="fourslash.ts" />
2+
// @target: esnext
3+
4+
// @filename: /foo.ts
5+
////export interface Foo {}
6+
7+
// @filename: /bar.ts
8+
////[|import type { F/**/ }|]
9+
10+
goTo.file("/bar.ts")
11+
verify.completions({
12+
marker: "",
13+
exact: [{
14+
name: "Foo",
15+
sourceDisplay: "./foo",
16+
source: "./foo",
17+
insertText: "import type { Foo } from \"./foo\";",
18+
replacementSpan: test.ranges()[0]
19+
}],
20+
isNewIdentifierLocation: true,
21+
preferences: {
22+
includeCompletionsForModuleExports: true,
23+
includeCompletionsForImportStatements: true,
24+
includeCompletionsWithInsertText: true
25+
}
26+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path="fourslash.ts" />
2+
// @esModuleInterop: true
3+
4+
// @Filename: /foo.ts
5+
////interface Foo { };
6+
////export = Foo;
7+
8+
// @Filename: /bar.ts
9+
//// [|import type f/**/|]
10+
11+
goTo.file("/bar.ts")
12+
verify.completions({
13+
marker: "",
14+
exact: [{
15+
name: "Foo",
16+
sourceDisplay: "./foo",
17+
source: "./foo",
18+
insertText: "import type Foo from \"./foo\";",
19+
replacementSpan: test.ranges()[0]
20+
}],
21+
isNewIdentifierLocation: true,
22+
preferences: {
23+
includeCompletionsForModuleExports: true,
24+
includeCompletionsForImportStatements: true,
25+
includeCompletionsWithInsertText: true
26+
}
27+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @esModuleInterop: false
4+
5+
// @Filename: /foo.ts
6+
////interface Foo { };
7+
////export = Foo;
8+
9+
// @Filename: /bar.ts
10+
//// [|import type f/**/|]
11+
12+
goTo.file("/bar.ts")
13+
verify.completions({
14+
marker: "",
15+
exact: [{
16+
name: "Foo",
17+
sourceDisplay: "./foo",
18+
source: "./foo",
19+
insertText: "import type Foo = require(\"./foo\");",
20+
replacementSpan: test.ranges()[0]
21+
}],
22+
isNewIdentifierLocation: true,
23+
preferences: {
24+
includeCompletionsForModuleExports: true,
25+
includeCompletionsForImportStatements: true,
26+
includeCompletionsWithInsertText: true
27+
}
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @module: esnext
4+
5+
// @Filename: /foo.ts
6+
////export const foo = { };
7+
////export interface Foo { };
8+
9+
// @Filename: /bar.ts
10+
//// [|import type * as f/**/|]
11+
12+
goTo.file("/bar.ts")
13+
verify.completions({
14+
marker: "",
15+
exact: [{
16+
name: "Foo",
17+
sourceDisplay: "./foo",
18+
source: "./foo",
19+
insertText: "import type { Foo } from \"./foo\";",
20+
replacementSpan: test.ranges()[0]
21+
}],
22+
isNewIdentifierLocation: true,
23+
preferences: {
24+
includeCompletionsForModuleExports: true,
25+
includeCompletionsForImportStatements: true,
26+
includeCompletionsWithInsertText: true
27+
}
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @target: es2020
4+
// @module: esnext
5+
6+
// @Filename: /foo.d.ts
7+
//// declare namespace Foo {}
8+
//// export = Foo;
9+
10+
// @Filename: /test.ts
11+
//// [|import F/**/|]
12+
13+
goTo.file("/test.ts")
14+
verify.completions({
15+
marker: "",
16+
exact: [{
17+
name: "Foo",
18+
sourceDisplay: "./foo",
19+
source: "./foo",
20+
insertText: "import * as Foo from \"./foo\";",
21+
replacementSpan: test.ranges()[0]
22+
}],
23+
isNewIdentifierLocation: true,
24+
preferences: {
25+
includeCompletionsForModuleExports: true,
26+
includeCompletionsForImportStatements: true,
27+
includeCompletionsWithInsertText: true
28+
}
29+
});

0 commit comments

Comments
 (0)