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