UNPKG

6.17 kBPlain TextView Raw
1import * as _ from 'lodash';
2import { ts, SyntaxKind } from 'ts-simple-ast';
3
4import { JSDocParameterTagExt } from '../app/nodes/jsdoc-parameter-tag.node';
5
6export class JsdocParserUtil {
7 public isVariableLike(node: ts.Node): node is ts.VariableLikeDeclaration {
8 if (node) {
9 switch (node.kind) {
10 case SyntaxKind.BindingElement:
11 case SyntaxKind.EnumMember:
12 case SyntaxKind.Parameter:
13 case SyntaxKind.PropertyAssignment:
14 case SyntaxKind.PropertyDeclaration:
15 case SyntaxKind.PropertySignature:
16 case SyntaxKind.ShorthandPropertyAssignment:
17 case SyntaxKind.VariableDeclaration:
18 return true;
19 }
20 }
21 return false;
22 }
23
24 public getMainCommentOfNode(node: ts.Node): string {
25 let description: string = '';
26 if (node.jsDoc) {
27 if (node.jsDoc.length > 0) {
28 if (typeof node.jsDoc[0].comment !== 'undefined') {
29 description = node.jsDoc[0].comment;
30 }
31 }
32 }
33 return description;
34 }
35
36 private getJSDocTags(node: ts.Node, kind: SyntaxKind): ts.JSDocTag[] {
37 const docs = this.getJSDocs(node);
38 if (docs) {
39 const result: ts.JSDocTag[] = [];
40 for (const doc of docs) {
41 if (ts.isJSDocParameterTag(doc)) {
42 if (doc.kind === kind) {
43 result.push(doc);
44 }
45 } else if (ts.isJSDoc(doc)) {
46 result.push(..._.filter(doc.tags, tag => tag.kind === kind));
47 } else {
48 throw new Error('Unexpected type');
49 }
50 }
51 return result;
52 }
53 }
54
55 public getJSDocs(node: ts.Node): ReadonlyArray<ts.JSDoc | ts.JSDocTag> {
56 // TODO: jsDocCache is internal, see if there's a way around it
57 let cache: ReadonlyArray<ts.JSDoc | ts.JSDocTag> = (node as any).jsDocCache;
58 if (!cache) {
59 cache = this.getJSDocsWorker(node, []).filter(x => x);
60 (node as any).jsDocCache = cache;
61 }
62 return cache;
63 }
64
65 // Try to recognize this pattern when node is initializer
66 // of variable declaration and JSDoc comments are on containing variable statement.
67 // /**
68 // * @param {number} name
69 // * @returns {number}
70 // */
71 // var x = function(name) { return name.length; }
72 private getJSDocsWorker(node: ts.Node, cache): ReadonlyArray<any> {
73 const parent = node.parent;
74 const isInitializerOfVariableDeclarationInStatement =
75 this.isVariableLike(parent) &&
76 parent.initializer === node &&
77 ts.isVariableStatement(parent.parent.parent);
78 const isVariableOfVariableDeclarationStatement =
79 this.isVariableLike(node) && ts.isVariableStatement(parent.parent);
80 const variableStatementNode = isInitializerOfVariableDeclarationInStatement
81 ? parent.parent.parent
82 : isVariableOfVariableDeclarationStatement
83 ? parent.parent
84 : undefined;
85 if (variableStatementNode) {
86 cache = this.getJSDocsWorker(variableStatementNode, cache);
87 }
88
89 // Also recognize when the node is the RHS of an assignment expression
90 const isSourceOfAssignmentExpressionStatement =
91 parent &&
92 parent.parent &&
93 ts.isBinaryExpression(parent) &&
94 parent.operatorToken.kind === SyntaxKind.EqualsToken &&
95 ts.isExpressionStatement(parent.parent);
96 if (isSourceOfAssignmentExpressionStatement) {
97 cache = this.getJSDocsWorker(parent.parent, cache);
98 }
99
100 const isModuleDeclaration =
101 ts.isModuleDeclaration(node) && parent && ts.isModuleDeclaration(parent);
102 const isPropertyAssignmentExpression = parent && ts.isPropertyAssignment(parent);
103 if (isModuleDeclaration || isPropertyAssignmentExpression) {
104 cache = this.getJSDocsWorker(parent, cache);
105 }
106
107 // Pull parameter comments from declaring function as well
108 if (ts.isParameter(node)) {
109 cache = _.concat(cache, this.getJSDocParameterTags(node));
110 }
111
112 if (this.isVariableLike(node) && node.initializer) {
113 cache = _.concat(cache, node.initializer.jsDoc);
114 }
115
116 cache = _.concat(cache, node.jsDoc);
117
118 return cache;
119 }
120
121 private getJSDocParameterTags(
122 param: ts.ParameterDeclaration
123 ): ReadonlyArray<ts.JSDocParameterTag> {
124 const func = param.parent as ts.FunctionLikeDeclaration;
125 const tags = this.getJSDocTags(
126 func,
127 SyntaxKind.JSDocParameterTag
128 ) as ts.JSDocParameterTag[];
129
130 if (!param.name) {
131 // this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
132 const i = func.parameters.indexOf(param);
133 const paramTags = _.filter(tags, tag => ts.isJSDocParameterTag(tag));
134
135 if (paramTags && 0 <= i && i < paramTags.length) {
136 return [paramTags[i]];
137 }
138 } else if (ts.isIdentifier(param.name)) {
139 const name = param.name.text;
140 return _.filter(tags, tag => {
141 if (ts && ts.isJSDocParameterTag(tag)) {
142 let t: JSDocParameterTagExt = tag;
143 if (typeof t.parameterName !== 'undefined') {
144 return t.parameterName.text === name;
145 } else if (typeof t.name !== 'undefined') {
146 if (typeof t.name.escapedText !== 'undefined') {
147 return t.name.escapedText === name;
148 }
149 }
150 }
151 });
152 } else {
153 // TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines
154 // But multi-line object types aren't supported yet either
155 return undefined;
156 }
157 }
158}