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