* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
forEachChild,
isIdentifier,
} from 'typescript';
import type {
Identifier,
Node,
SourceFile,
StructDeclaration,
} from 'typescript';
import type { IOptions } from '../configs/IOptions';
import { LocalVariableCollections, PropCollections, UnobfuscationCollections } from './CommonCollections';
import { historyUnobfuscatedNamesMap } from '../transformers/rename/RenameIdentifierTransformer';
export interface ReservedNameInfo {
universalReservedArray: RegExp[];
specificReservedArray: string[];
}
* collect exist identifier names in current source file
* @param sourceFile
*/
export function collectExistNames(sourceFile: SourceFile): Set<string> {
const identifiers: Set<string> = new Set<string>();
let visit = (node: Node): void => {
if (isIdentifier(node)) {
identifiers.add(node.text);
}
forEachChild(node, visit);
};
forEachChild(sourceFile, visit);
return identifiers;
}
type IdentifiersAndStructs = {shadowIdentifiers: Identifier[], shadowStructs: StructDeclaration[]};
* separate wildcards from specific items.
*/
export function separateUniversalReservedItem(originalArray: string[] | undefined,
shouldPrintKeptName: boolean): ReservedNameInfo {
if (!originalArray) {
throw new Error('Unable to handle the empty array.');
}
const reservedInfo: ReservedNameInfo = {
universalReservedArray: [],
specificReservedArray: []
};
originalArray.forEach(reservedItem => {
if (containWildcards(reservedItem)) {
const regexPattern = wildcardTransformer(reservedItem);
const regexOperator = new RegExp(`^${regexPattern}$`);
reservedInfo.universalReservedArray.push(regexOperator);
recordWildcardMapping(reservedItem, regexOperator, shouldPrintKeptName);
} else {
reservedInfo.specificReservedArray.push(reservedItem);
}
});
return reservedInfo;
}
function recordWildcardMapping(originString: string, regExpression: RegExp,
shouldPrintKeptName: boolean): void {
if (shouldPrintKeptName) {
UnobfuscationCollections.reservedWildcardMap.set(regExpression, originString);
}
}
* check if the item contains '*', '?'.
*/
export function containWildcards(item: string): boolean {
return /[\*\?]/.test(item);
}
* Convert specific characters into regular expressions.
*/
export function wildcardTransformer(wildcard: string, isPath?: boolean): string {
let escapedItem = wildcard.replace(/[\\+^${}()|\[\]\.]/g, '\\$&');
if (isPath) {
return escapedItem.replace(/\*\*/g, '.*').replace(/(?<!\.)\*/g, '[^/]*').replace(/\?/g, '[^/]');
}
return escapedItem.replace(/\*/g, '.*').replace(/\?/g, '.');
}
* Determine whether the original name needs to be preserved.
*/
export function needToBeReserved(reservedSet: Set<string>, universalArray: RegExp[], originalName: string): boolean {
return reservedSet.has(originalName) || isMatchWildcard(universalArray, originalName);
}
* Determine whether it can match the wildcard character in the array.
*/
export function isMatchWildcard(wildcardArray: RegExp[], item: string): boolean {
for (const wildcard of wildcardArray) {
if (wildcard.test(item)) {
return true;
}
}
return false;
}
* Separate parts of an array that contain wildcard characters.
*/
export function handleReservedConfig(config: IOptions, optionName: string, reservedListName: string,
universalListName: string, enableRemove?: string, enablePrint?: boolean): void {
const reservedConfig = config?.[optionName];
let needSeparate: boolean = !!(reservedConfig?.[reservedListName]);
if (enableRemove) {
needSeparate &&= reservedConfig[enableRemove];
}
if (needSeparate) {
const shouldPrintKeptName = enablePrint === undefined ? !!(config.mUnobfuscationOption?.mPrintKeptNames) : enablePrint;
const reservedInfo: ReservedNameInfo = separateUniversalReservedItem(reservedConfig[reservedListName], shouldPrintKeptName);
reservedConfig[reservedListName] = reservedInfo.specificReservedArray;
reservedConfig[universalListName] = reservedInfo.universalReservedArray;
}
}
export function isReservedLocalVariable(mangledName: string): boolean {
return LocalVariableCollections.reservedLangForLocal.has(mangledName) ||
LocalVariableCollections.reservedConfig?.has(mangledName) ||
UnobfuscationCollections.reservedSdkApiForLocal?.has(mangledName) ||
UnobfuscationCollections.reservedExportName?.has(mangledName);
}
export function isReservedTopLevel(originalName: string, enablePropertyObf: boolean): boolean {
if (enablePropertyObf) {
return isReservedProperty(originalName);
}
return UnobfuscationCollections.reservedLangForTopLevel.has(originalName) ||
UnobfuscationCollections.reservedSdkApiForGlobal?.has(originalName) ||
UnobfuscationCollections.reservedExportName?.has(originalName) ||
PropCollections.reservedProperties?.has(originalName) ||
isMatchWildcard(PropCollections.universalReservedProperties, originalName);
}
export function isReservedProperty(originalName: string): boolean {
return UnobfuscationCollections.reservedSdkApiForProp?.has(originalName) ||
UnobfuscationCollections.reservedLangForProperty?.has(originalName) ||
UnobfuscationCollections.reservedStruct?.has(originalName) ||
UnobfuscationCollections.reservedExportNameAndProp?.has(originalName) ||
UnobfuscationCollections.reservedStrProp?.has(originalName) ||
UnobfuscationCollections.reservedObjProp?.has(originalName) ||
UnobfuscationCollections.reservedEnum?.has(originalName) ||
PropCollections.reservedProperties?.has(originalName) ||
isMatchWildcard(PropCollections.universalReservedProperties, originalName);
}
* Reasons for not being obfuscated.
*/
export enum WhitelistType {
SDK = 'sdk',
LANG = 'lang',
CONF = 'conf',
STRUCT = 'struct',
EXPORT = 'exported',
STRPROP = 'strProp',
OBJECTPROP = 'objectProp',
ENUM = 'enum'
}
function needToRecordTopLevel(originalName: string, recordMap: Map<string, Set<string>>,
nameWithScope: string, enablePropertyObf: boolean): boolean {
if (enablePropertyObf) {
return needToRecordProperty(originalName, recordMap, nameWithScope);
}
let reservedFlag = false;
if (UnobfuscationCollections.reservedLangForTopLevel.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.LANG, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedSdkApiForGlobal?.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.SDK, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedExportName?.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.EXPORT, recordMap);
reservedFlag = true;
}
if (PropCollections.reservedProperties?.has(originalName) ||
isMatchWildcard(PropCollections.universalReservedProperties, originalName)) {
recordReservedName(nameWithScope, WhitelistType.CONF, recordMap);
reservedFlag = true;
}
return reservedFlag;
}
function needToReservedLocal(originalName: string, recordMap: Map<string, Set<string>>, nameWithScope: string): boolean {
let reservedFlag = false;
if (LocalVariableCollections.reservedLangForLocal.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.LANG, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedSdkApiForLocal?.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.SDK, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedExportName?.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.EXPORT, recordMap);
reservedFlag = true;
}
if (LocalVariableCollections.reservedConfig?.has(originalName)) {
recordReservedName(nameWithScope, WhitelistType.CONF, recordMap);
reservedFlag = true;
}
return reservedFlag;
}
* If the property name is in the whitelist, record the reason for not being obfuscated.
* @param nameWithScope: If both property obfuscation and top-level obfuscation or export obfuscation are enabled,
* this interface is also used to record the reasons why the top-level names or export names were not obfuscated,
* and the top-level names or export names include the scope.
*/
export function needToRecordProperty(originalName: string, recordMap?: Map<string, Set<string>>, nameWithScope?: string): boolean {
let reservedFlag = false;
let recordName = nameWithScope ? nameWithScope : originalName;
if (UnobfuscationCollections.reservedSdkApiForProp?.has(originalName)) {
recordReservedName(recordName, WhitelistType.SDK, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedLangForProperty?.has(originalName)) {
recordReservedName(recordName, WhitelistType.LANG, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedStruct?.has(originalName)) {
recordReservedName(recordName, WhitelistType.STRUCT, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedExportNameAndProp?.has(originalName)) {
recordReservedName(recordName, WhitelistType.EXPORT, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedObjProp?.has(originalName)) {
recordReservedName(recordName, WhitelistType.OBJECTPROP, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedStrProp?.has(originalName)) {
recordReservedName(recordName, WhitelistType.STRPROP, recordMap);
reservedFlag = true;
}
if (UnobfuscationCollections.reservedEnum?.has(originalName)) {
recordReservedName(recordName, WhitelistType.ENUM, recordMap);
reservedFlag = true;
}
if (PropCollections.reservedProperties?.has(originalName) ||
isMatchWildcard(PropCollections.universalReservedProperties, originalName)) {
recordReservedName(recordName, WhitelistType.CONF, recordMap);
reservedFlag = true;
}
return reservedFlag;
}
export function isInTopLevelWhitelist(originalName: string, recordMap: Map<string, Set<string>>,
nameWithScope: string, enablePropertyObf: boolean, shouldPrintKeptNames: boolean): boolean {
if (shouldPrintKeptNames) {
return needToRecordTopLevel(originalName, recordMap, nameWithScope, enablePropertyObf);
}
return isReservedTopLevel(originalName, enablePropertyObf);
}
export function isInPropertyWhitelist(originalName: string, recordMap: Map<string, Set<string>>,
shouldPrintKeptNames: boolean): boolean {
if (shouldPrintKeptNames) {
return needToRecordProperty(originalName, recordMap);
}
return isReservedProperty(originalName);
}
export function isInLocalWhitelist(originalName: string, recordMap: Map<string, Set<string>>,
nameWithScope: string, shouldPrintKeptNames: boolean): boolean {
if (shouldPrintKeptNames) {
return needToReservedLocal(originalName, recordMap, nameWithScope);
}
return isReservedLocalVariable(originalName);
}
function recordReservedName(originalName: string, type: string, recordObj: Map<string, Set<string>>): void {
if (!recordObj.has(originalName)) {
recordObj.set(originalName, new Set());
}
recordObj.get(originalName).add(type);
}
export function recordHistoryUnobfuscatedNames(nameWithScope: string): void {
if (historyUnobfuscatedNamesMap?.has(nameWithScope)) {
UnobfuscationCollections.unobfuscatedNamesMap.set(nameWithScope,
new Set(historyUnobfuscatedNamesMap.get(nameWithScope)));
}
}