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 { FileSystem } = require('./utils'); 17const path = require('path'); 18const ts = require('typescript'); 19const fs = require('fs'); 20 21class SystemApiRecognizer { 22 constructor(systemRoot) { 23 this.systemRoot = systemRoot; 24 this.arkUIDecorator = new Set(['@Builder', '@Styles', '@Extend']); 25 this.arkUIRender = new Set(['build']); 26 this.componentAttributeMap = new Map(); 27 this.componentAttributeTypeSymbolMap = new Map(); 28 this.forEachComponents = new Set(['LazyForEach', 'ForEach']); 29 } 30 31 /** 32 * 遍历AST, 识别系统API调用。 33 * 34 * @param {ts.Node} node 35 * @param {string} fileName 36 */ 37 visitNode(node, fileName) { 38 if (this.isArkUIRenderMethod(node)) { 39 this.visitUIRenderNode(node.body, fileName); 40 } else { 41 this.visitNormalNode(node, fileName); 42 } 43 } 44 45 /** 46 * 遍历访问TypeScript 节点 47 * 48 * @param {ts.Node} node 49 * @param {string} fileName 50 */ 51 visitNormalNode(node, fileName) { 52 if (node) { 53 if (ts.isCallExpression(node)) { 54 this.recognizeNormalCallExpression(node, fileName); 55 } else { 56 this.recognizeNormal(node, fileName); 57 ts.forEachChild(node, (child) => { 58 this.visitNode(child, fileName); 59 }); 60 } 61 } 62 } 63 64 /** 65 * 遍历访问 UI 渲染节点 66 * 67 * @param {ts.Block} node 68 * @param {string} fileName 69 */ 70 visitUIRenderNode(node, fileName) { 71 node.statements.forEach((statement) => { 72 this.recognizeUIComponents(statement, fileName); 73 }); 74 } 75 76 setTypeChecker(typeChecker) { 77 this.typeChecker = typeChecker; 78 } 79 80 /** 81 * 保存系统API 82 * 83 * @param {ApiDeclarationInformation} apiInfo 84 */ 85 addApiInformation(apiInfo) { 86 if (!this.apiInfos) { 87 this.apiInfos = []; 88 } 89 this.apiInfos.push(apiInfo); 90 } 91 92 getApiInformations() { 93 const apiDecInfos = this.apiInfos ? this.apiInfos : []; 94 apiDecInfos.forEach((apiInfo) => { 95 apiInfo.setApiNode(undefined); 96 }); 97 return apiDecInfos; 98 } 99 100 isSdkApi(apiFilePath) { 101 return FileSystem.isInDirectory(this.systemRoot, apiFilePath); 102 } 103 104 /** 105 * 判断是否为创建ArkUI组件的方法 106 * 107 * @param {ts.Node} node 108 */ 109 isArkUIRenderMethod(node) { 110 if (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) { 111 return this.isBuildMethodInStruct(node) || this.hasArkUIDecortor(node); 112 } 113 return false; 114 } 115 116 /** 117 * 是否为struct 中的 build 方法。 118 * 119 * @param {ts.Node} node 120 * @returns ture or false 121 */ 122 isBuildMethodInStruct(node) { 123 return node.name && this.arkUIRender.has(node.name.getText()) && ts.isStructDeclaration(node.parent); 124 } 125 126 /** 127 * 判断是否有ArkUI注解 128 * 129 * @param {ts.Node} node 130 * @returns true or false 131 */ 132 hasArkUIDecortor(node) { 133 if (node.decorators) { 134 for (const decorator of node.decorators) { 135 const decoratorName = this.getDecortorIdefiner(decorator); 136 if (!decoratorName) { 137 continue; 138 } 139 const decoratorStr = `@${decoratorName.getText()}`; 140 if (!this.arkUIDecorator.has(decoratorStr)) { 141 continue; 142 } 143 const decoratroSymbol = this.typeChecker.getSymbolAtLocation(decoratorName); 144 if (this.isSdkApi(decoratroSymbol.valueDeclaration.getSourceFile().fileName)) { 145 return true; 146 } 147 } 148 } 149 return false; 150 } 151 152 getDecortorIdefiner(decoratorExp) { 153 if (ts.isDecorator(decoratorExp) || ts.isCallExpression(decoratorExp)) { 154 return this.getDecortorIdefiner(decoratorExp.expression); 155 } 156 return ts.isIdentifier(decoratorExp) ? decoratorExp : undefined; 157 } 158 159 /** 160 * 获取AST节点的API信息。 161 * 162 * @param {ts.Node} node 163 * @param {string} fileName 164 * @param {Function} positionCallback 165 * @returns {ApiDeclarationInformation | undefined} apiDecInfo 166 */ 167 recognizeApiWithNode(node, fileName, positionCallback, useDeclarations) { 168 if (!node) { 169 return undefined; 170 } 171 const symbol = this.typeChecker.getSymbolAtLocation(node); 172 return this.recognizeApiWithNodeAndSymbol(node, symbol, fileName, positionCallback, useDeclarations); 173 } 174 175 recognizeApiWithNodeAndSymbol(node, symbol, fileName, positionCallback, useDeclarations) { 176 if (symbol) { 177 const apiDecInfo = this.getSdkApiDeclarationWithSymbol(symbol, node, useDeclarations); 178 if (apiDecInfo) { 179 apiDecInfo.setPosition(ts.getLineAndCharacterOfPosition(node.getSourceFile(), positionCallback(node))); 180 apiDecInfo.setSourceFileName(fileName); 181 this.addApiInformation(apiDecInfo); 182 return apiDecInfo; 183 } 184 } 185 return undefined; 186 } 187 188 /** 189 * 识别TS代码节点中的系统API 190 * 191 * @param {ts.Node} node 192 * @param {string} fileName 193 */ 194 recognizeNormal(node, fileName) { 195 if (ts.isPropertyAccessExpression(node)) { 196 this.recognizePropertyAccessExpression(node, fileName); 197 } else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) { 198 this.recognizeHeritageClauses(node, fileName); 199 } else if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) { 200 this.recognizeApiWithNode(node.expression, fileName, (node) => node.getStart()); 201 } else if (ts.isStructDeclaration(node)) { 202 this.recognizeHeritageClauses(node, fileName); 203 } 204 } 205 206 recognizeNormalCallExpression(node, fileName) { 207 if (!node) { 208 return undefined; 209 } 210 if (ts.isCallExpression(node)) { 211 const apiDecInfo = this.recognizeNormalCallExpression(node.expression, fileName); 212 this.recognizeNormalCallExpressionArguments(apiDecInfo, node.arguments, fileName); 213 return apiDecInfo; 214 } else if (ts.isPropertyAccessExpression(node)) { 215 this.recognizeNormalCallExpression(node.expression, fileName); 216 return this.recognizePropertyAccessExpression(node, fileName); 217 } else { 218 return undefined; 219 } 220 } 221 222 recognizeNormalCallExpressionArguments(apiDecInfo, args, fileName) { 223 if (args.length === 0) { 224 return; 225 } 226 const parameters = apiDecInfo ? apiDecInfo.apiNode.parameters : undefined; 227 args.forEach((arg, index) => { 228 // interface 定义作为函数入参时, 统计为API 229 if (parameters && parameters[index] && parameters[index].type) { 230 const paramType = parameters[index].type; 231 if (ts.isTypeReferenceNode(paramType)) { 232 const paramTypeApiDecInfo = this.recognizeApiWithNode(paramType.typeName, fileName, (node) => node.getStart(), true); 233 if (paramTypeApiDecInfo) { 234 this.modifyTypeReferenceSourceFileName(paramType.typeName, paramTypeApiDecInfo); 235 paramTypeApiDecInfo.setApiType('interface'); 236 paramTypeApiDecInfo.setPosition(ts.getLineAndCharacterOfPosition(arg.getSourceFile(), arg.getStart())); 237 } 238 } 239 } 240 this.recognizeArgument(arg, fileName); 241 }); 242 } 243 244 modifyTypeReferenceSourceFileName(typeNameNode, apiDecInfo) { 245 const symbol = this.typeChecker.getSymbolAtLocation(typeNameNode); 246 if (symbol) { 247 const typeDec = symbol.declarations[0]; 248 let importDec = typeDec; 249 while (importDec && !ts.isImportDeclaration(importDec)) { 250 importDec = importDec.parent; 251 } 252 if (!importDec) { 253 return; 254 } 255 const moduleSpecifier = importDec.moduleSpecifier; 256 this.saveApiDecInfo(moduleSpecifier, apiDecInfo, typeNameNode); 257 } 258 } 259 260 saveApiDecInfo(moduleSpecifier, apiDecInfo, typeNameNode) { 261 const specialInterfaceSet = new Set(['Callback', 'AsyncCallback']); 262 if (ts.isStringLiteral(moduleSpecifier, apiDecInfo)) { 263 const useTypeFileName = apiDecInfo.apiNode.getSourceFile().fileName; 264 const moduleRelativePaths = moduleSpecifier.getText().match(/^['"](.*)['"]$/); 265 if (moduleRelativePaths.length < 2) { 266 return; 267 } 268 const modulePath = path.resolve(path.dirname(useTypeFileName), `${moduleRelativePaths[1]}.d.ts`); 269 if (fs.existsSync(modulePath)) { 270 if (specialInterfaceSet.has(typeNameNode.getText())) { 271 apiDecInfo.apiText = this.getSpecialInterfaceText(modulePath, typeNameNode.getText()); 272 } 273 const dtsPath = path.relative(this.systemRoot, modulePath).replace(/\\/g, '/'); 274 apiDecInfo.dtsName = path.basename(modulePath); 275 apiDecInfo.packageName = path.relative(this.systemRoot, modulePath); 276 apiDecInfo.dtsPath = this.formatDtsPath(dtsPath); 277 apiDecInfo.typeName = apiDecInfo.propertyName; 278 } 279 } 280 } 281 282 getSpecialInterfaceText(modulePath, interfaceName) { 283 const fileContent = fs.readFileSync(modulePath, 'utf-8'); 284 const apiFileName = path.basename(modulePath).replace('d.ts', '.ts'); 285 const sourceFile = ts.createSourceFile(apiFileName, fileContent, ts.ScriptTarget.ES2017, true); 286 let interfaceText = ''; 287 sourceFile.statements.forEach(stat => { 288 if (ts.isInterfaceDeclaration(stat) && stat.name.escapedText === interfaceName) { 289 interfaceText = stat.getText().split('{')[0]; 290 } 291 }); 292 return interfaceText; 293 } 294 295 formatDtsPath(dtsPath) { 296 if (dtsPath.indexOf('api/@internal/full/canvaspattern.d.ts') > -1) { 297 return dtsPath.replace('api/@internal/full/', 'interface/sdk-js/api/common/full/'); 298 } else if (dtsPath.indexOf('api/@internal/full/featureability.d.ts') > -1) { 299 return dtsPath.replace('api/@internal/full/', '/interface/sdk-js/api/common/full/'); 300 } else if (dtsPath.indexOf('api/internal/full') > -1) { 301 return dtsPath.replace('api/@internal/full/', '/interface/sdk-js/api/@internal/ets/'); 302 } else if (dtsPath.indexOf('component/') > -1) { 303 return dtsPath.replace('component/', 'interface/sdk-js/api/@internal/component/ets/'); 304 } else { 305 return path.join('/interface/sdk-js', dtsPath).replace(/\\/g, '/'); 306 } 307 } 308 309 /** 310 * 识别UI组件及其属性 311 * 312 * @param {ts.Node} node 313 * @param {string} fileName 314 */ 315 recognizeUIComponents(node, fileName) { 316 if (ts.isEtsComponentExpression(node)) { 317 // ETS组件的声明, EtsComponentExpression 表示有子组件的组件. 318 this.recognizeEtsComponentExpression(node, fileName); 319 } else if (ts.isCallExpression(node)) { 320 // 组件链式调用 321 this.recognizeComponentAttributeChain(node, fileName, []); 322 } else if (ts.isIfStatement(node)) { 323 this.recognizeIfStatement(node, fileName); 324 } else { 325 ts.forEachChild(node, (child) => { 326 this.recognizeUIComponents(child, fileName); 327 }); 328 } 329 } 330 331 /** 332 * 识别条件渲染 333 * @param {ts.Node} node 334 * @param {string} fileName 335 */ 336 recognizeIfStatement(node, fileName) { 337 this.recognizeArgument(node.expression, fileName); 338 const thenStatements = ts.isBlock(node) ? node.statements : 339 (ts.isBlock(node.thenStatement) ? node.thenStatement.statements : undefined); 340 if (thenStatements) { 341 thenStatements.forEach((child) => { 342 this.recognizeUIComponents(child, fileName); 343 }); 344 } 345 if (node.elseStatement) { 346 this.recognizeIfStatement(node.elseStatement, fileName); 347 } 348 } 349 350 /** 351 * 识别组件链式调用中的API 352 * @param {ts.Node} node 353 * @param {string} fileName 354 */ 355 recognizeComponentAttributeChain(node, fileName) { 356 if (ts.isCallExpression(node)) { 357 const chainResult = this.recognizeComponentAttributeChain(node.expression, fileName); 358 this.recognizeArguments(node.arguments, fileName); 359 return new ComponentAttrResult(chainResult.componentInfo, undefined); 360 } else if (ts.isPropertyAccessExpression(node)) { 361 const chainResult = this.recognizeComponentAttributeChain(node.expression, fileName); 362 const attrInfo = this.recognizeEtsComponentAndAttributeApi(node, fileName); 363 if (chainResult.componentInfo && attrInfo) { 364 attrInfo.setComponentName(chainResult.componentInfo.propertyName); 365 } 366 return new ComponentAttrResult(chainResult.componentInfo, attrInfo); 367 } else if (ts.isEtsComponentExpression(node)) { 368 return new ComponentAttrResult(this.recognizeEtsComponentExpression(node, fileName), undefined); 369 } else if (ts.isIdentifier(node)) { 370 return new ComponentAttrResult(this.recognizeEtsComponentAndAttributeApi(node, fileName), undefined); 371 } else { 372 return new ComponentAttrResult(undefined, undefined); 373 } 374 } 375 376 /** 377 * 识别ArkUI组件表达式 378 * @param {ts.EtsComponentExpression} node 379 * @param {string} fileName 380 * @returns 381 */ 382 recognizeEtsComponentExpression(node, fileName) { 383 // node.expression is component name Identifier 384 const apiDecInfo = this.recognizeEtsComponentAndAttributeApi(node.expression, fileName); 385 386 if (node.arguments) { 387 this.recognizeComponentArguments(apiDecInfo, node.arguments, fileName); 388 } 389 390 if (node.body) { 391 node.body.statements.forEach((statement) => { 392 this.recognizeUIComponents(statement, fileName); 393 }); 394 } 395 396 return apiDecInfo; 397 } 398 399 /** 400 * 识别组件入参 401 * @param {ApiDeclarationInformation} apiDecInfo 402 * @param {ts.Node[]} args 403 * @param {string} fileName 404 */ 405 recognizeComponentArguments(apiDecInfo, args, fileName) { 406 // ForEach, LazyForEach 407 if (apiDecInfo && this.forEachComponents.has(apiDecInfo.propertyName)) { 408 args.forEach((arg, index) => { 409 // itemGenerator 410 if (index === 1) { 411 this.visitUIRenderNode(arg.body ? arg.body : arg, fileName); 412 } else { 413 this.recognizeArgument(arg, fileName); 414 } 415 }); 416 } else { 417 this.recognizeArguments(args, fileName); 418 } 419 } 420 421 /** 422 * 识别参数中的API 423 * @param {ts.Node[]} args 424 * @param {string} fileName 425 */ 426 recognizeArguments(args, fileName) { 427 args.forEach((arg) => { 428 this.recognizeArgument(arg, fileName); 429 }); 430 } 431 432 recognizeArgument(node, fileName) { 433 if (!node) { 434 return; 435 } 436 this.recognizeNormal(node, fileName); 437 ts.forEachChild(node, (child) => { 438 this.recognizeArgument(child, fileName); 439 }); 440 } 441 442 443 /** 444 * 获取组件或属性的API详情 445 * 446 * @param {ts.Identifier} node 447 * @param {string} fileName 448 * @returns {ApiDeclarationInformation | undefined} apiDecInfo 449 */ 450 recognizeEtsComponentAndAttributeApi(node, fileName) { 451 // recognize component 452 const apiDecInfo = this.recognizeApiWithNode(node, fileName, 453 (node) => ts.isPropertyAccessExpression(node) ? node.name.getStart() : node.getStart()); 454 return apiDecInfo; 455 } 456 457 /** 458 * 通过语义分析,识别继承关系中的系统API 459 * 460 * @param {ts.ClassDeclaration | ts.InterfaceDeclaration} node 461 * @param {ts.ClassDeclaration|ts.InterfaceDeclaration} node 462 */ 463 recognizeHeritageClauses(node, fileName) { 464 if (!node.heritageClauses) { 465 return; 466 } 467 const nodeType = this.typeChecker.getTypeAtLocation(node); 468 const nodeSymbol = nodeType.getSymbol(); 469 if (!nodeSymbol.members) { 470 return; 471 } 472 const heritagePropertyMap = this.collectAllHeritageMethods(node); 473 if (!nodeSymbol.valueDeclaration) { 474 return; 475 } 476 if (!nodeSymbol.valueDeclaration.heritageClauses) { 477 return; 478 } 479 if (heritagePropertyMap.size === 0) { 480 const extendNodes = nodeSymbol.valueDeclaration.heritageClauses[0].types; 481 this.getExtendClassPropertyMap(extendNodes, heritagePropertyMap); 482 } 483 if (heritagePropertyMap === 0) { 484 return; 485 } 486 nodeSymbol.members.forEach((memberSymbol, memberName) => { 487 if (heritagePropertyMap.has(memberName)) { 488 const apiDecInfo = this.getSdkApiDeclarationWithSymbol(heritagePropertyMap.get(memberName), undefined); 489 if (apiDecInfo) { 490 apiDecInfo.setPosition(ts.getLineAndCharacterOfPosition(node.getSourceFile(), memberSymbol.valueDeclaration.getStart())); 491 apiDecInfo.setSourceFileName(fileName); 492 this.addApiInformation(apiDecInfo); 493 } 494 } 495 }); 496 } 497 498 /** 499 * 通过向上查找继承的节点,收集多次继承情况下的API 500 * @param {ts.Node} extendNodes 501 * @param {Map} extendClassPropertyMap 502 */ 503 getExtendClassPropertyMap(extendNodes, extendClassPropertyMap) { 504 extendNodes.forEach(extendNode => { 505 const extendNodeType = this.typeChecker.getTypeAtLocation(extendNode); 506 if (!extendNodeType) { 507 return; 508 } 509 const extendNodeSymbol = extendNodeType.getSymbol(); 510 if (!extendNodeSymbol) { 511 return; 512 } 513 const valueDeclaration = extendNodeSymbol.declarations[0]; 514 if (valueDeclaration && !this.isSdkApi(valueDeclaration.getSourceFile().fileName) && extendNodeSymbol.valueDeclaration && 515 extendNodeSymbol.valueDeclaration.heritageClauses) { 516 const parentNodes = extendNodeSymbol.valueDeclaration.heritageClauses[0].types; 517 this.getExtendClassPropertyMap(parentNodes, extendClassPropertyMap); 518 } else { 519 extendNodeSymbol.members.forEach((memberSymbol, memberName) => { 520 extendClassPropertyMap.set(memberName, memberSymbol); 521 }); 522 } 523 }); 524 } 525 526 /** 527 * 搜集所有父类(属于SDK中的API)的方法和属性。 528 * 529 * @param {ts.Node} node 530 * @returns Map 531 */ 532 collectAllHeritageMethods(node) { 533 const heritageMembers = new Map(); 534 if (!node.heritageClauses) { 535 return heritageMembers; 536 } 537 node.heritageClauses.forEach((heritage) => { 538 heritage.types.forEach((child) => { 539 const childSymbol = this.typeChecker.getSymbolAtLocation(child.expression); 540 if (!childSymbol) { 541 return; 542 } 543 const type = this.typeChecker.getTypeOfSymbolAtLocation(childSymbol, child); 544 const typeSymbol = type.getSymbol(); 545 if (!typeSymbol) { 546 return; 547 } 548 if (!typeSymbol && childSymbol.members) { 549 childSymbol.members.forEach((memberSymbol, memberName) => { 550 heritageMembers.set(memberName, memberSymbol); 551 }); 552 return; 553 } 554 const valueDeclaration = typeSymbol.valueDeclaration; 555 if (!valueDeclaration || !this.isSdkApi(valueDeclaration.getSourceFile().fileName)) { 556 return; 557 } 558 typeSymbol.members.forEach((memberSymbol, memberName) => { 559 heritageMembers.set(memberName, memberSymbol); 560 }); 561 }); 562 }); 563 return heritageMembers; 564 } 565 566 /** 567 * 解析属性访问 568 * 569 * @param {ts.PropertyAccessExpression} node 570 */ 571 recognizePropertyAccessExpression(node, fileName) { 572 return this.recognizeApiWithNode(node, fileName, (node) => node.name.getStart()); 573 } 574 575 /** 576 * 获取 export {xx} 中xx的定义 577 * @param {ts.Symbol} symbol 578 */ 579 getAliasExcludesRealValueDeclarations(symbol) { 580 const symbolName = symbol.escapedName; 581 const sourceFile = symbol.declarations[0].getSourceFile(); 582 const realSymbol = sourceFile.locals ? sourceFile.locals.get(symbolName) : undefined; 583 return realSymbol ? realSymbol.valueDeclaration : undefined; 584 } 585 586 getApiRawText(node) { 587 switch (node.kind) { 588 case ts.SyntaxKind.ClassDeclaration: 589 return `class ${node.name ? node.name.getText() : ''}`; 590 case ts.SyntaxKind.InterfaceDeclaration: 591 return `interface ${node.name ? node.name.getText() : ''}`; 592 case ts.SyntaxKind.EnumDeclaration: 593 return `enum ${node.name ? node.name.getText() : ''}`; 594 case ts.SyntaxKind.ModuleDeclaration: 595 return `namespace ${node.name ? node.name.getText() : ''}`; 596 case node.getText() === 'AsyncCallback' || node.getText() === 'Callback': 597 return; 598 default: 599 return node.getText(); 600 } 601 } 602 603 getApiTypeName(node) { 604 if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || 605 ts.isModuleDeclaration(node)) { 606 return node.name ? node.name.getText() : ''; 607 } 608 return undefined; 609 } 610 611 /** 612 * 获取节点定义在哪个文件哪个类型下。 613 * 614 * @param {ts.Symbol} type 615 */ 616 getSdkApiDeclarationWithSymbol(symbol, node, useDeclarations) { 617 const sourceSymbol = symbol; 618 let valudeDec = sourceSymbol && sourceSymbol.valueDeclaration ? sourceSymbol.valueDeclaration : 619 (sourceSymbol.flags & ts.SymbolFlags.AliasExcludes ? this.getAliasExcludesRealValueDeclarations(symbol) : undefined); 620 valudeDec = !valudeDec && useDeclarations ? symbol.declarations[0] : valudeDec; 621 if (!valudeDec) { 622 return undefined; 623 } 624 if (valudeDec.kind === ts.SyntaxKind.TypeParameter) { 625 return undefined; 626 } 627 const sourceFile = valudeDec.getSourceFile(); 628 if (!this.isSdkApi(sourceFile.fileName)) { 629 return undefined; 630 } 631 let apiDecInfo = undefined; 632 if (symbol.declarations.length > 1) { 633 apiDecInfo = this.getSdkApiFromMultiDeclarations(symbol, node); 634 } 635 return apiDecInfo ? apiDecInfo : this.getSdkApiFromValueDeclaration(valudeDec); 636 } 637 638 getSdkApiFromMultiDeclarations(symbol, node) { 639 if (!node) { 640 return undefined; 641 } 642 let callExpressionNode = node; 643 while (callExpressionNode && !ts.isCallExpression(callExpressionNode)) { 644 callExpressionNode = callExpressionNode.parent; 645 } 646 if (!callExpressionNode) { 647 return undefined; 648 } 649 const matchedDec = this.findBestMatchedDeclaration(callExpressionNode, symbol); 650 if (!matchedDec) { 651 return undefined; 652 } 653 return this.getSdkApiFromValueDeclaration(matchedDec); 654 } 655 656 /** 657 * 查找参数个数匹配,参数类型匹配的API 658 * @param {number} argumentLength 659 * @param {ts.Symbol} symbol 660 * @returns api declaration node. 661 */ 662 findBestMatchedDeclaration(callExpressionNode, symbol) { 663 const callExpArgLen = callExpressionNode.arguments.length; 664 let matchedDecs = []; 665 for (let dec of symbol.declarations) { 666 if (dec.parameters && dec.parameters.length === callExpArgLen) { 667 matchedDecs.push(dec); 668 } 669 } 670 if (matchedDecs.length === 1) { 671 return matchedDecs[0]; 672 } 673 if ('on' === callExpressionNode.expression.name.getText()) { 674 return matchedDecs[0]; 675 } 676 const lastArgument = callExpressionNode.arguments[callExpArgLen - 1]; 677 if (this.isAsyncCallbackCallExp(lastArgument)) { 678 matchedDecs = matchedDecs.filter((value) => { 679 return this.isAsyncCallbackApi(value); 680 }); 681 } else { 682 matchedDecs = matchedDecs.filter((value) => { 683 return !this.isAsyncCallbackApi(value); 684 }); 685 } 686 return matchedDecs.length > 0 ? matchedDecs[0] : undefined; 687 } 688 689 isAsyncCallbackCallExp(node) { 690 const type = this.typeChecker.getTypeAtLocation(node); 691 if (!type || !type.symbol || !type.symbol.valueDeclaration) { 692 return false; 693 } 694 const typeValueDec = type.symbol.valueDeclaration; 695 return ts.isArrowFunction(typeValueDec) || ts.isMethodDeclaration(typeValueDec) || 696 ts.isFunctionDeclaration(typeValueDec) || ts.isMethodSignature(typeValueDec) || 697 ts.isFunctionExpression(typeValueDec); 698 } 699 700 isAsyncCallbackApi(declaration) { 701 if (!declaration.parameters) { 702 return false; 703 } 704 const lastArgument = declaration.parameters[declaration.parameters.length - 1]; 705 const argumentType = this.typeChecker.getTypeAtLocation(lastArgument); 706 if (argumentType && argumentType.symbol) { 707 return argumentType.symbol.escapedName === 'AsyncCallback'; 708 } else { 709 if (!lastArgument.type || !lastArgument.type.typeName) { 710 return false; 711 } 712 return 'AsyncCallback' === lastArgument.type.typeName.getText(); 713 } 714 } 715 716 getSdkApiFromValueDeclaration(valudeDec) { 717 const sourceFile = valudeDec.getSourceFile(); 718 const apiInfo = new ApiDeclarationInformation(); 719 const dtsPath = sourceFile.fileName.replace(this.systemRoot.replace(/\\/g, '/'), ''); 720 apiInfo.setPropertyName(this.getMethodOrTypeName(valudeDec)); 721 apiInfo.setApiRawText(this.getApiRawText(valudeDec)); 722 apiInfo.setPackageName(this.getPackageName(sourceFile.fileName)); 723 apiInfo.setSdkFileName(path.basename(sourceFile.fileName)); 724 apiInfo.setTypeName(this.getApiTypeName(valudeDec)); 725 apiInfo.setApiNode(valudeDec); 726 apiInfo.setApiType(this.getApiTypeName(valudeDec)); 727 apiInfo.setDtsPath(this.formatDtsPath(dtsPath)); 728 apiInfo.setCompletedText(this.getApiText(valudeDec)); 729 730 let curDeclaration = valudeDec.parent; 731 while (!ts.isSourceFile(curDeclaration)) { 732 const nodeName = this.getMethodOrTypeName(curDeclaration); 733 if (nodeName) { 734 apiInfo.setTypeName(nodeName); 735 apiInfo.setQualifiedTypeName(nodeName); 736 } 737 curDeclaration = curDeclaration.parent; 738 } 739 const qualifiedName = apiInfo.qualifiedTypeName ? 740 `${apiInfo.qualifiedTypeName}.${apiInfo.propertyName}` : `${apiInfo.propertyName}`; 741 apiInfo.setQualifiedName(qualifiedName); 742 return this.fillJSDocInformation(apiInfo, valudeDec); 743 } 744 745 getApiType(valudeDec) { 746 let type = apiType.get(valudeDec.kind); 747 if (ts.isPropertySignature(valudeDec) && valudeDec.type && ts.isFunctionTypeNode(valudeDec.type)) { 748 type = 'method'; 749 } 750 return type; 751 } 752 753 getApiText(node) { 754 switch (node.kind) { 755 case ts.SyntaxKind.ClassDeclaration: 756 return node.getText().split('{')[0]; 757 case ts.SyntaxKind.InterfaceDeclaration: 758 return node.getText().split('{')[0]; 759 case ts.SyntaxKind.EnumDeclaration: 760 return node.getText().split('{')[0]; 761 case ts.SyntaxKind.ModuleDeclaration: 762 return node.getText().split('{')[0]; 763 default: 764 return node.getText(); 765 } 766 } 767 768 /** 769 * 补充JsDoc中的信息。 770 * 771 * @param {ApiDeclarationInformation} apiInfo 772 * @param {ts.Node} node 773 * @returns 774 */ 775 fillJSDocInformation(apiInfo, node) { 776 this.forEachJsDocTags(node, (tag) => { 777 (this.fillDeprecatedInfo(tag, apiInfo) || this.fillUseInsteadInfo(tag, apiInfo)); 778 }); 779 if (!apiInfo.deprecated) { 780 const thiz = this; 781 function reachJsDocTag(tag) { 782 thiz.fillDeprecatedInfo(tag, apiInfo); 783 return apiInfo.deprecated; 784 } 785 786 function reachParent(parent) { 787 thiz.forEachJsDocTags(parent, reachJsDocTag); 788 return apiInfo.deprecated; 789 } 790 this.forEachNodeParent(node, reachParent); 791 } 792 return apiInfo; 793 } 794 795 fillUseInsteadInfo(tag, apiInfo) { 796 if (tag.kind === ts.SyntaxKind.JSDocTag && 797 tag.tagName.getText() === 'useinstead') { 798 apiInfo.setUseInstead(tag.comment); 799 return true; 800 } 801 return false; 802 } 803 804 fillDeprecatedInfo(tag, apiInfo) { 805 if (tag.kind === ts.SyntaxKind.JSDocDeprecatedTag) { 806 apiInfo.setDeprecated(tag.comment); 807 return true; 808 } 809 return false; 810 } 811 812 forEachJsDocTags(node, callback) { 813 if (!node.jsDoc || !callback) { 814 return; 815 } 816 const latestJsDoc = node.jsDoc[node.jsDoc.length - 1]; 817 if (!latestJsDoc.tags) { 818 return; 819 } 820 for (const tag of latestJsDoc.tags) { 821 if (callback(tag)) { 822 break; 823 }; 824 }; 825 } 826 827 forEachNodeParent(node, callback) { 828 if (!node || !callback) { 829 return; 830 } 831 let curNode = node.parent; 832 while (curNode) { 833 if (callback(curNode)) { 834 break; 835 } 836 curNode = curNode.parent; 837 } 838 } 839 840 getMethodOrTypeName(node) { 841 return node.name ? node.name.getText() : undefined; 842 } 843 844 getPackageName(filePath) { 845 const isArkUI = FileSystem.isInDirectory(path.resolve(this.systemRoot, 'component'), filePath); 846 return isArkUI ? 'ArkUI' : path.relative(this.systemRoot, filePath); 847 } 848} 849 850const apiType = new Map([ 851 [ts.SyntaxKind.FunctionDeclaration, 'method'], 852 [ts.SyntaxKind.MethodSignature, 'method'], 853 [ts.SyntaxKind.MethodDeclaration, 'method'], 854 [ts.SyntaxKind.EnumMember, 'enum_instance'], 855 [ts.SyntaxKind.PropertySignature, 'field'], 856 [ts.SyntaxKind.VariableStatement, 'constant'], 857 [ts.SyntaxKind.VariableDeclaration, 'constant'], 858 [ts.SyntaxKind.VariableDeclarationList, 'constant'], 859 [ts.SyntaxKind.TypeAliasDeclaration, 'type'], 860 [ts.SyntaxKind.ClassDeclaration, 'class'], 861 [ts.SyntaxKind.InterfaceDeclaration, 'interface'], 862 [ts.SyntaxKind.EnumDeclaration, 'enum_class'], 863 [ts.SyntaxKind.PropertyDeclaration, 'field'] 864]); 865 866class ComponentAttrResult { 867 constructor(componentInfo, attrInfo) { 868 this.componentInfo = componentInfo; 869 this.attrInfo = attrInfo; 870 } 871} 872 873class ApiDeclarationInformation { 874 constructor() { 875 this.dtsName = ''; 876 this.packageName = ''; 877 this.propertyName = ''; 878 this.qualifiedTypeName = ''; 879 this.pos = ''; 880 this.sourceFileName = ''; 881 this.deprecated = ''; 882 this.apiRawText = ''; 883 this.qualifiedName = ''; 884 this.useInstead = ''; 885 this.typeName = ''; 886 } 887 888 setSdkFileName(fileName) { 889 this.dtsName = fileName; 890 } 891 892 setPackageName(packageName) { 893 this.packageName = packageName; 894 } 895 896 setPropertyName(propertyName) { 897 this.propertyName = propertyName; 898 } 899 900 setQualifiedTypeName(typeName) { 901 if (!this.qualifiedTypeName) { 902 this.qualifiedTypeName = typeName; 903 } else { 904 this.qualifiedTypeName = `${typeName}.${this.qualifiedTypeName}`; 905 } 906 } 907 908 setTypeName(typeName) { 909 if (typeName && (!this.typeName || this.typeName === '')) { 910 this.typeName = typeName; 911 } 912 } 913 914 setPosition(pos) { 915 const { line, character } = pos; 916 this.pos = `${line + 1},${character + 1}`; 917 } 918 919 setSourceFileName(sourceFileName) { 920 this.sourceFileName = sourceFileName; 921 } 922 923 /** 924 * 设置废弃版本号 925 * 926 * @param {string} deprecated 927 */ 928 setDeprecated(deprecated) { 929 const regExpResult = deprecated.match(/\s*since\s*(\d)+.*/); 930 if (regExpResult !== null && regExpResult.length === 2) { 931 this.deprecated = regExpResult[1]; 932 } 933 } 934 935 setApiRawText(apiRawText) { 936 this.apiRawText = apiRawText.replace(/\;/g, ''); 937 } 938 939 setQualifiedName(qualifiedName) { 940 this.qualifiedName = qualifiedName; 941 } 942 943 setUseInstead(useInstead) { 944 this.useInstead = useInstead; 945 } 946 947 setComponentName(componentName) { 948 this.componentName = componentName; 949 } 950 951 setApiNode(node) { 952 this.apiNode = node; 953 } 954 955 setApiType(apiType) { 956 this.apiType = apiType; 957 } 958 959 setDtsPath(dtsPath) { 960 this.dtsPath = dtsPath; 961 } 962 963 setCompletedText(completedText) { 964 this.apiText = completedText; 965 } 966} 967 968exports.SystemApiRecognizer = SystemApiRecognizer;