1/* 2 * Copyright (c) 2024-2025 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 path from 'path'; 17import { transfer2UnixPath } from '../../utils/pathTransfer'; 18import { ClassType, Type } from '../base/Type'; 19import { MethodParameter } from './builder/ArkMethodBuilder'; 20import { 21 ANONYMOUS_CLASS_PREFIX, 22 LEXICAL_ENV_NAME_PREFIX, 23 NAME_DELIMITER, 24 UNKNOWN_CLASS_NAME, 25 UNKNOWN_FILE_NAME, 26 UNKNOWN_NAMESPACE_NAME, 27 UNKNOWN_PROJECT_NAME, 28} from '../common/Const'; 29import { CryptoUtils } from '../../utils/crypto_utils'; 30 31export type Signature = FileSignature | NamespaceSignature | ClassSignature | MethodSignature | FieldSignature | LocalSignature | AliasTypeSignature; 32 33export interface ArkSignature { 34 getSignature(): Signature; 35} 36 37/** 38 * @category core/model 39 */ 40export class FileSignature { 41 private projectName: string; 42 private fileName: string; 43 private hashcode: number; 44 45 public static readonly DEFAULT: FileSignature = new FileSignature(UNKNOWN_PROJECT_NAME, UNKNOWN_FILE_NAME); 46 47 constructor(projectName: string, fileName: string) { 48 this.projectName = projectName; 49 this.fileName = transfer2UnixPath(fileName); 50 this.hashcode = CryptoUtils.hashcode(this.toString()); 51 } 52 53 public getProjectName(): string { 54 return this.projectName; 55 } 56 57 public getFileName(): string { 58 return this.fileName; 59 } 60 61 public toString(): string { 62 return `@${this.projectName}/${this.fileName}: `; 63 } 64 65 public toMapKey(): string { 66 return `${this.hashcode}${path.basename(this.fileName)}`; 67 } 68} 69 70export class NamespaceSignature { 71 private namespaceName: string; 72 private declaringFileSignature: FileSignature; 73 private declaringNamespaceSignature: NamespaceSignature | null; 74 75 public static readonly DEFAULT: NamespaceSignature = new NamespaceSignature(UNKNOWN_NAMESPACE_NAME, FileSignature.DEFAULT, null); 76 77 constructor(namespaceName: string, declaringFileSignature: FileSignature, declaringNamespaceSignature: NamespaceSignature | null = null) { 78 this.namespaceName = namespaceName; 79 this.declaringFileSignature = declaringFileSignature; 80 this.declaringNamespaceSignature = declaringNamespaceSignature; 81 } 82 83 public getNamespaceName(): string { 84 return this.namespaceName; 85 } 86 87 public getDeclaringFileSignature(): FileSignature { 88 return this.declaringFileSignature; 89 } 90 91 public getDeclaringNamespaceSignature(): NamespaceSignature | null { 92 return this.declaringNamespaceSignature; 93 } 94 95 public toString(): string { 96 if (this.declaringNamespaceSignature) { 97 return this.declaringNamespaceSignature.toString() + '.' + this.namespaceName; 98 } else { 99 return this.declaringFileSignature.toString() + this.namespaceName; 100 } 101 } 102 103 public toMapKey(): string { 104 if (this.declaringNamespaceSignature) { 105 return this.declaringNamespaceSignature.toMapKey() + '.' + this.namespaceName; 106 } else { 107 return this.declaringFileSignature.toMapKey() + this.namespaceName; 108 } 109 } 110} 111 112export class ClassSignature { 113 private declaringFileSignature: FileSignature; 114 private declaringNamespaceSignature: NamespaceSignature | null; 115 private className: string; 116 117 public static readonly DEFAULT: ClassSignature = new ClassSignature(UNKNOWN_CLASS_NAME, FileSignature.DEFAULT, null); 118 119 constructor(className: string, declaringFileSignature: FileSignature, declaringNamespaceSignature: NamespaceSignature | null = null) { 120 this.className = className; 121 this.declaringFileSignature = declaringFileSignature; 122 this.declaringNamespaceSignature = declaringNamespaceSignature; 123 } 124 125 /** 126 * Returns the declaring file signature. 127 * @returns The declaring file signature. 128 */ 129 public getDeclaringFileSignature(): FileSignature { 130 return this.declaringFileSignature; 131 } 132 133 /** 134 * Get the declaring namespace's signature. 135 * @returns the declaring namespace's signature. 136 */ 137 public getDeclaringNamespaceSignature(): NamespaceSignature | null { 138 return this.declaringNamespaceSignature; 139 } 140 141 /** 142 * Get the **string** name of class from the the class signature. The default value is `""`. 143 * @returns The name of this class. 144 */ 145 public getClassName(): string { 146 return this.className; 147 } 148 149 /** 150 * 151 * @returns The name of the declare class. 152 */ 153 public getDeclaringClassName(): string { 154 if (this.className.startsWith(ANONYMOUS_CLASS_PREFIX)) { 155 let temp = this.className; 156 do { 157 temp = temp.substring(temp.indexOf(NAME_DELIMITER) + 1, temp.lastIndexOf('.')); 158 } while (temp.startsWith(ANONYMOUS_CLASS_PREFIX)); 159 return temp; 160 } 161 return this.className; 162 } 163 164 public setClassName(className: string): void { 165 this.className = className; 166 } 167 168 public getType(): ClassType { 169 return new ClassType(this); 170 } 171 172 public toString(): string { 173 if (this.declaringNamespaceSignature) { 174 return this.declaringNamespaceSignature.toString() + '.' + this.className; 175 } else { 176 return this.declaringFileSignature.toString() + this.className; 177 } 178 } 179 180 public toMapKey(): string { 181 if (this.declaringNamespaceSignature) { 182 return this.declaringNamespaceSignature.toMapKey() + '.' + this.className; 183 } else { 184 return this.declaringFileSignature.toMapKey() + this.className; 185 } 186 } 187} 188 189/** 190 * `AliasClassSignature` is used to extend `ClassSignature`, preserving the actual name used during invocation. 191 */ 192export class AliasClassSignature extends ClassSignature { 193 private readonly aliasName: string; 194 195 constructor(aliasName: string, signature: ClassSignature) { 196 super(signature.getClassName(), signature.getDeclaringFileSignature(), signature.getDeclaringNamespaceSignature()); 197 this.aliasName = aliasName; 198 } 199 200 /** 201 * Returns the name used in the code. 202 */ 203 public getClassName(): string { 204 return this.aliasName; 205 } 206 207 /** 208 * Return the original name of declared class 209 */ 210 public getOriginName(): string { 211 return super.getClassName(); 212 } 213} 214 215export type BaseSignature = ClassSignature | NamespaceSignature; 216 217export class FieldSignature { 218 private declaringSignature: BaseSignature; 219 private fieldName: string; 220 private type: Type; 221 private staticFlag: boolean; 222 223 constructor(fieldName: string, declaringSignature: BaseSignature, type: Type, staticFlag: boolean = false) { 224 this.fieldName = fieldName; 225 this.declaringSignature = declaringSignature; 226 this.type = type; 227 this.staticFlag = staticFlag; 228 } 229 230 public getDeclaringSignature(): BaseSignature { 231 return this.declaringSignature; 232 } 233 234 public getBaseName(): string { 235 return this.declaringSignature instanceof ClassSignature ? this.declaringSignature.getClassName() : this.declaringSignature.getNamespaceName(); 236 } 237 238 public getFieldName(): string { 239 return this.fieldName; 240 } 241 242 public getType(): Type { 243 return this.type; 244 } 245 246 public isStatic(): boolean { 247 return this.staticFlag; 248 } 249 250 // temp for being compatible with existing type inference 251 public setType(type: Type): void { 252 this.type = type; 253 } 254 255 // temp for being compatible with existing type inference 256 public setStaticFlag(flag: boolean): void { 257 this.staticFlag = flag; 258 } 259 260 public toString(): string { 261 let tmpSig = this.fieldName; 262 if (this.isStatic()) { 263 tmpSig = '[static]' + tmpSig; 264 } 265 return this.getDeclaringSignature().toString() + '.' + tmpSig; 266 } 267} 268 269export class MethodSubSignature { 270 private methodName: string; 271 private parameters: MethodParameter[]; 272 private returnType: Type; 273 private staticFlag: boolean; 274 275 constructor(methodName: string, parameters: MethodParameter[], returnType: Type, staticFlag: boolean = false) { 276 this.methodName = methodName; 277 this.parameters = parameters; 278 this.returnType = returnType; 279 this.staticFlag = staticFlag; 280 } 281 282 public getMethodName(): string { 283 return this.methodName; 284 } 285 286 public getParameters(): MethodParameter[] { 287 return this.parameters; 288 } 289 290 public getParameterTypes(): Type[] { 291 const parameterTypes: Type[] = []; 292 this.parameters.forEach(parameter => { 293 parameterTypes.push(parameter.getType()); 294 }); 295 return parameterTypes; 296 } 297 298 public getReturnType(): Type { 299 return this.returnType; 300 } 301 302 public setReturnType(returnType: Type): void { 303 this.returnType = returnType; 304 } 305 306 public isStatic(): boolean { 307 return this.staticFlag; 308 } 309 310 public toString(ptrName?: string): string { 311 let paraStr = ''; 312 this.getParameterTypes().forEach(parameterType => { 313 paraStr += parameterType.toString() + ', '; 314 }); 315 paraStr = paraStr.replace(/, $/, ''); 316 let tmpSig = `${ptrName ?? this.getMethodName()}(${paraStr})`; 317 if (this.isStatic()) { 318 tmpSig = '[static]' + tmpSig; 319 } 320 return tmpSig; 321 } 322} 323 324/** 325 * @category core/model 326 */ 327export class MethodSignature { 328 private declaringClassSignature: ClassSignature; 329 private methodSubSignature: MethodSubSignature; 330 331 constructor(declaringClassSignature: ClassSignature, methodSubSignature: MethodSubSignature) { 332 this.declaringClassSignature = declaringClassSignature; 333 this.methodSubSignature = methodSubSignature; 334 } 335 336 /** 337 * Return the declaring class signature. 338 * A {@link ClassSignature} includes: 339 * - File Signature: including the **string** names of the project and file, respectively. 340 * The default value of project's name is "%unk" and the default value of file's name is "%unk". 341 * - Namespace Signature | **null**: it may be a namespace signature or **null**. 342 * A namespace signature can indicate its **string** name of namespace and its file signature. 343 * - Class Name: the **string** name of this class. 344 * @returns The declaring class signature. 345 * @example 346 * 1. get class signature from ArkMethod. 347 348 ```typescript 349 let methodSignature = expr.getMethodSignature(); 350 let name = methodSignature.getDeclaringClassSignature().getClassName(); 351 ``` 352 * 353 */ 354 public getDeclaringClassSignature(): ClassSignature { 355 return this.declaringClassSignature; 356 } 357 358 /** 359 * Returns the sub-signature of this method signature. 360 * The sub-signature is part of the method signature, which is used to 361 * identify the name of the method, its parameters and the return value type. 362 * @returns The sub-signature of this method signature. 363 */ 364 public getMethodSubSignature(): MethodSubSignature { 365 return this.methodSubSignature; 366 } 367 368 public getType(): Type { 369 return this.methodSubSignature.getReturnType(); 370 } 371 372 public toString(ptrName?: string): string { 373 return this.declaringClassSignature.toString() + '.' + this.methodSubSignature.toString(ptrName); 374 } 375 376 public toMapKey(): string { 377 return this.declaringClassSignature.toMapKey() + '.' + this.methodSubSignature.toString(); 378 } 379 380 public isMatch(signature: MethodSignature): boolean { 381 return this.toString() === signature.toString() && this.getType().toString() === signature.getType().toString(); 382 } 383 384 public getParamLength(): number { 385 return this.methodSubSignature.getParameters().filter(p => !p.getName().startsWith(LEXICAL_ENV_NAME_PREFIX)).length; 386 } 387} 388 389export class LocalSignature { 390 private name: string; 391 private declaringMethodSignature: MethodSignature; 392 393 constructor(name: string, declaringMethodSignature: MethodSignature) { 394 this.name = name; 395 this.declaringMethodSignature = declaringMethodSignature; 396 } 397 398 public getName(): string { 399 return this.name; 400 } 401 402 public getDeclaringMethodSignature(): MethodSignature { 403 return this.declaringMethodSignature; 404 } 405 406 public toString(): string { 407 return this.declaringMethodSignature.toString() + '#' + this.name; 408 } 409} 410 411export class AliasTypeSignature { 412 private name: string; 413 private declaringMethodSignature: MethodSignature; 414 415 constructor(name: string, declaringMethodSignature: MethodSignature) { 416 this.name = name; 417 this.declaringMethodSignature = declaringMethodSignature; 418 } 419 420 public getName(): string { 421 return this.name; 422 } 423 424 public getDeclaringMethodSignature(): MethodSignature { 425 return this.declaringMethodSignature; 426 } 427 428 public toString(): string { 429 return this.declaringMethodSignature.toString() + '#' + this.name; 430 } 431} 432 433//TODO, reconstruct 434export function fieldSignatureCompare(leftSig: FieldSignature, rightSig: FieldSignature): boolean { 435 if (leftSig.getDeclaringSignature().toString() === rightSig.getDeclaringSignature().toString() && leftSig.getFieldName() === rightSig.getFieldName()) { 436 return true; 437 } 438 return false; 439} 440 441export function methodSignatureCompare(leftSig: MethodSignature, rightSig: MethodSignature): boolean { 442 if ( 443 classSignatureCompare(leftSig.getDeclaringClassSignature(), rightSig.getDeclaringClassSignature()) && 444 methodSubSignatureCompare(leftSig.getMethodSubSignature(), rightSig.getMethodSubSignature()) 445 ) { 446 return true; 447 } 448 return false; 449} 450 451export function methodSubSignatureCompare(leftSig: MethodSubSignature, rightSig: MethodSubSignature): boolean { 452 if ( 453 leftSig.getMethodName() === rightSig.getMethodName() && 454 arrayCompare(leftSig.getParameterTypes(), rightSig.getParameterTypes()) && 455 leftSig.getReturnType() === rightSig.getReturnType() 456 ) { 457 return true; 458 } 459 return false; 460} 461 462export function classSignatureCompare(leftSig: ClassSignature, rightSig: ClassSignature): boolean { 463 if (fileSignatureCompare(leftSig.getDeclaringFileSignature(), rightSig.getDeclaringFileSignature()) && leftSig.getClassName() === rightSig.getClassName()) { 464 return true; 465 } 466 return false; 467} 468 469export function fileSignatureCompare(leftSig: FileSignature, rightSig: FileSignature): boolean { 470 if (leftSig.getFileName() === rightSig.getFileName() && leftSig.getProjectName() === rightSig.getProjectName()) { 471 return true; 472 } 473 return false; 474} 475 476function arrayCompare(leftArray: any[], rightArray: any[]): boolean { 477 if (leftArray.length !== rightArray.length) { 478 return false; 479 } 480 for (let i = 0; i < leftArray.length; i++) { 481 if (leftArray[i] !== rightArray[i]) { 482 return false; 483 } 484 } 485 return true; 486} 487 488export function genSignature4ImportClause(arkFileName: string, importClauseName: string): string { 489 return `<${arkFileName}>.<${importClauseName}>`; 490} 491