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