1/*
2 * Copyright (c) 2022-2024 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 * as path from 'node:path';
17import * as ts from 'typescript';
18import type { IsEtsFileCallback } from '../IsEtsFileCallback';
19import { FaultID } from '../Problems';
20import { ARKTS_IGNORE_DIRS, ARKTS_IGNORE_FILES } from './consts/ArktsIgnorePaths';
21import { ES_OBJECT } from './consts/ESObject';
22import { EXTENDED_BASE_TYPES } from './consts/ExtendedBaseTypes';
23import { SENDABLE_DECORATOR } from './consts/SendableAPI';
24import { USE_SHARED } from './consts/SharedModuleAPI';
25import { STANDARD_LIBRARIES } from './consts/StandardLibraries';
26import {
27  ARKTS_COLLECTIONS_D_ETS,
28  ARKTS_LANG_D_ETS,
29  COLLECTIONS_NAMESPACE,
30  ISENDABLE_TYPE,
31  LANG_NAMESPACE
32} from './consts/SupportedDetsIndexableTypes';
33import { TYPED_ARRAYS } from './consts/TypedArrays';
34import { forEachNodeInSubtree } from './functions/ForEachNodeInSubtree';
35import { getScriptKind } from './functions/GetScriptKind';
36import { isStdLibrarySymbol, isStdLibraryType } from './functions/IsStdLibrary';
37import { isStructDeclaration, isStructDeclarationKind } from './functions/IsStruct';
38import type { NameGenerator } from './functions/NameGenerator';
39import { srcFilePathContainsDirectory } from './functions/PathHelper';
40import { isAssignmentOperator } from './functions/isAssignmentOperator';
41import { isIntrinsicObjectType } from './functions/isIntrinsicObjectType';
42
43export const SYMBOL = 'Symbol';
44export const SYMBOL_CONSTRUCTOR = 'SymbolConstructor';
45const ITERATOR = 'iterator';
46
47export type CheckType = (this: TsUtils, t: ts.Type) => boolean;
48export class TsUtils {
49  constructor(
50    private readonly tsTypeChecker: ts.TypeChecker,
51    private readonly testMode: boolean,
52    private readonly advancedClassChecks: boolean,
53    private readonly useSdkLogic: boolean,
54    private readonly arkts2: boolean
55  ) {}
56
57  entityNameToString(name: ts.EntityName): string {
58    if (ts.isIdentifier(name)) {
59      return name.escapedText.toString();
60    }
61    return this.entityNameToString(name.left) + this.entityNameToString(name.right);
62  }
63
64  isNumberLikeType(tsType: ts.Type): boolean {
65    if (!this.useSdkLogic && tsType.isUnion()) {
66      for (const tsCompType of tsType.types) {
67        if ((tsCompType.flags & ts.TypeFlags.NumberLike) === 0) {
68          return false;
69        }
70      }
71      return true;
72    }
73    return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0;
74  }
75
76  static isBooleanLikeType(tsType: ts.Type): boolean {
77    return (tsType.getFlags() & ts.TypeFlags.BooleanLike) !== 0;
78  }
79
80  static isDestructuringAssignmentLHS(tsExpr: ts.ArrayLiteralExpression | ts.ObjectLiteralExpression): boolean {
81
82    /*
83     * Check whether given expression is the LHS part of the destructuring
84     * assignment (or is a nested element of destructuring pattern).
85     */
86    let tsParent = tsExpr.parent;
87    let tsCurrentExpr: ts.Node = tsExpr;
88    while (tsParent) {
89      if (
90        ts.isBinaryExpression(tsParent) &&
91        isAssignmentOperator(tsParent.operatorToken) &&
92        tsParent.left === tsCurrentExpr
93      ) {
94        return true;
95      }
96
97      if (
98        (ts.isForStatement(tsParent) || ts.isForInStatement(tsParent) || ts.isForOfStatement(tsParent)) &&
99        tsParent.initializer &&
100        tsParent.initializer === tsCurrentExpr
101      ) {
102        return true;
103      }
104
105      tsCurrentExpr = tsParent;
106      tsParent = tsParent.parent;
107    }
108
109    return false;
110  }
111
112  static isEnumType(tsType: ts.Type): boolean {
113    // when type equals `typeof <Enum>`, only symbol contains information about it's type.
114    const isEnumSymbol = tsType.symbol && this.isEnum(tsType.symbol);
115    // otherwise, we should analyze flags of the type itself
116    const isEnumType = !!(tsType.flags & ts.TypeFlags.Enum) || !!(tsType.flags & ts.TypeFlags.EnumLiteral);
117    return isEnumSymbol || isEnumType;
118  }
119
120  static isEnum(tsSymbol: ts.Symbol): boolean {
121    return !!(tsSymbol.flags & ts.SymbolFlags.Enum);
122  }
123
124  static hasModifier(tsModifiers: readonly ts.Modifier[] | undefined, tsModifierKind: number): boolean {
125    if (!tsModifiers) {
126      return false;
127    }
128
129    for (const tsModifier of tsModifiers) {
130      if (tsModifier.kind === tsModifierKind) {
131        return true;
132      }
133    }
134
135    return false;
136  }
137
138  static unwrapParenthesized(tsExpr: ts.Expression): ts.Expression {
139    let unwrappedExpr = tsExpr;
140    while (ts.isParenthesizedExpression(unwrappedExpr)) {
141      unwrappedExpr = unwrappedExpr.expression;
142    }
143
144    return unwrappedExpr;
145  }
146
147  followIfAliased(sym: ts.Symbol): ts.Symbol {
148    if ((sym.getFlags() & ts.SymbolFlags.Alias) !== 0) {
149      return this.tsTypeChecker.getAliasedSymbol(sym);
150    }
151    return sym;
152  }
153
154  private readonly trueSymbolAtLocationCache = new Map<ts.Node, ts.Symbol | null>();
155
156  trueSymbolAtLocation(node: ts.Node): ts.Symbol | undefined {
157    const cache = this.trueSymbolAtLocationCache;
158    const val = cache.get(node);
159    if (val !== undefined) {
160      return val !== null ? val : undefined;
161    }
162    let sym = this.tsTypeChecker.getSymbolAtLocation(node);
163    if (sym === undefined) {
164      cache.set(node, null);
165      return undefined;
166    }
167    sym = this.followIfAliased(sym);
168    cache.set(node, sym);
169    return sym;
170  }
171
172  private static isTypeDeclSyntaxKind(kind: ts.SyntaxKind): boolean {
173    return (
174      isStructDeclarationKind(kind) ||
175      kind === ts.SyntaxKind.EnumDeclaration ||
176      kind === ts.SyntaxKind.ClassDeclaration ||
177      kind === ts.SyntaxKind.InterfaceDeclaration ||
178      kind === ts.SyntaxKind.TypeAliasDeclaration
179    );
180  }
181
182  static symbolHasDuplicateName(symbol: ts.Symbol, tsDeclKind: ts.SyntaxKind): boolean {
183
184    /*
185     * Type Checker merges all declarations with the same name in one scope into one symbol.
186     * Thus, check whether the symbol of certain declaration has any declaration with
187     * different syntax kind.
188     */
189    const symbolDecls = symbol?.getDeclarations();
190    if (symbolDecls) {
191      for (const symDecl of symbolDecls) {
192        const declKind = symDecl.kind;
193        // we relax arkts-unique-names for namespace collision with class/interface/enum/type/struct
194        const isNamespaceTypeCollision =
195          TsUtils.isTypeDeclSyntaxKind(declKind) && tsDeclKind === ts.SyntaxKind.ModuleDeclaration ||
196          TsUtils.isTypeDeclSyntaxKind(tsDeclKind) && declKind === ts.SyntaxKind.ModuleDeclaration;
197
198        /*
199         * Don't count declarations with 'Identifier' syntax kind as those
200         * usually depict declaring an object's property through assignment.
201         */
202        if (declKind !== ts.SyntaxKind.Identifier && declKind !== tsDeclKind && !isNamespaceTypeCollision) {
203          return true;
204        }
205      }
206    }
207
208    return false;
209  }
210
211  static isPrimitiveType(type: ts.Type): boolean {
212    const f = type.getFlags();
213    return (
214      (f & ts.TypeFlags.Boolean) !== 0 ||
215      (f & ts.TypeFlags.BooleanLiteral) !== 0 ||
216      (f & ts.TypeFlags.Number) !== 0 ||
217      (f & ts.TypeFlags.NumberLiteral) !== 0
218
219    /*
220     *  In ArkTS 'string' is not a primitive type. So for the common subset 'string'
221     *  should be considered as a reference type. That is why next line is commented out.
222     * (f & ts.TypeFlags.String) != 0 || (f & ts.TypeFlags.StringLiteral) != 0
223     */
224    );
225  }
226
227  static isPrimitiveLiteralType(type: ts.Type): boolean {
228    return !!(
229      type.flags &
230      (ts.TypeFlags.BooleanLiteral |
231        ts.TypeFlags.NumberLiteral |
232        ts.TypeFlags.StringLiteral |
233        ts.TypeFlags.BigIntLiteral)
234    );
235  }
236
237  static isPurePrimitiveLiteralType(type: ts.Type): boolean {
238    return TsUtils.isPrimitiveLiteralType(type) && !(type.flags & ts.TypeFlags.EnumLiteral);
239  }
240
241  static isTypeSymbol(symbol: ts.Symbol | undefined): boolean {
242    return (
243      !!symbol &&
244      !!symbol.flags &&
245      ((symbol.flags & ts.SymbolFlags.Class) !== 0 || (symbol.flags & ts.SymbolFlags.Interface) !== 0)
246    );
247  }
248
249  // Check whether type is generic 'Array<T>' type defined in TypeScript standard library.
250  isGenericArrayType(tsType: ts.Type): tsType is ts.TypeReference {
251    return (
252      !(this.arkts2 && !isStdLibraryType(tsType)) &&
253      TsUtils.isTypeReference(tsType) &&
254      tsType.typeArguments?.length === 1 &&
255      tsType.target.typeParameters?.length === 1 &&
256      tsType.getSymbol()?.getName() === 'Array'
257    );
258  }
259
260  isReadonlyArrayType(tsType: ts.Type): boolean {
261    return (
262      !(this.arkts2 && !isStdLibraryType(tsType)) &&
263      TsUtils.isTypeReference(tsType) &&
264      tsType.typeArguments?.length === 1 &&
265      tsType.target.typeParameters?.length === 1 &&
266      tsType.getSymbol()?.getName() === 'ReadonlyArray'
267    );
268  }
269
270  static isConcatArrayType(tsType: ts.Type): boolean {
271    return (
272      isStdLibraryType(tsType) &&
273      TsUtils.isTypeReference(tsType) &&
274      tsType.typeArguments?.length === 1 &&
275      tsType.target.typeParameters?.length === 1 &&
276      tsType.getSymbol()?.getName() === 'ConcatArray'
277    );
278  }
279
280  static isArrayLikeType(tsType: ts.Type): boolean {
281    return (
282      isStdLibraryType(tsType) &&
283      TsUtils.isTypeReference(tsType) &&
284      tsType.typeArguments?.length === 1 &&
285      tsType.target.typeParameters?.length === 1 &&
286      tsType.getSymbol()?.getName() === 'ArrayLike'
287    );
288  }
289
290  isTypedArray(tsType: ts.Type): boolean {
291    const symbol = tsType.symbol;
292    if (!symbol) {
293      return false;
294    }
295    const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
296    if (this.isGlobalSymbol(symbol) && TYPED_ARRAYS.includes(name)) {
297      return true;
298    }
299    const decl = TsUtils.getDeclaration(symbol);
300    return (
301      !!decl && TsUtils.isArkTSCollectionsClassOrInterfaceDeclaration(decl) && TYPED_ARRAYS.includes(symbol.getName())
302    );
303  }
304
305  isArray(tsType: ts.Type): boolean {
306    return this.isGenericArrayType(tsType) || this.isReadonlyArrayType(tsType) || this.isTypedArray(tsType);
307  }
308
309  isIndexableArray(tsType: ts.Type): boolean {
310    return (
311      this.isGenericArrayType(tsType) ||
312      this.isReadonlyArrayType(tsType) ||
313      TsUtils.isConcatArrayType(tsType) ||
314      TsUtils.isArrayLikeType(tsType) ||
315      this.isTypedArray(tsType)
316    );
317  }
318
319  static isTuple(tsType: ts.Type): boolean {
320    return TsUtils.isTypeReference(tsType) && !!(tsType.objectFlags & ts.ObjectFlags.Tuple);
321  }
322
323  // does something similar to relatedByInheritanceOrIdentical function
324  isOrDerivedFrom(tsType: ts.Type, checkType: CheckType, checkedBaseTypes?: Set<ts.Type>): boolean {
325    // eslint-disable-next-line no-param-reassign
326    tsType = TsUtils.reduceReference(tsType);
327
328    if (checkType.call(this, tsType)) {
329      return true;
330    }
331
332    if (!tsType.symbol?.declarations) {
333      return false;
334    }
335
336    // Avoid type recursion in heritage by caching checked types.
337    // eslint-disable-next-line no-param-reassign
338    (checkedBaseTypes ||= new Set<ts.Type>()).add(tsType);
339
340    for (const tsTypeDecl of tsType.symbol.declarations) {
341      const isClassOrInterfaceDecl = ts.isClassDeclaration(tsTypeDecl) || ts.isInterfaceDeclaration(tsTypeDecl);
342      const isDerived = isClassOrInterfaceDecl && !!tsTypeDecl.heritageClauses;
343      if (!isDerived) {
344        continue;
345      }
346      for (const heritageClause of tsTypeDecl.heritageClauses) {
347        if (this.processParentTypesCheck(heritageClause.types, checkType, checkedBaseTypes)) {
348          return true;
349        }
350      }
351    }
352
353    return false;
354  }
355
356  static isTypeReference(tsType: ts.Type): tsType is ts.TypeReference {
357    return (
358      (tsType.getFlags() & ts.TypeFlags.Object) !== 0 &&
359      ((tsType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0
360    );
361  }
362
363  static isPrototypeSymbol(symbol: ts.Symbol | undefined): boolean {
364    return !!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Prototype) !== 0;
365  }
366
367  static isFunctionSymbol(symbol: ts.Symbol | undefined): boolean {
368    return !!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Function) !== 0;
369  }
370
371  static isInterfaceType(tsType: ts.Type | undefined): boolean {
372    return (
373      !!tsType && !!tsType.symbol && !!tsType.symbol.flags && (tsType.symbol.flags & ts.SymbolFlags.Interface) !== 0
374    );
375  }
376
377  static isAnyType(tsType: ts.Type): tsType is ts.TypeReference {
378    return (tsType.getFlags() & ts.TypeFlags.Any) !== 0;
379  }
380
381  static isUnknownType(tsType: ts.Type): boolean {
382    return (tsType.getFlags() & ts.TypeFlags.Unknown) !== 0;
383  }
384
385  static isUnsupportedType(tsType: ts.Type): boolean {
386    return (
387      !!tsType.flags &&
388      ((tsType.flags & ts.TypeFlags.Any) !== 0 ||
389        (tsType.flags & ts.TypeFlags.Unknown) !== 0 ||
390        (tsType.flags & ts.TypeFlags.Intersection) !== 0)
391    );
392  }
393
394  isUnsupportedTypeArkts2(tsType: ts.Type): boolean {
395    const typenode = this.tsTypeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.None);
396    return !!typenode && !this.isSupportedType(typenode);
397  }
398
399  static isNullableUnionType(type: ts.Type): boolean {
400    if (type.isUnion()) {
401      for (const t of type.types) {
402        if (!!(t.flags & ts.TypeFlags.Undefined) || !!(t.flags & ts.TypeFlags.Null)) {
403          return true;
404        }
405      }
406    }
407    return false;
408  }
409
410  static isMethodAssignment(tsSymbol: ts.Symbol | undefined): boolean {
411    return (
412      !!tsSymbol && (tsSymbol.flags & ts.SymbolFlags.Method) !== 0 && (tsSymbol.flags & ts.SymbolFlags.Assignment) !== 0
413    );
414  }
415
416  static getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | undefined {
417    if (tsSymbol?.declarations && tsSymbol.declarations.length > 0) {
418      return tsSymbol.declarations[0];
419    }
420    return undefined;
421  }
422
423  private static isVarDeclaration(tsDecl: ts.Node): boolean {
424    return ts.isVariableDeclaration(tsDecl) && ts.isVariableDeclarationList(tsDecl.parent);
425  }
426
427  isValidEnumMemberInit(tsExpr: ts.Expression): boolean {
428    if (this.isNumberConstantValue(tsExpr.parent as ts.EnumMember)) {
429      return true;
430    }
431    if (this.isStringConstantValue(tsExpr.parent as ts.EnumMember)) {
432      return true;
433    }
434    return this.isCompileTimeExpression(tsExpr);
435  }
436
437  private isCompileTimeExpressionHandlePropertyAccess(tsExpr: ts.Expression): boolean {
438    if (!ts.isPropertyAccessExpression(tsExpr)) {
439      return false;
440    }
441
442    /*
443     * if enum member is in current enum declaration try to get value
444     * if it comes from another enum consider as constant
445     */
446    const propertyAccess = tsExpr;
447    if (this.isNumberConstantValue(propertyAccess)) {
448      return true;
449    }
450    const leftHandSymbol = this.trueSymbolAtLocation(propertyAccess.expression);
451    if (!leftHandSymbol) {
452      return false;
453    }
454    const decls = leftHandSymbol.getDeclarations();
455    if (!decls || decls.length !== 1) {
456      return false;
457    }
458    return ts.isEnumDeclaration(decls[0]);
459  }
460
461  isCompileTimeExpression(tsExpr: ts.Expression): boolean {
462    if (
463      ts.isParenthesizedExpression(tsExpr) ||
464      ts.isAsExpression(tsExpr) && tsExpr.type.kind === ts.SyntaxKind.NumberKeyword
465    ) {
466      return this.isCompileTimeExpression(tsExpr.expression);
467    }
468
469    switch (tsExpr.kind) {
470      case ts.SyntaxKind.PrefixUnaryExpression:
471        return this.isPrefixUnaryExprValidEnumMemberInit(tsExpr as ts.PrefixUnaryExpression);
472      case ts.SyntaxKind.ParenthesizedExpression:
473      case ts.SyntaxKind.BinaryExpression:
474        return this.isBinaryExprValidEnumMemberInit(tsExpr as ts.BinaryExpression);
475      case ts.SyntaxKind.ConditionalExpression:
476        return this.isConditionalExprValidEnumMemberInit(tsExpr as ts.ConditionalExpression);
477      case ts.SyntaxKind.Identifier:
478        return this.isIdentifierValidEnumMemberInit(tsExpr as ts.Identifier);
479      case ts.SyntaxKind.NumericLiteral:
480        return true;
481      case ts.SyntaxKind.StringLiteral:
482        return true;
483      case ts.SyntaxKind.PropertyAccessExpression:
484        return this.isCompileTimeExpressionHandlePropertyAccess(tsExpr);
485      default:
486        return false;
487    }
488  }
489
490  private isPrefixUnaryExprValidEnumMemberInit(tsExpr: ts.PrefixUnaryExpression): boolean {
491    return TsUtils.isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && this.isCompileTimeExpression(tsExpr.operand);
492  }
493
494  private isBinaryExprValidEnumMemberInit(tsExpr: ts.BinaryExpression): boolean {
495    return (
496      TsUtils.isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) &&
497      this.isCompileTimeExpression(tsExpr.left) &&
498      this.isCompileTimeExpression(tsExpr.right)
499    );
500  }
501
502  private isConditionalExprValidEnumMemberInit(tsExpr: ts.ConditionalExpression): boolean {
503    return this.isCompileTimeExpression(tsExpr.whenTrue) && this.isCompileTimeExpression(tsExpr.whenFalse);
504  }
505
506  private isIdentifierValidEnumMemberInit(tsExpr: ts.Identifier): boolean {
507    const tsSymbol = this.trueSymbolAtLocation(tsExpr);
508    const tsDecl = TsUtils.getDeclaration(tsSymbol);
509    return (
510      !!tsDecl &&
511      (TsUtils.isVarDeclaration(tsDecl) && TsUtils.isConst(tsDecl.parent) || tsDecl.kind === ts.SyntaxKind.EnumMember)
512    );
513  }
514
515  private static isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: ts.PrefixUnaryOperator): boolean {
516    return (
517      tsPrefixUnaryOp === ts.SyntaxKind.PlusToken ||
518      tsPrefixUnaryOp === ts.SyntaxKind.MinusToken ||
519      tsPrefixUnaryOp === ts.SyntaxKind.TildeToken
520    );
521  }
522
523  private static isBinaryOpAllowedForEnumMemberInit(tsBinaryOp: ts.BinaryOperatorToken): boolean {
524    return (
525      tsBinaryOp.kind === ts.SyntaxKind.AsteriskToken ||
526      tsBinaryOp.kind === ts.SyntaxKind.SlashToken ||
527      tsBinaryOp.kind === ts.SyntaxKind.PercentToken ||
528      tsBinaryOp.kind === ts.SyntaxKind.MinusToken ||
529      tsBinaryOp.kind === ts.SyntaxKind.PlusToken ||
530      tsBinaryOp.kind === ts.SyntaxKind.LessThanLessThanToken ||
531      tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanToken ||
532      tsBinaryOp.kind === ts.SyntaxKind.BarBarToken ||
533      tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken ||
534      tsBinaryOp.kind === ts.SyntaxKind.AmpersandToken ||
535      tsBinaryOp.kind === ts.SyntaxKind.CaretToken ||
536      tsBinaryOp.kind === ts.SyntaxKind.BarToken ||
537      tsBinaryOp.kind === ts.SyntaxKind.AmpersandAmpersandToken
538    );
539  }
540
541  static isConst(tsNode: ts.Node): boolean {
542    return !!(ts.getCombinedNodeFlags(tsNode) & ts.NodeFlags.Const);
543  }
544
545  isNumberConstantValue(
546    tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral
547  ): boolean {
548    const tsConstValue =
549      tsExpr.kind === ts.SyntaxKind.NumericLiteral ?
550        Number(tsExpr.getText()) :
551        this.tsTypeChecker.getConstantValue(tsExpr);
552
553    return tsConstValue !== undefined && typeof tsConstValue === 'number';
554  }
555
556  isIntegerConstantValue(
557    tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral
558  ): boolean {
559    const tsConstValue =
560      tsExpr.kind === ts.SyntaxKind.NumericLiteral ?
561        Number(tsExpr.getText()) :
562        this.tsTypeChecker.getConstantValue(tsExpr);
563    return (
564      tsConstValue !== undefined &&
565      typeof tsConstValue === 'number' &&
566      tsConstValue.toFixed(0) === tsConstValue.toString()
567    );
568  }
569
570  isStringConstantValue(tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression): boolean {
571    const tsConstValue = this.tsTypeChecker.getConstantValue(tsExpr);
572    return tsConstValue !== undefined && typeof tsConstValue === 'string';
573  }
574
575  // Returns true if typeA is a subtype of typeB
576  relatedByInheritanceOrIdentical(typeA: ts.Type, typeB: ts.Type): boolean {
577    // eslint-disable-next-line no-param-reassign
578    typeA = TsUtils.reduceReference(typeA);
579    // eslint-disable-next-line no-param-reassign
580    typeB = TsUtils.reduceReference(typeB);
581
582    if (typeA === typeB || this.isObject(typeB)) {
583      return true;
584    }
585    if (!typeA.symbol?.declarations) {
586      return false;
587    }
588    const isBISendable = TsUtils.isISendableInterface(typeB);
589    for (const typeADecl of typeA.symbol.declarations) {
590      if (isBISendable && ts.isClassDeclaration(typeADecl) && TsUtils.hasSendableDecorator(typeADecl)) {
591        return true;
592      }
593      if (!ts.isClassDeclaration(typeADecl) && !ts.isInterfaceDeclaration(typeADecl)) {
594        continue;
595      }
596      if (this.processExtendedParentTypes(typeA, typeB)) {
597        return true;
598      }
599      if (!typeADecl.heritageClauses) {
600        continue;
601      }
602      for (const heritageClause of typeADecl.heritageClauses) {
603        const processInterfaces = typeA.isClass() ? heritageClause.token !== ts.SyntaxKind.ExtendsKeyword : true;
604        if (this.processParentTypes(heritageClause.types, typeB, processInterfaces)) {
605          return true;
606        }
607      }
608    }
609    return false;
610  }
611
612  static reduceReference(t: ts.Type): ts.Type {
613    return TsUtils.isTypeReference(t) && t.target !== t ? t.target : t;
614  }
615
616  private needToDeduceStructuralIdentityHandleUnions(
617    lhsType: ts.Type,
618    rhsType: ts.Type,
619    rhsExpr: ts.Expression,
620    isStrict: boolean
621  ): boolean {
622    if (rhsType.isUnion()) {
623      // Each Class/Interface of the RHS union type must be compatible with LHS type.
624      for (const compType of rhsType.types) {
625        if (this.needToDeduceStructuralIdentity(lhsType, compType, rhsExpr, isStrict)) {
626          return true;
627        }
628      }
629      return false;
630    }
631    if (lhsType.isUnion()) {
632      // RHS type needs to be compatible with at least one type of the LHS union.
633      for (const compType of lhsType.types) {
634        if (!this.needToDeduceStructuralIdentity(compType, rhsType, rhsExpr, isStrict)) {
635          return false;
636        }
637      }
638      return true;
639    }
640    // should be unreachable
641    return false;
642  }
643
644  // return true if two class types are not related by inheritance and structural identity check is needed
645  needToDeduceStructuralIdentity(
646    lhsType: ts.Type,
647    rhsType: ts.Type,
648    rhsExpr: ts.Expression,
649    isStrict: boolean = false
650  ): boolean {
651    // eslint-disable-next-line no-param-reassign
652    lhsType = this.getNonNullableType(lhsType);
653    // eslint-disable-next-line no-param-reassign
654    rhsType = this.getNonNullableType(rhsType);
655    if (this.isLibraryType(lhsType)) {
656      return false;
657    }
658    if (this.isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) {
659      return false;
660    }
661    // #14569: Check for Function type.
662    if (this.areCompatibleFunctionals(lhsType, rhsType)) {
663      return false;
664    }
665    if (rhsType.isUnion() || lhsType.isUnion()) {
666      return this.needToDeduceStructuralIdentityHandleUnions(lhsType, rhsType, rhsExpr, isStrict);
667    }
668    if (
669      this.advancedClassChecks &&
670      TsUtils.isClassValueType(rhsType) &&
671      lhsType !== rhsType &&
672      !TsUtils.isObjectType(lhsType)
673    ) {
674      // missing exact rule
675      return true;
676    }
677    // if isStrict, things like generics need to be checked
678    if (isStrict) {
679      if (TsUtils.isTypeReference(rhsType) && !!(rhsType.objectFlags & ts.ObjectFlags.ArrayLiteral)) {
680        // The 'arkts-sendable-obj-init' rule already exists. Wait for the new 'strict type' to be modified.
681        return false;
682      }
683      // eslint-disable-next-line no-param-reassign
684      lhsType = TsUtils.reduceReference(lhsType);
685      // eslint-disable-next-line no-param-reassign
686      rhsType = TsUtils.reduceReference(rhsType);
687    }
688    return (
689      lhsType.isClassOrInterface() &&
690      rhsType.isClassOrInterface() &&
691      !this.relatedByInheritanceOrIdentical(rhsType, lhsType)
692    );
693  }
694
695  // Does the 'arkts-no-structural-typing' rule need to be strictly enforced to complete previously missed scenarios
696  needStrictMatchType(lhsType: ts.Type, rhsType: ts.Type): boolean {
697    if (this.arkts2) {
698      return true;
699    }
700    if (this.isStrictSendableMatch(lhsType, rhsType)) {
701      return true;
702    }
703    // add other requirements with strict type requirements here
704    return false;
705  }
706
707  // For compatibility, left must all ClassOrInterface is sendable, right must has non-sendable ClassorInterface
708  private isStrictSendableMatch(lhsType: ts.Type, rhsType: ts.Type): boolean {
709    let isStrictLhs = false;
710    if (lhsType.isUnion()) {
711      for (let compType of lhsType.types) {
712        compType = TsUtils.reduceReference(compType);
713        if (!compType.isClassOrInterface()) {
714          continue;
715        }
716        if (!this.isSendableClassOrInterface(compType)) {
717          return false;
718        }
719        isStrictLhs = true;
720      }
721    } else {
722      isStrictLhs = this.isSendableClassOrInterface(lhsType);
723    }
724    return isStrictLhs && this.typeContainsNonSendableClassOrInterface(rhsType);
725  }
726
727  private processExtendedParentTypes(typeA: ts.Type, typeB: ts.Type): boolean {
728
729    /*
730     * Most standard types in TS Stdlib do not use explicit inheritance and rely on
731     * structural compatibility. In contrast, the type definitions in ArkTS stdlib
732     * use inheritance with explicit base types. We check the inheritance hierarchy
733     * for such types according to how they are defined in ArkTS Stdlib.
734     */
735
736    if (!this.arkts2) {
737      return false;
738    }
739    if (!isStdLibrarySymbol(typeA.symbol) && !isStdLibrarySymbol(typeB.symbol)) {
740      return false;
741    }
742    return this.checkExtendedParentTypes(typeA.symbol.name, typeB.symbol.name);
743  }
744
745  private checkExtendedParentTypes(typeA: string, typeB: string): boolean {
746    if (typeA === typeB) {
747      return true;
748    }
749    const extBaseTypes = EXTENDED_BASE_TYPES.get(typeA);
750    if (!extBaseTypes) {
751      return false;
752    }
753    for (const extBaseType of extBaseTypes) {
754      if (this.checkExtendedParentTypes(extBaseType, typeB)) {
755        return true;
756      }
757    }
758    return false;
759  }
760
761  private processParentTypes(
762    parentTypes: ts.NodeArray<ts.Expression>,
763    typeB: ts.Type,
764    processInterfaces: boolean
765  ): boolean {
766    for (const baseTypeExpr of parentTypes) {
767      const baseType = TsUtils.reduceReference(this.tsTypeChecker.getTypeAtLocation(baseTypeExpr));
768      if (
769        baseType &&
770        baseType.isClass() !== processInterfaces &&
771        this.relatedByInheritanceOrIdentical(baseType, typeB)
772      ) {
773        return true;
774      }
775    }
776    return false;
777  }
778
779  private processParentTypesCheck(
780    parentTypes: ts.NodeArray<ts.Expression>,
781    checkType: CheckType,
782    checkedBaseTypes: Set<ts.Type>
783  ): boolean {
784    for (const baseTypeExpr of parentTypes) {
785      const baseType = TsUtils.reduceReference(this.tsTypeChecker.getTypeAtLocation(baseTypeExpr));
786      if (baseType && !checkedBaseTypes.has(baseType) && this.isOrDerivedFrom(baseType, checkType, checkedBaseTypes)) {
787        return true;
788      }
789    }
790    return false;
791  }
792
793  isObject(tsType: ts.Type): boolean {
794    if (!tsType) {
795      return false;
796    }
797    if (tsType.symbol && tsType.isClassOrInterface() && tsType.symbol.name === 'Object') {
798      return true;
799    }
800    const node = this.tsTypeChecker.typeToTypeNode(tsType, undefined, undefined);
801    return node !== undefined && node.kind === ts.SyntaxKind.ObjectKeyword;
802  }
803
804  isCallToFunctionWithOmittedReturnType(tsExpr: ts.Expression): boolean {
805    if (ts.isCallExpression(tsExpr)) {
806      const tsCallSignature = this.tsTypeChecker.getResolvedSignature(tsExpr);
807      if (tsCallSignature) {
808        const tsSignDecl = tsCallSignature.getDeclaration();
809        // `tsSignDecl` is undefined when `getResolvedSignature` returns `unknownSignature`
810        if (!tsSignDecl?.type) {
811          return true;
812        }
813      }
814    }
815
816    return false;
817  }
818
819  private static hasReadonlyFields(type: ts.Type): boolean {
820    // No members -> no readonly fields
821    if (type.symbol.members === undefined) {
822      return false;
823    }
824
825    let result: boolean = false;
826
827    type.symbol.members.forEach((value) => {
828      if (
829        value.declarations !== undefined &&
830        value.declarations.length > 0 &&
831        ts.isPropertyDeclaration(value.declarations[0])
832      ) {
833        const propmMods = ts.getModifiers(value.declarations[0]);
834        if (TsUtils.hasModifier(propmMods, ts.SyntaxKind.ReadonlyKeyword)) {
835          result = true;
836        }
837      }
838    });
839
840    return result;
841  }
842
843  private static hasDefaultCtor(type: ts.Type): boolean {
844    // No members -> no explicit constructors -> there is default ctor
845    if (type.symbol.members === undefined) {
846      return true;
847    }
848
849    // has any constructor
850    let hasCtor: boolean = false;
851    // has default constructor
852    let hasDefaultCtor: boolean = false;
853
854    type.symbol.members.forEach((value) => {
855      if ((value.flags & ts.SymbolFlags.Constructor) === 0) {
856        return;
857      }
858      hasCtor = true;
859
860      if (value.declarations === undefined || value.declarations.length <= 0) {
861        return;
862      }
863
864      const declCtor = value.declarations[0] as ts.ConstructorDeclaration;
865      if (declCtor.parameters.length === 0) {
866        hasDefaultCtor = true;
867      }
868    });
869
870    // Has no any explicit constructor -> has implicit default constructor.
871    return !hasCtor || hasDefaultCtor;
872  }
873
874  private static isAbstractClass(type: ts.Type): boolean {
875    if (type.isClass() && type.symbol.declarations && type.symbol.declarations.length > 0) {
876      const declClass = type.symbol.declarations[0] as ts.ClassDeclaration;
877      const classMods = ts.getModifiers(declClass);
878      if (TsUtils.hasModifier(classMods, ts.SyntaxKind.AbstractKeyword)) {
879        return true;
880      }
881    }
882
883    return false;
884  }
885
886  static validateObjectLiteralType(type: ts.Type | undefined): boolean {
887    if (!type) {
888      return false;
889    }
890    // eslint-disable-next-line no-param-reassign
891    type = TsUtils.reduceReference(type);
892    return (
893      type.isClassOrInterface() &&
894      TsUtils.hasDefaultCtor(type) &&
895      !TsUtils.hasReadonlyFields(type) &&
896      !TsUtils.isAbstractClass(type)
897    );
898  }
899
900  hasMethods(type: ts.Type): boolean {
901    const properties = this.tsTypeChecker.getPropertiesOfType(type);
902    if (properties?.length) {
903      for (const prop of properties) {
904        if (prop.getFlags() & ts.SymbolFlags.Method) {
905          return true;
906        }
907      }
908    }
909    return false;
910  }
911
912  findProperty(type: ts.Type, name: string): ts.Symbol | undefined {
913    const properties = this.tsTypeChecker.getPropertiesOfType(type);
914    if (properties?.length) {
915      for (const prop of properties) {
916        if (prop.name === name) {
917          return prop;
918        }
919      }
920    }
921
922    return undefined;
923  }
924
925  checkTypeSet(typeSet: ts.Type, predicate: CheckType): boolean {
926    if (!typeSet.isUnionOrIntersection()) {
927      return predicate.call(this, typeSet);
928    }
929    for (const elemType of typeSet.types) {
930      if (this.checkTypeSet(elemType, predicate)) {
931        return true;
932      }
933    }
934    return false;
935  }
936
937  getNonNullableType(t: ts.Type): ts.Type {
938    const isNullableUnionType = this.useSdkLogic ? t.isUnion() : TsUtils.isNullableUnionType(t);
939    if (isNullableUnionType) {
940      return t.getNonNullableType();
941    }
942    return t;
943  }
944
945  private isObjectLiteralAssignableToUnion(lhsType: ts.UnionType, rhsExpr: ts.ObjectLiteralExpression): boolean {
946    for (const compType of lhsType.types) {
947      if (this.isObjectLiteralAssignable(compType, rhsExpr)) {
948        return true;
949      }
950    }
951    return false;
952  }
953
954  isObjectLiteralAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.ObjectLiteralExpression): boolean {
955    if (lhsType === undefined) {
956      return false;
957    }
958    // Always check with the non-nullable variant of lhs type.
959    // eslint-disable-next-line no-param-reassign
960    lhsType = this.getNonNullableType(lhsType);
961    if (lhsType.isUnion() && this.isObjectLiteralAssignableToUnion(lhsType, rhsExpr)) {
962      return true;
963    }
964
965    /*
966     * Allow initializing with anything when the type
967     * originates from the library.
968     */
969    if (TsUtils.isAnyType(lhsType) || this.isLibraryType(lhsType)) {
970      return true;
971    }
972
973    /*
974     * issue 13412:
975     * Allow initializing with a dynamic object when the LHS type
976     * is primitive or defined in standard library.
977     */
978    if (this.isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) {
979      return true;
980    }
981    // For Partial<T>, Required<T>, Readonly<T> types, validate their argument type.
982    if (this.isStdPartialType(lhsType) || this.isStdRequiredType(lhsType) || this.isStdReadonlyType(lhsType)) {
983      if (lhsType.aliasTypeArguments && lhsType.aliasTypeArguments.length === 1) {
984        // eslint-disable-next-line no-param-reassign
985        lhsType = lhsType.aliasTypeArguments[0];
986      } else {
987        return false;
988      }
989    }
990
991    /*
992     * Allow initializing Record objects with object initializer.
993     * Record supports any type for a its value, but the key value
994     * must be either a string or number literal.
995     */
996    if (this.isStdRecordType(lhsType)) {
997      return this.validateRecordObjectKeys(rhsExpr);
998    }
999    return (
1000      TsUtils.validateObjectLiteralType(lhsType) && !this.hasMethods(lhsType) && this.validateFields(lhsType, rhsExpr)
1001    );
1002  }
1003
1004  private isDynamicObjectAssignedToStdType(lhsType: ts.Type, rhsExpr: ts.Expression): boolean {
1005    if (isStdLibraryType(lhsType) || TsUtils.isPrimitiveType(lhsType)) {
1006      // eslint-disable-next-line no-nested-ternary
1007      const rhsSym = ts.isCallExpression(rhsExpr) ?
1008        this.getSymbolOfCallExpression(rhsExpr) :
1009        this.useSdkLogic ?
1010          this.tsTypeChecker.getSymbolAtLocation(rhsExpr) :
1011          this.trueSymbolAtLocation(rhsExpr);
1012      if (rhsSym && this.isLibrarySymbol(rhsSym)) {
1013        return true;
1014      }
1015    }
1016    return false;
1017  }
1018
1019  validateFields(objectType: ts.Type, objectLiteral: ts.ObjectLiteralExpression): boolean {
1020    for (const prop of objectLiteral.properties) {
1021      if (ts.isPropertyAssignment(prop)) {
1022        if (!this.validateField(objectType, prop)) {
1023          return false;
1024        }
1025      }
1026    }
1027
1028    return true;
1029  }
1030
1031  getPropertySymbol(type: ts.Type, prop: ts.PropertyAssignment): ts.Symbol | undefined {
1032    const propNameSymbol = this.tsTypeChecker.getSymbolAtLocation(prop.name);
1033    // eslint-disable-next-line no-nested-ternary
1034    const propName = propNameSymbol ?
1035      ts.symbolName(propNameSymbol) :
1036      ts.isMemberName(prop.name) ?
1037        ts.idText(prop.name) :
1038        prop.name.getText();
1039    const propSym = this.findProperty(type, propName);
1040    return propSym;
1041  }
1042
1043  private validateField(type: ts.Type, prop: ts.PropertyAssignment): boolean {
1044    // Issue 15497: Use unescaped property name to find correpsponding property.
1045    const propSym = this.getPropertySymbol(type, prop);
1046    if (!propSym?.declarations?.length) {
1047      return false;
1048    }
1049
1050    const propType = this.tsTypeChecker.getTypeOfSymbolAtLocation(propSym, propSym.declarations[0]);
1051    const initExpr = TsUtils.unwrapParenthesized(prop.initializer);
1052    const rhsType = this.tsTypeChecker.getTypeAtLocation(initExpr);
1053    if (ts.isObjectLiteralExpression(initExpr)) {
1054      if (!this.isObjectLiteralAssignable(propType, initExpr)) {
1055        return false;
1056      }
1057    } else {
1058      // Only check for structural sub-typing.
1059      if (
1060        this.needToDeduceStructuralIdentity(propType, rhsType, initExpr, this.needStrictMatchType(propType, rhsType))
1061      ) {
1062        return false;
1063      }
1064      if (this.isWrongSendableFunctionAssignment(propType, rhsType)) {
1065        return false;
1066      }
1067    }
1068
1069    return true;
1070  }
1071
1072  validateRecordObjectKeys(objectLiteral: ts.ObjectLiteralExpression): boolean {
1073    for (const prop of objectLiteral.properties) {
1074      if (!prop.name || !this.isValidRecordObjectLiteralKey(prop.name)) {
1075        return false;
1076      }
1077    }
1078    return true;
1079  }
1080
1081  isValidRecordObjectLiteralKey(propName: ts.PropertyName): boolean {
1082    if (ts.isComputedPropertyName(propName)) {
1083      return this.isValidComputedPropertyName(propName, true);
1084    }
1085    return ts.isStringLiteral(propName) || ts.isNumericLiteral(propName);
1086  }
1087
1088  private static isSupportedTypeNodeKind(kind: ts.SyntaxKind): boolean {
1089    return (
1090      kind !== ts.SyntaxKind.AnyKeyword &&
1091      kind !== ts.SyntaxKind.UnknownKeyword &&
1092      kind !== ts.SyntaxKind.SymbolKeyword &&
1093      kind !== ts.SyntaxKind.IndexedAccessType &&
1094      kind !== ts.SyntaxKind.ConditionalType &&
1095      kind !== ts.SyntaxKind.MappedType &&
1096      kind !== ts.SyntaxKind.InferType
1097    );
1098  }
1099
1100  private isSupportedTypeHandleUnionTypeNode(typeNode: ts.UnionTypeNode): boolean {
1101    for (const unionTypeElem of typeNode.types) {
1102      if (!this.isSupportedType(unionTypeElem)) {
1103        return false;
1104      }
1105    }
1106    return true;
1107  }
1108
1109  private isSupportedTypeHandleTupleTypeNode(typeNode: ts.TupleTypeNode): boolean {
1110    for (const elem of typeNode.elements) {
1111      if (ts.isTypeNode(elem) && !this.isSupportedType(elem)) {
1112        return false;
1113      }
1114      if (ts.isNamedTupleMember(elem) && !this.isSupportedType(elem.type)) {
1115        return false;
1116      }
1117    }
1118    return true;
1119  }
1120
1121  isSupportedType(typeNode: ts.TypeNode): boolean {
1122    if (ts.isParenthesizedTypeNode(typeNode)) {
1123      return this.isSupportedType(typeNode.type);
1124    }
1125
1126    if (ts.isArrayTypeNode(typeNode)) {
1127      return this.isSupportedType(typeNode.elementType);
1128    }
1129
1130    if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) {
1131      for (const typeArg of typeNode.typeArguments) {
1132        if (!this.isSupportedType(typeArg)) {
1133          return false;
1134        }
1135      }
1136      return true;
1137    }
1138
1139    if (ts.isUnionTypeNode(typeNode)) {
1140      return this.isSupportedTypeHandleUnionTypeNode(typeNode);
1141    }
1142
1143    if (ts.isTupleTypeNode(typeNode)) {
1144      return this.isSupportedTypeHandleTupleTypeNode(typeNode);
1145    }
1146
1147    return (
1148      !ts.isTypeLiteralNode(typeNode) &&
1149      (this.advancedClassChecks || !ts.isTypeQueryNode(typeNode)) &&
1150      !ts.isIntersectionTypeNode(typeNode) &&
1151      TsUtils.isSupportedTypeNodeKind(typeNode.kind)
1152    );
1153  }
1154
1155  isStructObjectInitializer(objectLiteral: ts.ObjectLiteralExpression): boolean {
1156    if (ts.isCallLikeExpression(objectLiteral.parent)) {
1157      const signature = this.tsTypeChecker.getResolvedSignature(objectLiteral.parent);
1158      const signDecl = signature?.declaration;
1159      return !!signDecl && ts.isConstructorDeclaration(signDecl) && isStructDeclaration(signDecl.parent);
1160    }
1161    return false;
1162  }
1163
1164  parentSymbolCache = new Map<ts.Symbol, string | undefined>();
1165
1166  getParentSymbolName(symbol: ts.Symbol): string | undefined {
1167    const cached = this.parentSymbolCache.get(symbol);
1168    if (cached) {
1169      return cached;
1170    }
1171
1172    const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
1173    const dotPosition = name.lastIndexOf('.');
1174    const result = dotPosition === -1 ? undefined : name.substring(0, dotPosition);
1175    this.parentSymbolCache.set(symbol, result);
1176    return result;
1177  }
1178
1179  isGlobalSymbol(symbol: ts.Symbol): boolean {
1180    const parentName = this.getParentSymbolName(symbol);
1181    return !parentName || parentName === 'global';
1182  }
1183
1184  isStdSymbol(symbol: ts.Symbol): boolean {
1185    const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
1186    return name === SYMBOL || name === SYMBOL_CONSTRUCTOR;
1187  }
1188
1189  isStdSymbolAPI(symbol: ts.Symbol): boolean {
1190    const parentName = this.getParentSymbolName(symbol);
1191    if (this.useSdkLogic) {
1192      const name = parentName ? parentName : symbol.escapedName;
1193      return name === SYMBOL || name === SYMBOL_CONSTRUCTOR;
1194    }
1195    return !!parentName && (parentName === SYMBOL || parentName === SYMBOL_CONSTRUCTOR);
1196  }
1197
1198  isSymbolIterator(symbol: ts.Symbol): boolean {
1199    if (this.useSdkLogic) {
1200      const name = symbol.name;
1201      const parName = this.getParentSymbolName(symbol);
1202      return (parName === SYMBOL || parName === SYMBOL_CONSTRUCTOR) && name === ITERATOR;
1203    }
1204    return this.isStdSymbolAPI(symbol) && symbol.name === ITERATOR;
1205  }
1206
1207  isSymbolIteratorExpression(expr: ts.Expression): boolean {
1208    const symbol = this.trueSymbolAtLocation(expr);
1209    return !!symbol && this.isSymbolIterator(symbol);
1210  }
1211
1212  static isDefaultImport(importSpec: ts.ImportSpecifier): boolean {
1213    return importSpec?.propertyName?.text === 'default';
1214  }
1215
1216  static getStartPos(nodeOrComment: ts.Node | ts.CommentRange): number {
1217    return nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia ||
1218      nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia ?
1219      (nodeOrComment as ts.CommentRange).pos :
1220      (nodeOrComment as ts.Node).getStart();
1221  }
1222
1223  static getEndPos(nodeOrComment: ts.Node | ts.CommentRange): number {
1224    return nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia ||
1225      nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia ?
1226      (nodeOrComment as ts.CommentRange).end :
1227      (nodeOrComment as ts.Node).getEnd();
1228  }
1229
1230  static getHighlightRange(nodeOrComment: ts.Node | ts.CommentRange, faultId: number): [number, number] {
1231    return (
1232      this.highlightRangeHandlers.get(faultId)?.call(this, nodeOrComment) ?? [
1233        this.getStartPos(nodeOrComment),
1234        this.getEndPos(nodeOrComment)
1235      ]
1236    );
1237  }
1238
1239  static highlightRangeHandlers = new Map([
1240    [FaultID.VarDeclaration, TsUtils.getVarDeclarationHighlightRange],
1241    [FaultID.CatchWithUnsupportedType, TsUtils.getCatchWithUnsupportedTypeHighlightRange],
1242    [FaultID.ForInStatement, TsUtils.getForInStatementHighlightRange],
1243    [FaultID.WithStatement, TsUtils.getWithStatementHighlightRange],
1244    [FaultID.DeleteOperator, TsUtils.getDeleteOperatorHighlightRange],
1245    [FaultID.TypeQuery, TsUtils.getTypeQueryHighlightRange],
1246    [FaultID.InstanceofUnsupported, TsUtils.getInstanceofUnsupportedHighlightRange],
1247    [FaultID.ConstAssertion, TsUtils.getConstAssertionHighlightRange],
1248    [FaultID.LimitedReturnTypeInference, TsUtils.getLimitedReturnTypeInferenceHighlightRange],
1249    [FaultID.LocalFunction, TsUtils.getLocalFunctionHighlightRange],
1250    [FaultID.FunctionBind, TsUtils.getFunctionApplyCallHighlightRange],
1251    [FaultID.FunctionBindError, TsUtils.getFunctionApplyCallHighlightRange],
1252    [FaultID.FunctionApplyCall, TsUtils.getFunctionApplyCallHighlightRange],
1253    [FaultID.DeclWithDuplicateName, TsUtils.getDeclWithDuplicateNameHighlightRange],
1254    [FaultID.ObjectLiteralNoContextType, TsUtils.getObjectLiteralNoContextTypeHighlightRange],
1255    [FaultID.ClassExpression, TsUtils.getClassExpressionHighlightRange],
1256    [FaultID.MultipleStaticBlocks, TsUtils.getMultipleStaticBlocksHighlightRange],
1257    [FaultID.ParameterProperties, TsUtils.getParameterPropertiesHighlightRange],
1258    [FaultID.SendableDefiniteAssignment, TsUtils.getSendableDefiniteAssignmentHighlightRange],
1259    [FaultID.ObjectTypeLiteral, TsUtils.getObjectTypeLiteralHighlightRange],
1260    [FaultID.StructuralIdentity, TsUtils.getStructuralIdentityHighlightRange]
1261  ]);
1262
1263  static getKeywordHighlightRange(nodeOrComment: ts.Node | ts.CommentRange, keyword: string): [number, number] {
1264    const start = this.getStartPos(nodeOrComment);
1265    return [start, start + keyword.length];
1266  }
1267
1268  static getVarDeclarationHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1269    return this.getKeywordHighlightRange(nodeOrComment, 'var');
1270  }
1271
1272  static getCatchWithUnsupportedTypeHighlightRange(
1273    nodeOrComment: ts.Node | ts.CommentRange
1274  ): [number, number] | undefined {
1275    const catchClauseNode = (nodeOrComment as ts.CatchClause).variableDeclaration;
1276    if (catchClauseNode !== undefined) {
1277      return [catchClauseNode.getStart(), catchClauseNode.getEnd()];
1278    }
1279
1280    return undefined;
1281  }
1282
1283  static getForInStatementHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1284    return [
1285      this.getEndPos((nodeOrComment as ts.ForInStatement).initializer) + 1,
1286      this.getStartPos((nodeOrComment as ts.ForInStatement).expression) - 1
1287    ];
1288  }
1289
1290  static getWithStatementHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1291    return [this.getStartPos(nodeOrComment), (nodeOrComment as ts.WithStatement).statement.getStart() - 1];
1292  }
1293
1294  static getDeleteOperatorHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1295    return this.getKeywordHighlightRange(nodeOrComment, 'delete');
1296  }
1297
1298  static getTypeQueryHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1299    return this.getKeywordHighlightRange(nodeOrComment, 'typeof');
1300  }
1301
1302  static getInstanceofUnsupportedHighlightRange(
1303    nodeOrComment: ts.Node | ts.CommentRange
1304  ): [number, number] | undefined {
1305    return this.getKeywordHighlightRange((nodeOrComment as ts.BinaryExpression).operatorToken, 'instanceof');
1306  }
1307
1308  static getConstAssertionHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1309    if (nodeOrComment.kind === ts.SyntaxKind.AsExpression) {
1310      return [
1311        (nodeOrComment as ts.AsExpression).expression.getEnd() + 1,
1312        (nodeOrComment as ts.AsExpression).type.getStart() - 1
1313      ];
1314    }
1315    return [
1316      (nodeOrComment as ts.TypeAssertion).expression.getEnd() + 1,
1317      (nodeOrComment as ts.TypeAssertion).type.getEnd() + 1
1318    ];
1319  }
1320
1321  static getLimitedReturnTypeInferenceHighlightRange(
1322    nodeOrComment: ts.Node | ts.CommentRange
1323  ): [number, number] | undefined {
1324    let node: ts.Node | undefined;
1325    if (nodeOrComment.kind === ts.SyntaxKind.FunctionExpression) {
1326      // we got error about return type so it should be present
1327      node = (nodeOrComment as ts.FunctionExpression).type;
1328    } else if (nodeOrComment.kind === ts.SyntaxKind.FunctionDeclaration) {
1329      node = (nodeOrComment as ts.FunctionDeclaration).name;
1330    } else if (nodeOrComment.kind === ts.SyntaxKind.MethodDeclaration) {
1331      node = (nodeOrComment as ts.MethodDeclaration).name;
1332    }
1333
1334    if (node !== undefined) {
1335      return [node.getStart(), node.getEnd()];
1336    }
1337
1338    return undefined;
1339  }
1340
1341  static getLocalFunctionHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1342    return this.getKeywordHighlightRange(nodeOrComment, 'function');
1343  }
1344
1345  static getFunctionApplyCallHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1346    const pointPos = (nodeOrComment as ts.Node).getText().lastIndexOf('.');
1347    return [this.getStartPos(nodeOrComment) + pointPos + 1, this.getEndPos(nodeOrComment)];
1348  }
1349
1350  static getDeclWithDuplicateNameHighlightRange(
1351    nodeOrComment: ts.Node | ts.CommentRange
1352  ): [number, number] | undefined {
1353    // in case of private identifier no range update is needed
1354    const nameNode: ts.Node | undefined = (nodeOrComment as ts.NamedDeclaration).name;
1355    if (nameNode !== undefined) {
1356      return [nameNode.getStart(), nameNode.getEnd()];
1357    }
1358
1359    return undefined;
1360  }
1361
1362  static getObjectLiteralNoContextTypeHighlightRange(
1363    nodeOrComment: ts.Node | ts.CommentRange
1364  ): [number, number] | undefined {
1365    return this.getKeywordHighlightRange(nodeOrComment, '{');
1366  }
1367
1368  static getClassExpressionHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1369    return this.getKeywordHighlightRange(nodeOrComment, 'class');
1370  }
1371
1372  static getMultipleStaticBlocksHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1373    return this.getKeywordHighlightRange(nodeOrComment, 'static');
1374  }
1375
1376  static getParameterPropertiesHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1377    const param = nodeOrComment as ts.ParameterDeclaration;
1378    const modifier = TsUtils.getAccessModifier(ts.getModifiers(param));
1379    if (modifier !== undefined) {
1380      return [modifier.getStart(), modifier.getEnd()];
1381    }
1382    return undefined;
1383  }
1384
1385  static getObjectTypeLiteralHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1386    return this.getKeywordHighlightRange(nodeOrComment, '{');
1387  }
1388
1389  // highlight ranges for Sendable rules
1390
1391  static getSendableDefiniteAssignmentHighlightRange(
1392    nodeOrComment: ts.Node | ts.CommentRange
1393  ): [number, number] | undefined {
1394    const name = (nodeOrComment as ts.PropertyDeclaration).name;
1395    const exclamationToken = (nodeOrComment as ts.PropertyDeclaration).exclamationToken;
1396    return [name.getStart(), exclamationToken ? exclamationToken.getEnd() : name.getEnd()];
1397  }
1398
1399  static getStructuralIdentityHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined {
1400    let node: ts.Node | undefined;
1401    if (nodeOrComment.kind === ts.SyntaxKind.ReturnStatement) {
1402      node = (nodeOrComment as ts.ReturnStatement).expression;
1403    } else if (nodeOrComment.kind === ts.SyntaxKind.PropertyDeclaration) {
1404      node = (nodeOrComment as ts.PropertyDeclaration).name;
1405    }
1406
1407    if (node !== undefined) {
1408      return [node.getStart(), node.getEnd()];
1409    }
1410
1411    return undefined;
1412  }
1413
1414  isStdRecordType(type: ts.Type): boolean {
1415
1416    /*
1417     * In TypeScript, 'Record<K, T>' is defined as type alias to a mapped type.
1418     * Thus, it should have 'aliasSymbol' and 'target' properties. The 'target'
1419     * in this case will resolve to origin 'Record' symbol.
1420     */
1421    if (type.aliasSymbol) {
1422      const target = (type as ts.TypeReference).target;
1423      if (target) {
1424        const sym = target.aliasSymbol;
1425        return !!sym && sym.getName() === 'Record' && this.isGlobalSymbol(sym);
1426      }
1427    }
1428
1429    return false;
1430  }
1431
1432  isStdErrorType(type: ts.Type): boolean {
1433    const symbol = type.symbol;
1434    if (!symbol) {
1435      return false;
1436    }
1437    const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
1438    return name === 'Error' && this.isGlobalSymbol(symbol);
1439  }
1440
1441  isStdPartialType(type: ts.Type): boolean {
1442    const sym = type.aliasSymbol;
1443    return !!sym && sym.getName() === 'Partial' && this.isGlobalSymbol(sym);
1444  }
1445
1446  isStdRequiredType(type: ts.Type): boolean {
1447    const sym = type.aliasSymbol;
1448    return !!sym && sym.getName() === 'Required' && this.isGlobalSymbol(sym);
1449  }
1450
1451  isStdReadonlyType(type: ts.Type): boolean {
1452    const sym = type.aliasSymbol;
1453    return !!sym && sym.getName() === 'Readonly' && this.isGlobalSymbol(sym);
1454  }
1455
1456  isLibraryType(type: ts.Type): boolean {
1457    const nonNullableType = type.getNonNullableType();
1458    if (nonNullableType.isUnion()) {
1459      for (const componentType of nonNullableType.types) {
1460        if (!this.isLibraryType(componentType)) {
1461          return false;
1462        }
1463      }
1464      return true;
1465    }
1466    return this.isLibrarySymbol(nonNullableType.aliasSymbol ?? nonNullableType.getSymbol());
1467  }
1468
1469  hasLibraryType(node: ts.Node): boolean {
1470    return this.isLibraryType(this.tsTypeChecker.getTypeAtLocation(node));
1471  }
1472
1473  isLibrarySymbol(sym: ts.Symbol | undefined): boolean {
1474    if (sym?.declarations && sym.declarations.length > 0) {
1475      const srcFile = sym.declarations[0].getSourceFile();
1476      if (!srcFile) {
1477        return false;
1478      }
1479      const fileName = srcFile.fileName;
1480
1481      /*
1482       * Symbols from both *.ts and *.d.ts files should obey interop rules.
1483       * We disable such behavior for *.ts files in the test mode due to lack of 'ets'
1484       * extension support.
1485       */
1486      const ext = path.extname(fileName).toLowerCase();
1487      const isThirdPartyCode =
1488        ARKTS_IGNORE_DIRS.some((ignore) => {
1489          return srcFilePathContainsDirectory(srcFile, ignore);
1490        }) ||
1491        ARKTS_IGNORE_FILES.some((ignore) => {
1492          return path.basename(fileName) === ignore;
1493        });
1494      const isEts = ext === '.ets';
1495      const isTs = ext === '.ts' && !srcFile.isDeclarationFile;
1496      const isStatic = (isEts || isTs && this.testMode) && !isThirdPartyCode;
1497      const isStdLib = STANDARD_LIBRARIES.includes(path.basename(fileName).toLowerCase());
1498
1499      /*
1500       * We still need to confirm support for certain API from the
1501       * TypeScript standard library in ArkTS. Thus, for now do not
1502       * count standard library modules as dynamic.
1503       */
1504      return !isStatic && !isStdLib;
1505    }
1506    return false;
1507  }
1508
1509  isDynamicType(type: ts.Type | undefined): boolean | undefined {
1510    if (type === undefined) {
1511      return false;
1512    }
1513
1514    /*
1515     * Return 'true' if it is an object of library type initialization, otherwise
1516     * return 'false' if it is not an object of standard library type one.
1517     * In the case of standard library type we need to determine context.
1518     */
1519
1520    /*
1521     * Check the non-nullable version of type to eliminate 'undefined' type
1522     * from the union type elements.
1523     */
1524    // eslint-disable-next-line no-param-reassign
1525    type = type.getNonNullableType();
1526
1527    if (type.isUnion()) {
1528      for (const compType of type.types) {
1529        const isDynamic = this.isDynamicType(compType);
1530        if (isDynamic || isDynamic === undefined) {
1531          return isDynamic;
1532        }
1533      }
1534      return false;
1535    }
1536
1537    if (this.isLibraryType(type)) {
1538      return true;
1539    }
1540
1541    if (!isStdLibraryType(type) && !isIntrinsicObjectType(type) && !TsUtils.isAnyType(type)) {
1542      return false;
1543    }
1544
1545    return undefined;
1546  }
1547
1548  static isObjectType(type: ts.Type): type is ts.ObjectType {
1549    return !!(type.flags & ts.TypeFlags.Object);
1550  }
1551
1552  private static isAnonymous(type: ts.Type): boolean {
1553    if (TsUtils.isObjectType(type)) {
1554      return !!(type.objectFlags & ts.ObjectFlags.Anonymous);
1555    }
1556    return false;
1557  }
1558
1559  private isDynamicLiteralInitializerHandleCallExpression(callExpr: ts.CallExpression): boolean {
1560    const type = this.tsTypeChecker.getTypeAtLocation(callExpr.expression);
1561
1562    if (TsUtils.isAnyType(type)) {
1563      return true;
1564    }
1565
1566    let sym: ts.Symbol | undefined = type.symbol;
1567    if (this.isLibrarySymbol(sym)) {
1568      return true;
1569    }
1570
1571    /*
1572     * #13483:
1573     * x.foo({ ... }), where 'x' is exported from some library:
1574     */
1575    if (ts.isPropertyAccessExpression(callExpr.expression)) {
1576      sym = this.trueSymbolAtLocation(callExpr.expression.expression);
1577      if (sym && this.isLibrarySymbol(sym)) {
1578        return true;
1579      }
1580    }
1581
1582    return false;
1583  }
1584
1585  isDynamicLiteralInitializer(expr: ts.Expression): boolean {
1586    if (!ts.isObjectLiteralExpression(expr) && !ts.isArrayLiteralExpression(expr)) {
1587      return false;
1588    }
1589
1590    /*
1591     * Handle nested literals:
1592     * { f: { ... } }
1593     */
1594    let curNode: ts.Node = expr;
1595    while (ts.isObjectLiteralExpression(curNode) || ts.isArrayLiteralExpression(curNode)) {
1596      const exprType = this.tsTypeChecker.getContextualType(curNode);
1597      if (exprType !== undefined && !TsUtils.isAnonymous(exprType)) {
1598        const res = this.isDynamicType(exprType);
1599        if (res !== undefined) {
1600          return res;
1601        }
1602      }
1603
1604      curNode = curNode.parent;
1605      if (ts.isPropertyAssignment(curNode)) {
1606        curNode = curNode.parent;
1607      }
1608    }
1609
1610    /*
1611     * Handle calls with literals:
1612     * foo({ ... })
1613     */
1614    if (ts.isCallExpression(curNode) && this.isDynamicLiteralInitializerHandleCallExpression(curNode)) {
1615      return true;
1616    }
1617
1618    /*
1619     * Handle property assignments with literals:
1620     * obj.f = { ... }
1621     */
1622    if (ts.isBinaryExpression(curNode)) {
1623      const binExpr = curNode;
1624      if (ts.isPropertyAccessExpression(binExpr.left)) {
1625        const propAccessExpr = binExpr.left;
1626        const type = this.tsTypeChecker.getTypeAtLocation(propAccessExpr.expression);
1627        return this.isLibrarySymbol(type.symbol);
1628      }
1629    }
1630
1631    return false;
1632  }
1633
1634  static isEsObjectType(typeNode: ts.TypeNode | undefined): boolean {
1635    return (
1636      !!typeNode &&
1637      ts.isTypeReferenceNode(typeNode) &&
1638      ts.isIdentifier(typeNode.typeName) &&
1639      typeNode.typeName.text === ES_OBJECT
1640    );
1641  }
1642
1643  static isInsideBlock(node: ts.Node): boolean {
1644    let par = node.parent;
1645    while (par) {
1646      if (ts.isBlock(par)) {
1647        return true;
1648      }
1649      par = par.parent;
1650    }
1651    return false;
1652  }
1653
1654  static isEsObjectPossiblyAllowed(typeRef: ts.TypeReferenceNode): boolean {
1655    return ts.isVariableDeclaration(typeRef.parent);
1656  }
1657
1658  isValueAssignableToESObject(node: ts.Node): boolean {
1659    if (ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node)) {
1660      return false;
1661    }
1662    const valueType = this.tsTypeChecker.getTypeAtLocation(node);
1663    return TsUtils.isUnsupportedType(valueType) || TsUtils.isAnonymousType(valueType);
1664  }
1665
1666  getVariableDeclarationTypeNode(node: ts.Node): ts.TypeNode | undefined {
1667    const sym = this.trueSymbolAtLocation(node);
1668    if (sym === undefined) {
1669      return undefined;
1670    }
1671    return TsUtils.getSymbolDeclarationTypeNode(sym);
1672  }
1673
1674  static getSymbolDeclarationTypeNode(sym: ts.Symbol): ts.TypeNode | undefined {
1675    const decl = TsUtils.getDeclaration(sym);
1676    if (!!decl && ts.isVariableDeclaration(decl)) {
1677      return decl.type;
1678    }
1679    return undefined;
1680  }
1681
1682  hasEsObjectType(node: ts.Node): boolean {
1683    const typeNode = this.getVariableDeclarationTypeNode(node);
1684    return typeNode !== undefined && TsUtils.isEsObjectType(typeNode);
1685  }
1686
1687  static symbolHasEsObjectType(sym: ts.Symbol): boolean {
1688    const typeNode = TsUtils.getSymbolDeclarationTypeNode(sym);
1689    return typeNode !== undefined && TsUtils.isEsObjectType(typeNode);
1690  }
1691
1692  static isEsObjectSymbol(sym: ts.Symbol): boolean {
1693    const decl = TsUtils.getDeclaration(sym);
1694    return (
1695      !!decl &&
1696      ts.isTypeAliasDeclaration(decl) &&
1697      decl.name.escapedText === ES_OBJECT &&
1698      decl.type.kind === ts.SyntaxKind.AnyKeyword
1699    );
1700  }
1701
1702  static isAnonymousType(type: ts.Type): boolean {
1703    if (type.isUnionOrIntersection()) {
1704      for (const compType of type.types) {
1705        if (TsUtils.isAnonymousType(compType)) {
1706          return true;
1707        }
1708      }
1709      return false;
1710    }
1711
1712    return (
1713      (type.flags & ts.TypeFlags.Object) !== 0 && ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0
1714    );
1715  }
1716
1717  getSymbolOfCallExpression(callExpr: ts.CallExpression): ts.Symbol | undefined {
1718    const signature = this.tsTypeChecker.getResolvedSignature(callExpr);
1719    const signDecl = signature?.getDeclaration();
1720    if (signDecl?.name) {
1721      return this.trueSymbolAtLocation(signDecl.name);
1722    }
1723    return undefined;
1724  }
1725
1726  static isClassValueType(type: ts.Type): boolean {
1727    if (
1728      (type.flags & ts.TypeFlags.Object) === 0 ||
1729      ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) === 0
1730    ) {
1731      return false;
1732    }
1733    return type.symbol && (type.symbol.flags & ts.SymbolFlags.Class) !== 0;
1734  }
1735
1736  isClassObjectExpression(expr: ts.Expression): boolean {
1737    if (!TsUtils.isClassValueType(this.tsTypeChecker.getTypeAtLocation(expr))) {
1738      return false;
1739    }
1740    const symbol = this.trueSymbolAtLocation(expr);
1741    return !symbol || (symbol.flags & ts.SymbolFlags.Class) === 0;
1742  }
1743
1744  isClassTypeExpression(expr: ts.Expression): boolean {
1745    const sym = this.trueSymbolAtLocation(expr);
1746    return sym !== undefined && (sym.flags & ts.SymbolFlags.Class) !== 0;
1747  }
1748
1749  isFunctionCalledRecursively(funcExpr: ts.FunctionExpression): boolean {
1750    if (!funcExpr.name) {
1751      return false;
1752    }
1753
1754    const sym = this.tsTypeChecker.getSymbolAtLocation(funcExpr.name);
1755    if (!sym) {
1756      return false;
1757    }
1758
1759    let found = false;
1760    const callback = (node: ts.Node): void => {
1761      if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
1762        const callSym = this.tsTypeChecker.getSymbolAtLocation(node.expression);
1763        if (callSym && callSym === sym) {
1764          found = true;
1765        }
1766      }
1767    };
1768
1769    const stopCondition = (node: ts.Node): boolean => {
1770      void node;
1771      return found;
1772    };
1773
1774    forEachNodeInSubtree(funcExpr, callback, stopCondition);
1775    return found;
1776  }
1777
1778  getTypeOrTypeConstraintAtLocation(expr: ts.Expression): ts.Type {
1779    const type = this.tsTypeChecker.getTypeAtLocation(expr);
1780    if (type.isTypeParameter()) {
1781      const constraint = type.getConstraint();
1782      if (constraint) {
1783        return constraint;
1784      }
1785    }
1786    return type;
1787  }
1788
1789  private areCompatibleFunctionals(lhsType: ts.Type, rhsType: ts.Type): boolean {
1790    return (
1791      (this.isStdFunctionType(lhsType) || TsUtils.isFunctionalType(lhsType)) &&
1792      (this.isStdFunctionType(rhsType) || TsUtils.isFunctionalType(rhsType))
1793    );
1794  }
1795
1796  private static isFunctionalType(type: ts.Type): boolean {
1797    const callSigns = type.getCallSignatures();
1798    return callSigns && callSigns.length > 0;
1799  }
1800
1801  private isStdFunctionType(type: ts.Type): boolean {
1802    const sym = type.getSymbol();
1803    return !!sym && sym.getName() === 'Function' && this.isGlobalSymbol(sym);
1804  }
1805
1806  isStdBigIntType(type: ts.Type): boolean {
1807    const sym = type.symbol;
1808    return !!sym && sym.getName() === 'BigInt' && this.isGlobalSymbol(sym);
1809  }
1810
1811  isStdNumberType(type: ts.Type): boolean {
1812    const sym = type.symbol;
1813    return !!sym && sym.getName() === 'Number' && this.isGlobalSymbol(sym);
1814  }
1815
1816  isStdBooleanType(type: ts.Type): boolean {
1817    const sym = type.symbol;
1818    return !!sym && sym.getName() === 'Boolean' && this.isGlobalSymbol(sym);
1819  }
1820
1821  isEnumStringLiteral(expr: ts.Expression): boolean {
1822    const symbol = this.trueSymbolAtLocation(expr);
1823    const isEnumMember = !!symbol && !!(symbol.flags & ts.SymbolFlags.EnumMember);
1824    const type = this.tsTypeChecker.getTypeAtLocation(expr);
1825    const isStringEnumLiteral = TsUtils.isEnumType(type) && !!(type.flags & ts.TypeFlags.StringLiteral);
1826    return isEnumMember && isStringEnumLiteral;
1827  }
1828
1829  isValidComputedPropertyName(computedProperty: ts.ComputedPropertyName, isRecordObjectInitializer = false): boolean {
1830    const expr = computedProperty.expression;
1831    if (!isRecordObjectInitializer) {
1832      if (this.isSymbolIteratorExpression(expr)) {
1833        return true;
1834      }
1835    }
1836    // We allow computed property names if expression is string literal or string Enum member
1837    return ts.isStringLiteralLike(expr) || this.isEnumStringLiteral(computedProperty.expression);
1838  }
1839
1840  skipPropertyInferredTypeCheck(
1841    decl: ts.PropertyDeclaration,
1842    sourceFile: ts.SourceFile | undefined,
1843    isEtsFileCb: IsEtsFileCallback | undefined
1844  ): boolean {
1845    if (!sourceFile) {
1846      return false;
1847    }
1848
1849    const isEts = this.useSdkLogic ?
1850      getScriptKind(sourceFile) === ts.ScriptKind.ETS :
1851      !!isEtsFileCb && isEtsFileCb(sourceFile);
1852    return (
1853      isEts &&
1854      sourceFile.isDeclarationFile &&
1855      !!decl.modifiers?.some((m) => {
1856        return m.kind === ts.SyntaxKind.PrivateKeyword;
1857      })
1858    );
1859  }
1860
1861  hasAccessModifier(decl: ts.HasModifiers): boolean {
1862    const modifiers = ts.getModifiers(decl);
1863    return (
1864      !!modifiers &&
1865      (this.useSdkLogic && TsUtils.hasModifier(modifiers, ts.SyntaxKind.ReadonlyKeyword) ||
1866        TsUtils.hasModifier(modifiers, ts.SyntaxKind.PublicKeyword) ||
1867        TsUtils.hasModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) ||
1868        TsUtils.hasModifier(modifiers, ts.SyntaxKind.PrivateKeyword))
1869    );
1870  }
1871
1872  static getModifier(
1873    modifiers: readonly ts.Modifier[] | undefined,
1874    modifierKind: ts.SyntaxKind
1875  ): ts.Modifier | undefined {
1876    if (!modifiers) {
1877      return undefined;
1878    }
1879    return modifiers.find((x) => {
1880      return x.kind === modifierKind;
1881    });
1882  }
1883
1884  static getAccessModifier(modifiers: readonly ts.Modifier[] | undefined): ts.Modifier | undefined {
1885    return (
1886      TsUtils.getModifier(modifiers, ts.SyntaxKind.PublicKeyword) ??
1887      TsUtils.getModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) ??
1888      TsUtils.getModifier(modifiers, ts.SyntaxKind.PrivateKeyword)
1889    );
1890  }
1891
1892  static getBaseClassType(type: ts.Type): ts.InterfaceType | undefined {
1893    const baseTypes = type.getBaseTypes();
1894    if (baseTypes) {
1895      for (const baseType of baseTypes) {
1896        if (baseType.isClass()) {
1897          return baseType;
1898        }
1899      }
1900    }
1901
1902    return undefined;
1903  }
1904
1905  static destructuringAssignmentHasSpreadOperator(node: ts.AssignmentPattern): boolean {
1906    if (ts.isArrayLiteralExpression(node)) {
1907      return node.elements.some((x) => {
1908        if (ts.isSpreadElement(x)) {
1909          return true;
1910        }
1911        if (ts.isObjectLiteralExpression(x) || ts.isArrayLiteralExpression(x)) {
1912          return TsUtils.destructuringAssignmentHasSpreadOperator(x);
1913        }
1914        return false;
1915      });
1916    }
1917
1918    return node.properties.some((x) => {
1919      if (ts.isSpreadAssignment(x)) {
1920        return true;
1921      }
1922      if (
1923        ts.isPropertyAssignment(x) &&
1924        (ts.isObjectLiteralExpression(x.initializer) || ts.isArrayLiteralExpression(x.initializer))
1925      ) {
1926        return TsUtils.destructuringAssignmentHasSpreadOperator(x.initializer);
1927      }
1928      return false;
1929    });
1930  }
1931
1932  static destructuringDeclarationHasSpreadOperator(node: ts.BindingPattern): boolean {
1933    return node.elements.some((x) => {
1934      if (ts.isBindingElement(x)) {
1935        if (x.dotDotDotToken) {
1936          return true;
1937        }
1938        if (ts.isArrayBindingPattern(x.name) || ts.isObjectBindingPattern(x.name)) {
1939          return TsUtils.destructuringDeclarationHasSpreadOperator(x.name);
1940        }
1941      }
1942      return false;
1943    });
1944  }
1945
1946  static hasNestedObjectDestructuring(node: ts.ArrayBindingOrAssignmentPattern): boolean {
1947    if (ts.isArrayLiteralExpression(node)) {
1948      return node.elements.some((x) => {
1949        const elem = ts.isSpreadElement(x) ? x.expression : x;
1950        if (ts.isArrayLiteralExpression(elem)) {
1951          return TsUtils.hasNestedObjectDestructuring(elem);
1952        }
1953        return ts.isObjectLiteralExpression(elem);
1954      });
1955    }
1956
1957    return node.elements.some((x) => {
1958      if (ts.isBindingElement(x)) {
1959        if (ts.isArrayBindingPattern(x.name)) {
1960          return TsUtils.hasNestedObjectDestructuring(x.name);
1961        }
1962        return ts.isObjectBindingPattern(x.name);
1963      }
1964      return false;
1965    });
1966  }
1967
1968  static getDecoratorName(decorator: ts.Decorator): string {
1969    let decoratorName = '';
1970    if (ts.isIdentifier(decorator.expression)) {
1971      decoratorName = decorator.expression.text;
1972    } else if (ts.isCallExpression(decorator.expression) && ts.isIdentifier(decorator.expression.expression)) {
1973      decoratorName = decorator.expression.expression.text;
1974    }
1975    return decoratorName;
1976  }
1977
1978  static unwrapParenthesizedTypeNode(typeNode: ts.TypeNode): ts.TypeNode {
1979    let unwrappedTypeNode = typeNode;
1980    while (ts.isParenthesizedTypeNode(unwrappedTypeNode)) {
1981      unwrappedTypeNode = unwrappedTypeNode.type;
1982    }
1983
1984    return unwrappedTypeNode;
1985  }
1986
1987  isSendableTypeNode(typeNode: ts.TypeNode, isShared: boolean = false): boolean {
1988
1989    /*
1990     * In order to correctly identify the usage of the enum member or
1991     * const enum in type annotation, we need to handle union type and
1992     * type alias cases by processing the type node and checking the
1993     * symbol in case of type reference node.
1994     */
1995
1996    // eslint-disable-next-line no-param-reassign
1997    typeNode = TsUtils.unwrapParenthesizedTypeNode(typeNode);
1998
1999    // Only a sendable union type is supported
2000    if (ts.isUnionTypeNode(typeNode)) {
2001      return typeNode.types.every((elemType) => {
2002        return this.isSendableTypeNode(elemType, isShared);
2003      });
2004    }
2005
2006    const sym = ts.isTypeReferenceNode(typeNode) ? this.trueSymbolAtLocation(typeNode.typeName) : undefined;
2007
2008    if (sym && sym.getFlags() & ts.SymbolFlags.TypeAlias) {
2009      const typeDecl = TsUtils.getDeclaration(sym);
2010      if (typeDecl && ts.isTypeAliasDeclaration(typeDecl)) {
2011        const typeArgs = (typeNode as ts.TypeReferenceNode).typeArguments;
2012        if (
2013          typeArgs &&
2014          !typeArgs.every((typeArg) => {
2015            return this.isSendableTypeNode(typeArg);
2016          })
2017        ) {
2018          return false;
2019        }
2020        return this.isSendableTypeNode(typeDecl.type, isShared);
2021      }
2022    }
2023
2024    // Const enum type is supported
2025    if (TsUtils.isConstEnum(sym)) {
2026      return true;
2027    }
2028    const type: ts.Type = this.tsTypeChecker.getTypeFromTypeNode(typeNode);
2029
2030    // In shared module, literal forms of primitive data types can be exported
2031    if (isShared && TsUtils.isPurePrimitiveLiteralType(type)) {
2032      return true;
2033    }
2034
2035    return this.isSendableType(type);
2036  }
2037
2038  isSendableType(type: ts.Type): boolean {
2039    if (
2040      (type.flags &
2041        (ts.TypeFlags.Boolean |
2042          ts.TypeFlags.Number |
2043          ts.TypeFlags.String |
2044          ts.TypeFlags.BigInt |
2045          ts.TypeFlags.Null |
2046          ts.TypeFlags.Undefined |
2047          ts.TypeFlags.TypeParameter)) !==
2048      0
2049    ) {
2050      return true;
2051    }
2052    if (this.isSendableTypeAlias(type)) {
2053      return true;
2054    }
2055    if (TsUtils.isSendableFunction(type)) {
2056      return true;
2057    }
2058
2059    return this.isSendableClassOrInterface(type);
2060  }
2061
2062  isShareableType(tsType: ts.Type): boolean {
2063    const sym = tsType.getSymbol();
2064    if (TsUtils.isConstEnum(sym)) {
2065      return true;
2066    }
2067
2068    if (tsType.isUnion()) {
2069      return tsType.types.every((elemType) => {
2070        return this.isShareableType(elemType);
2071      });
2072    }
2073
2074    if (TsUtils.isPurePrimitiveLiteralType(tsType)) {
2075      return true;
2076    }
2077
2078    return this.isSendableType(tsType);
2079  }
2080
2081  isSendableClassOrInterface(type: ts.Type): boolean {
2082    const sym = type.getSymbol();
2083    if (!sym) {
2084      return false;
2085    }
2086
2087    const targetType = TsUtils.reduceReference(type);
2088
2089    // class with @Sendable decorator
2090    if (targetType.isClass()) {
2091      if (sym.declarations?.length) {
2092        const decl = sym.declarations[0];
2093        if (ts.isClassDeclaration(decl)) {
2094          return TsUtils.hasSendableDecorator(decl);
2095        }
2096      }
2097    }
2098    // ISendable interface, or a class/interface that implements/extends ISendable interface
2099    return this.isOrDerivedFrom(type, TsUtils.isISendableInterface);
2100  }
2101
2102  typeContainsSendableClassOrInterface(type: ts.Type): boolean {
2103    // Only check type contains sendable class / interface
2104    if ((type.flags & ts.TypeFlags.Union) !== 0) {
2105      return !!(type as ts.UnionType)?.types?.some((type) => {
2106        return this.typeContainsSendableClassOrInterface(type);
2107      });
2108    }
2109
2110    return this.isSendableClassOrInterface(type);
2111  }
2112
2113  typeContainsNonSendableClassOrInterface(type: ts.Type): boolean {
2114    if (type.isUnion()) {
2115      return type.types.some((compType) => {
2116        return this.typeContainsNonSendableClassOrInterface(compType);
2117      });
2118    }
2119    // eslint-disable-next-line no-param-reassign
2120    type = TsUtils.reduceReference(type);
2121    return type.isClassOrInterface() && !this.isSendableClassOrInterface(type);
2122  }
2123
2124  static isConstEnum(sym: ts.Symbol | undefined): boolean {
2125    return !!sym && sym.flags === ts.SymbolFlags.ConstEnum;
2126  }
2127
2128  isSendableUnionType(type: ts.UnionType): boolean {
2129    const types = type?.types;
2130    if (!types) {
2131      return false;
2132    }
2133
2134    return types.every((type) => {
2135      return this.isSendableType(type);
2136    });
2137  }
2138
2139  static hasSendableDecorator(decl: ts.ClassDeclaration | ts.FunctionDeclaration | ts.TypeAliasDeclaration): boolean {
2140    return !!TsUtils.getSendableDecorator(decl);
2141  }
2142
2143  static getNonSendableDecorators(
2144    decl: ts.ClassDeclaration | ts.FunctionDeclaration | ts.TypeAliasDeclaration
2145  ): ts.Decorator[] | undefined {
2146    const decorators = ts.getAllDecorators(decl);
2147    return decorators?.filter((x) => {
2148      return TsUtils.getDecoratorName(x) !== SENDABLE_DECORATOR;
2149    });
2150  }
2151
2152  static getSendableDecorator(
2153    decl: ts.ClassDeclaration | ts.FunctionDeclaration | ts.TypeAliasDeclaration
2154  ): ts.Decorator | undefined {
2155    const decorators = ts.getAllDecorators(decl);
2156    return decorators?.find((x) => {
2157      return TsUtils.getDecoratorName(x) === SENDABLE_DECORATOR;
2158    });
2159  }
2160
2161  static getDecoratorsIfInSendableClass(declaration: ts.HasDecorators): readonly ts.Decorator[] | undefined {
2162    const classNode = TsUtils.getClassNodeFromDeclaration(declaration);
2163    if (classNode === undefined || !TsUtils.hasSendableDecorator(classNode)) {
2164      return undefined;
2165    }
2166    return ts.getDecorators(declaration);
2167  }
2168
2169  private static getClassNodeFromDeclaration(declaration: ts.HasDecorators): ts.ClassDeclaration | undefined {
2170    if (declaration.kind === ts.SyntaxKind.Parameter) {
2171      return ts.isClassDeclaration(declaration.parent.parent) ? declaration.parent.parent : undefined;
2172    }
2173    return ts.isClassDeclaration(declaration.parent) ? declaration.parent : undefined;
2174  }
2175
2176  static isISendableInterface(type: ts.Type): boolean {
2177    const symbol = type.aliasSymbol ?? type.getSymbol();
2178    if (symbol?.declarations === undefined || symbol.declarations.length < 1) {
2179      return false;
2180    }
2181
2182    return TsUtils.isArkTSISendableDeclaration(symbol.declarations[0]);
2183  }
2184
2185  private static isArkTSISendableDeclaration(decl: ts.Declaration): boolean {
2186    if (!ts.isInterfaceDeclaration(decl) || !decl.name || decl.name.text !== ISENDABLE_TYPE) {
2187      return false;
2188    }
2189
2190    if (!ts.isModuleBlock(decl.parent) || decl.parent.parent.name.text !== LANG_NAMESPACE) {
2191      return false;
2192    }
2193
2194    if (path.basename(decl.getSourceFile().fileName).toLowerCase() !== ARKTS_LANG_D_ETS) {
2195      return false;
2196    }
2197
2198    return true;
2199  }
2200
2201  isAllowedIndexSignature(node: ts.IndexSignatureDeclaration): boolean {
2202
2203    /*
2204     * For now, relax index signature only for specific array-like types
2205     * with the following signature: 'collections.Array<T>.[_: number]: T'.
2206     */
2207
2208    if (node.parameters.length !== 1) {
2209      return false;
2210    }
2211
2212    const paramType = this.tsTypeChecker.getTypeAtLocation(node.parameters[0]);
2213    if ((paramType.flags & ts.TypeFlags.Number) === 0) {
2214      return false;
2215    }
2216
2217    return this.isArkTSCollectionsArrayLikeDeclaration(node.parent);
2218  }
2219
2220  isArkTSCollectionsArrayLikeType(type: ts.Type): boolean {
2221    const symbol = type.aliasSymbol ?? type.getSymbol();
2222    if (symbol?.declarations === undefined || symbol.declarations.length < 1) {
2223      return false;
2224    }
2225
2226    return this.isArkTSCollectionsArrayLikeDeclaration(symbol.declarations[0]);
2227  }
2228
2229  private isArkTSCollectionsArrayLikeDeclaration(decl: ts.Declaration): boolean {
2230    if (!TsUtils.isArkTSCollectionsClassOrInterfaceDeclaration(decl)) {
2231      return false;
2232    }
2233    if (!this.tsTypeChecker.getTypeAtLocation(decl).getNumberIndexType()) {
2234      return false;
2235    }
2236    return true;
2237  }
2238
2239  static isArkTSCollectionsClassOrInterfaceDeclaration(decl: ts.Node): boolean {
2240    if (!ts.isClassDeclaration(decl) && !ts.isInterfaceDeclaration(decl) || !decl.name) {
2241      return false;
2242    }
2243    if (!ts.isModuleBlock(decl.parent) || decl.parent.parent.name.text !== COLLECTIONS_NAMESPACE) {
2244      return false;
2245    }
2246    if (path.basename(decl.getSourceFile().fileName).toLowerCase() !== ARKTS_COLLECTIONS_D_ETS) {
2247      return false;
2248    }
2249    return true;
2250  }
2251
2252  private proceedConstructorDeclaration(
2253    isFromPrivateIdentifierOrSdk: boolean,
2254    targetMember: ts.ClassElement,
2255    classMember: ts.ClassElement,
2256    isFromPrivateIdentifier: boolean
2257  ): boolean | undefined {
2258    if (
2259      isFromPrivateIdentifierOrSdk &&
2260      ts.isConstructorDeclaration(classMember) &&
2261      classMember.parameters.some((x) => {
2262        return (
2263          ts.isIdentifier(x.name) &&
2264          this.hasAccessModifier(x) &&
2265          this.isPrivateIdentifierDuplicateOfIdentifier(
2266            targetMember.name as ts.Identifier,
2267            x.name,
2268            isFromPrivateIdentifier
2269          )
2270        );
2271      })
2272    ) {
2273      return true;
2274    }
2275    return undefined;
2276  }
2277
2278  private proceedClassType(
2279    targetMember: ts.ClassElement,
2280    classType: ts.Type,
2281    isFromPrivateIdentifier: boolean
2282  ): boolean | undefined {
2283    if (classType) {
2284      const baseType = TsUtils.getBaseClassType(classType);
2285      if (baseType) {
2286        const baseDecl = baseType.getSymbol()?.valueDeclaration as ts.ClassLikeDeclaration;
2287        if (baseDecl) {
2288          return this.classMemberHasDuplicateName(targetMember, baseDecl, isFromPrivateIdentifier);
2289        }
2290      }
2291    }
2292    return undefined;
2293  }
2294
2295  classMemberHasDuplicateName(
2296    targetMember: ts.ClassElement,
2297    tsClassLikeDecl: ts.ClassLikeDeclaration,
2298    isFromPrivateIdentifier: boolean,
2299    classType?: ts.Type
2300  ): boolean {
2301
2302    /*
2303     * If two class members have the same name where one is a private identifer,
2304     * then such members are considered to have duplicate names.
2305     */
2306    if (!TsUtils.isIdentifierOrPrivateIdentifier(targetMember.name)) {
2307      return false;
2308    }
2309
2310    const isFromPrivateIdentifierOrSdk = this.isFromPrivateIdentifierOrSdk(isFromPrivateIdentifier);
2311    for (const classMember of tsClassLikeDecl.members) {
2312      if (targetMember === classMember) {
2313        continue;
2314      }
2315
2316      // Check constructor parameter properties.
2317      const constructorDeclarationProceedResult = this.proceedConstructorDeclaration(
2318        isFromPrivateIdentifierOrSdk,
2319        targetMember,
2320        classMember,
2321        isFromPrivateIdentifier
2322      );
2323      if (constructorDeclarationProceedResult) {
2324        return constructorDeclarationProceedResult;
2325      }
2326      if (!TsUtils.isIdentifierOrPrivateIdentifier(classMember.name)) {
2327        continue;
2328      }
2329      if (this.isPrivateIdentifierDuplicateOfIdentifier(targetMember.name, classMember.name, isFromPrivateIdentifier)) {
2330        return true;
2331      }
2332    }
2333
2334    if (isFromPrivateIdentifierOrSdk) {
2335      // eslint-disable-next-line no-param-reassign
2336      classType ??= this.tsTypeChecker.getTypeAtLocation(tsClassLikeDecl);
2337      const proceedClassTypeResult = this.proceedClassType(targetMember, classType, isFromPrivateIdentifier);
2338      if (proceedClassTypeResult) {
2339        return proceedClassTypeResult;
2340      }
2341    }
2342
2343    return false;
2344  }
2345
2346  private isFromPrivateIdentifierOrSdk(isFromPrivateIdentifier: boolean): boolean {
2347    return !this.useSdkLogic || isFromPrivateIdentifier;
2348  }
2349
2350  private static isIdentifierOrPrivateIdentifier(node?: ts.PropertyName): node is ts.Identifier | ts.PrivateIdentifier {
2351    if (!node) {
2352      return false;
2353    }
2354    return ts.isIdentifier(node) || ts.isPrivateIdentifier(node);
2355  }
2356
2357  private isPrivateIdentifierDuplicateOfIdentifier(
2358    ident1: ts.Identifier | ts.PrivateIdentifier,
2359    ident2: ts.Identifier | ts.PrivateIdentifier,
2360    isFromPrivateIdentifier: boolean
2361  ): boolean {
2362    if (ts.isIdentifier(ident1) && ts.isPrivateIdentifier(ident2)) {
2363      return ident1.text === ident2.text.substring(1);
2364    }
2365    if (ts.isIdentifier(ident2) && ts.isPrivateIdentifier(ident1)) {
2366      return ident2.text === ident1.text.substring(1);
2367    }
2368    if (
2369      this.isFromPrivateIdentifierOrSdk(isFromPrivateIdentifier) &&
2370      ts.isPrivateIdentifier(ident1) &&
2371      ts.isPrivateIdentifier(ident2)
2372    ) {
2373      return ident1.text.substring(1) === ident2.text.substring(1);
2374    }
2375    return false;
2376  }
2377
2378  findIdentifierNameForSymbol(symbol: ts.Symbol): string | undefined {
2379    let name = TsUtils.getIdentifierNameFromString(symbol.name);
2380    if (name === undefined || name === symbol.name) {
2381      return name;
2382    }
2383
2384    const parentType = this.getTypeByProperty(symbol);
2385    if (parentType === undefined) {
2386      return undefined;
2387    }
2388
2389    while (this.findProperty(parentType, name) !== undefined) {
2390      name = '_' + name;
2391    }
2392
2393    return name;
2394  }
2395
2396  private static getIdentifierNameFromString(str: string): string | undefined {
2397    let result: string = '';
2398
2399    let offset = 0;
2400    while (offset < str.length) {
2401      const codePoint = str.codePointAt(offset);
2402      if (!codePoint) {
2403        return undefined;
2404      }
2405
2406      const charSize = TsUtils.charSize(codePoint);
2407
2408      if (offset === 0 && !ts.isIdentifierStart(codePoint, undefined)) {
2409        result = '__';
2410      }
2411
2412      if (!ts.isIdentifierPart(codePoint, undefined)) {
2413        if (codePoint === 0x20) {
2414          result += '_';
2415        } else {
2416          result += 'x' + codePoint.toString(16);
2417        }
2418      } else {
2419        for (let i = 0; i < charSize; i++) {
2420          result += str.charAt(offset + i);
2421        }
2422      }
2423
2424      offset += charSize;
2425    }
2426
2427    return result;
2428  }
2429
2430  private static charSize(codePoint: number): number {
2431    return codePoint >= 0x10000 ? 2 : 1;
2432  }
2433
2434  private getTypeByProperty(symbol: ts.Symbol): ts.Type | undefined {
2435    if (symbol.declarations === undefined) {
2436      return undefined;
2437    }
2438
2439    for (const propDecl of symbol.declarations) {
2440      if (
2441        !ts.isPropertyDeclaration(propDecl) &&
2442        !ts.isPropertyAssignment(propDecl) &&
2443        !ts.isPropertySignature(propDecl)
2444      ) {
2445        return undefined;
2446      }
2447
2448      const type = this.tsTypeChecker.getTypeAtLocation(propDecl.parent);
2449      if (type !== undefined) {
2450        return type;
2451      }
2452    }
2453
2454    return undefined;
2455  }
2456
2457  static isPropertyOfInternalClassOrInterface(symbol: ts.Symbol): boolean {
2458    if (symbol.declarations === undefined) {
2459      return false;
2460    }
2461
2462    for (const propDecl of symbol.declarations) {
2463      if (!ts.isPropertyDeclaration(propDecl) && !ts.isPropertySignature(propDecl)) {
2464        return false;
2465      }
2466
2467      if (!ts.isClassDeclaration(propDecl.parent) && !ts.isInterfaceDeclaration(propDecl.parent)) {
2468        return false;
2469      }
2470
2471      if (TsUtils.hasModifier(ts.getModifiers(propDecl.parent), ts.SyntaxKind.ExportKeyword)) {
2472        return false;
2473      }
2474    }
2475
2476    return true;
2477  }
2478
2479  static isIntrinsicObjectType(type: ts.Type): boolean {
2480    return !!(type.flags & ts.TypeFlags.NonPrimitive);
2481  }
2482
2483  isStringType(tsType: ts.Type): boolean {
2484    if ((tsType.getFlags() & ts.TypeFlags.String) !== 0) {
2485      return true;
2486    }
2487
2488    if (!TsUtils.isTypeReference(tsType)) {
2489      return false;
2490    }
2491
2492    const symbol = tsType.symbol;
2493    const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
2494    return name === 'String' && this.isGlobalSymbol(symbol);
2495  }
2496
2497  isStdMapType(type: ts.Type): boolean {
2498    const sym = type.symbol;
2499    return !!sym && sym.getName() === 'Map' && this.isGlobalSymbol(sym);
2500  }
2501
2502  hasGenericTypeParameter(type: ts.Type): boolean {
2503    if (type.isUnionOrIntersection()) {
2504      return type.types.some((x) => {
2505        return this.hasGenericTypeParameter(x);
2506      });
2507    }
2508    if (TsUtils.isTypeReference(type)) {
2509      const typeArgs = this.tsTypeChecker.getTypeArguments(type);
2510      return typeArgs.some((x) => {
2511        return this.hasGenericTypeParameter(x);
2512      });
2513    }
2514    return type.isTypeParameter();
2515  }
2516
2517  static getEnclosingTopLevelStatement(node: ts.Node): ts.Node | undefined {
2518    return ts.findAncestor(node, (ancestor) => {
2519      return ts.isSourceFile(ancestor.parent);
2520    });
2521  }
2522
2523  static isDeclarationStatement(node: ts.Node): node is ts.DeclarationStatement {
2524    const kind = node.kind;
2525    return (
2526      kind === ts.SyntaxKind.FunctionDeclaration ||
2527      kind === ts.SyntaxKind.ModuleDeclaration ||
2528      kind === ts.SyntaxKind.ClassDeclaration ||
2529      kind === ts.SyntaxKind.StructDeclaration ||
2530      kind === ts.SyntaxKind.TypeAliasDeclaration ||
2531      kind === ts.SyntaxKind.InterfaceDeclaration ||
2532      kind === ts.SyntaxKind.EnumDeclaration ||
2533      kind === ts.SyntaxKind.MissingDeclaration ||
2534      kind === ts.SyntaxKind.ImportEqualsDeclaration ||
2535      kind === ts.SyntaxKind.ImportDeclaration ||
2536      kind === ts.SyntaxKind.NamespaceExportDeclaration
2537    );
2538  }
2539
2540  static declarationNameExists(srcFile: ts.SourceFile, name: string): boolean {
2541    return srcFile.statements.some((stmt) => {
2542      if (!ts.isImportDeclaration(stmt)) {
2543        return (
2544          TsUtils.isDeclarationStatement(stmt) &&
2545          stmt.name !== undefined &&
2546          ts.isIdentifier(stmt.name) &&
2547          stmt.name.text === name
2548        );
2549      }
2550
2551      if (!stmt.importClause) {
2552        return false;
2553      }
2554
2555      if (!stmt.importClause.namedBindings) {
2556        return stmt.importClause.name?.text === name;
2557      }
2558
2559      if (ts.isNamespaceImport(stmt.importClause.namedBindings)) {
2560        return stmt.importClause.namedBindings.name.text === name;
2561      }
2562      return stmt.importClause.namedBindings.elements.some((x) => {
2563        return x.name.text === name;
2564      });
2565    });
2566  }
2567
2568  static generateUniqueName(nameGenerator: NameGenerator, srcFile: ts.SourceFile): string | undefined {
2569    let newName: string | undefined;
2570
2571    do {
2572      newName = nameGenerator.getName();
2573      if (newName !== undefined && TsUtils.declarationNameExists(srcFile, newName)) {
2574        continue;
2575      }
2576      break;
2577    } while (newName !== undefined);
2578
2579    return newName;
2580  }
2581
2582  static isSharedModule(sourceFile: ts.SourceFile): boolean {
2583    const statements = sourceFile.statements;
2584    for (const statement of statements) {
2585      if (ts.isImportDeclaration(statement)) {
2586        continue;
2587      }
2588
2589      return (
2590        ts.isExpressionStatement(statement) &&
2591        ts.isStringLiteral(statement.expression) &&
2592        statement.expression.text === USE_SHARED
2593      );
2594    }
2595    return false;
2596  }
2597
2598  getDeclarationNode(node: ts.Node): ts.Declaration | undefined {
2599    const sym = this.trueSymbolAtLocation(node);
2600    return TsUtils.getDeclaration(sym);
2601  }
2602
2603  static isFunctionLikeDeclaration(node: ts.Declaration): boolean {
2604    return (
2605      ts.isFunctionDeclaration(node) ||
2606      ts.isMethodDeclaration(node) ||
2607      ts.isGetAccessorDeclaration(node) ||
2608      ts.isSetAccessorDeclaration(node) ||
2609      ts.isConstructorDeclaration(node) ||
2610      ts.isFunctionExpression(node) ||
2611      ts.isArrowFunction(node)
2612    );
2613  }
2614
2615  isShareableEntity(node: ts.Node): boolean {
2616    const decl = this.getDeclarationNode(node);
2617    const typeNode = (decl as any)?.type;
2618    return typeNode && !TsUtils.isFunctionLikeDeclaration(decl!) ?
2619      this.isSendableTypeNode(typeNode, true) :
2620      this.isShareableType(this.tsTypeChecker.getTypeAtLocation(decl ? decl : node));
2621  }
2622
2623  isSendableClassOrInterfaceEntity(node: ts.Node): boolean {
2624    const decl = this.getDeclarationNode(node);
2625    if (!decl) {
2626      return false;
2627    }
2628    if (ts.isClassDeclaration(decl)) {
2629      return TsUtils.hasSendableDecorator(decl);
2630    }
2631    if (ts.isInterfaceDeclaration(decl)) {
2632      return this.isOrDerivedFrom(this.tsTypeChecker.getTypeAtLocation(decl), TsUtils.isISendableInterface);
2633    }
2634    return false;
2635  }
2636
2637  static isInImportWhiteList(resolvedModule: ts.ResolvedModuleFull): boolean {
2638    if (
2639      !resolvedModule.resolvedFileName ||
2640      path.basename(resolvedModule.resolvedFileName).toLowerCase() !== ARKTS_LANG_D_ETS &&
2641        path.basename(resolvedModule.resolvedFileName).toLowerCase() !== ARKTS_COLLECTIONS_D_ETS
2642    ) {
2643      return false;
2644    }
2645    return true;
2646  }
2647
2648  // If it is an overloaded function, all declarations for that function are found
2649  static hasSendableDecoratorFunctionOverload(decl: ts.FunctionDeclaration): boolean {
2650    const decorators = TsUtils.getFunctionOverloadDecorators(decl);
2651    return !!decorators?.some((x) => {
2652      return TsUtils.getDecoratorName(x) === SENDABLE_DECORATOR;
2653    });
2654  }
2655
2656  static getFunctionOverloadDecorators(funcDecl: ts.FunctionDeclaration): readonly ts.Decorator[] | undefined {
2657    const decls = funcDecl.symbol.getDeclarations();
2658    if (!decls?.length) {
2659      return undefined;
2660    }
2661    let result: ts.Decorator[] = [];
2662    decls.forEach((decl) => {
2663      if (!ts.isFunctionDeclaration(decl)) {
2664        return;
2665      }
2666      const decorators = ts.getAllDecorators(decl);
2667      if (decorators?.length) {
2668        result = result.concat(decorators);
2669      }
2670    });
2671    return result.length ? result : undefined;
2672  }
2673
2674  static isSendableFunction(type: ts.Type): boolean {
2675    const callSigns = type.getCallSignatures();
2676    if (!callSigns?.length) {
2677      return false;
2678    }
2679    const decl = callSigns[0].declaration;
2680    if (!decl || !ts.isFunctionDeclaration(decl)) {
2681      return false;
2682    }
2683    return TsUtils.hasSendableDecoratorFunctionOverload(decl);
2684  }
2685
2686  isSendableTypeAlias(type: ts.Type): boolean {
2687    const decl = this.getTypsAliasOriginalDecl(type);
2688    return !!decl && TsUtils.hasSendableDecorator(decl);
2689  }
2690
2691  hasSendableTypeAlias(type: ts.Type): boolean {
2692    if (type.isUnion()) {
2693      return type.types.some((compType) => {
2694        return this.hasSendableTypeAlias(compType);
2695      });
2696    }
2697    return this.isSendableTypeAlias(type);
2698  }
2699
2700  isNonSendableFunctionTypeAlias(type: ts.Type): boolean {
2701    const decl = this.getTypsAliasOriginalDecl(type);
2702    return !!decl && ts.isFunctionTypeNode(decl.type) && !TsUtils.hasSendableDecorator(decl);
2703  }
2704
2705  // If the alias refers to another alias, the search continues
2706  private getTypsAliasOriginalDecl(type: ts.Type): ts.TypeAliasDeclaration | undefined {
2707    if (!type.aliasSymbol) {
2708      return undefined;
2709    }
2710    const decl = TsUtils.getDeclaration(type.aliasSymbol);
2711    if (!decl || !ts.isTypeAliasDeclaration(decl)) {
2712      return undefined;
2713    }
2714    if (ts.isTypeReferenceNode(decl.type)) {
2715      const targetType = this.tsTypeChecker.getTypeAtLocation(decl.type.typeName);
2716      if (targetType.aliasSymbol && targetType.aliasSymbol.getFlags() & ts.SymbolFlags.TypeAlias) {
2717        return this.getTypsAliasOriginalDecl(targetType);
2718      }
2719    }
2720    return decl;
2721  }
2722
2723  // not allow 'lhsType' contains 'sendable typeAlias' && 'rhsType' contains 'non-sendable function/non-sendable function typeAlias'
2724  isWrongSendableFunctionAssignment(lhsType: ts.Type, rhsType: ts.Type): boolean {
2725    // eslint-disable-next-line no-param-reassign
2726    lhsType = this.getNonNullableType(lhsType);
2727    // eslint-disable-next-line no-param-reassign
2728    rhsType = this.getNonNullableType(rhsType);
2729    if (!this.hasSendableTypeAlias(lhsType)) {
2730      return false;
2731    }
2732
2733    if (rhsType.isUnion()) {
2734      return rhsType.types.some((compType) => {
2735        return this.isInvalidSendableFunctionAssignmentType(compType);
2736      });
2737    }
2738    return this.isInvalidSendableFunctionAssignmentType(rhsType);
2739  }
2740
2741  private isInvalidSendableFunctionAssignmentType(type: ts.Type): boolean {
2742    if (type.aliasSymbol) {
2743      return this.isNonSendableFunctionTypeAlias(type);
2744    }
2745    if (TsUtils.isFunctionalType(type)) {
2746      return !TsUtils.isSendableFunction(type);
2747    }
2748    return false;
2749  }
2750
2751  static isSetExpression(accessExpr: ts.ElementAccessExpression): boolean {
2752    if (!ts.isBinaryExpression(accessExpr.parent)) {
2753      return false;
2754    }
2755    const binaryExpr = accessExpr.parent;
2756    return binaryExpr.operatorToken.kind === ts.SyntaxKind.EqualsToken && binaryExpr.left === accessExpr;
2757  }
2758
2759  haveSameBaseType(type1: ts.Type, type2: ts.Type): boolean {
2760    return this.tsTypeChecker.getBaseTypeOfLiteralType(type1) === this.tsTypeChecker.getBaseTypeOfLiteralType(type2);
2761  }
2762
2763  isGetIndexableType(type: ts.Type, indexType: ts.Type): boolean {
2764    const getDecls = type.getProperty('$_get')?.getDeclarations();
2765    if (getDecls?.length !== 1 || getDecls[0].kind !== ts.SyntaxKind.MethodDeclaration) {
2766      return false;
2767    }
2768    const getMethodDecl = getDecls[0] as ts.MethodDeclaration;
2769    const getParams = getMethodDecl.parameters;
2770    if (getMethodDecl.type === undefined || getParams.length !== 1 || getParams[0].type === undefined) {
2771      return false;
2772    }
2773
2774    return this.haveSameBaseType(this.tsTypeChecker.getTypeFromTypeNode(getParams[0].type), indexType);
2775  }
2776
2777  isSetIndexableType(type: ts.Type, indexType: ts.Type, valueType: ts.Type): boolean {
2778    const setProp = type.getProperty('$_set');
2779    const setDecls = setProp?.getDeclarations();
2780    if (setDecls?.length !== 1 || setDecls[0].kind !== ts.SyntaxKind.MethodDeclaration) {
2781      return false;
2782    }
2783    const setMethodDecl = setDecls[0] as ts.MethodDeclaration;
2784    const setParams = setMethodDecl.parameters;
2785    if (
2786      setMethodDecl.type !== undefined ||
2787      setParams.length !== 2 ||
2788      setParams[0].type === undefined ||
2789      setParams[1].type === undefined
2790    ) {
2791      return false;
2792    }
2793
2794    return (
2795      this.haveSameBaseType(this.tsTypeChecker.getTypeFromTypeNode(setParams[0].type), indexType) &&
2796      this.haveSameBaseType(this.tsTypeChecker.getTypeFromTypeNode(setParams[1].type), valueType)
2797    );
2798  }
2799
2800  // Search for and save the exported declaration in the specified file, re-exporting another module will not be included.
2801  searchFileExportDecl(sourceFile: ts.SourceFile, targetDecls?: ts.SyntaxKind[]): Set<ts.Node> {
2802    const exportDeclSet = new Set<ts.Node>();
2803    const appendDecl = (decl: ts.Node | undefined): void => {
2804      if (!decl || targetDecls && !targetDecls.includes(decl.kind)) {
2805        return;
2806      }
2807      exportDeclSet.add(decl);
2808    };
2809
2810    sourceFile.statements.forEach((statement: ts.Statement) => {
2811      if (ts.isExportAssignment(statement)) {
2812        // handle the case:"export default declName;"
2813        if (statement.isExportEquals) {
2814          return;
2815        }
2816        appendDecl(this.getDeclarationNode(statement.expression));
2817      } else if (ts.isExportDeclaration(statement)) {
2818        // handle the case:"export { declName1, declName2 };"
2819        if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
2820          return;
2821        }
2822        statement.exportClause.elements.forEach((specifier) => {
2823          appendDecl(this.getDeclarationNode(specifier.propertyName ?? specifier.name));
2824        });
2825      } else if (ts.canHaveModifiers(statement)) {
2826        // handle the case:"export const/class/function... decalName;"
2827        if (!TsUtils.hasModifier(ts.getModifiers(statement), ts.SyntaxKind.ExportKeyword)) {
2828          return;
2829        }
2830        if (!ts.isVariableStatement(statement)) {
2831          appendDecl(statement);
2832          return;
2833        }
2834        for (const exportDecl of statement.declarationList.declarations) {
2835          appendDecl(exportDecl);
2836        }
2837      }
2838    });
2839    return exportDeclSet;
2840  }
2841
2842  static isAmbientNode(node: ts.Node): boolean {
2843    // Ambient flag is not exposed, so we apply dirty hack to make it visible
2844    return !!(node.flags & (ts.NodeFlags as any).Ambient);
2845  }
2846}
2847