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