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