Support completions for qualified names in JSDoc (#16380)

* Support completions for qualified names in JSDoc

* Fix typo
This commit is contained in:
Andy
2017-06-08 17:21:36 -07:00
committed by GitHub
parent 1a1d5ea5f5
commit 0d36d0e39f
8 changed files with 79 additions and 53 deletions
+1 -1
View File
@@ -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);
}
+2 -2
View File
@@ -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);
}
+15
View File
@@ -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 {
+3 -3
View File
@@ -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;
}
+2 -2
View File
@@ -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
View File
@@ -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 {
+3 -3
View File
@@ -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");