mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Support completions for qualified names in JSDoc (#16380)
* Support completions for qualified names in JSDoc * Fix typo
This commit is contained in:
@@ -596,7 +596,7 @@ namespace ts {
|
||||
currentIdentifiers = node.identifiers;
|
||||
isCurrentFileExternalModule = isExternalModule(node);
|
||||
enclosingDeclaration = node;
|
||||
emitDetachedComments(currentText, currentLineMap, writer, writeCommentRange, node, newLine, /*removeComents*/ true);
|
||||
emitDetachedComments(currentText, currentLineMap, writer, writeCommentRange, node, newLine, /*removeComments*/ true);
|
||||
emitLines(node.statements);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace ts {
|
||||
else if (kind === SyntaxKind.Identifier) {
|
||||
return new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, pos, end);
|
||||
}
|
||||
else if (kind < SyntaxKind.FirstNode) {
|
||||
else if (!isNodeKind(kind)) {
|
||||
return new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, pos, end);
|
||||
}
|
||||
else {
|
||||
@@ -1103,7 +1103,7 @@ namespace ts {
|
||||
pos = scanner.getStartPos();
|
||||
}
|
||||
|
||||
return kind >= SyntaxKind.FirstNode ? new NodeConstructor(kind, pos, pos) :
|
||||
return isNodeKind(kind) ? new NodeConstructor(kind, pos, pos) :
|
||||
kind === SyntaxKind.Identifier ? new IdentifierConstructor(kind, pos, pos) :
|
||||
new TokenConstructor(kind, pos, pos);
|
||||
}
|
||||
|
||||
@@ -4670,6 +4670,16 @@ namespace ts {
|
||||
// All node tests in the following list should *not* reference parent pointers so that
|
||||
// they may be used with transformations.
|
||||
namespace ts {
|
||||
/* @internal */
|
||||
export function isNode(node: Node) {
|
||||
return isNodeKind(node.kind);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function isNodeKind(kind: SyntaxKind) {
|
||||
return kind >= SyntaxKind.FirstNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if node is of some token syntax kind.
|
||||
* For example, this is true for an IfKeyword but not for an IfStatement.
|
||||
@@ -5308,6 +5318,11 @@ namespace ts {
|
||||
return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode;
|
||||
}
|
||||
|
||||
/** True if node is of a kind that may contain comment text. */
|
||||
export function isJSDocCommentContainingNode(node: Node): boolean {
|
||||
return node.kind === SyntaxKind.JSDocComment || isJSDocTag(node);
|
||||
}
|
||||
|
||||
// TODO: determine what this does before making it public.
|
||||
/* @internal */
|
||||
export function isJSDocTag(node: Node): boolean {
|
||||
|
||||
@@ -724,8 +724,8 @@ namespace ts {
|
||||
pushCommentRange(pos, tag.pos - pos);
|
||||
}
|
||||
|
||||
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation);
|
||||
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName);
|
||||
pushClassification(tag.atToken.pos, tag.atToken.end - tag.atToken.pos, ClassificationType.punctuation); // "@"
|
||||
pushClassification(tag.tagName.pos, tag.tagName.end - tag.tagName.pos, ClassificationType.docCommentTagName); // e.g. "param"
|
||||
|
||||
pos = tag.tagName.end;
|
||||
|
||||
@@ -814,7 +814,7 @@ namespace ts {
|
||||
* False will mean that node is not classified and traverse routine should recurse into node contents.
|
||||
*/
|
||||
function tryClassifyNode(node: Node): boolean {
|
||||
if (isJSDocNode(node)) {
|
||||
if (isJSDoc(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -445,7 +445,7 @@ namespace ts.Completions {
|
||||
}
|
||||
|
||||
start = timestamp();
|
||||
const previousToken = findPrecedingToken(position, sourceFile);
|
||||
const previousToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined, /*includeJsDoc*/ true);
|
||||
log("getCompletionData: Get previous token 1: " + (timestamp() - start));
|
||||
|
||||
// The decision to provide completion depends on the contextToken, which is determined through the previousToken.
|
||||
@@ -456,7 +456,7 @@ namespace ts.Completions {
|
||||
// Skip this partial identifier and adjust the contextToken to the token that precedes it.
|
||||
if (contextToken && position <= contextToken.end && isWord(contextToken.kind)) {
|
||||
const start = timestamp();
|
||||
contextToken = findPrecedingToken(contextToken.getFullStart(), sourceFile);
|
||||
contextToken = findPrecedingToken(contextToken.getFullStart(), sourceFile, /*startNode*/ undefined, /*includeJsDoc*/ true);
|
||||
log("getCompletionData: Get previous token 2: " + (timestamp() - start));
|
||||
}
|
||||
|
||||
|
||||
+38
-42
@@ -37,7 +37,7 @@ namespace ts {
|
||||
let ruleProvider: formatting.RulesProvider;
|
||||
|
||||
function createNode<TKind extends SyntaxKind>(kind: TKind, pos: number, end: number, parent?: Node): NodeObject | TokenObject<TKind> | IdentifierObject {
|
||||
const node = kind >= SyntaxKind.FirstNode ? new NodeObject(kind, pos, end) :
|
||||
const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) :
|
||||
kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) :
|
||||
new TokenObject(kind, pos, end);
|
||||
node.parent = parent;
|
||||
@@ -103,10 +103,10 @@ namespace ts {
|
||||
return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
|
||||
}
|
||||
|
||||
private addSyntheticNodes(nodes: Node[], pos: number, end: number, useJSDocScanner?: boolean): number {
|
||||
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
|
||||
scanner.setTextPos(pos);
|
||||
while (pos < end) {
|
||||
const token = useJSDocScanner ? scanner.scanJSDocToken() : scanner.scan();
|
||||
const token = scanner.scan();
|
||||
Debug.assert(token !== SyntaxKind.EndOfFileToken); // Else it would infinitely loop
|
||||
const textPos = scanner.getTextPos();
|
||||
if (textPos <= end) {
|
||||
@@ -136,54 +136,50 @@ namespace ts {
|
||||
}
|
||||
|
||||
private createChildren(sourceFile?: SourceFileLike) {
|
||||
if (this.kind === SyntaxKind.JSDocComment || isJSDocTag(this)) {
|
||||
if (!isNodeKind(this.kind)) {
|
||||
this._children = emptyArray;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isJSDocCommentContainingNode(this)) {
|
||||
/** Don't add trivia for "tokens" since this is in a comment. */
|
||||
const children: Node[] = [];
|
||||
this.forEachChild(child => { children.push(child); });
|
||||
this._children = children;
|
||||
return;
|
||||
}
|
||||
else if (this.kind >= SyntaxKind.FirstNode) {
|
||||
const children: Node[] = [];
|
||||
scanner.setText((sourceFile || this.getSourceFile()).text);
|
||||
let pos = this.pos;
|
||||
const useJSDocScanner = isJSDocNode(this);
|
||||
const processNode = (node: Node) => {
|
||||
const isJSDocTagNode = isJSDocNode(node);
|
||||
if (!isJSDocTagNode && pos < node.pos) {
|
||||
pos = this.addSyntheticNodes(children, pos, node.pos, useJSDocScanner);
|
||||
}
|
||||
children.push(node);
|
||||
if (!isJSDocTagNode) {
|
||||
pos = node.end;
|
||||
}
|
||||
};
|
||||
const processNodes = (nodes: NodeArray<Node>) => {
|
||||
if (pos < nodes.pos) {
|
||||
pos = this.addSyntheticNodes(children, pos, nodes.pos, useJSDocScanner);
|
||||
}
|
||||
children.push(this.createSyntaxList(nodes));
|
||||
pos = nodes.end;
|
||||
};
|
||||
// jsDocComments need to be the first children
|
||||
if (this.jsDoc) {
|
||||
for (const jsDocComment of this.jsDoc) {
|
||||
processNode(jsDocComment);
|
||||
}
|
||||
|
||||
const children: Node[] = [];
|
||||
scanner.setText((sourceFile || this.getSourceFile()).text);
|
||||
let pos = this.pos;
|
||||
const processNode = (node: Node) => {
|
||||
pos = this.addSyntheticNodes(children, pos, node.pos);
|
||||
children.push(node);
|
||||
pos = node.end;
|
||||
};
|
||||
const processNodes = (nodes: NodeArray<Node>) => {
|
||||
if (pos < nodes.pos) {
|
||||
pos = this.addSyntheticNodes(children, pos, nodes.pos);
|
||||
}
|
||||
// For syntactic classifications, all trivia are classcified together, including jsdoc comments.
|
||||
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
|
||||
// Restoring the scanner position ensures that.
|
||||
pos = this.pos;
|
||||
forEachChild(this, processNode, processNodes);
|
||||
if (pos < this.end) {
|
||||
this.addSyntheticNodes(children, pos, this.end);
|
||||
children.push(this.createSyntaxList(nodes));
|
||||
pos = nodes.end;
|
||||
};
|
||||
// jsDocComments need to be the first children
|
||||
if (this.jsDoc) {
|
||||
for (const jsDocComment of this.jsDoc) {
|
||||
processNode(jsDocComment);
|
||||
}
|
||||
scanner.setText(undefined);
|
||||
this._children = children;
|
||||
}
|
||||
else {
|
||||
this._children = emptyArray;
|
||||
// For syntactic classifications, all trivia are classcified together, including jsdoc comments.
|
||||
// For that to work, the jsdoc comments should still be the leading trivia of the first child.
|
||||
// Restoring the scanner position ensures that.
|
||||
pos = this.pos;
|
||||
forEachChild(this, processNode, processNodes);
|
||||
if (pos < this.end) {
|
||||
this.addSyntheticNodes(children, pos, this.end);
|
||||
}
|
||||
scanner.setText(undefined);
|
||||
this._children = children;
|
||||
}
|
||||
|
||||
public getChildCount(sourceFile?: SourceFile): number {
|
||||
|
||||
@@ -710,7 +710,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node): Node {
|
||||
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node {
|
||||
return find(startNode || sourceFile);
|
||||
|
||||
function findRightmostToken(n: Node): Node {
|
||||
@@ -741,7 +741,7 @@ namespace ts {
|
||||
// NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia).
|
||||
// if this is the case - then we should assume that token in question is located in previous child.
|
||||
if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) {
|
||||
const start = child.getStart(sourceFile);
|
||||
const start = (includeJsDoc && child.jsDoc ? child.jsDoc[0] : child).getStart(sourceFile);
|
||||
const lookInPreviousChild =
|
||||
(start >= position) || // cursor in the leading trivia
|
||||
(child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText
|
||||
@@ -758,7 +758,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile);
|
||||
Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || isJSDocCommentContainingNode(n));
|
||||
|
||||
// Here we know that none of child token nodes embrace the position,
|
||||
// the only known case is when position is at the end of the file.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: /node_modules/foo/index.d.ts
|
||||
/////** tee */
|
||||
////export type T = number;
|
||||
|
||||
// @Filename: /a.js
|
||||
////import * as Foo from "foo";
|
||||
/////** @type {Foo./**/} */
|
||||
////const x = 0;
|
||||
|
||||
goTo.marker();
|
||||
verify.completionListContains("T", "type T = number", "tee ", "type");
|
||||
Reference in New Issue
Block a user