fix(45607): add snippet for type only import statements (#45873)

This commit is contained in:
Oleksandr T
2021-09-24 02:13:17 +03:00
committed by GitHub
parent 61d2939359
commit 1b6c8fd072
8 changed files with 195 additions and 6 deletions

View File

@@ -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)

View File

@@ -0,0 +1,26 @@
/// <reference path="fourslash.ts" />
// @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
}
});

View File

@@ -0,0 +1,20 @@
/// <reference path="fourslash.ts" />
// @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
}
});

View File

@@ -0,0 +1,26 @@
/// <reference path="fourslash.ts" />
// @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
}
});

View File

@@ -0,0 +1,27 @@
/// <reference path="fourslash.ts" />
// @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
}
});

View File

@@ -0,0 +1,28 @@
/// <reference path="fourslash.ts" />
// @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
}
});

View File

@@ -0,0 +1,28 @@
/// <reference path="fourslash.ts" />
// @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
}
});

View File

@@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
// @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
}
});