• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import ts from 'typescript';
17
18import { Comment } from '../../typedef/parser/Comment';
19import { LogUtil } from '../../utils/logUtil';
20import { StringUtils } from '../../utils/StringUtils';
21
22export class CommentHelper {
23  static licenseKeyword: string = 'Copyright';
24  static referenceRegexp: RegExp = /\/\/\/\s*<reference\s*path/g;
25  static referenceCommentRegexp: RegExp = /\/\s*<reference\s*path/g;
26  static mutiCommentDelimiter: string = '/**';
27
28  /**
29   * 获取指定AST节点上的注释,若无注释返回空数组。
30   *
31   * @param { ts.Node } node 当前节点
32   * @param { ts.SourceFile } sourceFile sourceFile节点
33   * @returns { Comment.CommentInfo[] } 处理后的JsDoc数组
34   */
35  static getNodeLeadingComments(node: ts.Node, sourceFile: ts.SourceFile): Comment.CommentInfo[] {
36    try {
37      const leadingCommentRange: ts.CommentRange[] | undefined = ts.getLeadingCommentRanges(
38        sourceFile.getFullText(),
39        node.getFullStart()
40      );
41      if (leadingCommentRange && leadingCommentRange.length) {
42        const parsedCommentInfos: Array<Comment.CommentInfo> = [];
43        leadingCommentRange.forEach((range: ts.CommentRange) => {
44          const comment: string = sourceFile.getFullText().slice(range.pos, range.end);
45          const commentInfo: Comment.CommentInfo = CommentHelper.parseComment(comment, range.kind, true);
46          commentInfo.pos = range.pos;
47          commentInfo.end = range.end;
48          parsedCommentInfos.push(commentInfo);
49        });
50        return parsedCommentInfos;
51      }
52      return [];
53    } catch (error) {
54      LogUtil.d('CommentHelper', `node(kind=${node.kind}) is created in memory.`);
55      return [];
56    }
57  }
58
59  /**
60   * 将多段注释文本解析成注释对象。
61   *
62   * @param { string } comment 多端JsDoc字符串
63   * @param { ts.CommentKind } commentKind 注释的类型
64   * @param { boolean } isLeading 是否是头注释
65   * @returns { Comment.CommentInfo } 返回处理后的JsDoc对象
66   */
67  static parseComment(comment: string, commentKind: ts.CommentKind, isLeading: boolean): Comment.CommentInfo {
68    const { parse } = require('comment-parser');
69    const commentInfo: Comment.CommentInfo = {
70      text: comment,
71      isMultiLine: commentKind === ts.SyntaxKind.MultiLineCommentTrivia,
72      isLeading: isLeading,
73      description: '',
74      commentTags: [],
75      parsedComment: undefined,
76      pos: -1,
77      end: -1,
78      ignore: false,
79      isApiComment: false,
80      isInstruct: false,
81    };
82    let commentString: string = comment;
83    let parsedComments = parse(commentString);
84    // 无法被解析的注释,可能以 /* 开头或是单行注释
85    if (parsedComments.length === 0) {
86      // 注释是 /// <reference path="" /> 或 单行注释
87      if (
88        StringUtils.hasSubstring(commentString, this.referenceRegexp) ||
89        commentKind === ts.SyntaxKind.SingleLineCommentTrivia
90      ) {
91        commentInfo.isMultiLine = false;
92        // 注释内容需丢弃 "//"
93        const startIndex: number = 2;
94        commentInfo.text = commentString.substring(startIndex, commentString.length);
95      }
96      return commentInfo;
97    }
98    commentInfo.parsedComment = parsedComments[0];
99    commentInfo.description = parsedComments[0].description;
100    for (let i = 0; i < parsedComments[0].tags.length; i++) {
101      const tag = parsedComments[0].tags[i];
102      commentInfo.commentTags.push({
103        tag: tag.tag,
104        name: tag.name,
105        type: tag.type,
106        optional: tag.optional,
107        description: tag.description,
108        source: tag.source[0].source,
109        lineNumber: tag.source[0].number,
110        tokenSource: tag.source,
111        defaultValue: tag.default ? tag.default : undefined,
112      });
113    }
114    commentInfo.isApiComment = true;
115    return commentInfo;
116  }
117}
118
119export class JsDocProcessorHelper {
120  static setSyscap(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
121    jsDocInfo.setSyscap(commentTag.name);
122  }
123
124  static setSince(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
125    jsDocInfo.setSince(commentTag.name);
126  }
127
128  static setIsForm(jsDocInfo: Comment.JsDocInfo): void {
129    jsDocInfo.setIsForm(true);
130  }
131
132  static setIsCrossPlatForm(jsDocInfo: Comment.JsDocInfo): void {
133    jsDocInfo.setIsCrossPlatForm(true);
134  }
135
136  static setIsSystemApi(jsDocInfo: Comment.JsDocInfo): void {
137    jsDocInfo.setIsSystemApi(true);
138  }
139
140  static setIsAtomicService(jsDocInfo: Comment.JsDocInfo): void {
141    jsDocInfo.setIsAtomicService(true);
142  }
143
144  static setDeprecatedVersion(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
145    jsDocInfo.setDeprecatedVersion(commentTag.description);
146  }
147
148  static setUseinstead(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
149    jsDocInfo.setUseinstead(commentTag.name);
150  }
151
152  static setPermission(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
153    const description: string = commentTag.description;
154    const name: string = commentTag.name;
155    const permissions: string = description ? `${name} ${description}` : `${name}`;
156    jsDocInfo.setPermission(permissions);
157  }
158
159  static addErrorCode(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
160    if (!commentTag || isNaN(Number(commentTag.name))) {
161      return;
162    }
163    jsDocInfo.addErrorCode(Number(commentTag.name));
164  }
165
166  static setTypeInfo(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
167    jsDocInfo.setTypeInfo(commentTag.type);
168  }
169
170  static setIsConstant(jsDocInfo: Comment.JsDocInfo): void {
171    jsDocInfo.setIsConstant(true);
172  }
173
174  static setModelLimitation(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
175    jsDocInfo.setModelLimitation(commentTag.tag);
176  }
177
178  /**
179   * 基于comment-parser解析的注释对象得到JsDocInfo对象
180   *
181   * @param { Comment.CommentInfo } jsDoc 一段JsDoc的信息解析的注释对象
182   * @returns 返回JsDoc得到的JsDocInfo对象
183   */
184  static processJsDoc(jsDoc: Comment.CommentInfo): Comment.JsDocInfo {
185    const jsDocInfo: Comment.JsDocInfo = new Comment.JsDocInfo();
186    jsDocInfo.setDescription(jsDoc.description);
187    for (let i = 0; i < jsDoc.commentTags.length; i++) {
188      const commentTag: Comment.CommentTag = jsDoc.commentTags[i];
189      jsDocInfo.addTag(commentTag);
190      const jsDocProcessor = jsDocProcessorMap.get(commentTag.tag.toLowerCase());
191      if (!jsDocProcessor) {
192        continue;
193      }
194      jsDocProcessor(jsDocInfo, commentTag);
195    }
196    return jsDocInfo;
197  }
198
199  /**
200   * 基于当前node节点,返回节点上多段JsDoc解析后的对象数组
201   *
202   * @param { ts.Node } node node节点
203   * @param { ts.SourceFile } sourceFile node节点的sourceFile
204   * @returns 返回解析后的多段JsDoc的信息数组
205   */
206  static processJsDocInfos(node: ts.Node): Comment.JsDocInfo[] {
207    const sourceFile = node.getSourceFile();
208    const allCommentInfos: Comment.CommentInfo[] = CommentHelper.getNodeLeadingComments(node, sourceFile);
209    const commentInfos: Comment.CommentInfo[] = allCommentInfos.filter((commentInfo: Comment.CommentInfo) => {
210      return commentInfo.isApiComment;
211    });
212    const jsDocInfos: Comment.JsDocInfo[] = [];
213    for (let i = 0; i < commentInfos.length; i++) {
214      const commentInfo: Comment.CommentInfo = commentInfos[i];
215      const jsDocInfo: Comment.JsDocInfo = JsDocProcessorHelper.processJsDoc(commentInfo);
216      jsDocInfos.push(jsDocInfo);
217    }
218    return jsDocInfos;
219  }
220}
221
222const jsDocProcessorMap: Map<string, Comment.JsDocProcessorInterface> = new Map([
223  [Comment.JsDocTag.SYSCAP, JsDocProcessorHelper.setSyscap],
224  [Comment.JsDocTag.SINCE, JsDocProcessorHelper.setSince],
225  [Comment.JsDocTag.FORM, JsDocProcessorHelper.setIsForm],
226  [Comment.JsDocTag.CROSS_PLAT_FORM, JsDocProcessorHelper.setIsCrossPlatForm],
227  [Comment.JsDocTag.SYSTEM_API, JsDocProcessorHelper.setIsSystemApi],
228  [Comment.JsDocTag.STAGE_MODEL_ONLY, JsDocProcessorHelper.setModelLimitation],
229  [Comment.JsDocTag.FA_MODEL_ONLY, JsDocProcessorHelper.setModelLimitation],
230  [Comment.JsDocTag.DEPRECATED, JsDocProcessorHelper.setDeprecatedVersion],
231  [Comment.JsDocTag.USEINSTEAD, JsDocProcessorHelper.setUseinstead],
232  [Comment.JsDocTag.TYPE, JsDocProcessorHelper.setTypeInfo],
233  [Comment.JsDocTag.PERMISSION, JsDocProcessorHelper.setPermission],
234  [Comment.JsDocTag.THROWS, JsDocProcessorHelper.addErrorCode],
235  [Comment.JsDocTag.CONSTANT, JsDocProcessorHelper.setIsConstant],
236  [Comment.JsDocTag.ATOMIC_SERVICE, JsDocProcessorHelper.setIsAtomicService],
237]);
238