• 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';
21import { ApiType } from '../../typedef/parser/ApiInfoDefination';
22
23export class CommentHelper {
24  static licenseKeyword: string = 'Copyright';
25  static referenceRegexp: RegExp = /\/\/\/\s*<reference\s*path/g;
26  static referenceCommentRegexp: RegExp = /\/\s*<reference\s*path/g;
27  static mutiCommentDelimiter: string = '/**';
28  static fileTag: RegExp = /\@file|\@kit/g;
29
30  /**
31   * 获取指定AST节点上的注释,若无注释返回空数组。
32   *
33   * @param { ts.Node } node 当前节点
34   * @param { ts.SourceFile } sourceFile sourceFile节点
35   * @returns { Comment.CommentInfo[] } 处理后的JsDoc数组
36   */
37  static getNodeLeadingComments(node: ts.Node, sourceFile: ts.SourceFile): Comment.CommentInfo[] {
38    try {
39      const leadingCommentRange: ts.CommentRange[] | undefined = ts.getLeadingCommentRanges(
40        sourceFile.getFullText(),
41        node.getFullStart()
42      );
43      if (leadingCommentRange && leadingCommentRange.length) {
44        const parsedCommentInfos: Array<Comment.CommentInfo> = [];
45        leadingCommentRange.forEach((range: ts.CommentRange) => {
46          const comment: string = sourceFile.getFullText().slice(range.pos, range.end);
47          const commentInfo: Comment.CommentInfo = CommentHelper.parseComment(comment, range.kind, true);
48          commentInfo.pos = range.pos;
49          commentInfo.end = range.end;
50          parsedCommentInfos.push(commentInfo);
51        });
52        return parsedCommentInfos;
53      }
54      return [];
55    } catch (error) {
56      LogUtil.d('CommentHelper', `node(kind=${node.kind}) is created in memory.`);
57      return [];
58    }
59  }
60
61  /**
62   * 将多段注释文本解析成注释对象。
63   *
64   * @param { string } comment 多端JsDoc字符串
65   * @param { ts.CommentKind } commentKind 注释的类型
66   * @param { boolean } isLeading 是否是头注释
67   * @returns { Comment.CommentInfo } 返回处理后的JsDoc对象
68   */
69  static parseComment(comment: string, commentKind: ts.CommentKind, isLeading: boolean): Comment.CommentInfo {
70    const { parse } = require('comment-parser');
71    const commentInfo: Comment.CommentInfo = {
72      text: comment,
73      isMultiLine: commentKind === ts.SyntaxKind.MultiLineCommentTrivia,
74      isLeading: isLeading,
75      description: '',
76      commentTags: [],
77      parsedComment: undefined,
78      pos: -1,
79      end: -1,
80      ignore: false,
81      isApiComment: false,
82      isInstruct: false,
83      isFileJsDoc: false,
84    };
85    let commentString: string = comment;
86    let parsedComments = parse(commentString);
87    // 无法被解析的注释,可能以 /* 开头或是单行注释
88    if (parsedComments.length === 0) {
89      // 注释是 /// <reference path="" /> 或 单行注释
90      if (
91        StringUtils.hasSubstring(commentString, this.referenceRegexp) ||
92        commentKind === ts.SyntaxKind.SingleLineCommentTrivia
93      ) {
94        commentInfo.isMultiLine = false;
95        // 注释内容需丢弃 "//"
96        const startIndex: number = 2;
97        commentInfo.text = commentString.substring(startIndex, commentString.length);
98      }
99      return commentInfo;
100    }
101    commentInfo.parsedComment = parsedComments[0];
102    commentInfo.description = parsedComments[0].description;
103    for (let i = 0; i < parsedComments[0].tags.length; i++) {
104      const tag = parsedComments[0].tags[i];
105      commentInfo.commentTags.push({
106        tag: tag.tag,
107        name: tag.name,
108        type: tag.type,
109        optional: tag.optional,
110        description: tag.description,
111        source: tag.source[0].source,
112        lineNumber: tag.source[0].number,
113        tokenSource: tag.source,
114        defaultValue: tag.default ? tag.default : undefined,
115      });
116    }
117    if (StringUtils.hasSubstring(commentString, this.fileTag)) {
118      commentInfo.isFileJsDoc = true;
119    }
120    commentInfo.isApiComment = true;
121    return commentInfo;
122  }
123}
124
125export class JsDocProcessorHelper {
126  static setSyscap(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
127    jsDocInfo.setSyscap(commentTag.name);
128  }
129
130  static setSince(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
131    jsDocInfo.setSince(commentTag.name);
132  }
133
134  static setIsForm(jsDocInfo: Comment.JsDocInfo): void {
135    jsDocInfo.setIsForm(true);
136  }
137
138  static setIsCrossPlatForm(jsDocInfo: Comment.JsDocInfo): void {
139    jsDocInfo.setIsCrossPlatForm(true);
140  }
141
142  static setIsSystemApi(jsDocInfo: Comment.JsDocInfo): void {
143    jsDocInfo.setIsSystemApi(true);
144  }
145
146  static setIsAtomicService(jsDocInfo: Comment.JsDocInfo): void {
147    jsDocInfo.setIsAtomicService(true);
148  }
149
150  static setDeprecatedVersion(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
151    jsDocInfo.setDeprecatedVersion(commentTag.description);
152  }
153
154  static setUseinstead(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
155    jsDocInfo.setUseinstead(commentTag.name);
156  }
157
158  static setPermission(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
159    const description: string = commentTag.description;
160    const name: string = commentTag.name;
161    const permissions: string = description ? `${name} ${description}` : `${name}`;
162    jsDocInfo.setPermission(permissions);
163  }
164
165  static addErrorCode(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
166    if (!commentTag || isNaN(Number(commentTag.name))) {
167      return;
168    }
169    jsDocInfo.addErrorCode(Number(commentTag.name));
170  }
171
172  static setTypeInfo(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
173    jsDocInfo.setTypeInfo(commentTag.type);
174  }
175
176  static setIsConstant(jsDocInfo: Comment.JsDocInfo): void {
177    jsDocInfo.setIsConstant(true);
178  }
179
180  static setModelLimitation(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
181    jsDocInfo.setModelLimitation(commentTag.tag);
182  }
183
184  static setKitContent(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
185    jsDocInfo.setKit(commentTag.source.replace(/\* @kit|\r|\n/g, '').trim());
186  }
187
188  static setIsFile(jsDocInfo: Comment.JsDocInfo, commentTag: Comment.CommentTag): void {
189    jsDocInfo.setFileTagContent(commentTag.source.replace(/\* @file|\r|\n/g, '').trim());
190  }
191
192  /**
193   * 基于comment-parser解析的注释对象得到JsDocInfo对象
194   *
195   * @param { Comment.CommentInfo } jsDoc 一段JsDoc的信息解析的注释对象
196   * @returns 返回JsDoc得到的JsDocInfo对象
197   */
198  static processJsDoc(jsDoc: Comment.CommentInfo, parentKitInfo: string, parentFileTag: string): Comment.JsDocInfo {
199    const jsDocInfo: Comment.JsDocInfo = new Comment.JsDocInfo();
200    jsDocInfo.setDescription(jsDoc.description);
201    jsDocInfo.setKit(parentKitInfo);
202    jsDocInfo.setFileTagContent(parentFileTag);
203    for (let i = 0; i < jsDoc.commentTags.length; i++) {
204      const commentTag: Comment.CommentTag = jsDoc.commentTags[i];
205      jsDocInfo.addTag(commentTag);
206      const jsDocProcessor = jsDocProcessorMap.get(commentTag.tag.toLowerCase());
207      if (!jsDocProcessor) {
208        continue;
209      }
210      jsDocProcessor(jsDocInfo, commentTag);
211    }
212    return jsDocInfo;
213  }
214
215  /**
216   * 基于当前node节点,返回节点上多段JsDoc解析后的对象数组
217   *
218   * @param { ts.Node } node node节点
219   * @param { ts.SourceFile } sourceFile node节点的sourceFile
220   * @returns 返回解析后的多段JsDoc的信息数组
221   */
222  static processJsDocInfos(node: ts.Node, apiType: string, parentKitInfo: string, parentFileTag: string): Comment.JsDocInfo[] {
223    const sourceFile = node.getSourceFile();
224    const allCommentInfos: Comment.CommentInfo[] = CommentHelper.getNodeLeadingComments(node, sourceFile);
225    const commentInfos: Comment.CommentInfo[] = allCommentInfos.filter((commentInfo: Comment.CommentInfo) => {
226      return (
227        (commentInfo.isApiComment && !commentInfo.isFileJsDoc && apiType !== ApiType.SOURCE_FILE) ||
228        (commentInfo.isApiComment && commentInfo.isFileJsDoc && apiType === ApiType.SOURCE_FILE)
229      );
230    });
231
232    const jsDocInfos: Comment.JsDocInfo[] = [];
233    if (commentInfos.length === 0 && parentKitInfo !== '') {
234      const jsDocInfo: Comment.JsDocInfo = new Comment.JsDocInfo();
235      jsDocInfo.setKit(parentKitInfo);
236      jsDocInfo.setFileTagContent(parentFileTag);
237      jsDocInfos.push(jsDocInfo);
238    }
239    for (let i = 0; i < commentInfos.length; i++) {
240      const commentInfo: Comment.CommentInfo = commentInfos[i];
241      const jsDocInfo: Comment.JsDocInfo = JsDocProcessorHelper.processJsDoc(commentInfo, parentKitInfo, parentFileTag);
242      jsDocInfos.push(jsDocInfo);
243    }
244    return jsDocInfos;
245  }
246}
247
248const jsDocProcessorMap: Map<string, Comment.JsDocProcessorInterface> = new Map([
249  [Comment.JsDocTag.SYSCAP, JsDocProcessorHelper.setSyscap],
250  [Comment.JsDocTag.SINCE, JsDocProcessorHelper.setSince],
251  [Comment.JsDocTag.FORM, JsDocProcessorHelper.setIsForm],
252  [Comment.JsDocTag.CROSS_PLAT_FORM, JsDocProcessorHelper.setIsCrossPlatForm],
253  [Comment.JsDocTag.SYSTEM_API, JsDocProcessorHelper.setIsSystemApi],
254  [Comment.JsDocTag.STAGE_MODEL_ONLY, JsDocProcessorHelper.setModelLimitation],
255  [Comment.JsDocTag.FA_MODEL_ONLY, JsDocProcessorHelper.setModelLimitation],
256  [Comment.JsDocTag.DEPRECATED, JsDocProcessorHelper.setDeprecatedVersion],
257  [Comment.JsDocTag.USEINSTEAD, JsDocProcessorHelper.setUseinstead],
258  [Comment.JsDocTag.TYPE, JsDocProcessorHelper.setTypeInfo],
259  [Comment.JsDocTag.PERMISSION, JsDocProcessorHelper.setPermission],
260  [Comment.JsDocTag.THROWS, JsDocProcessorHelper.addErrorCode],
261  [Comment.JsDocTag.CONSTANT, JsDocProcessorHelper.setIsConstant],
262  [Comment.JsDocTag.ATOMIC_SERVICE, JsDocProcessorHelper.setIsAtomicService],
263  [Comment.JsDocTag.KIT, JsDocProcessorHelper.setKitContent],
264  [Comment.JsDocTag.FILE, JsDocProcessorHelper.setIsFile],
265]);
266