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