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 {Expression, Identifier, Node, ObjectBindingPattern, SourceFile, TypeChecker} from 'typescript'; 17import { 18 Symbol, 19 SyntaxKind, 20 getModifiers, 21 isBinaryExpression, 22 isBindingElement, 23 isCallExpression, 24 isClassDeclaration, 25 isClassExpression, 26 isComputedPropertyName, 27 isConstructorDeclaration, 28 isElementAccessExpression, 29 isEnumMember, 30 isGetAccessor, 31 isIdentifier, 32 isMetaProperty, 33 isMethodDeclaration, 34 isMethodSignature, 35 isParameter, 36 isPrivateIdentifier, 37 isPropertyAccessExpression, 38 isPropertyAssignment, 39 isPropertyDeclaration, 40 isPropertySignature, 41 isQualifiedName, 42 isSetAccessor, 43 isVariableDeclaration, 44} from 'typescript'; 45import { isParameterPropertyModifier } from './OhsUtil'; 46import { Extension } from '../common/type'; 47 48export class NodeUtils { 49 public static isPropertyDeclarationNode(node: Node): boolean { 50 let parent: Node | undefined = node.parent; 51 if (!parent) { 52 return false; 53 } 54 55 /** eg: { 'name'' : 'akira' }, pass */ 56 if (isPropertyAssignment(parent)) { 57 return parent.name === node; 58 } 59 60 if (isComputedPropertyName(parent) && parent.expression === node) { 61 return true; 62 } 63 64 /** object binding pattern */ 65 if (isBindingElement(parent) && parent.propertyName === node) { 66 return true; 67 } 68 69 /** eg: interface/type inf { 'name' : string}, pass */ 70 if (isPropertySignature(parent) && parent.name === node) { 71 return true; 72 } 73 74 /** eg: interface/type T1 { func(arg: string): number;} */ 75 if (isMethodSignature(parent) && parent.name === node) { 76 return true; 77 } 78 79 /** eg: enum { xxx = 1}; */ 80 if (isEnumMember(parent) && parent.name === node) { 81 return true; 82 } 83 84 /** class { private name= 1}; */ 85 if (isPropertyDeclaration(parent) && parent.name === node) { 86 return true; 87 } 88 89 /** class {'getName': function() {}} let _ = { getName() [}} */ 90 if (isMethodDeclaration(parent) && parent.name === node) { 91 return true; 92 } 93 94 if (isSetAccessor(parent) && parent.name === node) { 95 return true; 96 } 97 98 return isGetAccessor(parent) && parent.name === node; 99 } 100 101 public static isPropertyOrElementAccessNode(node: Node): boolean { 102 return this.isPropertyAccessNode(node) || this.isElementAccessNode(node) || false; 103 } 104 105 public static isPropertyAccessNode(node: Node): boolean { 106 let parent: Node | undefined = node.parent; 107 if (!parent) { 108 return false; 109 } 110 111 /** eg: a.b = 1 */ 112 if (isPropertyAccessExpression(parent) && parent.name === node) { 113 return true; 114 } 115 if (isPrivateIdentifier(node) && NodeUtils.isInClassDeclaration(parent)) { 116 return NodeUtils.isInExpression(parent); 117 } 118 return isQualifiedName(parent) && parent.right === node; 119 } 120 121 private static isInClassDeclaration(node: Node | undefined): boolean { 122 if (!node) { 123 return false; 124 } 125 126 if (isClassDeclaration(node) || isClassExpression(node)) { 127 return true; 128 } 129 130 return NodeUtils.isInClassDeclaration(node.parent); 131 } 132 133 public static isInClassDeclarationForTest(node: Node | undefined): boolean { 134 return NodeUtils.isInClassDeclaration(node); 135 } 136 137 private static isInExpression(node: Node | undefined): boolean { 138 return !!node && NodeUtils.isInOperator(node); 139 } 140 141 public static isInExpressionForTest(node: Node | undefined): boolean { 142 return NodeUtils.isInExpression(node); 143 } 144 145 private static isInOperator(node: Node): boolean { 146 return isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.InKeyword; 147 } 148 149 public static isInOperatorForTest(node: Node | undefined): boolean { 150 return NodeUtils.isInOperator(node); 151 } 152 153 public static isElementAccessNode(node: Node): boolean { 154 let parent: Node | undefined = node.parent; 155 if (!parent) { 156 return false; 157 } 158 159 return isElementAccessExpression(parent) && parent.argumentExpression === node; 160 } 161 162 public static isClassPropertyInConstructorParams(node: Node): boolean { 163 if (!isIdentifier(node)) { 164 return false; 165 } 166 167 if (!node.parent || !isParameter(node.parent)) { 168 return false; 169 } 170 171 const modifiers = getModifiers(node.parent); 172 if (!modifiers || modifiers.length === 0 || !modifiers.find(modifier => isParameterPropertyModifier(modifier))) { 173 return false; 174 } 175 176 return node.parent.parent && isConstructorDeclaration(node.parent.parent); 177 } 178 179 public static isClassPropertyInConstructorBody(node: Node, constructorParams: Set<string>): boolean { 180 if (!isIdentifier(node)) { 181 return false; 182 } 183 184 const id: string = node.escapedText.toString(); 185 let curNode: Node = node.parent; 186 while (curNode) { 187 if (isConstructorDeclaration(curNode) && constructorParams.has(id)) { 188 return true; 189 } 190 191 curNode = curNode.parent; 192 } 193 194 return false; 195 } 196 197 public static isPropertyNode(node: Node): boolean { 198 if (this.isPropertyOrElementAccessNode(node)) { 199 return true; 200 } 201 202 return this.isPropertyDeclarationNode(node); 203 } 204 205 public static isObjectBindingPatternAssignment(node: ObjectBindingPattern): boolean { 206 if (!node || !node.parent || !isVariableDeclaration(node.parent)) { 207 return false; 208 } 209 210 const initializer: Expression = node.parent.initializer; 211 return initializer && isCallExpression(initializer); 212 } 213 214 public static isDeclarationFile(node: SourceFile): boolean { 215 return node.isDeclarationFile; 216 } 217 218 public static getSourceFileOfNode(node: Node): SourceFile { 219 while (node && node.kind !== SyntaxKind.SourceFile) { 220 node = node.parent; 221 } 222 return <SourceFile>node; 223 } 224 225 public static isDETSFile(node: Node | undefined): boolean { 226 return !!node && NodeUtils.getSourceFileOfNode(node).fileName.endsWith(Extension.DETS); 227 } 228 229 public static isNewTargetNode(node: Identifier): boolean { 230 if (isMetaProperty(node.parent) && node.parent.keywordToken === SyntaxKind.NewKeyword && node.escapedText === 'target') { 231 return true; 232 } 233 return false; 234 } 235 236 public static findSymbolOfIdentifier(checker: TypeChecker, node: Identifier): Symbol | undefined { 237 let sym: Symbol | undefined = checker.getSymbolAtLocation(node); 238 if (!sym || (sym && sym.name !== 'default')) { 239 return sym; 240 } 241 /* Handle default exports, eg. export default class Ability {}; 242 The expected symbol we want to find to obfuscate is named "Ability", 243 but `getSymbolAtLocation` will return the symbol named "default", so we need to continue to search. 244 */ 245 let localSyms: Symbol[] = checker.getSymbolsInScope(node, sym.flags); 246 for (let i = 0; i < localSyms.length; i++) { 247 const localSym = localSyms[i]; 248 // `localSym` named "Ability" has property `exportSymbol` named "default" that we find by `getSymbolAtLocation`, 249 // So the `localSym` is what we want to obfuscate. 250 if (localSym && localSym.name === node.text && localSym.exportSymbol === sym) { 251 sym = localSym; 252 break; 253 } 254 } 255 return sym; 256 } 257}