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