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