diff --git a/src/services/completions.ts b/src/services/completions.ts
index c5ddaeca0bf..4a907ed2fe1 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -602,6 +602,7 @@ namespace ts.Completions {
propertyAccessToConvert: PropertyAccessExpression | undefined,
isJsxInitializer: IsJsxInitializer | undefined,
importCompletionNode: Node | undefined,
+ isTypeOnlyImport: boolean,
useSemicolons: boolean,
options: CompilerOptions,
preferences: UserPreferences,
@@ -661,7 +662,7 @@ namespace ts.Completions {
if (originIsResolvedExport(origin)) {
sourceDisplay = [textPart(origin.moduleSpecifier)];
if (importCompletionNode) {
- ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, origin, useSemicolons, options, preferences));
+ ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, isTypeOnlyImport, origin, useSemicolons, options, preferences));
isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined;
}
}
@@ -746,7 +747,7 @@ namespace ts.Completions {
};
}
- function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
+ function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, isTypeOnly: boolean | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
const sourceFile = importCompletionNode.getSourceFile();
const replacementSpan = createTextSpanFromNode(importCompletionNode, sourceFile);
const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier);
@@ -756,12 +757,13 @@ namespace ts.Completions {
ExportKind.Named;
const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : "";
const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true);
+ const typeOnlyPrefix = isTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " ";
const suffix = useSemicolons ? ";" : "";
switch (importKind) {
- case ImportKind.CommonJS: return { replacementSpan, insertText: `import ${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
- case ImportKind.Default: return { replacementSpan, insertText: `import ${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
- case ImportKind.Namespace: return { replacementSpan, insertText: `import * as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
- case ImportKind.Named: return { replacementSpan, insertText: `import { ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
+ case ImportKind.CommonJS: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
+ case ImportKind.Default: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
+ case ImportKind.Namespace: return { replacementSpan, insertText: `import${typeOnlyPrefix}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
+ case ImportKind.Named: return { replacementSpan, insertText: `import${typeOnlyPrefix}{ ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
}
}
@@ -814,6 +816,7 @@ namespace ts.Completions {
const start = timestamp();
const variableDeclaration = getVariableDeclaration(location);
const useSemicolons = probablyUsesSemicolons(sourceFile);
+ const isTypeOnlyImport = !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent);
// Tracks unique names.
// Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
// 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 {
propertyAccessToConvert,
isJsxInitializer,
importCompletionNode,
+ isTypeOnlyImport,
useSemicolons,
compilerOptions,
preferences
@@ -1916,6 +1920,7 @@ namespace ts.Completions {
function isTypeOnlyCompletion(): boolean {
return insideJsDocTagTypeExpression
+ || !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent)
|| !isContextTokenValueLocation(contextToken) &&
(isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker)
|| isPartOfTypeNode(location)
diff --git a/tests/cases/fourslash/importTypeCompletions1.ts b/tests/cases/fourslash/importTypeCompletions1.ts
new file mode 100644
index 00000000000..d4bf3df2809
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions1.ts
@@ -0,0 +1,26 @@
+///
+// @target: esnext
+
+// @filename: /foo.ts
+////export interface Foo {}
+
+// @filename: /bar.ts
+////[|import type F/**/|]
+
+goTo.file("/bar.ts")
+verify.completions({
+ marker: "",
+ exact: [{
+ name: "Foo",
+ sourceDisplay: "./foo",
+ source: "./foo",
+ insertText: "import type { Foo } from \"./foo\";",
+ replacementSpan: test.ranges()[0]
+ }],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});
diff --git a/tests/cases/fourslash/importTypeCompletions2.ts b/tests/cases/fourslash/importTypeCompletions2.ts
new file mode 100644
index 00000000000..db252b9f2e9
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions2.ts
@@ -0,0 +1,20 @@
+///
+// @target: esnext
+
+// @filename: /foo.ts
+////export const Foo = {};
+
+// @filename: /bar.ts
+////[|import type F/**/|]
+
+goTo.file("/bar.ts")
+verify.completions({
+ marker: "",
+ exact: [],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});
diff --git a/tests/cases/fourslash/importTypeCompletions3.ts b/tests/cases/fourslash/importTypeCompletions3.ts
new file mode 100644
index 00000000000..56a242c7196
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions3.ts
@@ -0,0 +1,26 @@
+///
+// @target: esnext
+
+// @filename: /foo.ts
+////export interface Foo {}
+
+// @filename: /bar.ts
+////[|import type { F/**/ }|]
+
+goTo.file("/bar.ts")
+verify.completions({
+ marker: "",
+ exact: [{
+ name: "Foo",
+ sourceDisplay: "./foo",
+ source: "./foo",
+ insertText: "import type { Foo } from \"./foo\";",
+ replacementSpan: test.ranges()[0]
+ }],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});
diff --git a/tests/cases/fourslash/importTypeCompletions4.ts b/tests/cases/fourslash/importTypeCompletions4.ts
new file mode 100644
index 00000000000..690107cd19c
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions4.ts
@@ -0,0 +1,27 @@
+///
+// @esModuleInterop: true
+
+// @Filename: /foo.ts
+////interface Foo { };
+////export = Foo;
+
+// @Filename: /bar.ts
+//// [|import type f/**/|]
+
+goTo.file("/bar.ts")
+verify.completions({
+ marker: "",
+ exact: [{
+ name: "Foo",
+ sourceDisplay: "./foo",
+ source: "./foo",
+ insertText: "import type Foo from \"./foo\";",
+ replacementSpan: test.ranges()[0]
+ }],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});
diff --git a/tests/cases/fourslash/importTypeCompletions5.ts b/tests/cases/fourslash/importTypeCompletions5.ts
new file mode 100644
index 00000000000..270a79adbe9
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions5.ts
@@ -0,0 +1,28 @@
+///
+
+// @esModuleInterop: false
+
+// @Filename: /foo.ts
+////interface Foo { };
+////export = Foo;
+
+// @Filename: /bar.ts
+//// [|import type f/**/|]
+
+goTo.file("/bar.ts")
+verify.completions({
+ marker: "",
+ exact: [{
+ name: "Foo",
+ sourceDisplay: "./foo",
+ source: "./foo",
+ insertText: "import type Foo = require(\"./foo\");",
+ replacementSpan: test.ranges()[0]
+ }],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});
diff --git a/tests/cases/fourslash/importTypeCompletions6.ts b/tests/cases/fourslash/importTypeCompletions6.ts
new file mode 100644
index 00000000000..f860ddfa665
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions6.ts
@@ -0,0 +1,28 @@
+///
+
+// @module: esnext
+
+// @Filename: /foo.ts
+////export const foo = { };
+////export interface Foo { };
+
+// @Filename: /bar.ts
+//// [|import type * as f/**/|]
+
+goTo.file("/bar.ts")
+verify.completions({
+ marker: "",
+ exact: [{
+ name: "Foo",
+ sourceDisplay: "./foo",
+ source: "./foo",
+ insertText: "import type { Foo } from \"./foo\";",
+ replacementSpan: test.ranges()[0]
+ }],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});
diff --git a/tests/cases/fourslash/importTypeCompletions7.ts b/tests/cases/fourslash/importTypeCompletions7.ts
new file mode 100644
index 00000000000..85825b4ef50
--- /dev/null
+++ b/tests/cases/fourslash/importTypeCompletions7.ts
@@ -0,0 +1,29 @@
+///
+
+// @target: es2020
+// @module: esnext
+
+// @Filename: /foo.d.ts
+//// declare namespace Foo {}
+//// export = Foo;
+
+// @Filename: /test.ts
+//// [|import F/**/|]
+
+goTo.file("/test.ts")
+verify.completions({
+ marker: "",
+ exact: [{
+ name: "Foo",
+ sourceDisplay: "./foo",
+ source: "./foo",
+ insertText: "import * as Foo from \"./foo\";",
+ replacementSpan: test.ranges()[0]
+ }],
+ isNewIdentifierLocation: true,
+ preferences: {
+ includeCompletionsForModuleExports: true,
+ includeCompletionsForImportStatements: true,
+ includeCompletionsWithInsertText: true
+ }
+});