From d24afc2ad0af8834b635afd8305b44b1f5ad6f77 Mon Sep 17 00:00:00 2001 From: Zhengbo Li Date: Thu, 18 Aug 2016 00:06:48 -0700 Subject: [PATCH 1/3] Return non-JsDocComment children ... to make syntactic classification work --- src/services/services.ts | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 148d3af427ca2..c4b42f942268d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -21,6 +21,7 @@ namespace ts { getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; getChildren(sourceFile?: SourceFile): Node[]; + getNonJsDocCommentChildren?(sourceFile?: SourceFile): Node[]; getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; getFullStart(): number; getEnd(): number; @@ -196,6 +197,7 @@ namespace ts { public parent: Node; public jsDocComments: JSDocComment[]; private _children: Node[]; + private _nonJsDocCommentChildren: Node[]; constructor(kind: SyntaxKind, pos: number, end: number) { this.pos = pos; @@ -273,20 +275,22 @@ namespace ts { } private createChildren(sourceFile?: SourceFile) { - let children: Node[]; + let jsDocCommentChildren: Node[]; + let nonJsDocCommentChildren: Node[]; if (this.kind >= SyntaxKind.FirstNode) { scanner.setText((sourceFile || this.getSourceFile()).text); - children = []; + jsDocCommentChildren = []; + nonJsDocCommentChildren = []; let pos = this.pos; const useJSDocScanner = this.kind >= SyntaxKind.FirstJSDocTagNode && this.kind <= SyntaxKind.LastJSDocTagNode; - const processNode = (node: Node) => { + const processNode = (node: Node, children = nonJsDocCommentChildren) => { if (pos < node.pos) { pos = this.addSyntheticNodes(children, pos, node.pos, useJSDocScanner); } children.push(node); pos = node.end; }; - const processNodes = (nodes: NodeArray) => { + const processNodes = (nodes: NodeArray, children = nonJsDocCommentChildren) => { if (pos < nodes.pos) { pos = this.addSyntheticNodes(children, pos, nodes.pos, useJSDocScanner); } @@ -296,16 +300,21 @@ namespace ts { // jsDocComments need to be the first children if (this.jsDocComments) { for (const jsDocComment of this.jsDocComments) { - processNode(jsDocComment); + processNode(jsDocComment, jsDocCommentChildren); } } + // 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); + this.addSyntheticNodes(nonJsDocCommentChildren, pos, this.end); } scanner.setText(undefined); } - this._children = children || emptyArray; + this._nonJsDocCommentChildren = nonJsDocCommentChildren || emptyArray; + this._children = concatenate(jsDocCommentChildren, this._nonJsDocCommentChildren); } public getChildCount(sourceFile?: SourceFile): number { @@ -323,6 +332,18 @@ namespace ts { return this._children; } + public getNonJsDocCommentChildren(sourceFile?: SourceFile): Node[] { + // If the cached children were cleared, that means some node before the current node has changed. + // so even if we have a cached nonJsDocCommentChildren, it would be outdated as well. + if (!this._children) { + this.createChildren(sourceFile); + } + // If the node has cached children but not nonJsDocCommentChildren, it means the children is not created + // via calling the "createChildren" method, so it can only be a SyntaxList. As SyntaxList cannot have jsDocCommentChildren + // anyways, we can just return its children. + return this._nonJsDocCommentChildren ? this._nonJsDocCommentChildren : this._children; + } + public getFirstToken(sourceFile?: SourceFile): Node { const children = this.getChildren(sourceFile); if (!children.length) { @@ -7725,7 +7746,7 @@ namespace ts { if (decodedTextSpanIntersectsWith(spanStart, spanLength, element.pos, element.getFullWidth())) { checkForClassificationCancellation(element.kind); - const children = element.getChildren(sourceFile); + const children = element.getNonJsDocCommentChildren ? element.getNonJsDocCommentChildren(sourceFile) : element.getChildren(sourceFile); for (let i = 0, n = children.length; i < n; i++) { const child = children[i]; if (!tryClassifyNode(child)) { From e445e012c4fe9a3673204988491266a41d56fd7a Mon Sep 17 00:00:00 2001 From: zhengbli Date: Sat, 20 Aug 2016 20:41:33 -0700 Subject: [PATCH 2/3] Add test for jsdoc syntactic classification for function declaration --- .../syntacticClassificationsDocComment4.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/cases/fourslash/syntacticClassificationsDocComment4.ts diff --git a/tests/cases/fourslash/syntacticClassificationsDocComment4.ts b/tests/cases/fourslash/syntacticClassificationsDocComment4.ts new file mode 100644 index 0000000000000..d8ac2f12d8b32 --- /dev/null +++ b/tests/cases/fourslash/syntacticClassificationsDocComment4.ts @@ -0,0 +1,24 @@ +/// + +//// /** @param {number} p1 */ +//// function foo(p1) {} + +var c = classification; +verify.syntacticClassificationsAre( + c.comment("/** "), + c.punctuation("@"), + c.docCommentTagName("param"), + c.comment(" "), + c.punctuation("{"), + c.keyword("number"), + c.punctuation("}"), + c.comment(" "), + c.parameterName("p1"), + c.comment(" */"), + c.keyword("function"), + c.identifier("foo"), + c.punctuation("("), + c.parameterName("p1"), + c.punctuation(")"), + c.punctuation("{"), + c.punctuation("}")); From 6d2323b0d81bc85e33bb0bce8b292938d8a0de27 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Sat, 20 Aug 2016 20:41:47 -0700 Subject: [PATCH 3/3] Simplify implementation --- src/compiler/utilities.ts | 4 ++++ src/services/services.ts | 37 ++++++++++++------------------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0a1f43203ce80..f1573f9b58af2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -301,6 +301,10 @@ namespace ts { return node.kind >= SyntaxKind.FirstJSDocNode && node.kind <= SyntaxKind.LastJSDocNode; } + export function isJSDocTag(node: Node) { + return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode; + } + export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFile): number { if (nodeIsMissing(node) || !node.decorators) { return getTokenPosOfNode(node, sourceFile); diff --git a/src/services/services.ts b/src/services/services.ts index c4b42f942268d..22f5a74eb5010 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -21,7 +21,6 @@ namespace ts { getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; getChildren(sourceFile?: SourceFile): Node[]; - getNonJsDocCommentChildren?(sourceFile?: SourceFile): Node[]; getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; getFullStart(): number; getEnd(): number; @@ -197,7 +196,6 @@ namespace ts { public parent: Node; public jsDocComments: JSDocComment[]; private _children: Node[]; - private _nonJsDocCommentChildren: Node[]; constructor(kind: SyntaxKind, pos: number, end: number) { this.pos = pos; @@ -275,22 +273,20 @@ namespace ts { } private createChildren(sourceFile?: SourceFile) { - let jsDocCommentChildren: Node[]; - let nonJsDocCommentChildren: Node[]; + let children: Node[]; if (this.kind >= SyntaxKind.FirstNode) { scanner.setText((sourceFile || this.getSourceFile()).text); - jsDocCommentChildren = []; - nonJsDocCommentChildren = []; + children = []; let pos = this.pos; const useJSDocScanner = this.kind >= SyntaxKind.FirstJSDocTagNode && this.kind <= SyntaxKind.LastJSDocTagNode; - const processNode = (node: Node, children = nonJsDocCommentChildren) => { + const processNode = (node: Node) => { if (pos < node.pos) { pos = this.addSyntheticNodes(children, pos, node.pos, useJSDocScanner); } children.push(node); pos = node.end; }; - const processNodes = (nodes: NodeArray, children = nonJsDocCommentChildren) => { + const processNodes = (nodes: NodeArray) => { if (pos < nodes.pos) { pos = this.addSyntheticNodes(children, pos, nodes.pos, useJSDocScanner); } @@ -300,7 +296,7 @@ namespace ts { // jsDocComments need to be the first children if (this.jsDocComments) { for (const jsDocComment of this.jsDocComments) { - processNode(jsDocComment, jsDocCommentChildren); + processNode(jsDocComment); } } // For syntactic classifications, all trivia are classcified together, including jsdoc comments. @@ -309,12 +305,11 @@ namespace ts { pos = this.pos; forEachChild(this, processNode, processNodes); if (pos < this.end) { - this.addSyntheticNodes(nonJsDocCommentChildren, pos, this.end); + this.addSyntheticNodes(children, pos, this.end); } scanner.setText(undefined); } - this._nonJsDocCommentChildren = nonJsDocCommentChildren || emptyArray; - this._children = concatenate(jsDocCommentChildren, this._nonJsDocCommentChildren); + this._children = children || emptyArray; } public getChildCount(sourceFile?: SourceFile): number { @@ -332,18 +327,6 @@ namespace ts { return this._children; } - public getNonJsDocCommentChildren(sourceFile?: SourceFile): Node[] { - // If the cached children were cleared, that means some node before the current node has changed. - // so even if we have a cached nonJsDocCommentChildren, it would be outdated as well. - if (!this._children) { - this.createChildren(sourceFile); - } - // If the node has cached children but not nonJsDocCommentChildren, it means the children is not created - // via calling the "createChildren" method, so it can only be a SyntaxList. As SyntaxList cannot have jsDocCommentChildren - // anyways, we can just return its children. - return this._nonJsDocCommentChildren ? this._nonJsDocCommentChildren : this._children; - } - public getFirstToken(sourceFile?: SourceFile): Node { const children = this.getChildren(sourceFile); if (!children.length) { @@ -7591,6 +7574,10 @@ namespace ts { * False will mean that node is not classified and traverse routine should recurse into node contents. */ function tryClassifyNode(node: Node): boolean { + if (isJSDocTag(node)) { + return true; + } + if (nodeIsMissing(node)) { return true; } @@ -7746,7 +7733,7 @@ namespace ts { if (decodedTextSpanIntersectsWith(spanStart, spanLength, element.pos, element.getFullWidth())) { checkForClassificationCancellation(element.kind); - const children = element.getNonJsDocCommentChildren ? element.getNonJsDocCommentChildren(sourceFile) : element.getChildren(sourceFile); + const children = element.getChildren(sourceFile); for (let i = 0, n = children.length; i < n; i++) { const child = children[i]; if (!tryClassifyNode(child)) {