1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import type { 17 ElementAccessExpression, 18 EnumDeclaration, 19 ExportDeclaration, 20 ModifiersArray, 21 ModuleDeclaration, 22 Node, 23 ParameterDeclaration, 24 PropertyAccessExpression, 25 SourceFile 26} from 'typescript'; 27 28import { 29 createSourceFile, 30 forEachChild, 31 isBinaryExpression, 32 isClassDeclaration, 33 isClassExpression, 34 isStructDeclaration, 35 isExpressionStatement, 36 isEnumDeclaration, 37 isExportAssignment, 38 isExportDeclaration, 39 isExportSpecifier, 40 isIdentifier, 41 isInterfaceDeclaration, 42 isObjectLiteralExpression, 43 isTypeAliasDeclaration, 44 isVariableDeclaration, 45 isVariableStatement, 46 isElementAccessExpression, 47 isPropertyAccessExpression, 48 isStringLiteral, 49 ScriptTarget, 50 SyntaxKind, 51 sys, 52 isConstructorDeclaration, 53 getModifiers, 54 isNamedExports, 55 isNamespaceExport, 56 isPropertyDeclaration, 57 isPropertySignature, 58 isMethodDeclaration, 59 isMethodSignature, 60 isObjectLiteralElementLike, 61 isModuleDeclaration, 62 isPropertyAssignment, 63 isModuleBlock, 64 isFunctionDeclaration, 65 isEnumMember 66} from 'typescript'; 67 68import fs from 'fs'; 69import path from 'path'; 70import json5 from 'json5'; 71 72import { 73 exportOriginalNameSet, 74 getClassProperties, 75 getElementAccessExpressionProperties, 76 getEnumProperties, getInterfaceProperties, 77 getObjectExportNames, 78 getObjectProperties, 79 getTypeAliasProperties, 80 isParameterPropertyModifier, 81} from '../utils/OhsUtil'; 82import { scanProjectConfig } from './ApiReader'; 83import { stringPropsSet, enumPropsSet } from '../utils/OhsUtil'; 84import type { IOptions } from '../configs/IOptions'; 85import { FileUtils } from '../utils/FileUtils'; 86import { supportedParsingExtension } from './type'; 87 88export namespace ApiExtractor { 89 interface KeywordInfo { 90 hasExport: boolean, 91 hasDeclare: boolean 92 } 93 94 export enum ApiType { 95 API = 1, 96 COMPONENT = 2, 97 PROJECT_DEPENDS = 3, 98 PROJECT = 4, 99 CONSTRUCTOR_PROPERTY = 5 100 } 101 102 let mCurrentExportedPropertySet: Set<string> = new Set<string>(); 103 let mCurrentExportNameSet: Set<string> = new Set<string>(); 104 export let mPropertySet: Set<string> = new Set<string>(); 105 export let mExportNames: Set<string> = new Set<string>(); 106 export let mConstructorPropertySet: Set<string> = undefined; 107 export let mSystemExportSet: Set<string> = new Set<string>(); 108 /** 109 * filter classes or interfaces with export, default, etc 110 */ 111 const getKeyword = function (modifiers: ModifiersArray): KeywordInfo { 112 if (modifiers === undefined) { 113 return {hasExport: false, hasDeclare: false}; 114 } 115 116 let hasExport: boolean = false; 117 let hasDeclare: boolean = false; 118 119 for (const modifier of modifiers) { 120 if (modifier.kind === SyntaxKind.ExportKeyword) { 121 hasExport = true; 122 } 123 124 if (modifier.kind === SyntaxKind.DeclareKeyword) { 125 hasDeclare = true; 126 } 127 } 128 129 return {hasExport: hasExport, hasDeclare: hasDeclare}; 130 }; 131 132 /** 133 * get export name list 134 * @param astNode 135 */ 136 const visitExport = function (astNode, isSystemApi: boolean): void { 137 /** 138 * export = exportClass //collect exportClass 139 * 140 * function foo() 141 * export default foo //collect foo 142 */ 143 if (isExportAssignment(astNode)) { 144 let nodeName = astNode.expression.getText(); 145 if (!mCurrentExportNameSet.has(nodeName)) { 146 collectNodeName(nodeName); 147 } 148 return; 149 } 150 151 if (isExportDeclaration(astNode) && astNode.exportClause) { 152 /** 153 * export {name1, name2} //collect name1, name2 154 * export {name1 as n1, name2} //collect n1, name2 155 * export {name1 as default, name2, name3} //collect default, name2, name3 156 */ 157 if (isNamedExports(astNode.exportClause)) { 158 for (const element of astNode.exportClause.elements) { 159 const exportElementName = element.name.getText(); 160 if (!mCurrentExportNameSet.has(exportElementName)) { 161 collectNodeName(exportElementName); 162 } 163 } 164 } 165 166 /** 167 * export * as name1 from 'file.ts' //collect name1 168 */ 169 if (isNamespaceExport(astNode.exportClause)) { 170 const exportElementName = astNode.exportClause.name.getText(); 171 if (!mCurrentExportNameSet.has(exportElementName)) { 172 collectNodeName(exportElementName); 173 } 174 } 175 176 /** 177 * Other export syntax, which does not contain a name. such as: 178 * export * from 'file.ts' 179 */ 180 return; 181 } 182 183 let {hasExport, hasDeclare} = getKeyword(astNode.modifiers); 184 if (!hasExport) { 185 addCommonJsExports(astNode, isSystemApi); 186 return; 187 } 188 189 if (astNode.name) { 190 let nodeName = astNode.name.getText(); 191 if (!mCurrentExportNameSet.has(nodeName)) { 192 collectNodeName(nodeName); 193 } 194 195 return; 196 } 197 198 if (hasDeclare && astNode.declarationList) { 199 astNode.declarationList.declarations.forEach((declaration) => { 200 const declarationName = declaration.name.getText(); 201 if (!mCurrentExportNameSet.has(declarationName)) { 202 collectNodeName(declarationName); 203 } 204 }); 205 } 206 }; 207 208 const isCollectedToplevelElements = function (astNode): boolean { 209 if (astNode.name && !mCurrentExportNameSet.has(astNode.name.getText())) { 210 return false; 211 } 212 213 if (astNode.name === undefined) { 214 let {hasDeclare} = getKeyword(astNode.modifiers); 215 if (hasDeclare && astNode.declarationList && 216 !mCurrentExportNameSet.has(astNode.declarationList.declarations[0].name.getText())) { 217 return false; 218 } 219 } 220 221 return true; 222 }; 223 224 /** 225 * used only in oh sdk api extract or api of xxx.d.ts declaration file 226 * @param astNode 227 */ 228 const visitChildNode = function (astNode): void { 229 if (!astNode) { 230 return; 231 } 232 233 if (astNode.name !== undefined && !mCurrentExportedPropertySet.has(astNode.name.getText())) { 234 if (isStringLiteral(astNode.name)) { 235 mCurrentExportedPropertySet.add(astNode.name.text); 236 } else { 237 mCurrentExportedPropertySet.add(astNode.name.getText()); 238 } 239 } 240 241 astNode.forEachChild((childNode) => { 242 visitChildNode(childNode); 243 }); 244 }; 245 246 // Collect constructor properties from all files. 247 const visitNodeForConstructorProperty = function (astNode): void { 248 if (!astNode) { 249 return; 250 } 251 252 if (isConstructorDeclaration) { 253 const visitParam = (param: ParameterDeclaration): void => { 254 const modifiers = getModifiers(param); 255 if (!modifiers || modifiers.length <= 0) { 256 return; 257 } 258 259 const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier)); 260 if (!isIdentifier(param.name) || findRet === undefined) { 261 return; 262 } 263 mConstructorPropertySet?.add(param.name.getText()); 264 }; 265 266 astNode?.parameters?.forEach((param) => { 267 visitParam(param); 268 }); 269 } 270 271 astNode.forEachChild((childNode) => { 272 visitNodeForConstructorProperty(childNode); 273 }); 274 }; 275 /** 276 * visit ast of a file and collect api list 277 * used only in oh sdk api extract 278 * @param astNode node of ast 279 */ 280 const visitPropertyAndName = function (astNode): void { 281 if (!isCollectedToplevelElements(astNode)) { 282 /** 283 * Collects property names of elements within top-level elements that haven't been collected yet. 284 * @param astNode toplevel elements of sourcefile 285 */ 286 collectPropertyNames(astNode); 287 return; 288 } 289 290 visitChildNode(astNode); 291 }; 292 293 /** 294 * commonjs exports extract 295 * examples: 296 * - exports.A = 1; 297 * - exports.B = hello; // hello can be variable or class ... 298 * - exports.C = {}; 299 * - exports.D = class {}; 300 * - exports.E = function () {} 301 * - class F {} 302 * - exports.F = F; 303 * - module.exports = {G: {}}; 304 */ 305 const addCommonJsExports = function (astNode: Node, isRemoteHarOrSystemApi: boolean = false): void { 306 if (!isExpressionStatement(astNode) || !astNode.expression) { 307 return; 308 } 309 310 const expression = astNode.expression; 311 if (!isBinaryExpression(expression)) { 312 return; 313 } 314 315 const left = expression.left; 316 if (!isElementAccessExpression(left) && !isPropertyAccessExpression(left)) { 317 return; 318 } 319 320 if (!isModuleExports(left) || expression.operatorToken.kind !== SyntaxKind.EqualsToken) { 321 return; 322 } 323 324 if (isElementAccessExpression(left)) { 325 if (isStringLiteral(left.argumentExpression)) { 326 /** 327 * - module.exports['A'] = class {}; 328 * - module.exports['a'] = {}; 329 * - module.exports['a'] = A; 330 */ 331 mCurrentExportedPropertySet.add(left.argumentExpression.text); 332 mCurrentExportNameSet.add(left.argumentExpression.text); 333 } 334 } 335 336 if (isPropertyAccessExpression(left)) { 337 if (isIdentifier(left.name)) { 338 /** 339 * - module.exports.A = a; 340 * - module.exports.A = {}; 341 * - module.exports.A = class {}; 342 */ 343 mCurrentExportedPropertySet.add(left.name.getText()); 344 mCurrentExportNameSet.add(left.name.getText()); 345 } 346 } 347 348 if (isIdentifier(expression.right)) { 349 /** 350 * module.exports.A = a; 351 * exports.A = a; 352 * module.exports = a; 353 */ 354 let originalName = expression.right.getText(); 355 if (isRemoteHarOrSystemApi) { 356 // To achieve compatibility changes, originalName is still collected into mCurrentExportNameSet 357 // for both remoteHar and system API files. 358 359 // NOTE: This logic will be optimized later to avoid collecting originalName into mCurrentExportNameSet under any circumstances. 360 mCurrentExportNameSet.add(originalName); 361 } else { 362 exportOriginalNameSet.add(originalName); 363 } 364 return; 365 } 366 367 if (isClassDeclaration(expression.right) || isClassExpression(expression.right)) { 368 /** 369 * module.exports.A = class testClass {} 370 * module.exports = class testClass {} 371 * exports.A = class testClass {} 372 * module.exports.A = class {} 373 */ 374 getClassProperties(expression.right, mCurrentExportedPropertySet); 375 return; 376 } 377 378 if (isObjectLiteralExpression(expression.right)) { 379 /** 380 * module.exports = {a, b, c}; 381 * module.exports.A = {a, b, c}; 382 * exports.A = {a, b, c} 383 */ 384 getObjectProperties(expression.right, mCurrentExportedPropertySet); 385 // module.exports = {a, b, c}, {a, b, c} as the export content of the module 386 let defaultExport = left.expression.getText() === 'module'; 387 if (defaultExport) { 388 getObjectExportNames(expression.right, mCurrentExportNameSet); 389 } 390 return; 391 } 392 393 return; 394 }; 395 396 function isModuleExports(leftExpression: ElementAccessExpression | PropertyAccessExpression): boolean { 397 let leftExpressionText = leftExpression.expression.getText(); 398 if (isPropertyAccessExpression(leftExpression.expression)) { 399 /** 400 * For example: 401 * module.exports.a = A; 402 * module.exports['a'] = A; 403 */ 404 return leftExpressionText === 'module.exports'; 405 } 406 if (isIdentifier(leftExpression.expression)) { 407 if (leftExpressionText === 'module') { 408 // module.exports = {A}, A as the export content of the module 409 if (isPropertyAccessExpression(leftExpression) && leftExpression.name.getText() === 'exports') { 410 return true; 411 } 412 } 413 414 /** 415 * For example: 416 * exports.a = A; 417 */ 418 return leftExpressionText === 'exports'; 419 } 420 return false; 421 }; 422 423 /** 424 * extract project export name 425 * - export {xxx, xxx}; 426 * - export {xxx as xx, xxx as xx}; 427 * - export default function/class/...{}; 428 * - export class xxx{} 429 * - ... 430 * @param astNode 431 */ 432 const visitProjectExport = function (astNode, isRemoteHarFile: boolean): void { 433 if (isExportAssignment(astNode)) { 434 handleExportAssignment(astNode); 435 return; 436 } 437 438 if (isExportDeclaration(astNode)) { 439 handleExportDeclaration(astNode, isRemoteHarFile); 440 return; 441 } 442 443 let {hasExport} = getKeyword(astNode.modifiers); 444 if (!hasExport) { 445 addCommonJsExports(astNode, isRemoteHarFile); 446 forEachChild(astNode, node => visitProjectExport(node, isRemoteHarFile)); 447 return; 448 } 449 450 if (astNode.name) { 451 if (!mCurrentExportNameSet.has(astNode.name.getText())) { 452 mCurrentExportNameSet.add(astNode.name.getText()); 453 mCurrentExportedPropertySet.add(astNode.name.getText()); 454 } 455 456 forEachChild(astNode, node => visitProjectExport(node, isRemoteHarFile)); 457 return; 458 } 459 460 if (isClassDeclaration(astNode)) { 461 getClassProperties(astNode, mCurrentExportedPropertySet); 462 return; 463 } 464 465 if (isVariableStatement(astNode)) { 466 astNode.declarationList.forEachChild((child) => { 467 if (isVariableDeclaration(child) && !mCurrentExportNameSet.has(child.name.getText())) { 468 mCurrentExportNameSet.add(child.name.getText()); 469 mCurrentExportedPropertySet.add(child.name.getText()); 470 } 471 }); 472 473 return; 474 } 475 476 forEachChild(astNode, node => visitProjectExport(node, isRemoteHarFile)); 477 }; 478 479 function handleExportAssignment(astNode): void { 480 // let xxx; export default xxx = a; 481 if (isBinaryExpression(astNode.expression)) { 482 if (isObjectLiteralExpression(astNode.expression.right)) { 483 getObjectProperties(astNode.expression.right, mCurrentExportedPropertySet); 484 return; 485 } 486 487 if (isClassExpression(astNode.expression.right)) { 488 getClassProperties(astNode.expression.right, mCurrentExportedPropertySet); 489 } 490 491 return; 492 } 493 494 // export = xxx; The xxx here can't be obfuscated 495 // export default yyy; The yyy here can be obfuscated 496 if (isIdentifier(astNode.expression)) { 497 if (!mCurrentExportNameSet.has(astNode.expression.getText())) { 498 mCurrentExportNameSet.add(astNode.expression.getText()); 499 mCurrentExportedPropertySet.add(astNode.expression.getText()); 500 } 501 return; 502 } 503 504 if (isObjectLiteralExpression(astNode.expression)) { 505 getObjectProperties(astNode.expression, mCurrentExportedPropertySet); 506 } 507 } 508 509 function handleExportDeclaration(astNode: ExportDeclaration, isRemoteHarFile: boolean): void { 510 if (astNode.exportClause) { 511 if (astNode.exportClause.kind === SyntaxKind.NamedExports) { 512 astNode.exportClause.forEachChild((child) => { 513 if (!isExportSpecifier(child)) { 514 return; 515 } 516 517 if (child.propertyName) { 518 let originalName = child.propertyName.getText(); 519 if (isRemoteHarFile || astNode.moduleSpecifier) { 520 // For the first condition, this ensures that for remoteHar files, 521 // originalName is still collected into mCurrentExportNameSet to maintain compatibility. 522 // NOTE: This specification needs to be revised to determine whether to add originalName 523 // to mCurrentExportNameSet should be independent of whether it is in a remoteHar file. 524 525 // The second condition indicates that for `export {A as B} from './filePath'` statements, 526 // the original name (A) needs to be added to the export whitelist. 527 mCurrentExportNameSet.add(originalName); 528 } else { 529 /** 530 * In project source code: 531 * class A { 532 * prop1 = 1; 533 * prop2 = 2; 534 * } 535 * export {A as B}; // collect A to ensure we can collect prop1 and prop2 536 */ 537 exportOriginalNameSet.add(originalName); 538 } 539 } 540 541 let exportName = child.name.getText(); 542 mCurrentExportedPropertySet.add(exportName); 543 mCurrentExportNameSet.add(exportName); 544 }); 545 } 546 547 if (astNode.exportClause.kind === SyntaxKind.NamespaceExport) { 548 mCurrentExportedPropertySet.add(astNode.exportClause.name.getText()); 549 return; 550 } 551 } 552 } 553 554 /** 555 * extract the class, enum, and object properties of the export in the project before obfuscation 556 * class A{}; 557 * export = A; need to be considered 558 * export = namespace; 559 * This statement also needs to determine whether there is an export in the namespace, and namespaces are also allowed in the namespace 560 * @param astNode 561 */ 562 const visitProjectNode = function (astNode): void { 563 const currentPropsSet: Set<string> = new Set(); 564 let nodeName: string | undefined = astNode.name?.text; 565 if ((isClassDeclaration(astNode) || isStructDeclaration(astNode))) { 566 getClassProperties(astNode, currentPropsSet); 567 } else if (isEnumDeclaration(astNode)) { // collect export enum structure properties 568 getEnumProperties(astNode, currentPropsSet); 569 } else if (isVariableDeclaration(astNode)) { 570 if (astNode.initializer) { 571 if (isObjectLiteralExpression(astNode.initializer)) { 572 getObjectProperties(astNode.initializer, currentPropsSet); 573 } else if (isClassExpression(astNode.initializer)) { 574 getClassProperties(astNode.initializer, currentPropsSet); 575 } 576 } 577 nodeName = astNode.name?.getText(); 578 } else if (isInterfaceDeclaration(astNode)) { 579 getInterfaceProperties(astNode, currentPropsSet); 580 } else if (isTypeAliasDeclaration(astNode)) { 581 getTypeAliasProperties(astNode, currentPropsSet); 582 } else if (isElementAccessExpression(astNode)) { 583 getElementAccessExpressionProperties(astNode, currentPropsSet); 584 } else if (isObjectLiteralExpression(astNode)) { 585 getObjectProperties(astNode, currentPropsSet); 586 } else if (isClassExpression(astNode)) { 587 getClassProperties(astNode, currentPropsSet); 588 } 589 590 addPropWhiteList(nodeName, astNode, currentPropsSet); 591 592 forEachChild(astNode, visitProjectNode); 593 }; 594 595 function addPropWhiteList(nodeName: string | undefined, astNode: Node, currentPropsSet: Set<string>): void { 596 if (nodeName && (mCurrentExportNameSet.has(nodeName) || exportOriginalNameSet.has(nodeName))) { 597 addElement(currentPropsSet); 598 } 599 600 if (scanProjectConfig.isHarCompiled && scanProjectConfig.mPropertyObfuscation && isEnumDeclaration(astNode)) { 601 addEnumElement(currentPropsSet); 602 } 603 } 604 605 function addElement(currentPropsSet: Set<string>): void { 606 currentPropsSet.forEach((element: string) => { 607 mCurrentExportedPropertySet.add(element); 608 }); 609 } 610 611 function addEnumElement(currentPropsSet: Set<string>): void { 612 currentPropsSet.forEach((element: string) => { 613 enumPropsSet.add(element); 614 }); 615 } 616 /** 617 * parse file to api list and save to json object 618 * @param fileName file name of api file 619 * @param apiType 620 * @private 621 */ 622 const parseFile = function (fileName: string, apiType: ApiType): void { 623 if (!FileUtils.isReadableFile(fileName) || !isParsableFile(fileName)) { 624 return; 625 } 626 627 const sourceFile: SourceFile = createSourceFile(fileName, fs.readFileSync(fileName).toString(), ScriptTarget.ES2015, true); 628 mCurrentExportedPropertySet.clear(); 629 // get export name list 630 switch (apiType) { 631 case ApiType.COMPONENT: 632 forEachChild(sourceFile, visitChildNode); 633 break; 634 case ApiType.API: 635 mCurrentExportNameSet.clear(); 636 forEachChild(sourceFile, node => visitExport(node, true)); 637 mCurrentExportNameSet.forEach(item => mSystemExportSet.add(item)); 638 639 forEachChild(sourceFile, visitPropertyAndName); 640 mCurrentExportNameSet.clear(); 641 break; 642 case ApiType.PROJECT_DEPENDS: 643 case ApiType.PROJECT: 644 mCurrentExportNameSet.clear(); 645 if (fileName.endsWith('.d.ts') || fileName.endsWith('.d.ets')) { 646 forEachChild(sourceFile, visitChildNode); 647 } 648 649 let isRemoteHarFile = isRemoteHar(fileName); 650 forEachChild(sourceFile, node => visitProjectExport(node, isRemoteHarFile)); 651 forEachChild(sourceFile, visitProjectNode); 652 mCurrentExportedPropertySet = handleWhiteListWhenExportObfs(fileName, mCurrentExportedPropertySet); 653 mCurrentExportNameSet = handleWhiteListWhenExportObfs(fileName, mCurrentExportNameSet); 654 break; 655 case ApiType.CONSTRUCTOR_PROPERTY: 656 forEachChild(sourceFile, visitNodeForConstructorProperty); 657 break; 658 default: 659 break; 660 } 661 662 // collect export names. 663 mCurrentExportNameSet.forEach(item => mExportNames.add(item)); 664 mCurrentExportNameSet.clear(); 665 // collect export names and properties. 666 mCurrentExportedPropertySet.forEach(item => mPropertySet.add(item)); 667 mCurrentExportedPropertySet.clear(); 668 exportOriginalNameSet.clear(); 669 }; 670 671 function handleWhiteListWhenExportObfs(fileName: string, collectedExportNamesAndProperties: Set<string>): Set<string> { 672 // If mExportObfuscation is not enabled, collect the export names and their properties into the whitelist. 673 if (!scanProjectConfig.mExportObfuscation) { 674 return collectedExportNamesAndProperties; 675 } 676 // If the current file is a keep file or its dependent file, collect the export names and their properties into the whitelist. 677 if (scanProjectConfig.mkeepFilesAndDependencies?.has(fileName)) { 678 return collectedExportNamesAndProperties; 679 } 680 // If it is a project source code file, the names and their properties of the export will not be collected. 681 if (!isRemoteHar(fileName)) { 682 collectedExportNamesAndProperties.clear(); 683 return collectedExportNamesAndProperties; 684 } 685 // If it is a third-party library file. 686 return collectedExportNamesAndProperties; 687 } 688 689 const projectExtensions: string[] = ['.ets', '.ts', '.js']; 690 const projectDependencyExtensions: string[] = ['.d.ets', '.d.ts', '.ets', '.ts', '.js']; 691 const resolvedModules = new Set(); 692 693 function tryGetPackageID(filePath: string): string { 694 const ohPackageJsonPath = path.join(filePath, 'oh-package.json5'); 695 let packgeNameAndVersion = ''; 696 if (fs.existsSync(ohPackageJsonPath)) { 697 const ohPackageContent = json5.parse(fs.readFileSync(ohPackageJsonPath, 'utf-8')); 698 packgeNameAndVersion = ohPackageContent.name + ohPackageContent.version; 699 } 700 return packgeNameAndVersion; 701 } 702 703 function traverseFilesInDir(apiPath: string, apiType: ApiType): void { 704 let fileNames: string[] = fs.readdirSync(apiPath); 705 for (let fileName of fileNames) { 706 let filePath: string = path.join(apiPath, fileName); 707 try { 708 fs.accessSync(filePath, fs.constants.R_OK); 709 } catch (err) { 710 continue; 711 } 712 if (fs.statSync(filePath).isDirectory()) { 713 const packgeNameAndVersion = tryGetPackageID(filePath); 714 if (resolvedModules.has(packgeNameAndVersion)) { 715 continue; 716 } 717 traverseApiFiles(filePath, apiType); 718 packgeNameAndVersion.length > 0 && resolvedModules.add(packgeNameAndVersion); 719 continue; 720 } 721 const suffix: string = path.extname(filePath); 722 if ((apiType !== ApiType.PROJECT) && !projectDependencyExtensions.includes(suffix)) { 723 continue; 724 } 725 726 if (apiType === ApiType.PROJECT && !projectExtensions.includes(suffix)) { 727 continue; 728 } 729 parseFile(filePath, apiType); 730 } 731 } 732 733 /** 734 * traverse files of api directory 735 * @param apiPath api directory path 736 * @param apiType 737 * @private 738 */ 739 export const traverseApiFiles = function (apiPath: string, apiType: ApiType): void { 740 if (fs.statSync(apiPath).isDirectory()) { 741 traverseFilesInDir(apiPath, apiType); 742 } else { 743 parseFile(apiPath, apiType); 744 } 745 }; 746 747 /** 748 * desc: parse openHarmony sdk to get api list 749 * @param version version of api, e.g. version 5.0.1.0 for api 9 750 * @param sdkPath sdk real path of openHarmony 751 * @param isEts true for ets, false for js 752 * @param outputDir: sdk api output directory 753 */ 754 export function parseOhSdk(sdkPath: string, version: string, isEts: boolean, outputDir: string): void { 755 mPropertySet.clear(); 756 757 // visit api directory 758 const apiPath: string = path.join(sdkPath, (isEts ? 'ets' : 'js'), version, 'api'); 759 traverseApiFiles(apiPath, ApiType.API); 760 761 // visit component directory if ets 762 if (isEts) { 763 const componentPath: string = path.join(sdkPath, 'ets', version, 'component'); 764 traverseApiFiles(componentPath, ApiType.COMPONENT); 765 } 766 767 // visit the UI conversion API 768 const uiConversionPath: string = path.join(sdkPath, (isEts ? 'ets' : 'js'), version, 769 'build-tools', 'ets-loader', 'lib', 'pre_define.js'); 770 extractStringsFromFile(uiConversionPath); 771 772 const reservedProperties: string[] = [...mPropertySet.values()]; 773 mPropertySet.clear(); 774 775 writeToFile(reservedProperties, path.join(outputDir, 'propertiesReserved.json')); 776 } 777 778 export function extractStringsFromFile(filePath: string): void { 779 let collections: string[] = []; 780 const fileContent = fs.readFileSync(filePath, 'utf-8'); 781 const regex = /"([^"]*)"/g; 782 const matches = fileContent.match(regex); 783 784 if (matches) { 785 collections = matches.map(match => match.slice(1, -1)); 786 } 787 788 collections.forEach(name => mPropertySet.add(name)); 789 } 790 791 /** 792 * save api json object to file 793 * @private 794 */ 795 export function writeToFile(reservedProperties: string[], outputPath: string): void { 796 let str: string = JSON.stringify(reservedProperties, null, '\t'); 797 fs.writeFileSync(outputPath, str); 798 } 799 800 export function isRemoteHar(filePath: string): boolean { 801 const realPath: string = sys.realpath(filePath); 802 return isInOhModuleFile(realPath); 803 } 804 805 export function isInOhModuleFile(filePath: string): boolean { 806 return filePath.indexOf('/oh_modules/') !== -1 || filePath.indexOf('\\oh_modules\\') !== -1; 807 } 808 809 export function isParsableFile(path: string): boolean { 810 return supportedParsingExtension.some(extension => path.endsWith(extension)); 811 } 812 813 /** 814 * parse common project or file to extract exported api list 815 * @return reserved api names 816 */ 817 export function parseFileByPaths(projectPaths: Set<string>, scanningApiType: ApiType): 818 {reservedExportPropertyAndName: Set<string> | undefined; reservedExportNames: Set<string> | undefined} { 819 mPropertySet.clear(); 820 mExportNames.clear(); 821 projectPaths.forEach(path => { 822 parseFile(path, scanningApiType); 823 }); 824 let reservedExportPropertyAndName: Set<string>; 825 let reservedExportNames: Set<string>; 826 if (scanProjectConfig.mPropertyObfuscation) { 827 reservedExportPropertyAndName = new Set(mPropertySet); 828 } 829 if (scanProjectConfig.mExportObfuscation) { 830 reservedExportNames = new Set(mExportNames); 831 } 832 mPropertySet.clear(); 833 mExportNames.clear(); 834 return { 835 reservedExportPropertyAndName: reservedExportPropertyAndName, 836 reservedExportNames: reservedExportNames 837 }; 838 } 839 840 /** 841 * Collect all property names in the AST. 842 * @param astNode Nodes of the AST. 843 */ 844 function collectPropertyNames(astNode: Node): void { 845 visitElementsWithProperties(astNode); 846 } 847 848 /** 849 * Visit elements that can contain properties. 850 * @param node The current AST node. 851 */ 852 function visitElementsWithProperties(node: Node): void { 853 switch (node.kind) { 854 case SyntaxKind.ClassDeclaration: 855 forEachChild(node, visitClass); 856 break; 857 case SyntaxKind.InterfaceDeclaration: 858 case SyntaxKind.TypeLiteral: 859 forEachChild(node, visitInterfaceOrType); 860 break; 861 case SyntaxKind.EnumDeclaration: 862 forEachChild(node, visitEnum); 863 break; 864 case SyntaxKind.ObjectLiteralExpression: 865 forEachChild(node, visitObjectLiteral); 866 break; 867 case SyntaxKind.ModuleDeclaration: 868 forEachChild(node, visitModule); 869 break; 870 } 871 forEachChild(node, visitElementsWithProperties); 872 } 873 874 function visitClass(node: Node): void { 875 if (isPropertyDeclaration(node) || isMethodDeclaration(node)) { 876 if (isIdentifier(node.name)) { 877 mCurrentExportedPropertySet.add(node.name.text); 878 } 879 } 880 forEachChild(node, visitClass); 881 } 882 883 function visitInterfaceOrType(node: Node): void { 884 if (isPropertySignature(node) || isMethodSignature(node)) { 885 if (isIdentifier(node.name)) { 886 mCurrentExportedPropertySet.add(node.name.text); 887 } 888 } 889 forEachChild(node, visitInterfaceOrType); 890 } 891 892 function visitEnum(node: Node): void { 893 if (isEnumMember(node) && isIdentifier(node.name)) { 894 mCurrentExportedPropertySet.add(node.name.text); 895 } 896 } 897 898 function visitObjectLiteral(node: Node): void { 899 if (isPropertyAssignment(node)) { 900 if (isIdentifier(node.name)) { 901 mCurrentExportedPropertySet.add(node.name.text); 902 } 903 } 904 forEachChild(node, visitObjectLiteral); 905 } 906 907 function visitModule(node: Node): void { 908 forEachChild(node, visitElementsWithProperties); 909 } 910 911 function collectNodeName(name: string): void { 912 mCurrentExportNameSet.add(name); 913 mCurrentExportedPropertySet.add(name); 914 } 915} 916