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