1/* 2 * Copyright (c) 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 fs from 'fs'; 17import path from 'path'; 18import ts from 'typescript'; 19 20import { getResolveModule } from '../../../ets_checker'; 21import { 22 ARKTS_1_0, 23 ARKTS_1_1, 24 ARKTS_1_2, 25 ARKTS_HYBRID 26} from './pre_define'; 27import { 28 IFileLog, 29 LogType, 30 mkdirsSync, 31 toUnixPath 32} from '../../../utils'; 33import { getPkgInfo } from '../../../ark_utils'; 34import { 35 EXTNAME_D_ETS, 36 EXTNAME_ETS, 37 EXTNAME_TS, 38 SUPER_ARGS 39} from '../../../pre_define'; 40import { 41 CommonLogger, 42 LogData, 43 LogDataFactory 44} from '../logger'; 45import { 46 ArkTSErrorDescription, 47 ErrorCode 48} from '../error_code'; 49import createAstNodeUtils from '../../../create_ast_node_utils'; 50import { 51 red, 52 reset 53} from '../common/ark_define'; 54 55interface DeclFileConfig { 56 declPath: string; 57 ohmUrl: string; 58} 59 60interface DeclFilesConfig { 61 packageName: string; 62 files: { 63 [filePath: string]: DeclFileConfig; 64 } 65} 66 67export interface ArkTSEvolutionModule { 68 language: string; // "1.1" | "1.2" 69 packageName: string; 70 moduleName: string; 71 modulePath: string; 72 declgenV1OutPath?: string; 73 declgenV2OutPath?: string; 74 declgenBridgeCodePath?: string; 75 declFilesPath?: string; 76} 77 78interface ResolvedFileInfo { 79 moduleRequest: string; 80 resolvedFileName: string; 81} 82 83export const interopTransformLog: IFileLog = new createAstNodeUtils.FileLog(); 84 85export let pkgDeclFilesConfig: { [pkgName: string]: DeclFilesConfig } = {}; 86 87export let arkTSModuleMap: Map<string, ArkTSEvolutionModule> = new Map(); 88 89export let arkTSEvolutionModuleMap: Map<string, ArkTSEvolutionModule> = new Map(); 90 91export let arkTSHybridModuleMap: Map<string, ArkTSEvolutionModule> = new Map(); 92 93let arkTSEvoFileOHMUrlMap: Map<string, string> = new Map(); 94 95let declaredClassVars: Set<string> = new Set(); 96 97export function addDeclFilesConfig(filePath: string, projectConfig: Object, logger: Object, 98 pkgPath: string, pkgName: string): void { 99 const { projectFilePath, pkgInfo } = getPkgInfo(filePath, projectConfig, logger, pkgPath, pkgName); 100 const declgenV2OutPath: string = getDeclgenV2OutPath(pkgName); 101 if (!declgenV2OutPath) { 102 return; 103 } 104 if (!pkgDeclFilesConfig[pkgName]) { 105 pkgDeclFilesConfig[pkgName] = { packageName: pkgName, files: {} }; 106 } 107 if (pkgDeclFilesConfig[pkgName].files[projectFilePath]) { 108 return; 109 } 110 const isSO: string = pkgInfo.isSO ? 'Y' : 'N'; 111 // The module name of the entry module of the project during the current compilation process. 112 const mainModuleName: string = projectConfig.mainModuleName; 113 const bundleName: string = projectConfig.bundleName; 114 const normalizedFilePath: string = `${pkgName}/${projectFilePath}`; 115 const declPath: string = path.join(toUnixPath(declgenV2OutPath), projectFilePath) + EXTNAME_D_ETS; 116 const ohmUrl: string = `${isSO}&${mainModuleName}&${bundleName}&${normalizedFilePath}&${pkgInfo.version}`; 117 pkgDeclFilesConfig[pkgName].files[projectFilePath] = { declPath, ohmUrl: `@normalized:${ohmUrl}` }; 118} 119 120export function getArkTSEvoDeclFilePath(resolvedFileInfo: ResolvedFileInfo): string { 121 const { moduleRequest, resolvedFileName } = resolvedFileInfo; 122 let arktsEvoDeclFilePath: string = moduleRequest; 123 const combinedMap = new Map([...arkTSEvolutionModuleMap, ...arkTSHybridModuleMap]); 124 for (const [pkgName, arkTSEvolutionModuleInfo] of combinedMap) { 125 const declgenV1OutPath: string = toUnixPath(arkTSEvolutionModuleInfo.declgenV1OutPath); 126 const modulePath: string = toUnixPath(arkTSEvolutionModuleInfo.modulePath); 127 const declgenBridgeCodePath: string = toUnixPath(arkTSEvolutionModuleInfo.declgenBridgeCodePath); 128 if (resolvedFileName && resolvedFileName.startsWith(modulePath + '/') && 129 !resolvedFileName.startsWith(declgenBridgeCodePath + '/')) { 130 arktsEvoDeclFilePath = resolvedFileName 131 .replace(modulePath, toUnixPath(path.join(declgenV1OutPath, pkgName))) 132 .replace(EXTNAME_ETS, EXTNAME_D_ETS); 133 break; 134 } 135 if (moduleRequest === pkgName) { 136 arktsEvoDeclFilePath = path.join(declgenV1OutPath, pkgName, 'Index.d.ets'); 137 break; 138 } 139 if (moduleRequest.startsWith(pkgName + '/')) { 140 arktsEvoDeclFilePath = moduleRequest.replace( 141 pkgName, 142 toUnixPath(path.join(declgenV1OutPath, pkgName, 'src/main/ets')) 143 ) + EXTNAME_D_ETS; 144 145 if (fs.existsSync(arktsEvoDeclFilePath)) { 146 break; 147 } 148 /** 149 * If the import is exported via the package name, for example: 150 * import { xxx } from 'src/main/ets/xxx/xxx/...' 151 * there is no need to additionally concatenate 'src/main/ets' 152 */ 153 arktsEvoDeclFilePath = moduleRequest.replace( 154 pkgName, 155 toUnixPath(path.join(declgenV1OutPath, pkgName)) 156 ) + EXTNAME_D_ETS; 157 break; 158 } 159 } 160 return arktsEvoDeclFilePath; 161} 162 163export function collectArkTSEvolutionModuleInfo(share: Object): void { 164 if (!share.projectConfig.dependentModuleMap) { 165 return; 166 } 167 if (!share.projectConfig.useNormalizedOHMUrl) { 168 const errInfo: LogData = LogDataFactory.newInstance( 169 ErrorCode.ETS2BUNDLE_EXTERNAL_COLLECT_INTEROP_INFO_FAILED, 170 ArkTSErrorDescription, 171 'Failed to compile mixed project.', 172 `Failed to compile mixed project because useNormalizedOHMUrl is false.`, 173 ['Please check whether useNormalizedOHMUrl is true.'] 174 ); 175 CommonLogger.getInstance(share).printErrorAndExit(errInfo); 176 177 } 178 // dependentModuleMap Contents eg. 179 // 1.2 hap -> 1.1 har: It contains the information of 1.1 har 180 // 1.1 hap -> 1.2 har -> 1.1 har : There is information about 3 modules. 181 182 const throwCollectionError = (pkgName: string): void => { 183 share.throwArkTsCompilerError(red, 'ArkTS:INTERNAL ERROR: Failed to collect arkTs evolution module info.\n' + 184 `Error Message: Failed to collect arkTs evolution module "${pkgName}" info from rollup.`, reset); 185 }; 186 187 for (const [pkgName, dependentModuleInfo] of share.projectConfig.dependentModuleMap) { 188 switch (dependentModuleInfo.language) { 189 case ARKTS_1_2: 190 if (dependentModuleInfo.declgenV1OutPath && dependentModuleInfo.declgenBridgeCodePath) { 191 arkTSEvolutionModuleMap.set(pkgName, dependentModuleInfo); 192 } else { 193 throwCollectionError(pkgName); 194 } 195 break; 196 case ARKTS_HYBRID: 197 if (dependentModuleInfo.declgenV2OutPath && dependentModuleInfo.declFilesPath && dependentModuleInfo.declgenBridgeCodePath) { 198 arkTSHybridModuleMap.set(pkgName, dependentModuleInfo); 199 } else { 200 throwCollectionError(pkgName); 201 } 202 break; 203 case ARKTS_1_1: 204 case ARKTS_1_0: 205 if (dependentModuleInfo.declgenV2OutPath && dependentModuleInfo.declFilesPath) { 206 arkTSModuleMap.set(pkgName, dependentModuleInfo); 207 } else { 208 throwCollectionError(pkgName); 209 } 210 break; 211 } 212 } 213} 214 215export function cleanUpProcessArkTSEvolutionObj(): void { 216 arkTSModuleMap = new Map(); 217 arkTSEvolutionModuleMap = new Map(); 218 arkTSHybridModuleMap = new Map(); 219 pkgDeclFilesConfig = {}; 220 arkTSEvoFileOHMUrlMap = new Map(); 221 interopTransformLog.cleanUp(); 222 declaredClassVars = new Set(); 223} 224 225export async function writeBridgeCodeFileSyncByNode(node: ts.SourceFile, moduleId: string, 226 metaInfo: Object): Promise<void> { 227 const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 228 const writer: ts.EmitTextWriter = ts.createTextWriter( 229 // @ts-ignore 230 ts.getNewLineCharacter({ newLine: ts.NewLineKind.LineFeed, removeComments: false })); 231 printer.writeFile(node, writer, undefined); 232 const cacheFilePath: string = genCachePathForBridgeCode(moduleId, metaInfo); 233 mkdirsSync(path.dirname(cacheFilePath)); 234 fs.writeFileSync(cacheFilePath, writer.getText()); 235} 236 237export function genCachePathForBridgeCode(moduleId: string, metaInfo: Object, cachePath?: string): string { 238 const bridgeCodePath: string = getDeclgenBridgeCodePath(metaInfo.pkgName); 239 const filePath: string = toUnixPath(moduleId); 240 const relativeFilePath: string = filePath.replace( 241 toUnixPath(path.join(bridgeCodePath, metaInfo.pkgName)), metaInfo.moduleName); 242 const cacheFilePath: string = path.join(cachePath ? cachePath : process.env.cachePath, relativeFilePath); 243 return cacheFilePath; 244} 245 246export function getDeclgenBridgeCodePath(pkgName: string): string { 247 const combinedMap = new Map([...arkTSEvolutionModuleMap, ...arkTSHybridModuleMap]); 248 if (combinedMap.size && combinedMap.get(pkgName)) { 249 const arkTSEvolutionModuleInfo: ArkTSEvolutionModule = combinedMap.get(pkgName); 250 return arkTSEvolutionModuleInfo.declgenBridgeCodePath; 251 } 252 return ''; 253} 254 255function getDeclgenV2OutPath(pkgName: string): string { 256 const combinedMap = new Map([...arkTSModuleMap, ...arkTSHybridModuleMap]); 257 if (combinedMap.size && combinedMap.get(pkgName)) { 258 const arkTsModuleInfo: ArkTSEvolutionModule = combinedMap.get(pkgName); 259 return arkTsModuleInfo.declgenV2OutPath; 260 } 261 return ''; 262} 263 264export function isArkTSEvolutionFile(filePath: string, metaInfo: Object): boolean { 265 if (metaInfo.language === ARKTS_1_0 || metaInfo.language === ARKTS_1_1) { 266 return false; 267 } 268 269 if (metaInfo.language === ARKTS_1_2) { 270 return true; 271 } 272 273 if (metaInfo.language === ARKTS_HYBRID || arkTSHybridModuleMap.has(metaInfo.pkgName)) { 274 const hybridModule = arkTSHybridModuleMap.get(metaInfo.pkgName); 275 if (!hybridModule) { 276 return false; 277 } 278 279 const normalizedFilePath = toUnixPath(filePath); 280 const staticFileList = hybridModule.staticFiles || []; 281 282 // Concatenate the corresponding source code path based on the bridge code path. 283 const declgenCodeBrigdePath = path.join(toUnixPath(hybridModule.declgenBridgeCodePath), metaInfo.pkgName); 284 let moduleId = normalizedFilePath.replace(toUnixPath(declgenCodeBrigdePath), toUnixPath(metaInfo.pkgPath)); 285 const arktsEvolutionFile = moduleId.replace(new RegExp(`\\${EXTNAME_TS}$`), EXTNAME_ETS); 286 287 return new Set(staticFileList.map(toUnixPath)).has(arktsEvolutionFile); 288 } 289 290 return false; 291} 292 293export function interopTransform(program: ts.Program, id: string, mixCompile: boolean): ts.TransformerFactory<ts.SourceFile> { 294 if (!mixCompile || /\.ts$/.test(id)) { 295 return () => sourceFile => sourceFile; 296 } 297 // For specific scenarios, please refer to the test file process_arkts_evolution.test.ts 298 const typeChecker: ts.TypeChecker = program.getTypeChecker(); 299 return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => { 300 const scopeUsedNames: WeakMap<ts.Node, Set<string>> = new WeakMap<ts.Node, Set<string>>(); 301 const fullNameToTmpVar: Map<string, string> = new Map(); 302 const globalDeclarations: Map<string, ts.Statement> = new Map(); 303 return (rootNode: ts.SourceFile) => { 304 interopTransformLog.sourceFile = rootNode; 305 const classToInterfacesMap: Map<ts.ClassDeclaration, Set<string>> = collectInterfacesMap(rootNode, typeChecker); 306 // Support for creating 1.2 type object literals in 1.1 modules 307 const visitor: ts.Visitor = 308 createObjectLiteralVisitor(rootNode, context, typeChecker, scopeUsedNames, fullNameToTmpVar, globalDeclarations); 309 const processNode: ts.SourceFile = ts.visitEachChild(rootNode, visitor, context); 310 // Support 1.1 classes to implement 1.2 interfaces 311 const withHeritage: ts.SourceFile = classToInterfacesMap.size > 0 ? 312 ts.visitEachChild(processNode, transformHeritage(context, classToInterfacesMap), context) : processNode; 313 314 const importStmts: ts.ImportDeclaration[] = withHeritage.statements.filter(stmt => ts.isImportDeclaration(stmt)); 315 const otherStmts: ts.Statement[] = withHeritage.statements.filter(stmt => !ts.isImportDeclaration(stmt)); 316 const globalStmts: ts.Statement[] = Array.from(globalDeclarations.values()); 317 318 return ts.factory.updateSourceFile( 319 withHeritage, 320 [...importStmts, ...globalStmts, ...otherStmts], 321 withHeritage.isDeclarationFile, 322 withHeritage.referencedFiles, 323 withHeritage.typeReferenceDirectives, 324 withHeritage.hasNoDefaultLib, 325 withHeritage.libReferenceDirectives 326 ); 327 }; 328 }; 329} 330 331function isFromArkTSEvolutionModule(node: ts.Node): boolean { 332 const sourceFile: ts.SourceFile = node.getSourceFile(); 333 const filePath: string = toUnixPath(sourceFile.fileName); 334 const combinedMap = new Map([...arkTSEvolutionModuleMap, ...arkTSHybridModuleMap]); 335 for (const arkTSEvolutionModuleInfo of combinedMap.values()) { 336 const declgenV1OutPath: string = toUnixPath(arkTSEvolutionModuleInfo.declgenV1OutPath); 337 if (filePath.startsWith(declgenV1OutPath + '/')) { 338 const relative: string = filePath.replace(declgenV1OutPath + '/', '').replace(/\.d\.ets$/, ''); 339 if (!arkTSEvoFileOHMUrlMap.has(filePath)) { 340 arkTSEvoFileOHMUrlMap.set(filePath, relative); 341 } 342 return true; 343 } 344 } 345 return false; 346} 347 348function createObjectLiteralVisitor(rootNode: ts.SourceFile, context: ts.TransformationContext, typeChecker: ts.TypeChecker, 349 scopeUsedNames: WeakMap<ts.Node, Set<string>>, fullNameToTmpVar: Map<string, string>, 350 globalDeclarations: Map<string, ts.Statement>): ts.Visitor { 351 return function visitor(node: ts.SourceFile): ts.SourceFile { 352 if (!ts.isObjectLiteralExpression(node)) { 353 return ts.visitEachChild(node, visitor, context); 354 } 355 356 const contextualType: ts.Type | undefined = typeChecker.getContextualType(node); 357 if (!contextualType) { 358 return ts.visitEachChild(node, visitor, context); 359 } 360 const isRecordType: boolean = contextualType.aliasSymbol?.escapedName === 'Record' && 361 (typeof typeChecker.isStaticRecord === 'function' && typeChecker.isStaticRecord(contextualType)); 362 const finalType: ts.Type = unwrapType(node, contextualType); 363 const decl : ts.Declaration = (finalType.symbol?.declarations || finalType.aliasSymbol?.declarations)?.[0]; 364 365 let className: string; 366 let tmpObjName: string; 367 if (!isRecordType) { 368 if (!decl || !isFromArkTSEvolutionModule(decl)) { 369 return ts.visitEachChild(node, visitor, context); 370 } 371 className = finalType.symbol?.name || finalType.aliasSymbol?.name; 372 if (!className) { 373 return ts.visitEachChild(node, visitor, context); 374 } 375 376 if (ts.isClassDeclaration(decl) && !hasZeroArgConstructor(decl, className)) { 377 return ts.visitEachChild(node, visitor, context); 378 } 379 tmpObjName = getUniqueName(rootNode, 'tmpObj', scopeUsedNames); 380 declareGlobalTemp(tmpObjName, globalDeclarations); 381 } 382 383 const fullName: string = buildFullClassName(decl, finalType, className, isRecordType); 384 const getCtorExpr: ts.Expression = buildGetConstructorCall(fullName, isRecordType); 385 let tmpClassName: string; 386 if (fullNameToTmpVar.has(fullName)) { 387 tmpClassName = fullNameToTmpVar.get(fullName)!; 388 } else { 389 tmpClassName = getUniqueName(rootNode, isRecordType ? 'tmpRecord' : 'tmpClass', scopeUsedNames); 390 fullNameToTmpVar.set(fullName, tmpClassName); 391 declareGlobalTemp(tmpClassName, globalDeclarations, getCtorExpr); 392 } 393 394 return ts.factory.createParenthesizedExpression( 395 ts.factory.createCommaListExpression(buildCommaExpressions(node, isRecordType, tmpObjName, tmpClassName))); 396 }; 397} 398 399function unwrapType(node: ts.SourceFile, type: ts.Type): ts.Type { 400 // Unwrap parenthesized types recursively 401 if ((type.flags & ts.TypeFlags.Parenthesized) && 'type' in type) { 402 return unwrapType(node, (type as ts.ParenthesizedType).type); 403 } 404 405 // If union, pick the unique viable type 406 if (type.isUnion()) { 407 const arkTSEvolutionTypes: ts.Type[] = []; 408 409 for (const tpye of type.types) { 410 const symbol: ts.Symbol = tpye.aliasSymbol || tpye.getSymbol(); 411 const decls: ts.Declaration[] = symbol?.declarations; 412 if (!decls || decls.length === 0) { 413 continue; 414 } 415 416 const isArkTSEvolution: boolean = decls.some(decl => isFromArkTSEvolutionModule(decl)); 417 if (isArkTSEvolution) { 418 arkTSEvolutionTypes.push(tpye); 419 } 420 } 421 if (arkTSEvolutionTypes.length === 0) { 422 return type; 423 } 424 if (arkTSEvolutionTypes.length !== 1 || type.types.length > 1) { 425 const candidates: string = arkTSEvolutionTypes.map(tpye => tpye.symbol?.name || '(anonymous)').join(', '); 426 const errInfo: LogData = LogDataFactory.newInstance( 427 ErrorCode.ETS2BUNDLE_EXTERNAL_UNION_TYPE_AMBIGUITY, 428 ArkTSErrorDescription, 429 `Ambiguous union type: multiple valid ArkTSEvolution types found: [${candidates}].`, 430 '', 431 ['Please use type assertion as to disambiguate.'] 432 ); 433 interopTransformLog.errors.push({ 434 type: LogType.ERROR, 435 message: errInfo.toString(), 436 pos: node.getStart() 437 }); 438 return type; 439 } 440 return unwrapType(node, arkTSEvolutionTypes[0]); 441 } 442 return type; 443} 444 445function hasZeroArgConstructor(decl: ts.ClassDeclaration, className: string): boolean { 446 const ctors = decl.members.filter(member => 447 ts.isConstructorDeclaration(member) || ts.isConstructSignatureDeclaration(member)); 448 const hasZeroArgCtor: boolean = ctors.length === 0 || ctors.some(ctor => ctor.parameters.length === 0); 449 if (!hasZeroArgCtor) { 450 const errInfo: LogData = LogDataFactory.newInstance( 451 ErrorCode.ETS2BUNDLE_EXTERNAL_CLASS_HAS_NO_CONSTRUCTOR_WITHOUT_ARGS, 452 ArkTSErrorDescription, 453 `The class "${className}" does not has no no-argument constructor.`, 454 '', 455 [ 456 'Please confirm whether there is a no-argument constructor ' + 457 `in the ArkTS Evolution class "${className}" type in the object literal.` 458 ] 459 ); 460 interopTransformLog.errors.push({ 461 type: LogType.ERROR, 462 message: errInfo.toString(), 463 pos: decl.name?.getStart() ?? decl.getStart() 464 }); 465 return false; 466 } 467 return hasZeroArgCtor; 468} 469 470function buildFullClassName(decl: ts.Declaration, finalType: ts.Type, className: string, isRecordType: boolean): string { 471 if (isRecordType) { 472 return 'Lescompat/Record;'; 473 } 474 const basePath: string = getArkTSEvoFileOHMUrl(finalType); 475 return ts.isInterfaceDeclaration(decl) ? 476 `L${basePath}/${basePath.split('/').join('$')}$${className}$ObjectLiteral;` : 477 `L${basePath}/${className};`; 478} 479 480function buildGetConstructorCall(fullName: string, isRecord: boolean): ts.Expression { 481 return ts.factory.createCallExpression( 482 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('globalThis'), 483 isRecord ? 'Panda.getInstance' : 'Panda.getClass'), 484 undefined, 485 [ts.factory.createStringLiteral(fullName)] 486 ); 487} 488 489function buildPropertyAssignments(node: ts.ObjectLiteralExpression, tmpObjName: string, 490 usePropertyAccess: boolean = true): ts.Expression[] { 491 return node.properties.map(property => { 492 if (!ts.isPropertyAssignment(property)) { 493 return undefined; 494 } 495 const key = property.name; 496 const target = usePropertyAccess && ts.isIdentifier(key) ? 497 ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(tmpObjName), key) : 498 ts.factory.createElementAccessExpression(ts.factory.createIdentifier(tmpObjName), 499 ts.isIdentifier(key) ? ts.factory.createStringLiteral(key.text) : key); 500 return ts.factory.createAssignment(target, property.initializer); 501 }).filter(Boolean) as ts.Expression[]; 502} 503 504function buildCommaExpressions(node: ts.ObjectLiteralExpression, isRecordType: boolean, 505 tmpObjName: string, tmpClassName: string): ts.Expression[] { 506 const assignments: ts.Expression[] = 507 buildPropertyAssignments(node, isRecordType ? tmpClassName : tmpObjName, !isRecordType); 508 509 if (isRecordType) { 510 return [...assignments, ts.factory.createIdentifier(tmpClassName)]; 511 } 512 513 return [ 514 ts.factory.createAssignment( 515 ts.factory.createIdentifier(tmpObjName), 516 ts.factory.createNewExpression(ts.factory.createIdentifier(tmpClassName), undefined, []) 517 ), 518 ...assignments, 519 ts.factory.createIdentifier(tmpObjName) 520 ]; 521} 522 523function getArkTSEvoFileOHMUrl(contextualType: ts.Type): string { 524 const decl: ts.Declaration = (contextualType.symbol?.declarations || contextualType.aliasSymbol?.declarations)?.[0]; 525 if (!decl) { 526 return ''; 527 } 528 const sourceFilePath: string = toUnixPath(decl.getSourceFile().fileName); 529 return arkTSEvoFileOHMUrlMap.get(sourceFilePath); 530} 531 532function getUniqueName(scope: ts.Node, base: string, usedNames: WeakMap<ts.Node, Set<string>>): string { 533 if (!usedNames.has(scope)) { 534 usedNames.set(scope, new Set()); 535 } 536 const used: Set<string> = usedNames.get(scope)!; 537 let name: string = base; 538 let counter: number = 1; 539 while (used.has(name)) { 540 name = `${base}_${counter++}`; 541 } 542 used.add(name); 543 return name; 544} 545 546function declareGlobalTemp(name: string, globalDeclarations: Map<string, ts.Statement>, initializer?: ts.Expression): ts.Statement { 547 if (initializer && declaredClassVars.has(name)) { 548 return globalDeclarations.get(name)!; 549 } 550 551 if (!globalDeclarations.has(name)) { 552 const decl = ts.factory.createVariableStatement(undefined, 553 ts.factory.createVariableDeclarationList( 554 [ts.factory.createVariableDeclaration(name, undefined, undefined, initializer)], ts.NodeFlags.Let)); 555 globalDeclarations.set(name, decl); 556 if (initializer) { 557 declaredClassVars.add(name); 558 } 559 } 560 561 return globalDeclarations.get(name)!; 562} 563 564function collectInterfacesMap(rootNode: ts.Node, checker: ts.TypeChecker): Map<ts.ClassDeclaration, Set<string>> { 565 const classToInterfacesMap = new Map<ts.ClassDeclaration, Set<string>>(); 566 ts.forEachChild(rootNode, function visit(node) { 567 if (ts.isClassDeclaration(node)) { 568 const interfaces = new Set<string>(); 569 const visited = new Set<ts.Type>(); 570 collectDeepInheritedInterfaces(node, checker, visited, interfaces); 571 if (interfaces.size > 0) { 572 classToInterfacesMap.set(node, interfaces); 573 } 574 } 575 ts.forEachChild(node, visit); 576 }); 577 return classToInterfacesMap; 578} 579 580function collectDeepInheritedInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration, 581 checker: ts.TypeChecker, visited: Set<ts.Type>, interfaces: Set<string>): void { 582 const heritageClauses = node.heritageClauses; 583 if (!heritageClauses) { 584 return; 585 } 586 587 for (const clause of heritageClauses) { 588 for (const exprWithTypeArgs of clause.types) { 589 const type = checker.getTypeAtLocation(exprWithTypeArgs.expression); 590 collectDeepInheritedInterfacesFromType(type, checker, visited, interfaces); 591 } 592 } 593} 594 595function collectDeepInheritedInterfacesFromType(type: ts.Type, checker: ts.TypeChecker, 596 visited: Set<ts.Type>, interfaces: Set<string>): void { 597 if (visited.has(type)) { 598 return; 599 } 600 visited.add(type); 601 const decls: ts.Declaration[] = type.symbol?.declarations; 602 const isArkTSEvolution: boolean = decls?.some(decl => isFromArkTSEvolutionModule(decl)); 603 if (isArkTSEvolution) { 604 const ifacePath: string = getArkTSEvoFileOHMUrl(type); 605 interfaces.add(`L${ifacePath}/${type.symbol.name};`); 606 } 607 if (hasResolvedBaseTypes(type)) { 608 const baseTypes: ts.BaseType[] = checker.getBaseTypes(type) ?? []; 609 for (const baseType of baseTypes) { 610 collectDeepInheritedInterfacesFromType(baseType, checker, visited, interfaces); 611 } 612 } 613 614 if (decls) { 615 for (const decl of decls) { 616 if (ts.isClassDeclaration(decl) || ts.isInterfaceDeclaration(decl)) { 617 collectDeepInheritedInterfaces(decl, checker, visited, interfaces); 618 } 619 } 620 } 621} 622 623function hasResolvedBaseTypes(type: ts.Type): type is ts.InterfaceType { 624 return ( 625 (type.flags & ts.TypeFlags.Object) !== 0 && 626 ((type as ts.ObjectType).objectFlags & (ts.ObjectFlags.Class | ts.ObjectFlags.Interface)) !== 0 && 627 'resolvedBaseTypes' in type 628 ); 629} 630 631function transformHeritage(context: ts.TransformationContext, 632 classToInterfacesMap: Map<ts.ClassDeclaration, Set<string>>): ts.Visitor { 633 return function visitor(node: ts.SourceFile): ts.SourceFile { 634 if (ts.isClassDeclaration(node) && classToInterfacesMap.has(node)) { 635 const interfaceNames = classToInterfacesMap.get(node)!; 636 const updatedMembers = injectImplementsInConstructor(node, interfaceNames); 637 return ts.factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, 638 node.heritageClauses, updatedMembers); 639 } 640 return ts.visitEachChild(node, visitor, context); 641 }; 642} 643 644function injectImplementsInConstructor(node: ts.ClassDeclaration, interfaceNames: Set<string>): ts.ClassElement[] { 645 const members: ts.ClassElement[] = [...node.members]; 646 const params: ts.ParameterDeclaration[] = []; 647 const needSuper: boolean = 648 node.heritageClauses?.some(clause => clause.token === ts.SyntaxKind.ExtendsKeyword) || false; 649 const injectStatement: ts.ExpressionStatement[] = [ 650 ts.factory.createExpressionStatement( 651 ts.factory.createStringLiteral(`implements static:${(Array.from(interfaceNames)).join(',')}`) 652 ) 653 ]; 654 const ctorDecl: ts.ConstructorDeclaration | undefined = 655 members.find(element => ts.isConstructorDeclaration(element)) as ts.ConstructorDeclaration | undefined; 656 if (ctorDecl) { 657 const newCtorDecl: ts.ConstructorDeclaration = ts.factory.updateConstructorDeclaration( 658 ctorDecl, ctorDecl.modifiers, ctorDecl.parameters, 659 ts.factory.updateBlock( 660 ctorDecl.body ?? ts.factory.createBlock([], true), 661 [...injectStatement, ...(ctorDecl.body?.statements ?? [])] 662 ) 663 ); 664 const index: number = members.indexOf(ctorDecl); 665 members.splice(index, 1, newCtorDecl); 666 } else { 667 addSuper(needSuper, injectStatement, params); 668 const newCtorDecl: ts.ConstructorDeclaration = ts.factory.createConstructorDeclaration( 669 undefined, params, 670 ts.factory.createBlock([...injectStatement], true) 671 ); 672 members.push(newCtorDecl); 673 } 674 return members; 675} 676 677function addSuper(needSuper: boolean, injectStatement: ts.ExpressionStatement[], 678 params: ts.ParameterDeclaration[]): void { 679 if (needSuper) { 680 injectStatement.push( 681 ts.factory.createExpressionStatement( 682 ts.factory.createCallExpression( 683 ts.factory.createSuper(), undefined, [ts.factory.createSpreadElement(ts.factory.createIdentifier(SUPER_ARGS))]) 684 ) 685 ); 686 params.push( 687 ts.factory.createParameterDeclaration( 688 undefined, 689 ts.factory.createToken(ts.SyntaxKind.DotDotDotToken), 690 ts.factory.createIdentifier(SUPER_ARGS), 691 undefined, 692 undefined, 693 undefined) 694 ); 695 } 696} 697 698export function redirectToDeclFileForInterop(resolvedFileName: string): ts.ResolvedModuleFull { 699 const filePath: string = toUnixPath(resolvedFileName); 700 const resultDETSPath: string = getArkTSEvoDeclFilePath({ moduleRequest: '', resolvedFileName: filePath }); 701 if (ts.sys.fileExists(resultDETSPath)) { 702 return getResolveModule(resultDETSPath, EXTNAME_D_ETS); 703 } 704 return undefined; 705}