Skip to content

Commit 0b0029d

Browse files
committed
jsdoc import type completions
1 parent df23ce3 commit 0b0029d

7 files changed

+156
-16
lines changed

src/compiler/checker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -47625,6 +47625,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4762547625
if (
4762647626
(isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) ||
4762747627
((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent as ImportDeclaration).moduleSpecifier === node) ||
47628+
(isInJSFile(node) && isJSDocImportTypeTag(node.parent) && node.parent.moduleSpecifier === node) ||
4762847629
((isInJSFile(node) && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false)) || isImportCall(node.parent)) ||
4762947630
(isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent)
4763047631
) {

src/services/completions.ts

+23-16
Original file line numberDiff line numberDiff line change
@@ -3172,6 +3172,7 @@ function getCompletionData(
31723172
log("getCompletionData: Is inside comment: " + (timestamp() - start));
31733173

31743174
let insideJsDocTagTypeExpression = false;
3175+
let insideJsDocImportTypeTag = false;
31753176
let isInSnippetScope = false;
31763177
if (insideComment) {
31773178
if (hasDocComment(sourceFile, position)) {
@@ -3212,25 +3213,30 @@ function getCompletionData(
32123213
if (tag.tagName.pos <= position && position <= tag.tagName.end) {
32133214
return { kind: CompletionDataKind.JsDocTagName };
32143215
}
3215-
const typeExpression = tryGetTypeExpressionFromTag(tag);
3216-
if (typeExpression) {
3217-
currentToken = getTokenAtPosition(sourceFile, position);
3218-
if (
3219-
!currentToken ||
3220-
(!isDeclarationName(currentToken) &&
3221-
(currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag ||
3222-
(currentToken.parent as JSDocPropertyTag).name !== currentToken))
3223-
) {
3224-
// Use as type location if inside tag's type expression
3225-
insideJsDocTagTypeExpression = isCurrentlyEditingNode(typeExpression);
3226-
}
3216+
if (isJSDocImportTypeTag(tag)) {
3217+
insideJsDocImportTypeTag = true;
32273218
}
3228-
if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) {
3229-
return { kind: CompletionDataKind.JsDocParameterName, tag };
3219+
else {
3220+
const typeExpression = tryGetTypeExpressionFromTag(tag);
3221+
if (typeExpression) {
3222+
currentToken = getTokenAtPosition(sourceFile, position);
3223+
if (
3224+
!currentToken ||
3225+
(!isDeclarationName(currentToken) &&
3226+
(currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag ||
3227+
(currentToken.parent as JSDocPropertyTag).name !== currentToken))
3228+
) {
3229+
// Use as type location if inside tag's type expression
3230+
insideJsDocTagTypeExpression = isCurrentlyEditingNode(typeExpression);
3231+
}
3232+
}
3233+
if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) {
3234+
return { kind: CompletionDataKind.JsDocParameterName, tag };
3235+
}
32303236
}
32313237
}
32323238

3233-
if (!insideJsDocTagTypeExpression) {
3239+
if (!insideJsDocTagTypeExpression && !insideJsDocImportTypeTag) {
32343240
// Proceed if the current position is in jsDoc tag expression; otherwise it is a normal
32353241
// comment or the plain text part of a jsDoc comment, so no completion should be available
32363242
log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment.");
@@ -3241,7 +3247,7 @@ function getCompletionData(
32413247
start = timestamp();
32423248
// The decision to provide completion depends on the contextToken, which is determined through the previousToken.
32433249
// Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
3244-
const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile);
3250+
const isJsOnlyLocation = !insideJsDocTagTypeExpression && !insideJsDocImportTypeTag && isSourceFileJS(sourceFile);
32453251
const tokens = getRelevantTokens(position, sourceFile);
32463252
const previousToken = tokens.previousToken!;
32473253
let contextToken = tokens.contextToken!;
@@ -3922,6 +3928,7 @@ function getCompletionData(
39223928

39233929
function isTypeOnlyCompletion(): boolean {
39243930
return insideJsDocTagTypeExpression
3931+
|| insideJsDocImportTypeTag
39253932
|| !!importStatementCompletion && isTypeOnlyImportOrExportDeclaration(location.parent)
39263933
|| !isContextTokenValueLocation(contextToken) &&
39273934
(isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker)

src/services/stringCompletions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL
418418
case SyntaxKind.ImportDeclaration:
419419
case SyntaxKind.ExportDeclaration:
420420
case SyntaxKind.ExternalModuleReference:
421+
case SyntaxKind.JSDocImportTypeTag:
421422
// Get all known external module names or complete a path to a module
422423
// i.e. import * as ns from "/*completion position*/";
423424
// var y = import("/*completion position*/");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// === Completions ===
2+
=== /b.js ===
3+
// /**
4+
// * @importType { } from "./a"
5+
// ^
6+
// | ----------------------------------------------------------------------
7+
// | interface A
8+
// | ----------------------------------------------------------------------
9+
// */
10+
11+
[
12+
{
13+
"marker": {
14+
"fileName": "/b.js",
15+
"position": 21,
16+
"name": ""
17+
},
18+
"item": {
19+
"flags": 0,
20+
"isGlobalCompletion": false,
21+
"isMemberCompletion": true,
22+
"isNewIdentifierLocation": false,
23+
"entries": [
24+
{
25+
"name": "A",
26+
"kind": "interface",
27+
"kindModifiers": "export",
28+
"sortText": "11",
29+
"displayParts": [
30+
{
31+
"text": "interface",
32+
"kind": "keyword"
33+
},
34+
{
35+
"text": " ",
36+
"kind": "space"
37+
},
38+
{
39+
"text": "A",
40+
"kind": "interfaceName"
41+
}
42+
],
43+
"documentation": []
44+
}
45+
]
46+
}
47+
}
48+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// === Completions ===
2+
=== /tests/cases/fourslash/./c.js ===
3+
// /**
4+
// * @importType * as types from "./"
5+
// ^
6+
// | ----------------------------------------------------------------------
7+
// | a
8+
// | b
9+
// | ----------------------------------------------------------------------
10+
// */
11+
12+
[
13+
{
14+
"marker": {
15+
"fileName": "/tests/cases/fourslash/./c.js",
16+
"position": 38,
17+
"name": ""
18+
},
19+
"item": {
20+
"isGlobalCompletion": false,
21+
"isMemberCompletion": false,
22+
"isNewIdentifierLocation": true,
23+
"entries": [
24+
{
25+
"name": "a",
26+
"kind": "script",
27+
"kindModifiers": ".ts",
28+
"sortText": "11",
29+
"displayParts": [
30+
{
31+
"text": "a",
32+
"kind": "text"
33+
}
34+
]
35+
},
36+
{
37+
"name": "b",
38+
"kind": "script",
39+
"kindModifiers": ".ts",
40+
"sortText": "11",
41+
"displayParts": [
42+
{
43+
"text": "b",
44+
"kind": "text"
45+
}
46+
]
47+
}
48+
]
49+
}
50+
}
51+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
///<reference path="fourslash.ts" />
2+
3+
// @allowJS: true
4+
// @checkJs: true
5+
6+
// @filename: /a.ts
7+
////export interface A {}
8+
9+
// @filename: /b.js
10+
/////**
11+
//// * @importType { /**/ } from "./a"
12+
//// */
13+
14+
verify.baselineCompletions();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
///<reference path="fourslash.ts" />
2+
3+
// @allowJS: true
4+
// @checkJs: true
5+
// @module: esnext
6+
7+
// @filename: ./a.ts
8+
////export interface A {}
9+
10+
// @filename: ./b.ts
11+
////export interface B {}
12+
13+
// @filename: ./c.js
14+
/////**
15+
//// * @importType * as types from ".//**/"
16+
//// */
17+
18+
verify.baselineCompletions();

0 commit comments

Comments
 (0)