• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}