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 16const path = require('path'); 17const fs = require('fs'); 18const ts = require('typescript'); 19const { parse } = require('comment-parser'); 20const { ApiDigestInfo, ApiType, apiDataHelper } = require('./api_data'); 21const { isInDirectory } = require('./util'); 22 23/** 24 * api 接口摘要信息包装方法 25 * 26 * @param {ts.Node} astNode node节点对象 27 * @param {string} className 类名 28 * @param {ApiType} apiType 方法名 29 * @param {string} rawText 原始文本 30 * @param {VisitExt} ext 扩展参数 31 * @returns {Object} 32 */ 33function wrapApiDigestInfo(astNode, className, apiName, apiType, rawText, ext) { 34 const digestInfo = new ApiDigestInfo(); 35 const jsdoc = getNodeLeadingJSDoc(astNode); 36 digestInfo.setAstNode(astNode) 37 .setApiType(apiType) 38 .setClassName(className) 39 .setPackageName(ext.packageName) 40 .setRawText(rawText.trim()) 41 .setPath(ext.dtsPath) 42 .setApiSignature(astNode, ext.packageName, className) 43 .setApiName(apiName) 44 .setJSdoc(jsdoc ? parseJSDocs(jsdoc) : undefined); 45 return digestInfo; 46} 47 48/** 49 * 接口属性签名信息 50 * 51 * @param {ts.PropertySignature} propertySignature 52 * @param {ApiDigestInfo} parentApiDigest 53 * @param {VisitExt} ext 54 * @returns {Object} 55 */ 56function getPropertySignatureInfo(propertySignature, parentApiDigest, ext) { 57 const className = parentApiDigest.getClassName(); 58 const rawText = propertySignature.getText(); 59 const apiName = propertySignature.name.getText(); 60 return wrapApiDigestInfo(propertySignature, className, apiName, ApiType.InterfaceProperty, rawText, ext); 61} 62 63/** 64 * 65 * 66 * @param {ts.MethodDeclaration} methodDeclaration 67 * @param {ApiDigestInfo} parentApiDigest 68 * @param {VisitExt} ext 69 * @returns {Object} 70 */ 71function getMethodDeclarationInfo(methodDeclaration, parentApiDigest, ext) { 72 const className = parentApiDigest.getClassName(); 73 const rawText = methodDeclaration.getText(); 74 const apiName = methodDeclaration.name.getText(); 75 return wrapApiDigestInfo(methodDeclaration, className, apiName, ApiType.ClassMethod, rawText, ext); 76} 77 78function getTypeAliasDeclarationInfo(typeAliasDeclaration, parentApiDigest, ext) { 79 const className = parentApiDigest.getClassName(); 80 const rawText = typeAliasDeclaration.getText(); 81 const apiName = typeAliasDeclaration.name.getText(); 82 return wrapApiDigestInfo(typeAliasDeclaration, className, apiName, ApiType.TypeAliasDeclaration, rawText, ext); 83} 84 85function getCallSignature(callSignature, parentApiDigest, ext) { 86 const className = parentApiDigest.getClassName(); 87 const rawText = callSignature.getText(); 88 const apiName = callSignature.name ? callSignature.name.getText() : className; 89 return wrapApiDigestInfo(callSignature, className, apiName, ApiType.CallSignature, rawText, ext); 90} 91 92/** 93 * 获取方法签名信息 94 * 95 * @param {ts.MethodSignature} methodSignature 方法签名节点 96 * @param {ApiDigestInfo} parentApiDigest 97 * @param {VisitExt} ext 98 * @returns {Object} 99 */ 100function getMethodSignatureInfo(methodSignature, parentApiDigest, ext) { 101 const className = parentApiDigest.getClassName(); 102 const rawText = methodSignature.getText(); 103 const apiName = methodSignature.name.getText(); 104 return wrapApiDigestInfo(methodSignature, className, apiName, ApiType.InterfaceMethod, rawText, ext); 105} 106 107/** 108 * 获取接口定义信息 109 * 110 * @param {ts.InterfaceDeclaration} interfaceDec 111 * @param {ApiDigestInfo} parentApiDigest 112 * @param {VisitExt} ext 113 * @returns {Object} 114 */ 115function getInterfaceDeclarationInfo(interfaceDec, parentApiDigest, ext) { 116 const className = interfaceDec.name.getText(); 117 let rawText = ''; 118 if (interfaceDec.modifiers) { 119 interfaceDec.modifiers.forEach((modifier) => { 120 rawText += ` ${modifier.getText()}`; 121 }); 122 } 123 rawText += ` interface ${className}`; 124 return wrapApiDigestInfo(interfaceDec, className, className, ApiType.InterfaceType, rawText, ext); 125} 126 127/** 128 * 获取类属性定义 129 * 130 * @param {ts.PropertyDeclaration} property 131 * @param {ApiDigestInfo} parentApiDigest 132 * @param {VisitExt} ext 133 * @returns {Object} 134 */ 135function getPropertyDeclarationInfo(property, parentApiDigest, ext) { 136 const className = parentApiDigest.getClassName(); 137 const apiName = property.name.getText(); 138 return wrapApiDigestInfo(property, className, apiName, ApiType.ClassProperty, property.getText(), ext); 139} 140 141/** 142 * 收集构造方法信息 143 * 144 * @param {ts.ConstructorDeclaration} constructorDec 构造方法定义节点 145 * @param {ApiDigestInfo} parentApiDigest 父类信息 146 * @param {Object} ext 扩展参数 147 * @returns {Object} 148 */ 149function getConstructorInfo(constructorDec, parentApiDigest, ext) { 150 const className = parentApiDigest ? parentApiDigest.getClassName() : ''; 151 const apiName = 'constructor'; 152 return wrapApiDigestInfo(constructorDec, className, apiName, ApiType.Constructor, constructorDec.getText(), ext); 153} 154 155/** 156 * 搜集 Class 定义的信息 157 * 158 * @param {ts.ClassDeclaration} classDec Class定义节点 159 * @param {ApiDigestInfo} parentApiDigest 父类摘要信息 160 * @param {VisitExt} ext 扩展参数 161 * @returns {Object} 162 */ 163function getClassDigestInfo(classDec, parentApiDigest, ext) { 164 const className = classDec.name ? classDec.name.text : ''; 165 let rawText = ''; 166 if (classDec.modifiers) { 167 classDec.modifiers.forEach((modifier) => { 168 rawText += ` ${modifier.getText()}`; 169 }); 170 } 171 rawText += ` class ${className}`; 172 return wrapApiDigestInfo(classDec, className, className, ApiType.ClassType, rawText, ext); 173} 174 175/** 176 * 获取 Enum 成员的摘要信息 177 * 178 * @param {ts.EnumMember} enumMember 179 * @param {ApiDigestInfo} parentApiDigest 180 * @param {VisitExt} ext 181 * @returns {Object} 182 */ 183function getEnumMemberInfo(enumMember, parentApiDigest, ext) { 184 const className = parentApiDigest ? parentApiDigest.getClassName() : ''; 185 const apiName = enumMember.name.getText(); 186 return wrapApiDigestInfo(enumMember, className, apiName, ApiType.EnumMember, enumMember.getText(), ext); 187} 188 189/** 190 * 获取 enum 定义的信息 191 * 192 * @param {ts.EnumDeclaration} enumNode Enum节点 193 * @param {ApiDigestInfo} parentApiDigest 父类摘要信息 194 * @param {VisitExt} ext 扩展参数 195 * @returns {Object} 196 */ 197function getEnumDigestInfo(enumNode, parentApiDigest, ext) { 198 const className = enumNode.name.getText(); 199 let rawText = ''; 200 if (enumNode.modifiers) { 201 enumNode.modifiers.forEach((modifier) => { 202 rawText += ` ${modifier.getText()}`; 203 }); 204 } 205 rawText += ` enum ${className}`; 206 return wrapApiDigestInfo(enumNode, className, className, ApiType.EnumType, rawText, ext); 207} 208 209/** 210 * 获取 function 定义的API信息 211 * 212 * @param {ts.FunctionDeclaration} fun 方法定义节点 213 * @param {ApiDigestInfo} parentApiDigest 父类的摘要信息 214 * @param {VisitExt} ext 扩展参数 215 * @returns {Object} 216 */ 217function getFunctionDigestInfo(fun, parentApiDigest, ext) { 218 const className = parentApiDigest ? parentApiDigest.getClassName() : ''; 219 const apiName = fun.name ? fun.name.getText() : ''; 220 return wrapApiDigestInfo(fun, className, apiName, ApiType.FunctionType, fun.getText(), ext); 221} 222 223/** 224 * 解析模块定义(namespace)类型。 225 * 226 * @param {ts.ModuleDeclaration} module 模块定义类型对象 227 * @param {VisitExt} ext 扩展参数 228 * @returns {Object} 229 */ 230function getModuleDigestInfo(module, parentApiDigest, ext) { 231 const className = module.name.getText(); 232 let rawText = ''; 233 module.forEachChild((child) => { 234 if (!ts.isModuleBlock(child)) { 235 rawText += ` ${child.getText()}`; 236 } 237 }); 238 return wrapApiDigestInfo(module, className, className, ApiType.NamespaceType, rawText, ext); 239} 240 241function getSourceFile(sourceFile, parentApiDigest, ext) { 242 const className = 'sourcefile'; 243 const rawText = 'sourcefile'; 244 return wrapApiDigestInfo(sourceFile, className, className, ApiType.SourceFile, rawText, ext); 245} 246/** 247 * 所有特定类型API的处理方法集合。 248 */ 249const apiDigestMethodMap = new Map([ 250 [ts.SyntaxKind.ModuleDeclaration, getModuleDigestInfo], 251 [ts.SyntaxKind.FunctionDeclaration, getFunctionDigestInfo], 252 [ts.SyntaxKind.EnumDeclaration, getEnumDigestInfo], 253 [ts.SyntaxKind.EnumMember, getEnumMemberInfo], 254 [ts.SyntaxKind.ClassDeclaration, getClassDigestInfo], 255 [ts.SyntaxKind.Constructor, getConstructorInfo], 256 [ts.SyntaxKind.PropertyDeclaration, getPropertyDeclarationInfo], 257 [ts.SyntaxKind.InterfaceDeclaration, getInterfaceDeclarationInfo], 258 [ts.SyntaxKind.MethodSignature, getMethodSignatureInfo], 259 [ts.SyntaxKind.PropertySignature, getPropertySignatureInfo], 260 [ts.SyntaxKind.MethodDeclaration, getMethodDeclarationInfo], 261 [ts.SyntaxKind.TypeAliasDeclaration, getTypeAliasDeclarationInfo], 262 [ts.SyntaxKind.CallSignature, getCallSignature], 263 [ts.SyntaxKind.SourceFile, getSourceFile], 264]); 265 266/** 267 * 解析节点的JSDoc信息,可能包含多段。 268 * 269 * @param {string[]} jsdocText 270 * @returns {Array} 271 */ 272function parseJSDocs(jsdocText) { 273 return parse(jsdocText); 274} 275 276/** 277 * 获取节点JSDoc的文本 278 * 279 * @param {ts.Node} astNode 280 * @returns {string} 281 */ 282function getNodeLeadingJSDoc(astNode) { 283 if (astNode.kind === ts.SyntaxKind.SourceFile) { 284 return ''; 285 } 286 const sourceFile = astNode.getSourceFile(); 287 const leadingCommentRange = ts.getLeadingCommentRanges(sourceFile.getFullText(), astNode.getFullStart()); 288 if (!leadingCommentRange) { 289 return undefined; 290 } 291 let commentText = ''; 292 leadingCommentRange.map((range) => { 293 commentText += sourceFile.getFullText().slice(range.pos, range.end); 294 commentText += '\n'; 295 }); 296 return commentText; 297} 298 299/** 300 * 获取API的摘要信息 301 * 302 * @param {ts.Node} astNode ast 节点对象 303 * @param {VisitExt} ext 可扩展参数对象 304 * @returns {@link ApiDigestInfo} 305 */ 306function getApiDigestInfo(astNode, parentApiDigest, ext) { 307 const digestMethod = apiDigestMethodMap.get(astNode.kind); 308 if (digestMethod) { 309 return digestMethod(astNode, parentApiDigest, ext); 310 } 311 return undefined; 312} 313 314/** 315 * 非 API 节点的摘要信息,用于向上追溯父类API信息。 316 * 317 * @param {ts.Node} astNode 318 * @returns {ApiDigestInfo} ApiDigestInfo 319 */ 320function getDummyApiDigestInfo(astNode) { 321 const dummyDigestInfo = new ApiDigestInfo(); 322 dummyDigestInfo.setAstNode(astNode); 323 return dummyDigestInfo; 324} 325 326/** 327 * 是否需要遍历特定节点类型的子节点。 328 * 329 * @param {ts.Node} astNode 330 * @returns {Boolean} true or false 331 */ 332function shouldVisitChildren(astNode) { 333 return ts.isModuleDeclaration(astNode) || ts.isEnumDeclaration(astNode) || ts.isInterfaceDeclaration(astNode) || 334 ts.isClassDeclaration(astNode) || ts.isModuleBlock(astNode) || ts.isSourceFile(astNode); 335} 336 337/** 338 * 递归遍历AST树的回调方法。 339 * 340 * @param {ts.Node} astNode ast节点对象 341 * @param {Map} apiMap api摘要信息的集合 342 * @param {ApiDigestInfo} parentApiDigest 父节点摘要信息 343 * @param {VisitExt} ext 扩展参数 344 */ 345function visitAstNode(astNode, apiMap, parentApiDigest, ext) { 346 let apiDigestInfo = getApiDigestInfo(astNode, parentApiDigest, ext); 347 if (apiDigestInfo) { 348 apiDataHelper.addApiDigestInfo(apiMap, apiDigestInfo); 349 } else { 350 apiDigestInfo = getDummyApiDigestInfo(astNode); 351 } 352 apiDigestInfo.setParent(parentApiDigest); 353 if (shouldVisitChildren(astNode)) { 354 astNode.forEachChild((child) => { 355 visitAstNode(child, apiMap, apiDigestInfo, ext); 356 }); 357 } 358} 359 360class VisitExt { 361 constructor(packageName, filePath) { 362 this.packageName = packageName; 363 this.dtsPath = filePath; 364 } 365} 366 367/** 368 * 搜集API的主入口 369 * 370 * @param {string} filePath d.ts路径 371 * @param {string} rootDir sdk根目录 372 * @returns {Map} api集合 373 */ 374function collectApi(filePath, rootDir, resultMap) { 375 const apiMap = resultMap ? resultMap : new Map(); 376 const apiFileName = path.basename(filePath, '.d.ts'); 377 const fileContent = fs.readFileSync(filePath, 'utf-8'); 378 const sourceFile = ts.createSourceFile(apiFileName, fileContent, ts.ScriptTarget.ES2017, true); 379 const isArkUI = isInDirectory(path.resolve(rootDir, 'component'), filePath); 380 const packageName = isArkUI ? 'ArkUI' : path.relative(rootDir, filePath); 381 const dtsPath = path.relative(rootDir, filePath).replace(/\\/g, '/'); 382 visitAstNode(sourceFile, apiMap, undefined, new VisitExt(packageName, dtsPath)); 383 return apiMap; 384} 385 386exports.ApiCollector = { 387 collectApi: collectApi, 388};