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 AnnotationDeclaration, 18 ClassDeclaration, 19 CommentRange, 20 CompilerOptions, 21 Decorator, 22 ElementAccessExpression, 23 EnumDeclaration, 24 ExportDeclaration, 25 Expression, 26 FunctionDeclaration, 27 InterfaceDeclaration, 28 ModifiersArray, 29 ModuleDeclaration, 30 NamedDeclaration, 31 Node, 32 ParameterDeclaration, 33 PropertyAccessExpression, 34 SourceFile, 35 TypeAliasDeclaration, 36 VariableDeclaration, 37 VariableStatement 38} from 'typescript'; 39 40import { 41 createSourceFile, 42 ClassElement, 43 forEachChild, 44 getLeadingCommentRangesOfNode, 45 isAnnotationDeclaration, 46 isBinaryExpression, 47 isClassDeclaration, 48 isClassExpression, 49 isExpressionStatement, 50 isEnumDeclaration, 51 isExportAssignment, 52 isExportDeclaration, 53 isExportSpecifier, 54 isGetAccessor, 55 isIdentifier, 56 isInterfaceDeclaration, 57 isModuleBlock, 58 isObjectLiteralExpression, 59 isParameterPropertyDeclaration, 60 isStructDeclaration, 61 isSourceFile, 62 isTypeAliasDeclaration, 63 isVariableDeclaration, 64 isVariableStatement, 65 isElementAccessExpression, 66 isPropertyAccessExpression, 67 isSetAccessor, 68 isStringLiteral, 69 ScriptTarget, 70 SyntaxKind, 71 sys, 72 isConstructorDeclaration, 73 getModifiers, 74 isNamedExports, 75 isNamespaceExport, 76 isPropertyDeclaration, 77 isPropertySignature, 78 isMethodDeclaration, 79 isMethodSignature, 80 isPropertyAssignment, 81 isEnumMember, 82 isParameter, 83 isTypeParameterDeclaration, 84 isIndexedAccessTypeNode, 85 Extension, 86 isCallExpression, 87 isDecorator 88} from 'typescript'; 89 90import fs from 'fs'; 91import path from 'path'; 92import json5 from 'json5'; 93 94import { 95 exportOriginalNameSet, 96 getClassProperties, 97 getElementAccessExpressionProperties, 98 getEnumProperties, 99 getIndexedAccessTypeProperties, 100 getInterfaceProperties, 101 getObjectExportNames, 102 getObjectProperties, 103 getTypeAliasProperties, 104 isParameterPropertyModifier, 105} from '../utils/OhsUtil'; 106import { scanProjectConfig } from './ApiReader'; 107import { enumPropsSet } from '../utils/OhsUtil'; 108import { FileUtils } from '../utils/FileUtils'; 109import { supportedParsingExtension } from './type'; 110import { 111 addToSet, 112 DECORATOR_WHITE_LIST, 113 FileWhiteList, KeepInfo, 114 projectWhiteListManager 115} from '../utils/ProjectCollections'; 116import { AtKeepCollections, BytecodeObfuscationCollections, PropCollections } from '../utils/CommonCollections'; 117import { hasExportModifier } from '../utils/NodeUtils'; 118 119export namespace ApiExtractor { 120 interface KeywordInfo { 121 hasExport: boolean, 122 hasDeclare: boolean 123 } 124 125 export enum ApiType { 126 API, 127 COMPONENT, 128 PROJECT, 129 CONSTRUCTOR_PROPERTY, 130 KEEP_DTS 131 } 132 133 export enum AtKeepType { 134 None, 135 KeepSymbol, 136 KeepAsConsumer 137 } 138 139 export enum WhiteListType { 140 PropertyName, 141 GlobalName, 142 } 143 144 type KeepTargetNode = 145 | ClassDeclaration 146 | InterfaceDeclaration 147 | EnumDeclaration 148 | FunctionDeclaration 149 | ModuleDeclaration 150 | VariableDeclaration 151 | TypeAliasDeclaration 152 | AnnotationDeclaration; 153 154 const KEEP_SYMBOL = '//@KeepSymbol'; 155 const KEEP_AS_CONSUMER = '//@KeepAsConsumer'; 156 157 let mCurrentExportedPropertySet: Set<string> = new Set<string>(); 158 let mCurrentExportNameSet: Set<string> = new Set<string>(); 159 160 let decoratorMap: Map<string, Set<string>> = new Map<string, Set<string>>(); 161 162 let keepSymbolTemp: KeepInfo = { 163 propertyNames: new Set<string>(), 164 globalNames: new Set<string>(), 165 }; 166 let keepAsConsumerTemp: KeepInfo = { 167 propertyNames: new Set<string>(), 168 globalNames: new Set<string>(), 169 }; 170 171 function clearAtKeepTemp(): void { 172 keepSymbolTemp.propertyNames.clear(); 173 keepSymbolTemp.globalNames.clear(); 174 keepAsConsumerTemp.propertyNames.clear(); 175 keepAsConsumerTemp.globalNames.clear(); 176 } 177 178 export let mPropertySet: Set<string> = new Set<string>(); 179 export let mExportNames: Set<string> = new Set<string>(); 180 export let mConstructorPropertySet: Set<string> = new Set<string>(); 181 export let mEnumMemberSet: Set<string> = new Set<string>(); 182 export let mSystemExportSet: Set<string> = new Set<string>(); 183 /** 184 * filter classes or interfaces with export, default, etc 185 */ 186 const getKeyword = function (modifiers: ModifiersArray): KeywordInfo { 187 if (modifiers === undefined) { 188 return {hasExport: false, hasDeclare: false}; 189 } 190 191 let hasExport: boolean = false; 192 let hasDeclare: boolean = false; 193 194 for (const modifier of modifiers) { 195 if (modifier.kind === SyntaxKind.ExportKeyword) { 196 hasExport = true; 197 } 198 199 if (modifier.kind === SyntaxKind.DeclareKeyword) { 200 hasDeclare = true; 201 } 202 } 203 204 return {hasExport: hasExport, hasDeclare: hasDeclare}; 205 }; 206 207 /** 208 * get export name list 209 * @param astNode 210 */ 211 const visitExport = function (astNode, isSystemApi: boolean): void { 212 /** 213 * export = exportClass //collect exportClass 214 * 215 * function foo() 216 * export default foo //collect foo 217 */ 218 if (isExportAssignment(astNode)) { 219 let nodeName = astNode.expression.getText(); 220 if (!mCurrentExportNameSet.has(nodeName)) { 221 collectNodeName(nodeName); 222 } 223 return; 224 } 225 226 if (isExportDeclaration(astNode) && astNode.exportClause) { 227 /** 228 * export {name1, name2} //collect name1, name2 229 * export {name1 as n1, name2} //collect n1, name2 230 * export {name1 as default, name2, name3} //collect default, name2, name3 231 */ 232 if (isNamedExports(astNode.exportClause)) { 233 for (const element of astNode.exportClause.elements) { 234 const exportElementName = element.name.getText(); 235 if (!mCurrentExportNameSet.has(exportElementName)) { 236 collectNodeName(exportElementName); 237 } 238 } 239 } 240 241 /** 242 * export * as name1 from 'file.ts' //collect name1 243 */ 244 if (isNamespaceExport(astNode.exportClause)) { 245 const exportElementName = astNode.exportClause.name.getText(); 246 if (!mCurrentExportNameSet.has(exportElementName)) { 247 collectNodeName(exportElementName); 248 } 249 } 250 251 /** 252 * Other export syntax, which does not contain a name. such as: 253 * export * from 'file.ts' 254 */ 255 return; 256 } 257 258 let {hasExport, hasDeclare} = getKeyword(astNode.modifiers); 259 if (!hasExport) { 260 return; 261 } 262 263 if (astNode.name) { 264 let nodeName = astNode.name.getText(); 265 if (!mCurrentExportNameSet.has(nodeName)) { 266 collectNodeName(nodeName); 267 } 268 269 return; 270 } 271 272 if (hasDeclare && astNode.declarationList) { 273 astNode.declarationList.declarations.forEach((declaration) => { 274 const declarationName = declaration.name.getText(); 275 if (!mCurrentExportNameSet.has(declarationName)) { 276 collectNodeName(declarationName); 277 } 278 }); 279 } 280 }; 281 282 const isCollectedExportNames = function (astNode): boolean { 283 if (astNode.name && !mCurrentExportNameSet.has(astNode.name.getText())) { 284 return false; 285 } 286 287 if (astNode.name === undefined) { 288 let {hasDeclare} = getKeyword(astNode.modifiers); 289 if (hasDeclare && astNode.declarationList && 290 !mCurrentExportNameSet.has(astNode.declarationList.declarations[0].name.getText())) { 291 return false; 292 } 293 } 294 295 return true; 296 }; 297 298 /** 299 * used only in oh sdk api extract or api of xxx.d.ts declaration file 300 * @param astNode 301 */ 302 const visitChildNode = function (astNode, isSdkApi: boolean = false): void { 303 if (!astNode) { 304 return; 305 } 306 if (astNode.name !== undefined && !mCurrentExportedPropertySet.has(astNode.name.getText())) { 307 const notAddParameter: boolean = scanProjectConfig.mStripSystemApiArgs && isSdkApi; 308 if (!notAddParameter || (!isParameter(astNode) && !isTypeParameterDeclaration(astNode))) { 309 const nameToAdd = isStringLiteral(astNode.name) ? astNode.name.text : astNode.name.getText(); 310 mCurrentExportedPropertySet.add(nameToAdd); 311 } 312 } 313 314 astNode.forEachChild((childNode) => { 315 visitChildNode(childNode, isSdkApi); 316 }); 317 }; 318 319 // Collect constructor properties from all files. 320 // To avoid generating the same name as the constructor property when obfuscating identifier names. 321 const visitNodeForConstructorProperty = function (astNode): void { 322 if (!astNode) { 323 return; 324 } 325 326 if (isConstructorDeclaration(astNode)) { 327 const visitParam = (param: ParameterDeclaration): void => { 328 const modifiers = getModifiers(param); 329 if (!modifiers || modifiers.length <= 0) { 330 return; 331 } 332 333 const findRet = modifiers.find(modifier => isParameterPropertyModifier(modifier)); 334 if (!isIdentifier(param.name) || findRet === undefined) { 335 return; 336 } 337 mConstructorPropertySet.add(param.name.getText()); 338 projectWhiteListManager?.fileWhiteListInfo?.fileReservedInfo.propertyParams.add(param.name.getText()); 339 }; 340 341 astNode?.parameters?.forEach((param) => { 342 visitParam(param); 343 }); 344 } 345 346 astNode.forEachChild((childNode) => { 347 visitNodeForConstructorProperty(childNode); 348 }); 349 }; 350 /** 351 * visit ast of a file and collect api list 352 * used only in oh sdk api extract 353 * @param astNode node of ast 354 */ 355 const visitPropertyAndNameForSdk = function (astNode): void { 356 if (!isCollectedExportNames(astNode)) { 357 /** 358 * Collects property names of elements that haven't been collected yet. 359 * @param astNode elements of sourcefile 360 */ 361 collectPropertyNames(astNode); 362 return; 363 } 364 365 visitChildNode(astNode, true); 366 }; 367 368 /** 369 * commonjs exports extract 370 * examples: 371 * - exports.A = 1; 372 * - exports.B = hello; // hello can be variable or class ... 373 * - exports.C = {}; 374 * - exports.D = class {}; 375 * - exports.E = function () {} 376 * - class F {} 377 * - exports.F = F; 378 * - module.exports = {G: {}}; 379 */ 380 const addCommonJsExports = function (astNode: Node, isRemoteHarOrSystemApi: boolean = false): void { 381 if (!isExpressionStatement(astNode) || !astNode.expression) { 382 return; 383 } 384 385 const expression = astNode.expression; 386 if (!isBinaryExpression(expression)) { 387 return; 388 } 389 390 const left = expression.left; 391 if (!isElementAccessExpression(left) && !isPropertyAccessExpression(left)) { 392 return; 393 } 394 395 if (!isModuleExports(left) || expression.operatorToken.kind !== SyntaxKind.EqualsToken) { 396 return; 397 } 398 399 if (isElementAccessExpression(left)) { 400 if (isStringLiteral(left.argumentExpression)) { 401 /** 402 * - module.exports['A'] = class {}; 403 * - module.exports['a'] = {}; 404 * - module.exports['a'] = A; 405 */ 406 mCurrentExportedPropertySet.add(left.argumentExpression.text); 407 mCurrentExportNameSet.add(left.argumentExpression.text); 408 } 409 } 410 411 if (isPropertyAccessExpression(left)) { 412 if (isIdentifier(left.name)) { 413 /** 414 * - module.exports.A = a; 415 * - module.exports.A = {}; 416 * - module.exports.A = class {}; 417 */ 418 mCurrentExportedPropertySet.add(left.name.getText()); 419 mCurrentExportNameSet.add(left.name.getText()); 420 } 421 } 422 423 if (isIdentifier(expression.right)) { 424 /** 425 * module.exports.A = a; 426 * exports.A = a; 427 * module.exports = a; 428 */ 429 let originalName = expression.right.getText(); 430 if (isRemoteHarOrSystemApi) { 431 // To achieve compatibility changes, originalName is still collected into mCurrentExportNameSet 432 // for both remoteHar and system API files. 433 434 // NOTE: This logic will be optimized later to avoid collecting originalName into mCurrentExportNameSet under any circumstances. 435 mCurrentExportNameSet.add(originalName); 436 } else { 437 exportOriginalNameSet.add(originalName); 438 } 439 return; 440 } 441 442 if (isClassDeclaration(expression.right) || isClassExpression(expression.right)) { 443 /** 444 * module.exports.A = class testClass {} 445 * module.exports = class testClass {} 446 * exports.A = class testClass {} 447 * module.exports.A = class {} 448 */ 449 getClassProperties(expression.right, mCurrentExportedPropertySet); 450 return; 451 } 452 453 if (isObjectLiteralExpression(expression.right)) { 454 /** 455 * module.exports = {a, b, c}; 456 * module.exports.A = {a, b, c}; 457 * exports.A = {a, b, c} 458 */ 459 getObjectProperties(expression.right, mCurrentExportedPropertySet); 460 // module.exports = {a, b, c}, {a, b, c} as the export content of the module 461 let defaultExport = left.expression.getText() === 'module'; 462 if (defaultExport) { 463 getObjectExportNames(expression.right, mCurrentExportNameSet); 464 } 465 return; 466 } 467 468 return; 469 }; 470 471 function isModuleExports(leftExpression: ElementAccessExpression | PropertyAccessExpression): boolean { 472 let leftExpressionText = leftExpression.expression.getText(); 473 if (isPropertyAccessExpression(leftExpression.expression)) { 474 /** 475 * For example: 476 * module.exports.a = A; 477 * module.exports['a'] = A; 478 */ 479 return leftExpressionText === 'module.exports'; 480 } 481 if (isIdentifier(leftExpression.expression)) { 482 if (leftExpressionText === 'module') { 483 // module.exports = {A}, A as the export content of the module 484 if (isPropertyAccessExpression(leftExpression) && leftExpression.name.getText() === 'exports') { 485 return true; 486 } 487 } 488 489 /** 490 * For example: 491 * exports.a = A; 492 */ 493 return leftExpressionText === 'exports'; 494 } 495 return false; 496 }; 497 498 /** 499 * extract project export name 500 * - export {xxx, xxx}; 501 * - export {xxx as xx, xxx as xx}; 502 * - export default function/class/...{}; 503 * - export class xxx{} 504 * - ... 505 * @param astNode 506 */ 507 const visitProjectExport = function (astNode, isRemoteHarFile: boolean): void { 508 if (isExportAssignment(astNode)) { 509 handleExportAssignment(astNode); 510 return; 511 } 512 513 if (isExportDeclaration(astNode)) { 514 handleExportDeclaration(astNode, isRemoteHarFile); 515 return; 516 } 517 518 let {hasExport} = getKeyword(astNode.modifiers); 519 if (!hasExport) { 520 addCommonJsExports(astNode, isRemoteHarFile); 521 forEachChild(astNode, node => visitProjectExport(node, isRemoteHarFile)); 522 return; 523 } 524 525 if (astNode.name) { 526 if (!mCurrentExportNameSet.has(astNode.name.getText())) { 527 mCurrentExportNameSet.add(astNode.name.getText()); 528 mCurrentExportedPropertySet.add(astNode.name.getText()); 529 } 530 531 forEachChild(astNode, node => visitProjectExport(node, isRemoteHarFile)); 532 return; 533 } 534 535 if (isClassDeclaration(astNode)) { 536 getClassProperties(astNode, mCurrentExportedPropertySet); 537 return; 538 } 539 540 if (isVariableStatement(astNode)) { 541 astNode.declarationList.forEachChild((child) => { 542 if (isVariableDeclaration(child) && !mCurrentExportNameSet.has(child.name.getText())) { 543 mCurrentExportNameSet.add(child.name.getText()); 544 mCurrentExportedPropertySet.add(child.name.getText()); 545 } 546 }); 547 548 return; 549 } 550 551 forEachChild(astNode, node => visitProjectExport(node, isRemoteHarFile)); 552 }; 553 554 function handleExportAssignment(astNode): void { 555 // let xxx; export default xxx = a; 556 if (isBinaryExpression(astNode.expression)) { 557 if (isObjectLiteralExpression(astNode.expression.right)) { 558 getObjectProperties(astNode.expression.right, mCurrentExportedPropertySet); 559 return; 560 } 561 562 if (isClassExpression(astNode.expression.right)) { 563 getClassProperties(astNode.expression.right, mCurrentExportedPropertySet); 564 } 565 566 return; 567 } 568 569 // export = xxx; The xxx here can't be obfuscated 570 // export default yyy; The yyy here can be obfuscated 571 if (isIdentifier(astNode.expression)) { 572 if (!mCurrentExportNameSet.has(astNode.expression.getText())) { 573 mCurrentExportNameSet.add(astNode.expression.getText()); 574 mCurrentExportedPropertySet.add(astNode.expression.getText()); 575 } 576 return; 577 } 578 579 if (isObjectLiteralExpression(astNode.expression)) { 580 getObjectProperties(astNode.expression, mCurrentExportedPropertySet); 581 } 582 } 583 584 function handleExportDeclaration(astNode: ExportDeclaration, isRemoteHarFile: boolean): void { 585 if (astNode.exportClause) { 586 if (astNode.exportClause.kind === SyntaxKind.NamedExports) { 587 astNode.exportClause.forEachChild((child) => { 588 if (!isExportSpecifier(child)) { 589 return; 590 } 591 592 if (child.propertyName) { 593 let originalName = child.propertyName.getText(); 594 if (isRemoteHarFile || astNode.moduleSpecifier) { 595 // For the first condition, this ensures that for remoteHar files, 596 // originalName is still collected into mCurrentExportNameSet to maintain compatibility. 597 // NOTE: This specification needs to be revised to determine whether to add originalName 598 // to mCurrentExportNameSet should be independent of whether it is in a remoteHar file. 599 600 // The second condition indicates that for `export {A as B} from './filePath'` statements, 601 // the original name (A) needs to be added to the export whitelist. 602 mCurrentExportNameSet.add(originalName); 603 } else { 604 /** 605 * In project source code: 606 * class A { 607 * prop1 = 1; 608 * prop2 = 2; 609 * } 610 * export {A as B}; // collect A to ensure we can collect prop1 and prop2 611 */ 612 exportOriginalNameSet.add(originalName); 613 } 614 } 615 616 let exportName = child.name.getText(); 617 mCurrentExportedPropertySet.add(exportName); 618 mCurrentExportNameSet.add(exportName); 619 }); 620 } 621 622 if (astNode.exportClause.kind === SyntaxKind.NamespaceExport) { 623 mCurrentExportedPropertySet.add(astNode.exportClause.name.getText()); 624 mCurrentExportNameSet.add(astNode.exportClause.name.getText()); 625 return; 626 } 627 } 628 } 629 630 /** 631 * extract the class, enum, and object properties of the export in the project before obfuscation 632 * class A{}; 633 * export = A; need to be considered 634 * export = namespace; 635 * This statement also needs to determine whether there is an export in the namespace, and namespaces are also allowed in the namespace 636 * @param astNode 637 */ 638 const visitProjectNode = function (astNode): void { 639 const currentPropsSet: Set<string> = new Set(); 640 let nodeName: string | undefined = astNode.name?.text; 641 if ((isClassDeclaration(astNode) || isStructDeclaration(astNode))) { 642 getClassProperties(astNode, currentPropsSet); 643 } else if (isEnumDeclaration(astNode)) { // collect export enum structure properties 644 getEnumProperties(astNode, currentPropsSet); 645 } else if (isVariableDeclaration(astNode)) { 646 if (astNode.initializer) { 647 if (isObjectLiteralExpression(astNode.initializer)) { 648 getObjectProperties(astNode.initializer, currentPropsSet); 649 } else if (isClassExpression(astNode.initializer)) { 650 getClassProperties(astNode.initializer, currentPropsSet); 651 } 652 } 653 nodeName = astNode.name?.getText(); 654 } else if (isInterfaceDeclaration(astNode)) { 655 getInterfaceProperties(astNode, currentPropsSet); 656 } else if (isTypeAliasDeclaration(astNode)) { 657 getTypeAliasProperties(astNode, currentPropsSet); 658 } else if (isElementAccessExpression(astNode)) { 659 getElementAccessExpressionProperties(astNode); 660 } else if (isIndexedAccessTypeNode(astNode)) { 661 getIndexedAccessTypeProperties(astNode); 662 } else if (isObjectLiteralExpression(astNode)) { 663 getObjectProperties(astNode, currentPropsSet); 664 } else if (isClassExpression(astNode)) { 665 getClassProperties(astNode, currentPropsSet); 666 } 667 668 addPropWhiteList(nodeName, astNode, currentPropsSet); 669 670 forEachChild(astNode, visitProjectNode); 671 }; 672 673 function addPropWhiteList(nodeName: string | undefined, astNode: Node, currentPropsSet: Set<string>): void { 674 if (nodeName && (mCurrentExportNameSet.has(nodeName) || exportOriginalNameSet.has(nodeName))) { 675 addElement(currentPropsSet); 676 } 677 678 if (scanProjectConfig.isHarCompiled && scanProjectConfig.mPropertyObfuscation && isEnumDeclaration(astNode)) { 679 addEnumElement(currentPropsSet); 680 } 681 } 682 683 function addElement(currentPropsSet: Set<string>): void { 684 currentPropsSet.forEach((element: string) => { 685 mCurrentExportedPropertySet.add(element); 686 }); 687 } 688 689 function addEnumElement(currentPropsSet: Set<string>): void { 690 currentPropsSet.forEach((element: string) => { 691 enumPropsSet.add(element); 692 projectWhiteListManager?.fileWhiteListInfo?.fileKeepInfo.enumProperties.add(element); 693 }); 694 } 695 /** 696 * parse file to api list and save to json object 697 * @param fileName file name of api file 698 * @param apiType 699 * @private 700 */ 701 const parseFile = function (fileName: string, apiType: ApiType): void { 702 if (!FileUtils.isReadableFile(fileName) || !isParsableFile(fileName)) { 703 return; 704 } 705 706 projectWhiteListManager?.setCurrentCollector(fileName); 707 708 let sourceFile: SourceFile = createSourceFile(fileName, fs.readFileSync(fileName).toString(), ScriptTarget.ES2015, true, undefined, { 709 etsAnnotationsEnable: true 710 }, true); 711 mCurrentExportedPropertySet.clear(); 712 713 collectWhiteListByApiType(sourceFile, apiType, fileName); 714 715 // collect field decorated by UI 716 if (scanProjectConfig.scanDecorator) { 717 collectAndAddFieldDecorator(sourceFile); 718 } 719 720 // collect names marked with '// @KeepSymbol' or '// @KeepAsConsumer', only support .ts/.ets 721 if (shouldCollectAtKeep(fileName)) { 722 collectAndAddAtKeepNames(sourceFile); 723 } 724 725 // collect origin source file white lists 726 if (shouldCollectFileWhiteLists(apiType)) { 727 collectFileWhiteLists(); 728 } 729 730 // collect export names. 731 mCurrentExportNameSet.forEach(item => mExportNames.add(item)); 732 mCurrentExportNameSet.clear(); 733 // collect export names and properties. 734 mCurrentExportedPropertySet.forEach(item => mPropertySet.add(item)); 735 mCurrentExportedPropertySet.clear(); 736 exportOriginalNameSet.clear(); 737 decoratorMap.clear(); 738 }; 739 740 function shouldCollectAtKeep(fileName: string): boolean { 741 return scanProjectConfig.mEnableAtKeep && 742 !(fileName.endsWith(Extension.Dts) || fileName.endsWith(Extension.Dets)) && 743 (fileName.endsWith(Extension.Ts) || fileName.endsWith(Extension.Ets)); 744 } 745 746 function collectAndAddAtKeepNames(sourceFile: SourceFile): void { 747 clearAtKeepTemp(); 748 collectNamesWithAtKeep(sourceFile, sourceFile); 749 addToSet(AtKeepCollections.keepSymbol.globalNames, keepSymbolTemp.globalNames); 750 addToSet(AtKeepCollections.keepSymbol.propertyNames, keepSymbolTemp.propertyNames); 751 addToSet(AtKeepCollections.keepAsConsumer.globalNames, keepAsConsumerTemp.globalNames); 752 addToSet(AtKeepCollections.keepAsConsumer.propertyNames, keepAsConsumerTemp.propertyNames); 753 } 754 755 function shouldCollectFileWhiteLists(apiType: ApiType): boolean { 756 return apiType === ApiType.PROJECT || apiType === ApiType.CONSTRUCTOR_PROPERTY; 757 } 758 759 function collectFileWhiteLists(): void { 760 const fileWhiteLists: FileWhiteList | undefined = projectWhiteListManager?.fileWhiteListInfo; 761 if (!fileWhiteLists) { 762 return; 763 } 764 765 if (scanProjectConfig.mPropertyObfuscation) { 766 addToSet(fileWhiteLists.fileKeepInfo.exported.propertyNames, mCurrentExportedPropertySet); 767 if (!scanProjectConfig.mKeepStringProperty) { 768 fileWhiteLists.fileKeepInfo.stringProperties.clear(); 769 } 770 } else { 771 fileWhiteLists.fileKeepInfo.structProperties.clear(); 772 fileWhiteLists.fileKeepInfo.stringProperties.clear(); 773 fileWhiteLists.fileKeepInfo.enumProperties.clear(); 774 } 775 if (scanProjectConfig.mExportObfuscation) { 776 addToSet(fileWhiteLists.fileKeepInfo.exported.globalNames, mCurrentExportNameSet); 777 } 778 if (scanProjectConfig.mEnableAtKeep) { 779 addToSet(fileWhiteLists.fileKeepInfo.keepSymbol.globalNames, keepSymbolTemp.globalNames); 780 addToSet(fileWhiteLists.fileKeepInfo.keepSymbol.propertyNames, keepSymbolTemp.propertyNames); 781 addToSet(fileWhiteLists.fileKeepInfo.keepAsConsumer.globalNames, keepAsConsumerTemp.globalNames); 782 addToSet(fileWhiteLists.fileKeepInfo.keepAsConsumer.propertyNames, keepAsConsumerTemp.propertyNames); 783 } 784 if (scanProjectConfig.scanDecorator) { 785 const convertedMap = new Map( 786 Array.from(decoratorMap.entries()).map(([key, value]) => [key, Array.from(value)])); 787 if (!fileWhiteLists.bytecodeObfuscateKeepInfo) { 788 fileWhiteLists.bytecodeObfuscateKeepInfo = {}; 789 } 790 fileWhiteLists.bytecodeObfuscateKeepInfo.decoratorMap = Object.fromEntries(convertedMap); 791 const allProp = Array.from(convertedMap.values()).flat(); 792 allProp.forEach(value => BytecodeObfuscationCollections.decoratorProp.add(value)); 793 } 794 } 795 796 function collectAndAddFieldDecorator(sourceFile: SourceFile): void { 797 visitDecorator(sourceFile); 798 } 799 800 function getDecorators(node: Node): Decorator[] { 801 const decorators: Decorator[] = []; 802 forEachChild(node, child => { 803 if (isDecorator(child)) { 804 decorators.push(child); 805 } 806 }); 807 return decorators; 808 } 809 810 function visitDecorator(node: Node): void { 811 const decorators = getDecorators(node) || []; 812 const propertyName = (node as NamedDeclaration).name?.getText(); 813 if (!propertyName) { 814 forEachChild(node, visitDecorator); 815 return; 816 } 817 818 decorators.forEach(decorator => { 819 const expr = decorator.expression; 820 const decoratorName = getDecoratorName(expr); 821 if (!decoratorName || !isWhiteListedDecorator(decoratorName)) { 822 return; 823 } 824 ensureEntry(decoratorMap, decoratorName, () => new Set<string>()); 825 const decoratorSet = decoratorMap.get(decoratorName)!; 826 827 if (isCallExpression(expr)) { 828 expr.arguments.forEach(arg => { 829 const stripped = arg.getText().replace(/^['"]|['"]$/g, '').split('.'); 830 stripped.forEach(item => decoratorSet.add(item)); 831 }); 832 } 833 decoratorSet.add(propertyName); 834 835 }); 836 forEachChild(node, visitDecorator); 837 } 838 839 function collectWhiteListByApiType(sourceFile: SourceFile, apiType: ApiType, fileName: string): void { 840 // get export name list 841 switch (apiType) { 842 case ApiType.COMPONENT: 843 forEachChild(sourceFile, node => visitChildNode(node, true)); 844 break; 845 case ApiType.KEEP_DTS: 846 forEachChild(sourceFile, visitChildNode); 847 break; 848 case ApiType.API: 849 mCurrentExportNameSet.clear(); 850 forEachChild(sourceFile, node => visitExport(node, true)); 851 mCurrentExportNameSet.forEach(item => mSystemExportSet.add(item)); 852 853 forEachChild(sourceFile, visitPropertyAndNameForSdk); 854 mCurrentExportNameSet.clear(); 855 break; 856 case ApiType.PROJECT: 857 mCurrentExportNameSet.clear(); 858 if (fileName.endsWith('.d.ts') || fileName.endsWith('.d.ets')) { 859 forEachChild(sourceFile, visitChildNode); 860 } 861 862 let isRemoteHarFile = isRemoteHar(fileName); 863 forEachChild(sourceFile, node => visitProjectExport(node, isRemoteHarFile)); 864 forEachChild(sourceFile, visitProjectNode); 865 collectEnumMembersOfFile(sourceFile); 866 mCurrentExportedPropertySet = handleWhiteListWhenExportObfs(fileName, mCurrentExportedPropertySet); 867 mCurrentExportNameSet = handleWhiteListWhenExportObfs(fileName, mCurrentExportNameSet); 868 break; 869 case ApiType.CONSTRUCTOR_PROPERTY: 870 forEachChild(sourceFile, visitNodeForConstructorProperty); 871 collectEnumMembersOfFile(sourceFile); 872 break; 873 default: 874 break; 875 } 876 } 877 878 function handleWhiteListWhenExportObfs(fileName: string, collectedExportNamesAndProperties: Set<string>): Set<string> { 879 // If mExportObfuscation is not enabled, collect the export names and their properties into the whitelist. 880 if (!scanProjectConfig.mExportObfuscation) { 881 return collectedExportNamesAndProperties; 882 } 883 // If the current file is a keep file or its dependent file, collect the export names and their properties into the whitelist. 884 if (scanProjectConfig.mkeepFilesAndDependencies?.has(fileName)) { 885 return collectedExportNamesAndProperties; 886 } 887 // If it is a project source code file, the names and their properties of the export will not be collected. 888 if (!isRemoteHar(fileName)) { 889 collectedExportNamesAndProperties.clear(); 890 return collectedExportNamesAndProperties; 891 } 892 // If it is a third-party library file. 893 return collectedExportNamesAndProperties; 894 } 895 896 const projectExtensions: string[] = ['.ets', '.ts', '.js']; 897 const projectDependencyExtensions: string[] = ['.d.ets', '.d.ts', '.ets', '.ts', '.js']; 898 const resolvedModules = new Set(); 899 900 function tryGetPackageID(filePath: string): string { 901 const ohPackageJsonPath = path.join(filePath, 'oh-package.json5'); 902 let packgeNameAndVersion = ''; 903 if (fs.existsSync(ohPackageJsonPath)) { 904 const ohPackageContent = json5.parse(fs.readFileSync(ohPackageJsonPath, 'utf-8')); 905 packgeNameAndVersion = ohPackageContent.name + ohPackageContent.version; 906 } 907 return packgeNameAndVersion; 908 } 909 910 function traverseFilesInDir(apiPath: string, apiType: ApiType): void { 911 let fileNames: string[] = fs.readdirSync(apiPath); 912 for (let fileName of fileNames) { 913 let filePath: string = path.join(apiPath, fileName); 914 try { 915 fs.accessSync(filePath, fs.constants.R_OK); 916 } catch (err) { 917 continue; 918 } 919 if (fs.statSync(filePath).isDirectory()) { 920 const packgeNameAndVersion = tryGetPackageID(filePath); 921 if (resolvedModules.has(packgeNameAndVersion)) { 922 continue; 923 } 924 traverseApiFiles(filePath, apiType); 925 packgeNameAndVersion.length > 0 && resolvedModules.add(packgeNameAndVersion); 926 continue; 927 } 928 const suffix: string = path.extname(filePath); 929 if ((apiType !== ApiType.PROJECT) && !projectDependencyExtensions.includes(suffix)) { 930 continue; 931 } 932 933 if (apiType === ApiType.PROJECT && !projectExtensions.includes(suffix)) { 934 continue; 935 } 936 parseFile(filePath, apiType); 937 } 938 } 939 940 /** 941 * traverse files of api directory 942 * @param apiPath api directory path 943 * @param apiType 944 * @private 945 */ 946 export const traverseApiFiles = function (apiPath: string, apiType: ApiType): void { 947 if (fs.statSync(apiPath).isDirectory()) { 948 traverseFilesInDir(apiPath, apiType); 949 } else { 950 parseFile(apiPath, apiType); 951 } 952 }; 953 954 /** 955 * desc: parse openHarmony sdk to get api list 956 * @param version version of api, e.g. version 5.0.1.0 for api 9 957 * @param sdkPath sdk real path of openHarmony 958 * @param isEts true for ets, false for js 959 * @param outputDir: sdk api output directory 960 */ 961 export function parseOhSdk(sdkPath: string, version: string, isEts: boolean, outputDir: string): void { 962 mPropertySet.clear(); 963 964 // visit api directory 965 const apiPath: string = path.join(sdkPath, (isEts ? 'ets' : 'js'), version, 'api'); 966 traverseApiFiles(apiPath, ApiType.API); 967 968 // visit component directory if ets 969 if (isEts) { 970 const componentPath: string = path.join(sdkPath, 'ets', version, 'component'); 971 traverseApiFiles(componentPath, ApiType.COMPONENT); 972 } 973 974 // visit the UI conversion API 975 const uiConversionPath: string = path.join(sdkPath, (isEts ? 'ets' : 'js'), version, 976 'build-tools', 'ets-loader', 'lib', 'pre_define.js'); 977 extractStringsFromFile(uiConversionPath); 978 979 const reservedProperties: string[] = [...mPropertySet.values()]; 980 mPropertySet.clear(); 981 982 writeToFile(reservedProperties, path.join(outputDir, 'propertiesReserved.json')); 983 } 984 985 export function extractStringsFromFile(filePath: string): void { 986 let collections: string[] = []; 987 const fileContent = fs.readFileSync(filePath, 'utf-8'); 988 const regex = /"([^"]*)"/g; 989 const matches = fileContent.match(regex); 990 991 if (matches) { 992 collections = matches.map(match => match.slice(1, -1)); 993 } 994 995 collections.forEach(name => mPropertySet.add(name)); 996 } 997 998 /** 999 * save api json object to file 1000 * @private 1001 */ 1002 export function writeToFile(reservedProperties: string[], outputPath: string): void { 1003 let str: string = JSON.stringify(reservedProperties, null, '\t'); 1004 fs.writeFileSync(outputPath, str); 1005 } 1006 1007 export function isRemoteHar(filePath: string): boolean { 1008 const realPath: string = sys.realpath(filePath); 1009 return isInOhModuleFile(realPath); 1010 } 1011 1012 export function isInOhModuleFile(filePath: string): boolean { 1013 return filePath.indexOf('/oh_modules/') !== -1 || filePath.indexOf('\\oh_modules\\') !== -1; 1014 } 1015 1016 export function isParsableFile(path: string): boolean { 1017 return supportedParsingExtension.some(extension => path.endsWith(extension)); 1018 } 1019 1020 /** 1021 * parse common project or file to extract exported api list 1022 * @return reserved api names 1023 */ 1024 export function parseFileByPaths(projectPaths: Set<string>, scanningApiType: ApiType): 1025 {reservedExportPropertyAndName: Set<string> | undefined; reservedExportNames: Set<string> | undefined} { 1026 mPropertySet.clear(); 1027 mExportNames.clear(); 1028 projectPaths.forEach(path => { 1029 parseFile(path, scanningApiType); 1030 }); 1031 let reservedExportPropertyAndName: Set<string>; 1032 let reservedExportNames: Set<string>; 1033 if (scanProjectConfig.mPropertyObfuscation) { 1034 reservedExportPropertyAndName = new Set(mPropertySet); 1035 } 1036 if (scanProjectConfig.mExportObfuscation) { 1037 reservedExportNames = new Set(mExportNames); 1038 } 1039 mPropertySet.clear(); 1040 mExportNames.clear(); 1041 return { 1042 reservedExportPropertyAndName: reservedExportPropertyAndName, 1043 reservedExportNames: reservedExportNames 1044 }; 1045 } 1046 1047 /** 1048 * Collect all property names in the AST. 1049 * @param astNode Nodes of the AST. 1050 */ 1051 function collectPropertyNames(astNode: Node): void { 1052 visitElementsWithProperties(astNode); 1053 } 1054 1055 /** 1056 * Visit elements that can contain properties. 1057 * @param node The current AST node. 1058 */ 1059 function visitElementsWithProperties(node: Node): void { 1060 switch (node.kind) { 1061 case SyntaxKind.ClassDeclaration: 1062 forEachChild(node, visitClass); 1063 break; 1064 case SyntaxKind.InterfaceDeclaration: 1065 case SyntaxKind.TypeLiteral: 1066 forEachChild(node, visitInterfaceOrType); 1067 break; 1068 case SyntaxKind.EnumDeclaration: 1069 forEachChild(node, visitEnum); 1070 break; 1071 case SyntaxKind.ObjectLiteralExpression: 1072 forEachChild(node, visitObjectLiteral); 1073 break; 1074 case SyntaxKind.ModuleDeclaration: 1075 forEachChild(node, visitModule); 1076 break; 1077 } 1078 forEachChild(node, visitElementsWithProperties); 1079 } 1080 1081 function visitClass(node: Node): void { 1082 if (isPropertyDeclaration(node) || isMethodDeclaration(node)) { 1083 if (isIdentifier(node.name)) { 1084 mCurrentExportedPropertySet.add(node.name.text); 1085 } 1086 } 1087 forEachChild(node, visitClass); 1088 } 1089 1090 function visitInterfaceOrType(node: Node): void { 1091 if (isPropertySignature(node) || isMethodSignature(node)) { 1092 if (isIdentifier(node.name)) { 1093 mCurrentExportedPropertySet.add(node.name.text); 1094 } 1095 } 1096 forEachChild(node, visitInterfaceOrType); 1097 } 1098 1099 function visitEnum(node: Node): void { 1100 if (isEnumMember(node) && isIdentifier(node.name)) { 1101 mCurrentExportedPropertySet.add(node.name.text); 1102 } 1103 } 1104 1105 function visitObjectLiteral(node: Node): void { 1106 if (isPropertyAssignment(node)) { 1107 if (isIdentifier(node.name)) { 1108 mCurrentExportedPropertySet.add(node.name.text); 1109 } 1110 } 1111 forEachChild(node, visitObjectLiteral); 1112 } 1113 1114 function visitModule(node: Node): void { 1115 forEachChild(node, visitElementsWithProperties); 1116 } 1117 1118 function collectNodeName(name: string): void { 1119 mCurrentExportNameSet.add(name); 1120 mCurrentExportedPropertySet.add(name); 1121 } 1122 1123 function containsIdentifier(node: Node, found: { value: boolean }): void { 1124 if (found.value) { 1125 return; 1126 } 1127 if (isIdentifier(node)) { 1128 found.value = true; 1129 return; 1130 } 1131 forEachChild(node, childNode => { 1132 containsIdentifier(childNode, found); 1133 }); 1134 } 1135 1136 function shouldCollectEnum(node: EnumDeclaration): boolean { 1137 const members = node.members; 1138 for (const member of members) { 1139 if (isEnumMember(member) && member.initializer) { 1140 const initializer = member.initializer; 1141 const found = { value: false }; 1142 containsIdentifier(initializer, found); 1143 if (found.value) { 1144 return true; 1145 } 1146 } 1147 } 1148 return false; 1149 } 1150 1151 function collectEnumMember(node: Node): void { 1152 if (isEnumMember(node) && isIdentifier(node.name)) { 1153 mEnumMemberSet.add(node.name.text); 1154 projectWhiteListManager?.fileWhiteListInfo?.fileReservedInfo.enumProperties.add(node.name.text); 1155 } 1156 } 1157 1158 /** 1159 * Visit and collect enum members 1160 * @param node The current AST node. 1161 */ 1162 function visitEnumMembers(node: Node): void { 1163 if (isEnumDeclaration(node)) { 1164 if (!shouldCollectEnum(node)) { 1165 return; 1166 } 1167 for (const member of node.members) { 1168 collectEnumMember(member); 1169 } 1170 return; 1171 } 1172 forEachChild(node, visitEnumMembers); 1173 } 1174 1175 /** 1176 * Visit and collect enum members of non-js file 1177 * @param sourceFile The sourceFile to collect. 1178 */ 1179 function collectEnumMembersOfFile(sourceFile: SourceFile): void { 1180 if (sourceFile.fileName.endsWith(Extension.Js)) { 1181 return; 1182 } 1183 forEachChild(sourceFile, visitEnumMembers); 1184 } 1185 1186 function collectNamesWithAtKeep(node: Node, sourceFile: SourceFile): void { 1187 switch (node.kind) { 1188 case SyntaxKind.ClassDeclaration: 1189 collectClassDeclaration(node as ClassDeclaration, sourceFile); 1190 break; 1191 case SyntaxKind.InterfaceDeclaration: 1192 collectInterfaceDeclaration(node as InterfaceDeclaration, sourceFile); 1193 break; 1194 case SyntaxKind.EnumDeclaration: 1195 collectEnumDeclaration(node as EnumDeclaration, sourceFile); 1196 break; 1197 case SyntaxKind.FunctionDeclaration: 1198 collectFunctionDeclaration(node as FunctionDeclaration, sourceFile); 1199 break; 1200 case SyntaxKind.VariableStatement: 1201 collectVariableDeclararion(node as VariableStatement, sourceFile); 1202 break; 1203 case SyntaxKind.ModuleDeclaration: 1204 collectModuleDeclaration(node as ModuleDeclaration, sourceFile); 1205 break; 1206 case SyntaxKind.AnnotationDeclaration: 1207 collectAnnotationDeclaration(node as AnnotationDeclaration, sourceFile); 1208 break; 1209 } 1210 forEachChild(node, child => collectNamesWithAtKeep(child, sourceFile)); 1211 } 1212 1213 function collectClassDeclaration(node: ClassDeclaration, sourceFile: SourceFile): void { 1214 const atKeepType: AtKeepType = getAtKeepType(node, sourceFile); 1215 const isToplevel: boolean = isSourceFile(node.parent); 1216 const isExported: boolean = hasExportModifier(node); 1217 1218 if (atKeepType === AtKeepType.KeepAsConsumer) { 1219 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1220 collectClassDeclarationMembers(node, atKeepType); 1221 } else if (atKeepType === AtKeepType.KeepSymbol) { 1222 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1223 collectClassDeclarationMembers(node, atKeepType); 1224 scanAndCollectClassDeclarationMembers(node, sourceFile, isToplevel, isExported); 1225 } else { // atKeepType === AtKeepType.None 1226 scanAndCollectClassDeclarationMembers(node, sourceFile, isToplevel, isExported); 1227 } 1228 } 1229 1230 function collectClassDeclarationMembers(node: ClassDeclaration, atKeepType: AtKeepType): void { 1231 for (const member of node.members) { 1232 collectClassMemberNames(member, atKeepType); 1233 collectParameterPropertyNames(member, atKeepType); 1234 } 1235 } 1236 1237 function collectClassMemberNames(member: ClassElement, atKeepType: AtKeepType): void { 1238 if (isPropertyDeclaration(member) || isMethodDeclaration(member) || isGetAccessor(member) || isSetAccessor(member)) { 1239 if (isIdentifier(member.name)) { 1240 collectAtKeepNamesByType(member.name.text, atKeepType, WhiteListType.PropertyName); 1241 } 1242 } 1243 } 1244 1245 function collectParameterPropertyNames(member: ClassElement, atKeepType: AtKeepType): void { 1246 if (isConstructorDeclaration(member)) { 1247 member.parameters.forEach((param) => { 1248 if (isParameterPropertyDeclaration(param, member) && isIdentifier(param.name)) { 1249 collectAtKeepNamesByType(param.name.text, atKeepType, WhiteListType.PropertyName); 1250 } 1251 }); 1252 } 1253 } 1254 1255 function scanAndCollectClassDeclarationMembers(node: ClassDeclaration, sourceFile: SourceFile, isToplevel: boolean, isExported: boolean): void { 1256 let shouldKeepClassName: boolean = false; 1257 let atKeepTypeOfClass: AtKeepType = AtKeepType.KeepSymbol; 1258 for (const member of node.members) { 1259 const atKeepType: AtKeepType = getAtKeepType(member, sourceFile); 1260 if (atKeepType === AtKeepType.None) { 1261 continue; 1262 } 1263 if (atKeepType === AtKeepType.KeepAsConsumer) { 1264 atKeepTypeOfClass = AtKeepType.KeepAsConsumer; 1265 } 1266 if (isPropertyDeclaration(member) || isMethodDeclaration(member) || isGetAccessor(member) || isSetAccessor(member)) { 1267 if (isIdentifier(member.name)) { 1268 shouldKeepClassName = true; 1269 collectAtKeepNamesByType(member.name.text, atKeepType, WhiteListType.PropertyName); 1270 } 1271 } 1272 if (isConstructorDeclaration(member)) { 1273 shouldKeepClassName = true; 1274 } 1275 } 1276 if (shouldKeepClassName) { 1277 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepTypeOfClass); 1278 } 1279 } 1280 1281 function collectInterfaceDeclaration(node: InterfaceDeclaration, sourceFile: SourceFile): void { 1282 const atKeepType: AtKeepType = getAtKeepType(node, sourceFile); 1283 const isToplevel: boolean = isSourceFile(node.parent); 1284 const isExported: boolean = hasExportModifier(node); 1285 1286 if (atKeepType === AtKeepType.KeepAsConsumer) { 1287 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1288 collectInterfaceDeclarationMembers(node, atKeepType); 1289 } else if (atKeepType === AtKeepType.KeepSymbol) { 1290 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1291 collectInterfaceDeclarationMembers(node, atKeepType); 1292 scanAndCollectInterfaceDeclarationMembers(node, sourceFile, isToplevel, isExported); 1293 } else { // atKeepType === AtKeepType.None 1294 scanAndCollectInterfaceDeclarationMembers(node, sourceFile, isToplevel, isExported); 1295 } 1296 } 1297 1298 function collectInterfaceDeclarationMembers(node: InterfaceDeclaration, atKeepType: AtKeepType): void { 1299 for (const member of node.members) { 1300 if (isPropertySignature(member) || isMethodSignature(member)) { 1301 if (isIdentifier(member.name)) { 1302 collectAtKeepNamesByType(member.name.text, atKeepType, WhiteListType.PropertyName); 1303 } 1304 } 1305 } 1306 } 1307 1308 function scanAndCollectInterfaceDeclarationMembers(node: InterfaceDeclaration, sourceFile: SourceFile, isToplevel: boolean, isExported: boolean): void { 1309 let shouldKeepInterfaceName: boolean = false; 1310 let atKeepTypeOfInterface: AtKeepType = AtKeepType.KeepSymbol; 1311 for (const member of node.members) { 1312 const atKeepType: AtKeepType = getAtKeepType(member, sourceFile); 1313 if (atKeepType === AtKeepType.None) { 1314 continue; 1315 } 1316 if (atKeepType === AtKeepType.KeepAsConsumer) { 1317 atKeepTypeOfInterface = AtKeepType.KeepAsConsumer; 1318 } 1319 if (isPropertySignature(member) || isMethodSignature(member)) { 1320 if (isIdentifier(member.name)) { 1321 shouldKeepInterfaceName = true; 1322 collectAtKeepNamesByType(member.name.text, atKeepType, WhiteListType.PropertyName); 1323 } 1324 } 1325 } 1326 if (shouldKeepInterfaceName) { 1327 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepTypeOfInterface); 1328 } 1329 } 1330 1331 function collectEnumDeclaration(node: EnumDeclaration, sourceFile: SourceFile): void { 1332 const atKeepType: AtKeepType = getAtKeepType(node, sourceFile); 1333 const isToplevel: boolean = isSourceFile(node.parent); 1334 const isExported: boolean = hasExportModifier(node); 1335 1336 if (atKeepType === AtKeepType.KeepAsConsumer) { 1337 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1338 collectEnumDeclarationMembers(node, atKeepType); 1339 } else if (atKeepType === AtKeepType.KeepSymbol) { 1340 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1341 collectEnumDeclarationMembers(node, atKeepType); 1342 scanAndCollectEnumDeclarationMembers(node, sourceFile, isToplevel, isExported); 1343 } else { // atKeepType === AtKeepType.None 1344 scanAndCollectEnumDeclarationMembers(node, sourceFile, isToplevel, isExported); 1345 } 1346 } 1347 1348 function collectEnumDeclarationMembers(node: EnumDeclaration, atKeepType: AtKeepType): void { 1349 for (const member of node.members) { 1350 if (isEnumMember(member)) { 1351 if (isIdentifier(member.name)) { 1352 collectAtKeepNamesByType(member.name.text, atKeepType, WhiteListType.PropertyName); 1353 } 1354 } 1355 } 1356 } 1357 1358 function scanAndCollectEnumDeclarationMembers(node: EnumDeclaration, sourceFile: SourceFile, isToplevel: boolean, isExported: boolean): void { 1359 let shouldKeepEnumName: boolean = false; 1360 let atKeepTypeOfEnum: AtKeepType = AtKeepType.KeepSymbol; 1361 for (const member of node.members) { 1362 const atKeepType: AtKeepType = getAtKeepType(member, sourceFile); 1363 if (atKeepType === AtKeepType.None) { 1364 continue; 1365 } 1366 if (atKeepType === AtKeepType.KeepAsConsumer) { 1367 atKeepTypeOfEnum = AtKeepType.KeepAsConsumer; 1368 } 1369 if (isEnumMember(member)) { 1370 if (isIdentifier(member.name)) { 1371 shouldKeepEnumName = true; 1372 collectAtKeepNamesByType(member.name.text, atKeepType, WhiteListType.PropertyName); 1373 } 1374 } 1375 } 1376 if (shouldKeepEnumName) { 1377 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepTypeOfEnum); 1378 } 1379 } 1380 1381 function collectFunctionDeclaration(node: FunctionDeclaration, sourceFile: SourceFile): void { 1382 const atKeepType: AtKeepType = getAtKeepType(node, sourceFile); 1383 const isToplevel: boolean = isSourceFile(node.parent); 1384 const isExported: boolean = hasExportModifier(node); 1385 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1386 } 1387 1388 function collectVariableDeclararion(node: VariableStatement, sourceFile: SourceFile): void { 1389 const atKeepType: AtKeepType = getAtKeepType(node, sourceFile); 1390 const isToplevel: boolean = isSourceFile(node.parent); 1391 const isExported: boolean = hasExportModifier(node); 1392 node.declarationList.forEachChild((child) => { 1393 if (isVariableDeclaration(child)) { 1394 collectToplevelOrExportedNames(child, isToplevel, isExported, atKeepType); 1395 } 1396 }); 1397 } 1398 1399 function collectModuleDeclaration(node: ModuleDeclaration, sourceFile: SourceFile, atKeepTypeOuter?: AtKeepType): void { 1400 let atKeepType: AtKeepType; 1401 if (atKeepTypeOuter) { 1402 atKeepType = atKeepTypeOuter; 1403 } else { 1404 atKeepType = getAtKeepType(node, sourceFile); 1405 } 1406 const isToplevel: boolean = isSourceFile(node.parent); 1407 const isExported: boolean = hasExportModifier(node); 1408 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1409 1410 if (atKeepType !== AtKeepType.None && isModuleBlock(node.body)) { 1411 node.body.statements.forEach((child) => { 1412 collectModuleChild(child, atKeepType, sourceFile); 1413 }); 1414 } 1415 } 1416 1417 function collectModuleChild(child: Node, atKeepType: AtKeepType, sourceFile: SourceFile): void { 1418 const isExportedChild: boolean = hasExportModifier(child); 1419 if (!isExportedChild) { 1420 return; 1421 } 1422 1423 switch (child.kind) { 1424 case SyntaxKind.ClassDeclaration: 1425 case SyntaxKind.InterfaceDeclaration: 1426 case SyntaxKind.EnumDeclaration: 1427 case SyntaxKind.FunctionDeclaration: 1428 case SyntaxKind.TypeAliasDeclaration: 1429 collectToplevelOrExportedNames(child as KeepTargetNode, false, true, atKeepType); 1430 break; 1431 case SyntaxKind.ModuleDeclaration: 1432 collectToplevelOrExportedNames(child as ModuleDeclaration, false, true, atKeepType); 1433 collectModuleDeclaration(child as ModuleDeclaration, sourceFile, atKeepType); 1434 break; 1435 case SyntaxKind.VariableStatement: 1436 (child as VariableStatement).declarationList.forEachChild((variableDeclaration) => { 1437 if (isVariableDeclaration(variableDeclaration)) { 1438 collectToplevelOrExportedNames(variableDeclaration, false, true, atKeepType); 1439 } 1440 }); 1441 break; 1442 } 1443 } 1444 1445 function collectAnnotationDeclaration(node: AnnotationDeclaration, sourceFile: SourceFile): void { 1446 const atKeepType: AtKeepType = getAtKeepType(node, sourceFile); 1447 const isToplevel: boolean = isSourceFile(node.parent); 1448 const isExported: boolean = hasExportModifier(node); 1449 collectToplevelOrExportedNames(node, isToplevel, isExported, atKeepType); 1450 } 1451 1452 function getAtKeepType(node: Node, sourceFile: SourceFile): AtKeepType { 1453 const ranges: CommentRange[] | undefined = getLeadingCommentRangesOfNode(node, sourceFile); 1454 let atKeepType: AtKeepType = AtKeepType.None; 1455 if (!ranges?.length) { 1456 return atKeepType; 1457 } 1458 for (const range of ranges) { 1459 if (range.kind !== SyntaxKind.SingleLineCommentTrivia) { 1460 continue; 1461 } 1462 const comment: string = sourceFile.text.slice(range.pos, range.end).replace(/\s+/g, ''); 1463 if (comment === KEEP_AS_CONSUMER) { 1464 atKeepType = AtKeepType.KeepAsConsumer; 1465 return atKeepType; 1466 } 1467 if (comment === KEEP_SYMBOL) { 1468 atKeepType = AtKeepType.KeepSymbol; 1469 } 1470 } 1471 return atKeepType; 1472 } 1473 1474 function collectToplevelOrExportedNames(node: KeepTargetNode, isToplevel: boolean, isExported: boolean, atKeepType: AtKeepType): void { 1475 if ((!isToplevel && !isExported) || !isIdentifier(node.name)) { 1476 return; 1477 } 1478 1479 collectAtKeepNamesByType(node.name.text, atKeepType, WhiteListType.GlobalName); 1480 1481 if (isExported) { 1482 collectAtKeepNamesByType(node.name.text, atKeepType, WhiteListType.PropertyName); 1483 } 1484 } 1485 1486 function collectAtKeepNamesByType(name: string, atKeepType: AtKeepType, whiteListType: WhiteListType): void { 1487 if (atKeepType === AtKeepType.None) { 1488 return; 1489 } 1490 1491 const targetCollection = atKeepType === AtKeepType.KeepSymbol 1492 ? keepSymbolTemp 1493 : keepAsConsumerTemp; 1494 1495 updateKeepCollection(targetCollection, whiteListType, name); 1496 } 1497 1498 function updateKeepCollection(collector: KeepInfo, whiteListType: WhiteListType, name: string): void { 1499 if (whiteListType === WhiteListType.PropertyName) { 1500 collector.propertyNames.add(name); 1501 } else { 1502 collector.globalNames.add(name); 1503 } 1504 } 1505 1506 function ensureEntry<K, V>(map: Map<K, V>, key: K, createValue: () => V): void { 1507 if (!map.has(key)) { 1508 map.set(key, createValue()); 1509 } 1510 } 1511 1512 function isWhiteListedDecorator(name: string): boolean { 1513 return DECORATOR_WHITE_LIST.includes(name); 1514 } 1515 1516 function getDecoratorName(expr: Expression): string | undefined { 1517 if (isCallExpression(expr)) { 1518 return expr.expression.getText(); 1519 } 1520 if (isIdentifier(expr)) { 1521 return expr.text; 1522 } 1523 return undefined; 1524 } 1525}