1/* 2 * Copyright (c) 2022-2025 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 * as fs from 'fs'; 19import type { IsEtsFileCallback } from '../IsEtsFileCallback'; 20import { FaultID } from '../Problems'; 21import { ARKTS_IGNORE_DIRS, ARKTS_IGNORE_DIRS_OH_MODULES, ARKTS_IGNORE_FILES } from './consts/ArktsIgnorePaths'; 22import { ES_VALUE } from './consts/ESObject'; 23import { EXTENDED_BASE_TYPES } from './consts/ExtendedBaseTypes'; 24import { SENDABLE_DECORATOR } from './consts/SendableAPI'; 25import { USE_SHARED } from './consts/SharedModuleAPI'; 26import { STANDARD_LIBRARIES } from './consts/StandardLibraries'; 27import { 28 ARKTS_COLLECTIONS_D_ETS, 29 ARKTS_LANG_D_ETS, 30 COLLECTIONS_NAMESPACE, 31 ISENDABLE_TYPE, 32 LANG_NAMESPACE 33} from './consts/SupportedDetsIndexableTypes'; 34import { TYPED_ARRAYS } from './consts/TypedArrays'; 35import { TYPED_COLLECTIONS } from './consts/TypedCollections'; 36import { forEachNodeInSubtree } from './functions/ForEachNodeInSubtree'; 37import { getScriptKind } from './functions/GetScriptKind'; 38import { isStdLibrarySymbol, isStdLibraryType } from './functions/IsStdLibrary'; 39import { isStructDeclaration, isStructDeclarationKind } from './functions/IsStruct'; 40import type { NameGenerator } from './functions/NameGenerator'; 41import { srcFilePathContainsDirectory } from './functions/PathHelper'; 42import { isAssignmentOperator } from './functions/isAssignmentOperator'; 43import { isIntrinsicObjectType } from './functions/isIntrinsicObjectType'; 44import type { LinterOptions } from '../LinterOptions'; 45import { ETS } from './consts/TsSuffix'; 46import { STRINGLITERAL_NUMBER, STRINGLITERAL_NUMBER_ARRAY } from './consts/StringLiteral'; 47import { ETS_MODULE, PATH_SEPARATOR, VALID_OHM_COMPONENTS_MODULE_PATH } from './consts/OhmUrl'; 48import { EXTNAME_ETS, EXTNAME_JS, EXTNAME_D_ETS } from './consts/ExtensionName'; 49import { STRING_ERROR_LITERAL } from './consts/Literals'; 50 51export const PROMISE_METHODS = new Set(['all', 'race', 'any', 'resolve', 'allSettled']); 52export const SYMBOL = 'Symbol'; 53export const SYMBOL_CONSTRUCTOR = 'SymbolConstructor'; 54const ITERATOR = 'iterator'; 55 56export type CheckType = (this: TsUtils, t: ts.Type) => boolean; 57export class TsUtils { 58 constructor( 59 private readonly tsTypeChecker: ts.TypeChecker, 60 private readonly options: LinterOptions 61 ) {} 62 63 entityNameToString(name: ts.EntityName): string { 64 if (ts.isIdentifier(name)) { 65 return name.escapedText.toString(); 66 } 67 return this.entityNameToString(name.left) + this.entityNameToString(name.right); 68 } 69 70 isNumberLikeType(tsType: ts.Type): boolean { 71 if (this.options.useRtLogic && tsType.isUnion()) { 72 for (const tsCompType of tsType.types) { 73 if ((tsCompType.flags & ts.TypeFlags.NumberLike) === 0) { 74 return false; 75 } 76 } 77 return true; 78 } 79 return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0; 80 } 81 82 static isBooleanLikeType(tsType: ts.Type): boolean { 83 return (tsType.getFlags() & ts.TypeFlags.BooleanLike) !== 0; 84 } 85 86 static isDestructuringAssignmentLHS(tsExpr: ts.ArrayLiteralExpression | ts.ObjectLiteralExpression): boolean { 87 88 /* 89 * Check whether given expression is the LHS part of the destructuring 90 * assignment (or is a nested element of destructuring pattern). 91 */ 92 let tsParent = tsExpr.parent; 93 let tsCurrentExpr: ts.Node = tsExpr; 94 while (tsParent) { 95 if ( 96 ts.isBinaryExpression(tsParent) && 97 isAssignmentOperator(tsParent.operatorToken) && 98 tsParent.left === tsCurrentExpr 99 ) { 100 return true; 101 } 102 103 if ( 104 (ts.isForStatement(tsParent) || ts.isForInStatement(tsParent) || ts.isForOfStatement(tsParent)) && 105 tsParent.initializer && 106 tsParent.initializer === tsCurrentExpr 107 ) { 108 return true; 109 } 110 111 tsCurrentExpr = tsParent; 112 tsParent = tsParent.parent; 113 } 114 115 return false; 116 } 117 118 static isEnumType(tsType: ts.Type): boolean { 119 // when type equals `typeof <Enum>`, only symbol contains information about it's type. 120 const isEnumSymbol = tsType.symbol && this.isEnum(tsType.symbol); 121 // otherwise, we should analyze flags of the type itself 122 const isEnumType = !!(tsType.flags & ts.TypeFlags.Enum) || !!(tsType.flags & ts.TypeFlags.EnumLiteral); 123 return isEnumSymbol || isEnumType; 124 } 125 126 static isEnum(tsSymbol: ts.Symbol): boolean { 127 return !!(tsSymbol.flags & ts.SymbolFlags.Enum); 128 } 129 130 static hasModifier( 131 tsModifiers: readonly ts.Modifier[] | ts.NodeArray<ts.ModifierLike> | undefined, 132 tsModifierKind: number 133 ): boolean { 134 if (!tsModifiers) { 135 return false; 136 } 137 138 for (const tsModifier of tsModifiers) { 139 if (tsModifier.kind === tsModifierKind) { 140 return true; 141 } 142 } 143 144 return false; 145 } 146 147 static unwrapParenthesized(tsExpr: ts.Expression): ts.Expression { 148 let unwrappedExpr = tsExpr; 149 while (ts.isParenthesizedExpression(unwrappedExpr)) { 150 unwrappedExpr = unwrappedExpr.expression; 151 } 152 153 return unwrappedExpr; 154 } 155 156 followIfAliased(sym: ts.Symbol): ts.Symbol { 157 if ((sym.getFlags() & ts.SymbolFlags.Alias) !== 0) { 158 return this.tsTypeChecker.getAliasedSymbol(sym); 159 } 160 return sym; 161 } 162 163 private readonly trueSymbolAtLocationCache = new Map<ts.Node, ts.Symbol | null>(); 164 165 trueSymbolAtLocation(node: ts.Node): ts.Symbol | undefined { 166 const cache = this.trueSymbolAtLocationCache; 167 const val = cache.get(node); 168 if (val !== undefined) { 169 return val !== null ? val : undefined; 170 } 171 let sym = this.tsTypeChecker.getSymbolAtLocation(node); 172 if (sym === undefined) { 173 cache.set(node, null); 174 return undefined; 175 } 176 sym = this.followIfAliased(sym); 177 cache.set(node, sym); 178 return sym; 179 } 180 181 private static isTypeDeclSyntaxKind(kind: ts.SyntaxKind): boolean { 182 return ( 183 isStructDeclarationKind(kind) || 184 kind === ts.SyntaxKind.EnumDeclaration || 185 kind === ts.SyntaxKind.ClassDeclaration || 186 kind === ts.SyntaxKind.InterfaceDeclaration || 187 kind === ts.SyntaxKind.TypeAliasDeclaration 188 ); 189 } 190 191 static symbolHasDuplicateName(symbol: ts.Symbol, tsDeclKind: ts.SyntaxKind): boolean { 192 193 /* 194 * Type Checker merges all declarations with the same name in one scope into one symbol. 195 * Thus, check whether the symbol of certain declaration has any declaration with 196 * different syntax kind. 197 */ 198 const symbolDecls = symbol?.getDeclarations(); 199 if (symbolDecls) { 200 for (const symDecl of symbolDecls) { 201 const declKind = symDecl.kind; 202 // we relax arkts-unique-names for namespace collision with class/interface/enum/type/struct 203 const isNamespaceTypeCollision = 204 TsUtils.isTypeDeclSyntaxKind(declKind) && tsDeclKind === ts.SyntaxKind.ModuleDeclaration || 205 TsUtils.isTypeDeclSyntaxKind(tsDeclKind) && declKind === ts.SyntaxKind.ModuleDeclaration; 206 207 /* 208 * Don't count declarations with 'Identifier' syntax kind as those 209 * usually depict declaring an object's property through assignment. 210 */ 211 if (declKind !== ts.SyntaxKind.Identifier && declKind !== tsDeclKind && !isNamespaceTypeCollision) { 212 return true; 213 } 214 } 215 } 216 217 return false; 218 } 219 220 static isPrimitiveType(type: ts.Type): boolean { 221 const f = type.getFlags(); 222 return ( 223 (f & ts.TypeFlags.Boolean) !== 0 || 224 (f & ts.TypeFlags.BooleanLiteral) !== 0 || 225 (f & ts.TypeFlags.Number) !== 0 || 226 (f & ts.TypeFlags.NumberLiteral) !== 0 227 228 /* 229 * In ArkTS 'string' is not a primitive type. So for the common subset 'string' 230 * should be considered as a reference type. That is why next line is commented out. 231 * (f & ts.TypeFlags.String) != 0 || (f & ts.TypeFlags.StringLiteral) != 0 232 */ 233 ); 234 } 235 236 checkStatementForErrorClass(stmt: ts.ThrowStatement): boolean { 237 const newExpr = stmt.expression; 238 if (!ts.isNewExpression(newExpr)) { 239 return true; 240 } 241 const ident = newExpr.expression; 242 if (!ts.isIdentifier(ident)) { 243 return true; 244 } 245 246 if (ident.text === STRING_ERROR_LITERAL) { 247 return false; 248 } 249 250 const declaration = this.getDeclarationNode(ident); 251 if (!declaration || ident.text.includes(STRING_ERROR_LITERAL)) { 252 return false; 253 } 254 255 if (!declaration || !ident.text.includes(STRING_ERROR_LITERAL)) { 256 return true; 257 } 258 259 if (!ts.isClassDeclaration(declaration)) { 260 return true; 261 } 262 263 if (!declaration.heritageClauses) { 264 return true; 265 } 266 267 return !this.includesErrorClass(declaration.heritageClauses); 268 } 269 270 includesErrorClass(hClauses: ts.NodeArray<ts.HeritageClause>): boolean { 271 void this; 272 let includesErrorClass = false; 273 274 for (const hClause of hClauses) { 275 for (const type of hClause.types) { 276 if (!ts.isIdentifier(type.expression)) { 277 continue; 278 } 279 if (type.expression.text === 'Error') { 280 includesErrorClass = true; 281 } 282 } 283 } 284 285 return includesErrorClass; 286 } 287 288 static isPrimitiveLiteralType(type: ts.Type): boolean { 289 return !!( 290 type.flags & 291 (ts.TypeFlags.BooleanLiteral | 292 ts.TypeFlags.NumberLiteral | 293 ts.TypeFlags.StringLiteral | 294 ts.TypeFlags.BigIntLiteral) 295 ); 296 } 297 298 static isPurePrimitiveLiteralType(type: ts.Type): boolean { 299 return TsUtils.isPrimitiveLiteralType(type) && !(type.flags & ts.TypeFlags.EnumLiteral); 300 } 301 302 static isTypeSymbol(symbol: ts.Symbol | undefined): boolean { 303 return ( 304 !!symbol && 305 !!symbol.flags && 306 ((symbol.flags & ts.SymbolFlags.Class) !== 0 || (symbol.flags & ts.SymbolFlags.Interface) !== 0) 307 ); 308 } 309 310 // Check whether type is generic 'Array<T>' type defined in TypeScript standard library. 311 isGenericArrayType(tsType: ts.Type): tsType is ts.TypeReference { 312 return ( 313 !(this.options.arkts2 && !isStdLibraryType(tsType)) && 314 TsUtils.isTypeReference(tsType) && 315 tsType.typeArguments?.length === 1 && 316 tsType.target.typeParameters?.length === 1 && 317 tsType.getSymbol()?.getName() === 'Array' 318 ); 319 } 320 321 isReadonlyArrayType(tsType: ts.Type): boolean { 322 return ( 323 !(this.options.arkts2 && !isStdLibraryType(tsType)) && 324 TsUtils.isTypeReference(tsType) && 325 tsType.typeArguments?.length === 1 && 326 tsType.target.typeParameters?.length === 1 && 327 tsType.getSymbol()?.getName() === 'ReadonlyArray' 328 ); 329 } 330 331 static isConcatArrayType(tsType: ts.Type): boolean { 332 return ( 333 isStdLibraryType(tsType) && 334 TsUtils.isTypeReference(tsType) && 335 tsType.typeArguments?.length === 1 && 336 tsType.target.typeParameters?.length === 1 && 337 tsType.getSymbol()?.getName() === 'ConcatArray' 338 ); 339 } 340 341 static isArrayLikeType(tsType: ts.Type): boolean { 342 return ( 343 isStdLibraryType(tsType) && 344 TsUtils.isTypeReference(tsType) && 345 tsType.typeArguments?.length === 1 && 346 tsType.target.typeParameters?.length === 1 && 347 tsType.getSymbol()?.getName() === 'ArrayLike' 348 ); 349 } 350 351 isTypedArray(tsType: ts.Type, allowTypeArrays: string[]): boolean { 352 const symbol = tsType.symbol; 353 if (!symbol) { 354 return false; 355 } 356 const name = this.tsTypeChecker.getFullyQualifiedName(symbol); 357 if (this.isGlobalSymbol(symbol) && allowTypeArrays.includes(name)) { 358 return true; 359 } 360 const decl = TsUtils.getDeclaration(symbol); 361 return ( 362 !!decl && 363 TsUtils.isArkTSCollectionsClassOrInterfaceDeclaration(decl) && 364 allowTypeArrays.includes(symbol.getName()) 365 ); 366 } 367 368 isArray(tsType: ts.Type): boolean { 369 return ( 370 this.isGenericArrayType(tsType) || this.isReadonlyArrayType(tsType) || this.isTypedArray(tsType, TYPED_ARRAYS) 371 ); 372 } 373 374 isCollectionArrayType(tsType: ts.Type): boolean { 375 return this.isTypedArray(tsType, TYPED_COLLECTIONS); 376 } 377 378 isIndexableArray(tsType: ts.Type): boolean { 379 return ( 380 this.isGenericArrayType(tsType) || 381 this.isReadonlyArrayType(tsType) || 382 TsUtils.isConcatArrayType(tsType) || 383 TsUtils.isArrayLikeType(tsType) || 384 this.isTypedArray(tsType, TYPED_ARRAYS) || 385 this.isTypedArray(tsType, TYPED_COLLECTIONS) 386 ); 387 } 388 389 static isTuple(tsType: ts.Type): boolean { 390 return TsUtils.isTypeReference(tsType) && !!(tsType.objectFlags & ts.ObjectFlags.Tuple); 391 } 392 393 // does something similar to relatedByInheritanceOrIdentical function 394 isOrDerivedFrom(tsType: ts.Type, checkType: CheckType, checkedBaseTypes?: Set<ts.Type>): boolean { 395 // eslint-disable-next-line no-param-reassign 396 tsType = TsUtils.reduceReference(tsType); 397 398 if (checkType.call(this, tsType)) { 399 return true; 400 } 401 402 if (!tsType.symbol?.declarations) { 403 return false; 404 } 405 406 // Avoid type recursion in heritage by caching checked types. 407 (checkedBaseTypes = checkedBaseTypes || new Set<ts.Type>()).add(tsType); 408 409 for (const tsTypeDecl of tsType.symbol.declarations) { 410 const isClassOrInterfaceDecl = ts.isClassDeclaration(tsTypeDecl) || ts.isInterfaceDeclaration(tsTypeDecl); 411 const isDerived = isClassOrInterfaceDecl && !!tsTypeDecl.heritageClauses; 412 if (!isDerived) { 413 continue; 414 } 415 for (const heritageClause of tsTypeDecl.heritageClauses) { 416 if (this.processParentTypesCheck(heritageClause.types, checkType, checkedBaseTypes)) { 417 return true; 418 } 419 } 420 } 421 422 return false; 423 } 424 425 static isTypeReference(tsType: ts.Type): tsType is ts.TypeReference { 426 return ( 427 (tsType.getFlags() & ts.TypeFlags.Object) !== 0 && 428 ((tsType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 429 ); 430 } 431 432 static isPrototypeSymbol(symbol: ts.Symbol | undefined): boolean { 433 return !!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Prototype) !== 0; 434 } 435 436 static isFunctionSymbol(symbol: ts.Symbol | undefined): boolean { 437 return !!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Function) !== 0; 438 } 439 440 static isInterfaceType(tsType: ts.Type | undefined): boolean { 441 return ( 442 !!tsType && !!tsType.symbol && !!tsType.symbol.flags && (tsType.symbol.flags & ts.SymbolFlags.Interface) !== 0 443 ); 444 } 445 446 static isAnyType(tsType: ts.Type): tsType is ts.TypeReference { 447 return (tsType.getFlags() & ts.TypeFlags.Any) !== 0; 448 } 449 450 static isUnknownType(tsType: ts.Type): boolean { 451 return (tsType.getFlags() & ts.TypeFlags.Unknown) !== 0; 452 } 453 454 static isUnsupportedType(tsType: ts.Type): boolean { 455 return ( 456 !!tsType.flags && 457 ((tsType.flags & ts.TypeFlags.Any) !== 0 || 458 (tsType.flags & ts.TypeFlags.Unknown) !== 0 || 459 (tsType.flags & ts.TypeFlags.Intersection) !== 0) 460 ); 461 } 462 463 isUnsupportedTypeArkts2(tsType: ts.Type): boolean { 464 const typenode = this.tsTypeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.None); 465 return !!typenode && !this.isSupportedType(typenode); 466 } 467 468 static isNullableUnionType(type: ts.Type): boolean { 469 if (type.isUnion()) { 470 for (const t of type.types) { 471 if (!!(t.flags & ts.TypeFlags.Undefined) || !!(t.flags & ts.TypeFlags.Null)) { 472 return true; 473 } 474 } 475 } 476 return false; 477 } 478 479 static isMethodAssignment(tsSymbol: ts.Symbol | undefined): boolean { 480 return ( 481 !!tsSymbol && (tsSymbol.flags & ts.SymbolFlags.Method) !== 0 && (tsSymbol.flags & ts.SymbolFlags.Assignment) !== 0 482 ); 483 } 484 485 static getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | undefined { 486 if (tsSymbol?.declarations && tsSymbol.declarations.length > 0) { 487 return tsSymbol.declarations[0]; 488 } 489 return undefined; 490 } 491 492 private static isVarDeclaration(tsDecl: ts.Node): boolean { 493 return ts.isVariableDeclaration(tsDecl) && ts.isVariableDeclarationList(tsDecl.parent); 494 } 495 496 isValidEnumMemberInit(tsExpr: ts.Expression): boolean { 497 if (this.isNumberConstantValue(tsExpr.parent as ts.EnumMember)) { 498 return true; 499 } 500 if (this.isStringConstantValue(tsExpr.parent as ts.EnumMember)) { 501 return true; 502 } 503 return this.isCompileTimeExpression(tsExpr); 504 } 505 506 private isCompileTimeExpressionHandlePropertyAccess(tsExpr: ts.Expression): boolean { 507 if (!ts.isPropertyAccessExpression(tsExpr)) { 508 return false; 509 } 510 511 /* 512 * if enum member is in current enum declaration try to get value 513 * if it comes from another enum consider as constant 514 */ 515 const propertyAccess = tsExpr; 516 if (this.isNumberConstantValue(propertyAccess)) { 517 return true; 518 } 519 const leftHandSymbol = this.trueSymbolAtLocation(propertyAccess.expression); 520 if (!leftHandSymbol) { 521 return false; 522 } 523 const decls = leftHandSymbol.getDeclarations(); 524 if (!decls || decls.length !== 1) { 525 return false; 526 } 527 return ts.isEnumDeclaration(decls[0]); 528 } 529 530 isCompileTimeExpression(tsExpr: ts.Expression): boolean { 531 if ( 532 ts.isParenthesizedExpression(tsExpr) || 533 ts.isAsExpression(tsExpr) && tsExpr.type.kind === ts.SyntaxKind.NumberKeyword 534 ) { 535 return this.isCompileTimeExpression(tsExpr.expression); 536 } 537 538 switch (tsExpr.kind) { 539 case ts.SyntaxKind.PrefixUnaryExpression: 540 return this.isPrefixUnaryExprValidEnumMemberInit(tsExpr as ts.PrefixUnaryExpression); 541 case ts.SyntaxKind.ParenthesizedExpression: 542 case ts.SyntaxKind.BinaryExpression: 543 return this.isBinaryExprValidEnumMemberInit(tsExpr as ts.BinaryExpression); 544 case ts.SyntaxKind.ConditionalExpression: 545 return this.isConditionalExprValidEnumMemberInit(tsExpr as ts.ConditionalExpression); 546 case ts.SyntaxKind.Identifier: 547 return this.isIdentifierValidEnumMemberInit(tsExpr as ts.Identifier); 548 case ts.SyntaxKind.NumericLiteral: 549 return true; 550 case ts.SyntaxKind.StringLiteral: 551 return true; 552 case ts.SyntaxKind.PropertyAccessExpression: 553 return this.isCompileTimeExpressionHandlePropertyAccess(tsExpr); 554 default: 555 return false; 556 } 557 } 558 559 private isPrefixUnaryExprValidEnumMemberInit(tsExpr: ts.PrefixUnaryExpression): boolean { 560 return TsUtils.isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && this.isCompileTimeExpression(tsExpr.operand); 561 } 562 563 private isBinaryExprValidEnumMemberInit(tsExpr: ts.BinaryExpression): boolean { 564 return ( 565 TsUtils.isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) && 566 this.isCompileTimeExpression(tsExpr.left) && 567 this.isCompileTimeExpression(tsExpr.right) 568 ); 569 } 570 571 private isConditionalExprValidEnumMemberInit(tsExpr: ts.ConditionalExpression): boolean { 572 return this.isCompileTimeExpression(tsExpr.whenTrue) && this.isCompileTimeExpression(tsExpr.whenFalse); 573 } 574 575 private isIdentifierValidEnumMemberInit(tsExpr: ts.Identifier): boolean { 576 const tsSymbol = this.trueSymbolAtLocation(tsExpr); 577 const tsDecl = TsUtils.getDeclaration(tsSymbol); 578 return ( 579 !!tsDecl && 580 (TsUtils.isVarDeclaration(tsDecl) && TsUtils.isConst(tsDecl.parent) || tsDecl.kind === ts.SyntaxKind.EnumMember) 581 ); 582 } 583 584 private static isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: ts.PrefixUnaryOperator): boolean { 585 return ( 586 tsPrefixUnaryOp === ts.SyntaxKind.PlusToken || 587 tsPrefixUnaryOp === ts.SyntaxKind.MinusToken || 588 tsPrefixUnaryOp === ts.SyntaxKind.TildeToken 589 ); 590 } 591 592 private static isBinaryOpAllowedForEnumMemberInit(tsBinaryOp: ts.BinaryOperatorToken): boolean { 593 return ( 594 tsBinaryOp.kind === ts.SyntaxKind.AsteriskToken || 595 tsBinaryOp.kind === ts.SyntaxKind.SlashToken || 596 tsBinaryOp.kind === ts.SyntaxKind.PercentToken || 597 tsBinaryOp.kind === ts.SyntaxKind.MinusToken || 598 tsBinaryOp.kind === ts.SyntaxKind.PlusToken || 599 tsBinaryOp.kind === ts.SyntaxKind.LessThanLessThanToken || 600 tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanToken || 601 tsBinaryOp.kind === ts.SyntaxKind.BarBarToken || 602 tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken || 603 tsBinaryOp.kind === ts.SyntaxKind.AmpersandToken || 604 tsBinaryOp.kind === ts.SyntaxKind.CaretToken || 605 tsBinaryOp.kind === ts.SyntaxKind.BarToken || 606 tsBinaryOp.kind === ts.SyntaxKind.AmpersandAmpersandToken 607 ); 608 } 609 610 static isConst(tsNode: ts.Node): boolean { 611 return !!(ts.getCombinedNodeFlags(tsNode) & ts.NodeFlags.Const); 612 } 613 614 isNumberConstantValue( 615 tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral 616 ): boolean { 617 const tsConstValue = 618 tsExpr.kind === ts.SyntaxKind.NumericLiteral ? 619 Number(tsExpr.getText()) : 620 this.tsTypeChecker.getConstantValue(tsExpr); 621 622 return tsConstValue !== undefined && typeof tsConstValue === 'number'; 623 } 624 625 isIntegerConstantValue( 626 tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral 627 ): boolean { 628 const tsConstValue = 629 tsExpr.kind === ts.SyntaxKind.NumericLiteral ? 630 Number(tsExpr.getText()) : 631 this.tsTypeChecker.getConstantValue(tsExpr); 632 return ( 633 tsConstValue !== undefined && 634 typeof tsConstValue === 'number' && 635 tsConstValue.toFixed(0) === tsConstValue.toString() 636 ); 637 } 638 639 isStringConstantValue(tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression): boolean { 640 const tsConstValue = this.tsTypeChecker.getConstantValue(tsExpr); 641 return tsConstValue !== undefined && typeof tsConstValue === 'string'; 642 } 643 644 // Returns true if typeA is a subtype of typeB 645 relatedByInheritanceOrIdentical(typeA: ts.Type, typeB: ts.Type): boolean { 646 // eslint-disable-next-line no-param-reassign 647 typeA = TsUtils.reduceReference(typeA); 648 // eslint-disable-next-line no-param-reassign 649 typeB = TsUtils.reduceReference(typeB); 650 651 if (typeA === typeB || this.isObject(typeB)) { 652 return true; 653 } 654 if (!typeA.symbol?.declarations) { 655 return false; 656 } 657 const isBISendable = TsUtils.isISendableInterface(typeB); 658 for (const typeADecl of typeA.symbol.declarations) { 659 if (this.relatedByInheritanceOrIdenticalCheckParentTypes(typeA, typeB, typeADecl, isBISendable)) { 660 return true; 661 } 662 } 663 return false; 664 } 665 666 private relatedByInheritanceOrIdenticalCheckParentTypes( 667 typeA: ts.Type, 668 typeB: ts.Type, 669 typeADecl: ts.Declaration, 670 isBISendable = false 671 ): boolean { 672 if (isBISendable && ts.isClassDeclaration(typeADecl) && TsUtils.hasSendableDecorator(typeADecl)) { 673 return true; 674 } 675 if (!ts.isClassDeclaration(typeADecl) && !ts.isInterfaceDeclaration(typeADecl)) { 676 return false; 677 } 678 if (this.processExtendedParentTypes(typeA, typeB)) { 679 return true; 680 } 681 if (this.isStdIterableType(typeB) && this.hasSymbolIteratorMethod(typeA)) { 682 return true; 683 } 684 if (!typeADecl.heritageClauses) { 685 return false; 686 } 687 for (const heritageClause of typeADecl.heritageClauses) { 688 const processInterfaces = typeA.isClass() ? heritageClause.token !== ts.SyntaxKind.ExtendsKeyword : true; 689 if (this.processParentTypes(heritageClause.types, typeB, processInterfaces)) { 690 return true; 691 } 692 } 693 return false; 694 } 695 696 hasSymbolIteratorMethod(type: ts.Type): boolean { 697 const rhsTypeProps = this.tsTypeChecker.getPropertiesOfType(type); 698 return rhsTypeProps.some((prop) => { 699 const propDecl = TsUtils.getDeclaration(prop); 700 return ( 701 propDecl && 702 (ts.isMethodSignature(propDecl) || ts.isMethodDeclaration(propDecl)) && 703 ts.isComputedPropertyName(propDecl.name) && 704 this.isSymbolIteratorExpression(propDecl.name.expression) 705 ); 706 }); 707 } 708 709 isStdIterableType(type: ts.Type): boolean { 710 void this; 711 const sym = type.getSymbol(); 712 return !!sym && sym.getName() === 'Iterable' && isStdLibrarySymbol(sym); 713 } 714 715 static reduceReference(t: ts.Type): ts.Type { 716 return TsUtils.isTypeReference(t) && t.target !== t ? t.target : t; 717 } 718 719 private needToDeduceStructuralIdentityHandleUnions( 720 lhsType: ts.Type, 721 rhsType: ts.Type, 722 rhsExpr: ts.Expression, 723 isStrict: boolean 724 ): boolean { 725 if (this.needToDeduceStructuralIdentityHandleUnionsIsStrict(lhsType, rhsType, rhsExpr, isStrict)) { 726 return true; 727 } 728 if (rhsType.isUnion()) { 729 // Each Class/Interface of the RHS union type must be compatible with LHS type. 730 for (const compType of rhsType.types) { 731 if (this.needToDeduceStructuralIdentity(lhsType, compType, rhsExpr, isStrict)) { 732 return true; 733 } 734 } 735 return false; 736 } 737 if (lhsType.isUnion() && TsUtils.isTypeReference(rhsType)) { 738 let needDeduce = false; 739 // RHS type needs to be compatible with at least one type of the LHS union. 740 for (const compType of lhsType.types) { 741 if (!TsUtils.isTypeReference(compType) && !TsUtils.isISendableInterface(compType)) { 742 continue; 743 } 744 if (this.needToDeduceStructuralIdentity(compType, rhsType, rhsExpr, isStrict)) { 745 needDeduce = true; 746 } else { 747 return false; 748 } 749 } 750 return needDeduce; 751 } 752 // should be unreachable 753 return false; 754 } 755 756 needToDeduceStructuralIdentityHandleUnionsIsStrict( 757 lhsType: ts.Type, 758 rhsType: ts.Type, 759 rhsExpr: ts.Expression, 760 isStrict: boolean 761 ): boolean { 762 if (rhsType.isUnion() && lhsType.isUnion()) { 763 return rhsType.types.some((compRhsType) => { 764 return lhsType.types.every((compLhsType) => { 765 return this.needToDeduceStructuralIdentity(compLhsType, compRhsType, rhsExpr, isStrict); 766 }); 767 }); 768 } 769 return false; 770 } 771 772 // return true if two class types are not related by inheritance and structural identity check is needed 773 needToDeduceStructuralIdentity( 774 lhsType: ts.Type, 775 rhsType: ts.Type, 776 rhsExpr: ts.Expression, 777 isStrict: boolean = false 778 ): boolean { 779 // eslint-disable-next-line no-param-reassign 780 lhsType = this.getNonNullableType(lhsType); 781 // eslint-disable-next-line no-param-reassign 782 rhsType = this.getNonNullableType(rhsType); 783 if (this.isLibraryType(lhsType)) { 784 return false; 785 } 786 if (this.isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) { 787 return false; 788 } 789 // #14569: Check for Function type. 790 if (this.areCompatibleFunctionals(lhsType, rhsType)) { 791 return false; 792 } 793 if (rhsType.isUnion() || lhsType.isUnion()) { 794 return this.needToDeduceStructuralIdentityHandleUnions(lhsType, rhsType, rhsExpr, isStrict); 795 } 796 if (this.needToDeduceStructuralIdentityAdvancedClassChecks(lhsType, rhsType)) { 797 // missing exact rule 798 return true; 799 } 800 // if isStrict, things like generics need to be checked 801 if (isStrict) { 802 if (TsUtils.isTypeReference(rhsType) && !!(rhsType.objectFlags & ts.ObjectFlags.ArrayLiteral)) { 803 // The 'arkts-sendable-obj-init' rule already exists. Wait for the new 'strict type' to be modified. 804 return false; 805 } 806 if (this.needToDeduceStructuralIdentityIsStrict(lhsType, rhsType, rhsExpr, isStrict)) { 807 return true; 808 } 809 // eslint-disable-next-line no-param-reassign 810 lhsType = TsUtils.reduceReference(lhsType); 811 // eslint-disable-next-line no-param-reassign 812 rhsType = TsUtils.reduceReference(rhsType); 813 } 814 return ( 815 lhsType.isClassOrInterface() && 816 rhsType.isClassOrInterface() && 817 !this.relatedByInheritanceOrIdentical(rhsType, lhsType) 818 ); 819 } 820 821 needToDeduceStructuralIdentityIsStrict( 822 lhsType: ts.Type, 823 rhsType: ts.Type, 824 rhsExpr: ts.Expression, 825 isStrict: boolean = false 826 ): boolean { 827 if ( 828 TsUtils.reduceReference(lhsType) !== TsUtils.reduceReference(rhsType) || 829 !TsUtils.isTypeReference(lhsType) || 830 !TsUtils.isTypeReference(rhsType) 831 ) { 832 return false; 833 } 834 const lhsArgs = lhsType.typeArguments; 835 const rhsArgs = rhsType.typeArguments; 836 if (lhsArgs && lhsArgs.length > 0) { 837 if (rhsArgs && rhsArgs.length > 0) { 838 if (rhsArgs[0] === lhsArgs[0]) { 839 return false; 840 } 841 return this.needToDeduceStructuralIdentity(lhsArgs[0], rhsArgs[0], rhsExpr, isStrict); 842 } 843 return this.needToDeduceStructuralIdentity(lhsArgs[0], rhsType, rhsExpr, isStrict); 844 } 845 return false; 846 } 847 848 private needToDeduceStructuralIdentityAdvancedClassChecks(lhsType: ts.Type, rhsType: ts.Type): boolean { 849 return ( 850 !!this.options.advancedClassChecks && 851 TsUtils.isClassValueType(rhsType) && 852 lhsType !== rhsType && 853 !TsUtils.isObjectType(lhsType) 854 ); 855 } 856 857 // Does the 'arkts-no-structural-typing' rule need to be strictly enforced to complete previously missed scenarios 858 needStrictMatchType(lhsType: ts.Type, rhsType: ts.Type): boolean { 859 if (this.options.arkts2) { 860 return true; 861 } 862 if (this.isStrictSendableMatch(lhsType, rhsType)) { 863 return true; 864 } 865 // add other requirements with strict type requirements here 866 return false; 867 } 868 869 // For compatibility, left must all ClassOrInterface is sendable, right must has non-sendable ClassorInterface 870 private isStrictSendableMatch(lhsType: ts.Type, rhsType: ts.Type): boolean { 871 let isStrictLhs = false; 872 if (lhsType.isUnion()) { 873 for (let compType of lhsType.types) { 874 compType = TsUtils.reduceReference(compType); 875 if (!compType.isClassOrInterface()) { 876 continue; 877 } 878 if (!this.isSendableClassOrInterface(compType)) { 879 return false; 880 } 881 isStrictLhs = true; 882 } 883 } else { 884 isStrictLhs = this.isSendableClassOrInterface(lhsType); 885 } 886 return isStrictLhs && this.typeContainsNonSendableClassOrInterface(rhsType); 887 } 888 889 private processExtendedParentTypes(typeA: ts.Type, typeB: ts.Type): boolean { 890 891 /* 892 * Most standard types in TS Stdlib do not use explicit inheritance and rely on 893 * structural compatibility. In contrast, the type definitions in ArkTS stdlib 894 * use inheritance with explicit base types. We check the inheritance hierarchy 895 * for such types according to how they are defined in ArkTS Stdlib. 896 */ 897 898 if (!this.options.arkts2) { 899 return false; 900 } 901 if (!isStdLibrarySymbol(typeA.symbol) && !isStdLibrarySymbol(typeB.symbol)) { 902 return false; 903 } 904 return this.checkExtendedParentTypes(typeA.symbol.name, typeB.symbol.name); 905 } 906 907 private checkExtendedParentTypes(typeA: string, typeB: string): boolean { 908 if (typeA === typeB) { 909 return true; 910 } 911 const extBaseTypes = EXTENDED_BASE_TYPES.get(typeA); 912 if (!extBaseTypes) { 913 return false; 914 } 915 for (const extBaseType of extBaseTypes) { 916 if (this.checkExtendedParentTypes(extBaseType, typeB)) { 917 return true; 918 } 919 } 920 return false; 921 } 922 923 private processParentTypes( 924 parentTypes: ts.NodeArray<ts.Expression>, 925 typeB: ts.Type, 926 processInterfaces: boolean 927 ): boolean { 928 for (const baseTypeExpr of parentTypes) { 929 const baseType = TsUtils.reduceReference(this.tsTypeChecker.getTypeAtLocation(baseTypeExpr)); 930 if ( 931 baseType && 932 baseType.isClass() !== processInterfaces && 933 this.relatedByInheritanceOrIdentical(baseType, typeB) 934 ) { 935 return true; 936 } 937 } 938 return false; 939 } 940 941 private processParentTypesCheck( 942 parentTypes: ts.NodeArray<ts.Expression>, 943 checkType: CheckType, 944 checkedBaseTypes: Set<ts.Type> 945 ): boolean { 946 for (const baseTypeExpr of parentTypes) { 947 const baseType = TsUtils.reduceReference(this.tsTypeChecker.getTypeAtLocation(baseTypeExpr)); 948 if (baseType && !checkedBaseTypes.has(baseType) && this.isOrDerivedFrom(baseType, checkType, checkedBaseTypes)) { 949 return true; 950 } 951 } 952 return false; 953 } 954 955 isObject(tsType: ts.Type): boolean { 956 if (!tsType) { 957 return false; 958 } 959 if (tsType.symbol && tsType.isClassOrInterface() && tsType.symbol.name === 'Object') { 960 return true; 961 } 962 const node = this.tsTypeChecker.typeToTypeNode(tsType, undefined, undefined); 963 return node !== undefined && node.kind === ts.SyntaxKind.ObjectKeyword; 964 } 965 966 isCallToFunctionWithOmittedReturnType(tsExpr: ts.Expression): boolean { 967 if (ts.isCallExpression(tsExpr)) { 968 const tsCallSignature = this.tsTypeChecker.getResolvedSignature(tsExpr); 969 if (tsCallSignature) { 970 const tsSignDecl = tsCallSignature.getDeclaration(); 971 // `tsSignDecl` is undefined when `getResolvedSignature` returns `unknownSignature` 972 if (!tsSignDecl?.type) { 973 return true; 974 } 975 } 976 } 977 978 return false; 979 } 980 981 private static hasReadonlyFields(type: ts.Type): boolean { 982 // No members -> no readonly fields 983 if (type.symbol.members === undefined) { 984 return false; 985 } 986 987 let result: boolean = false; 988 989 type.symbol.members.forEach((value) => { 990 if ( 991 value.declarations !== undefined && 992 value.declarations.length > 0 && 993 ts.isPropertyDeclaration(value.declarations[0]) 994 ) { 995 const propmMods = ts.getModifiers(value.declarations[0]); 996 if (TsUtils.hasModifier(propmMods, ts.SyntaxKind.ReadonlyKeyword)) { 997 result = true; 998 } 999 } 1000 }); 1001 1002 return result; 1003 } 1004 1005 private hasDefaultCtor(type: ts.Type): boolean { 1006 const checkBaseTypes = (type: ts.Type): boolean => { 1007 if (!this.options.arkts2) { 1008 return true; 1009 } 1010 const baseTypes = type.getBaseTypes()?.filter((baseType) => { 1011 return baseType.isClass(); 1012 }); 1013 if (!baseTypes || baseTypes.length === 0) { 1014 return true; 1015 } 1016 return baseTypes.some((baseType: ts.Type) => { 1017 return this.hasDefaultCtor(baseType); 1018 }); 1019 }; 1020 1021 // No members -> no explicit constructors -> there is default ctor 1022 if (type.symbol.members === undefined) { 1023 return checkBaseTypes(type); 1024 } 1025 1026 // has any constructor 1027 let hasCtor: boolean = false; 1028 // has default constructor 1029 let hasDefaultCtor: boolean = false; 1030 1031 type.symbol.members.forEach((value) => { 1032 if ((value.flags & ts.SymbolFlags.Constructor) === 0) { 1033 return; 1034 } 1035 hasCtor = true; 1036 1037 if (value.declarations === undefined || value.declarations.length <= 0) { 1038 return; 1039 } 1040 1041 const declCtor = value.declarations[0] as ts.ConstructorDeclaration; 1042 if (declCtor.parameters.length === 0) { 1043 hasDefaultCtor = true; 1044 } 1045 }); 1046 1047 // Has no any explicit constructor -> has implicit default constructor. 1048 if (!hasCtor) { 1049 return checkBaseTypes(type); 1050 } 1051 1052 return hasDefaultCtor; 1053 } 1054 1055 private static isAbstractClass(type: ts.Type): boolean { 1056 if (type.isClass() && type.symbol.declarations && type.symbol.declarations.length > 0) { 1057 const declClass = type.symbol.declarations[0] as ts.ClassDeclaration; 1058 const classMods = ts.getModifiers(declClass); 1059 if (TsUtils.hasModifier(classMods, ts.SyntaxKind.AbstractKeyword)) { 1060 return true; 1061 } 1062 } 1063 1064 return false; 1065 } 1066 1067 isAbstractMethodInAbstractClass(node: ts.Node): boolean { 1068 const type = this.tsTypeChecker.getTypeAtLocation(node); 1069 const funcDeclParentType = this.tsTypeChecker.getTypeAtLocation(node.parent); 1070 if ( 1071 TsUtils.isAbstractClass(funcDeclParentType) && 1072 type.symbol?.declarations && 1073 type.symbol.declarations.length > 0 1074 ) { 1075 const declClass = type.symbol.declarations[0] as ts.MethodDeclaration; 1076 const classMods = ts.getModifiers(declClass); 1077 if (TsUtils.hasModifier(classMods, ts.SyntaxKind.AbstractKeyword)) { 1078 return true; 1079 } 1080 } 1081 return false; 1082 } 1083 1084 validateObjectLiteralType(type: ts.Type | undefined): boolean { 1085 if (!type) { 1086 return false; 1087 } 1088 // eslint-disable-next-line no-param-reassign 1089 type = TsUtils.reduceReference(type); 1090 return ( 1091 type.isClassOrInterface() && 1092 this.hasDefaultCtor(type) && 1093 !TsUtils.hasReadonlyFields(type) && 1094 !TsUtils.isAbstractClass(type) 1095 ); 1096 } 1097 1098 hasMethods(type: ts.Type): boolean { 1099 const properties = this.tsTypeChecker.getPropertiesOfType(type); 1100 if (properties?.length) { 1101 for (const prop of properties) { 1102 if (prop.getFlags() & ts.SymbolFlags.Method) { 1103 return true; 1104 } 1105 } 1106 } 1107 return false; 1108 } 1109 1110 findProperty(type: ts.Type, name: string): ts.Symbol | undefined { 1111 const properties = this.tsTypeChecker.getPropertiesOfType(type); 1112 if (properties?.length) { 1113 for (const prop of properties) { 1114 if (prop.name === name) { 1115 return prop; 1116 } 1117 } 1118 } 1119 1120 return undefined; 1121 } 1122 1123 checkTypeSet(typeSet: ts.Type, predicate: CheckType): boolean { 1124 if (!typeSet.isUnionOrIntersection()) { 1125 return predicate.call(this, typeSet); 1126 } 1127 for (const elemType of typeSet.types) { 1128 if (this.checkTypeSet(elemType, predicate)) { 1129 return true; 1130 } 1131 } 1132 return false; 1133 } 1134 1135 getNonNullableType(t: ts.Type): ts.Type { 1136 const isNullableUnionType = this.options.useRtLogic ? TsUtils.isNullableUnionType(t) : t.isUnion(); 1137 if (isNullableUnionType) { 1138 return t.getNonNullableType(); 1139 } 1140 return t; 1141 } 1142 1143 private isObjectLiteralAssignableToUnion(lhsType: ts.UnionType, rhsExpr: ts.ObjectLiteralExpression): boolean { 1144 for (const compType of lhsType.types) { 1145 if (this.isObjectLiteralAssignable(compType, rhsExpr)) { 1146 return true; 1147 } 1148 } 1149 return false; 1150 } 1151 1152 isObjectLiteralAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.ObjectLiteralExpression): boolean { 1153 if (lhsType === undefined) { 1154 return false; 1155 } 1156 // Always check with the non-nullable variant of lhs type. 1157 // eslint-disable-next-line no-param-reassign 1158 lhsType = this.getNonNullableType(lhsType); 1159 if (lhsType.isUnion() && this.isObjectLiteralAssignableToUnion(lhsType, rhsExpr)) { 1160 return true; 1161 } 1162 1163 /* 1164 * Allow initializing with anything when the type 1165 * originates from the library. 1166 */ 1167 if (TsUtils.isAnyType(lhsType) || this.isLibraryType(lhsType)) { 1168 return true; 1169 } 1170 1171 /* 1172 * issue 13412: 1173 * Allow initializing with a dynamic object when the LHS type 1174 * is primitive or defined in standard library. 1175 */ 1176 if (this.isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) { 1177 return true; 1178 } 1179 // For Partial<T>, Required<T>, Readonly<T> types, validate their argument type. 1180 if (this.isStdPartialType(lhsType) || this.isStdRequiredType(lhsType) || this.isStdReadonlyType(lhsType)) { 1181 if (lhsType.aliasTypeArguments && lhsType.aliasTypeArguments.length === 1) { 1182 // eslint-disable-next-line no-param-reassign 1183 lhsType = lhsType.aliasTypeArguments[0]; 1184 } else { 1185 return false; 1186 } 1187 } 1188 1189 /* 1190 * Allow initializing Record objects with object initializer. 1191 * Record supports any type for a its value, but the key value 1192 * must be either a string or number literal. 1193 */ 1194 if (this.isStdRecordType(lhsType)) { 1195 return this.validateRecordObjectKeys(rhsExpr); 1196 } 1197 return ( 1198 this.validateObjectLiteralType(lhsType) && !this.hasMethods(lhsType) && this.validateFields(lhsType, rhsExpr) 1199 ); 1200 } 1201 1202 private isDynamicObjectAssignedToStdType(lhsType: ts.Type, rhsExpr: ts.Expression): boolean { 1203 if (isStdLibraryType(lhsType) || TsUtils.isPrimitiveType(lhsType)) { 1204 // eslint-disable-next-line no-nested-ternary 1205 const rhsSym = ts.isCallExpression(rhsExpr) ? 1206 this.getSymbolOfCallExpression(rhsExpr) : 1207 this.options.useRtLogic ? 1208 this.trueSymbolAtLocation(rhsExpr) : 1209 this.tsTypeChecker.getSymbolAtLocation(rhsExpr); 1210 if (rhsSym && this.isLibrarySymbol(rhsSym)) { 1211 return true; 1212 } 1213 } 1214 return false; 1215 } 1216 1217 validateFields(objectType: ts.Type, objectLiteral: ts.ObjectLiteralExpression): boolean { 1218 for (const prop of objectLiteral.properties) { 1219 if (ts.isPropertyAssignment(prop)) { 1220 if (!this.validateField(objectType, prop)) { 1221 return false; 1222 } 1223 } 1224 } 1225 1226 return true; 1227 } 1228 1229 getPropertySymbol(type: ts.Type, prop: ts.PropertyAssignment): ts.Symbol | undefined { 1230 const propNameSymbol = this.tsTypeChecker.getSymbolAtLocation(prop.name); 1231 // eslint-disable-next-line no-nested-ternary 1232 const propName = propNameSymbol ? 1233 ts.symbolName(propNameSymbol) : 1234 ts.isMemberName(prop.name) ? 1235 ts.idText(prop.name) : 1236 prop.name.getText(); 1237 const propSym = this.findProperty(type, propName); 1238 return propSym; 1239 } 1240 1241 private validateField(type: ts.Type, prop: ts.PropertyAssignment): boolean { 1242 // Issue 15497: Use unescaped property name to find correpsponding property. 1243 const propSym = this.getPropertySymbol(type, prop); 1244 if (!propSym?.declarations?.length) { 1245 return false; 1246 } 1247 1248 const propType = this.tsTypeChecker.getTypeOfSymbolAtLocation(propSym, propSym.declarations[0]); 1249 const initExpr = TsUtils.unwrapParenthesized(prop.initializer); 1250 const rhsType = this.tsTypeChecker.getTypeAtLocation(initExpr); 1251 if (ts.isObjectLiteralExpression(initExpr)) { 1252 if (!this.isObjectLiteralAssignable(propType, initExpr)) { 1253 return false; 1254 } 1255 } else { 1256 // Only check for structural sub-typing. 1257 if ( 1258 this.needToDeduceStructuralIdentity(propType, rhsType, initExpr, this.needStrictMatchType(propType, rhsType)) 1259 ) { 1260 return false; 1261 } 1262 if (this.isWrongSendableFunctionAssignment(propType, rhsType)) { 1263 return false; 1264 } 1265 } 1266 1267 return true; 1268 } 1269 1270 validateRecordObjectKeys(objectLiteral: ts.ObjectLiteralExpression): boolean { 1271 for (const prop of objectLiteral.properties) { 1272 if (!prop.name || !this.isValidRecordObjectLiteralKey(prop.name)) { 1273 return false; 1274 } 1275 } 1276 return true; 1277 } 1278 1279 isValidRecordObjectLiteralKey(propName: ts.PropertyName): boolean { 1280 if (ts.isComputedPropertyName(propName)) { 1281 return this.isValidComputedPropertyName(propName, true); 1282 } 1283 return ts.isStringLiteral(propName) || ts.isNumericLiteral(propName); 1284 } 1285 1286 private static isSupportedTypeNodeKind(kind: ts.SyntaxKind): boolean { 1287 return ( 1288 kind !== ts.SyntaxKind.AnyKeyword && 1289 kind !== ts.SyntaxKind.UnknownKeyword && 1290 kind !== ts.SyntaxKind.SymbolKeyword && 1291 kind !== ts.SyntaxKind.IndexedAccessType && 1292 kind !== ts.SyntaxKind.ConditionalType && 1293 kind !== ts.SyntaxKind.MappedType && 1294 kind !== ts.SyntaxKind.InferType 1295 ); 1296 } 1297 1298 private isSupportedTypeHandleUnionTypeNode(typeNode: ts.UnionTypeNode): boolean { 1299 for (const unionTypeElem of typeNode.types) { 1300 if (!this.isSupportedType(unionTypeElem)) { 1301 return false; 1302 } 1303 } 1304 return true; 1305 } 1306 1307 private isSupportedTypeHandleTupleTypeNode(typeNode: ts.TupleTypeNode): boolean { 1308 for (const elem of typeNode.elements) { 1309 if (ts.isTypeNode(elem) && !this.isSupportedType(elem)) { 1310 return false; 1311 } 1312 if (ts.isNamedTupleMember(elem) && !this.isSupportedType(elem.type)) { 1313 return false; 1314 } 1315 } 1316 return true; 1317 } 1318 1319 isSupportedType(typeNode: ts.TypeNode): boolean { 1320 if (ts.isParenthesizedTypeNode(typeNode)) { 1321 return this.isSupportedType(typeNode.type); 1322 } 1323 1324 if (ts.isArrayTypeNode(typeNode)) { 1325 return this.isSupportedType(typeNode.elementType); 1326 } 1327 1328 if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) { 1329 for (const typeArg of typeNode.typeArguments) { 1330 if (!this.isSupportedType(typeArg)) { 1331 return false; 1332 } 1333 } 1334 return true; 1335 } 1336 1337 if (ts.isUnionTypeNode(typeNode)) { 1338 return this.isSupportedTypeHandleUnionTypeNode(typeNode); 1339 } 1340 1341 if (ts.isTupleTypeNode(typeNode)) { 1342 return this.isSupportedTypeHandleTupleTypeNode(typeNode); 1343 } 1344 1345 return ( 1346 !ts.isTypeLiteralNode(typeNode) && 1347 (this.options.advancedClassChecks || !ts.isTypeQueryNode(typeNode)) && 1348 !ts.isIntersectionTypeNode(typeNode) && 1349 TsUtils.isSupportedTypeNodeKind(typeNode.kind) 1350 ); 1351 } 1352 1353 isStructObjectInitializer(objectLiteral: ts.ObjectLiteralExpression): boolean { 1354 if (ts.isCallLikeExpression(objectLiteral.parent)) { 1355 const signature = this.tsTypeChecker.getResolvedSignature(objectLiteral.parent); 1356 const signDecl = signature?.declaration; 1357 return !!signDecl && ts.isConstructorDeclaration(signDecl) && isStructDeclaration(signDecl.parent); 1358 } 1359 return false; 1360 } 1361 1362 parentSymbolCache = new Map<ts.Symbol, string | undefined>(); 1363 1364 getParentSymbolName(symbol: ts.Symbol): string | undefined { 1365 const cached = this.parentSymbolCache.get(symbol); 1366 if (cached) { 1367 return cached; 1368 } 1369 1370 const name = this.tsTypeChecker.getFullyQualifiedName(symbol); 1371 const dotPosition = name.lastIndexOf('.'); 1372 const result = dotPosition === -1 ? undefined : name.substring(0, dotPosition); 1373 this.parentSymbolCache.set(symbol, result); 1374 return result; 1375 } 1376 1377 isGlobalSymbol(symbol: ts.Symbol): boolean { 1378 const parentName = this.getParentSymbolName(symbol); 1379 return !parentName || parentName === 'global'; 1380 } 1381 1382 isStdSymbol(symbol: ts.Symbol): boolean { 1383 const name = this.tsTypeChecker.getFullyQualifiedName(symbol); 1384 return name === SYMBOL || name === SYMBOL_CONSTRUCTOR; 1385 } 1386 1387 isStdSymbolAPI(symbol: ts.Symbol): boolean { 1388 const parentName = this.getParentSymbolName(symbol); 1389 if (!this.options.useRtLogic) { 1390 const name = parentName ? parentName : symbol.escapedName; 1391 return name === SYMBOL || name === SYMBOL_CONSTRUCTOR; 1392 } 1393 return !!parentName && (parentName === SYMBOL || parentName === SYMBOL_CONSTRUCTOR); 1394 } 1395 1396 isSymbolIterator(symbol: ts.Symbol): boolean { 1397 if (!this.options.useRtLogic) { 1398 const name = symbol.name; 1399 const parName = this.getParentSymbolName(symbol); 1400 return (parName === SYMBOL || parName === SYMBOL_CONSTRUCTOR) && name === ITERATOR; 1401 } 1402 return this.isStdSymbolAPI(symbol) && symbol.name === ITERATOR; 1403 } 1404 1405 isSymbolIteratorExpression(expr: ts.Expression): boolean { 1406 const symbol = this.trueSymbolAtLocation(expr); 1407 return !!symbol && this.isSymbolIterator(symbol); 1408 } 1409 1410 static isDefaultImport(importSpec: ts.ImportSpecifier): boolean { 1411 return importSpec?.propertyName?.text === 'default'; 1412 } 1413 1414 static getStartPos(nodeOrComment: ts.Node | ts.CommentRange): number { 1415 return nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia || 1416 nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia ? 1417 (nodeOrComment as ts.CommentRange).pos : 1418 (nodeOrComment as ts.Node).getStart(); 1419 } 1420 1421 static getEndPos(nodeOrComment: ts.Node | ts.CommentRange): number { 1422 return nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia || 1423 nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia ? 1424 (nodeOrComment as ts.CommentRange).end : 1425 (nodeOrComment as ts.Node).getEnd(); 1426 } 1427 1428 static getHighlightRange(nodeOrComment: ts.Node | ts.CommentRange, faultId: number): [number, number] { 1429 return ( 1430 this.highlightRangeHandlers.get(faultId)?.call(this, nodeOrComment) ?? [ 1431 this.getStartPos(nodeOrComment), 1432 this.getEndPos(nodeOrComment) 1433 ] 1434 ); 1435 } 1436 1437 static highlightRangeHandlers = new Map([ 1438 [FaultID.VarDeclaration, TsUtils.getVarDeclarationHighlightRange], 1439 [FaultID.CatchWithUnsupportedType, TsUtils.getCatchWithUnsupportedTypeHighlightRange], 1440 [FaultID.ForInStatement, TsUtils.getForInStatementHighlightRange], 1441 [FaultID.WithStatement, TsUtils.getWithStatementHighlightRange], 1442 [FaultID.DeleteOperator, TsUtils.getDeleteOperatorHighlightRange], 1443 [FaultID.TypeQuery, TsUtils.getTypeQueryHighlightRange], 1444 [FaultID.InstanceofUnsupported, TsUtils.getInstanceofUnsupportedHighlightRange], 1445 [FaultID.ConstAssertion, TsUtils.getConstAssertionHighlightRange], 1446 [FaultID.LimitedReturnTypeInference, TsUtils.getLimitedReturnTypeInferenceHighlightRange], 1447 [FaultID.LocalFunction, TsUtils.getLocalFunctionHighlightRange], 1448 [FaultID.FunctionBind, TsUtils.getFunctionApplyCallHighlightRange], 1449 [FaultID.FunctionBindError, TsUtils.getFunctionApplyCallHighlightRange], 1450 [FaultID.FunctionApplyCall, TsUtils.getFunctionApplyCallHighlightRange], 1451 [FaultID.DeclWithDuplicateName, TsUtils.getDeclWithDuplicateNameHighlightRange], 1452 [FaultID.ObjectLiteralNoContextType, TsUtils.getObjectLiteralNoContextTypeHighlightRange], 1453 [FaultID.ClassExpression, TsUtils.getClassExpressionHighlightRange], 1454 [FaultID.MultipleStaticBlocks, TsUtils.getMultipleStaticBlocksHighlightRange], 1455 [FaultID.ParameterProperties, TsUtils.getParameterPropertiesHighlightRange], 1456 [FaultID.SendableDefiniteAssignment, TsUtils.getSendableDefiniteAssignmentHighlightRange], 1457 [FaultID.ObjectTypeLiteral, TsUtils.getObjectTypeLiteralHighlightRange], 1458 [FaultID.StructuralIdentity, TsUtils.getStructuralIdentityHighlightRange], 1459 [FaultID.VoidOperator, TsUtils.getVoidOperatorHighlightRange], 1460 [FaultID.ImportLazyIdentifier, TsUtils.getImportLazyHighlightRange], 1461 [FaultID.IncompationbleFunctionType, TsUtils.getIncompationbleFunctionTypeHighlightRange] 1462 ]); 1463 1464 static getKeywordHighlightRange(nodeOrComment: ts.Node | ts.CommentRange, keyword: string): [number, number] { 1465 const start = this.getStartPos(nodeOrComment); 1466 return [start, start + keyword.length]; 1467 } 1468 1469 static getVarDeclarationHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1470 return this.getKeywordHighlightRange(nodeOrComment, 'var'); 1471 } 1472 1473 static getCatchWithUnsupportedTypeHighlightRange( 1474 nodeOrComment: ts.Node | ts.CommentRange 1475 ): [number, number] | undefined { 1476 const catchClauseNode = (nodeOrComment as ts.CatchClause).variableDeclaration; 1477 if (catchClauseNode !== undefined) { 1478 return [catchClauseNode.getStart(), catchClauseNode.getEnd()]; 1479 } 1480 1481 return undefined; 1482 } 1483 1484 static getForInStatementHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1485 return [ 1486 this.getEndPos((nodeOrComment as ts.ForInStatement).initializer) + 1, 1487 this.getStartPos((nodeOrComment as ts.ForInStatement).expression) - 1 1488 ]; 1489 } 1490 1491 static getWithStatementHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1492 return [this.getStartPos(nodeOrComment), (nodeOrComment as ts.WithStatement).statement.getStart() - 1]; 1493 } 1494 1495 static getDeleteOperatorHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1496 return this.getKeywordHighlightRange(nodeOrComment, 'delete'); 1497 } 1498 1499 static getTypeQueryHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1500 return this.getKeywordHighlightRange(nodeOrComment, 'typeof'); 1501 } 1502 1503 static getInstanceofUnsupportedHighlightRange( 1504 nodeOrComment: ts.Node | ts.CommentRange 1505 ): [number, number] | undefined { 1506 return this.getKeywordHighlightRange((nodeOrComment as ts.BinaryExpression).operatorToken, 'instanceof'); 1507 } 1508 1509 static getConstAssertionHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1510 if (nodeOrComment.kind === ts.SyntaxKind.AsExpression) { 1511 return [ 1512 (nodeOrComment as ts.AsExpression).expression.getEnd() + 1, 1513 (nodeOrComment as ts.AsExpression).type.getStart() - 1 1514 ]; 1515 } 1516 return [ 1517 (nodeOrComment as ts.TypeAssertion).expression.getEnd() + 1, 1518 (nodeOrComment as ts.TypeAssertion).type.getEnd() + 1 1519 ]; 1520 } 1521 1522 static getLimitedReturnTypeInferenceHighlightRange( 1523 nodeOrComment: ts.Node | ts.CommentRange 1524 ): [number, number] | undefined { 1525 let node: ts.Node | undefined; 1526 if (nodeOrComment.kind === ts.SyntaxKind.FunctionExpression) { 1527 // we got error about return type so it should be present 1528 node = (nodeOrComment as ts.FunctionExpression).type; 1529 } else if (nodeOrComment.kind === ts.SyntaxKind.FunctionDeclaration) { 1530 node = (nodeOrComment as ts.FunctionDeclaration).name; 1531 } else if (nodeOrComment.kind === ts.SyntaxKind.MethodDeclaration) { 1532 node = (nodeOrComment as ts.MethodDeclaration).name; 1533 } 1534 1535 if (node !== undefined) { 1536 return [node.getStart(), node.getEnd()]; 1537 } 1538 1539 return undefined; 1540 } 1541 1542 static getLocalFunctionHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1543 return this.getKeywordHighlightRange(nodeOrComment, 'function'); 1544 } 1545 1546 static getFunctionApplyCallHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1547 const pointPos = (nodeOrComment as ts.Node).getText().lastIndexOf('.'); 1548 return [this.getStartPos(nodeOrComment) + pointPos + 1, this.getEndPos(nodeOrComment)]; 1549 } 1550 1551 static getDeclWithDuplicateNameHighlightRange( 1552 nodeOrComment: ts.Node | ts.CommentRange 1553 ): [number, number] | undefined { 1554 // in case of private identifier no range update is needed 1555 const nameNode: ts.Node | undefined = (nodeOrComment as ts.NamedDeclaration).name; 1556 if (nameNode !== undefined) { 1557 return [nameNode.getStart(), nameNode.getEnd()]; 1558 } 1559 1560 return undefined; 1561 } 1562 1563 static getObjectLiteralNoContextTypeHighlightRange( 1564 nodeOrComment: ts.Node | ts.CommentRange 1565 ): [number, number] | undefined { 1566 return this.getKeywordHighlightRange(nodeOrComment, '{'); 1567 } 1568 1569 static getClassExpressionHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1570 return this.getKeywordHighlightRange(nodeOrComment, 'class'); 1571 } 1572 1573 static getMultipleStaticBlocksHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1574 return this.getKeywordHighlightRange(nodeOrComment, 'static'); 1575 } 1576 1577 static getParameterPropertiesHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1578 const param = nodeOrComment as ts.ParameterDeclaration; 1579 const modifier = TsUtils.getAccessModifier(ts.getModifiers(param)); 1580 if (modifier !== undefined) { 1581 return [modifier.getStart(), modifier.getEnd()]; 1582 } 1583 return undefined; 1584 } 1585 1586 static getObjectTypeLiteralHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1587 return this.getKeywordHighlightRange(nodeOrComment, '{'); 1588 } 1589 1590 // highlight ranges for Sendable rules 1591 1592 static getSendableDefiniteAssignmentHighlightRange( 1593 nodeOrComment: ts.Node | ts.CommentRange 1594 ): [number, number] | undefined { 1595 const name = (nodeOrComment as ts.PropertyDeclaration).name; 1596 const exclamationToken = (nodeOrComment as ts.PropertyDeclaration).exclamationToken; 1597 return [name.getStart(), exclamationToken ? exclamationToken.getEnd() : name.getEnd()]; 1598 } 1599 1600 static getStructuralIdentityHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1601 let node: ts.Node | undefined; 1602 if (nodeOrComment.kind === ts.SyntaxKind.ReturnStatement) { 1603 node = (nodeOrComment as ts.ReturnStatement).expression; 1604 } else if (nodeOrComment.kind === ts.SyntaxKind.PropertyDeclaration) { 1605 node = (nodeOrComment as ts.PropertyDeclaration).name; 1606 } 1607 1608 if (node !== undefined) { 1609 return [node.getStart(), node.getEnd()]; 1610 } 1611 1612 return undefined; 1613 } 1614 1615 static getIncompationbleFunctionTypeHighlightRange( 1616 nodeOrComment: ts.Node | ts.CommentRange 1617 ): [number, number] | undefined { 1618 const node = nodeOrComment as ts.Node; 1619 if (ts.isArrowFunction(node)) { 1620 const parameters = node.parameters; 1621 if (parameters.length > 0) { 1622 const firstParamStart = parameters[0].getStart(); 1623 const lastParamEnd = parameters[parameters.length - 1].getEnd(); 1624 return [firstParamStart, lastParamEnd]; 1625 } 1626 } 1627 return [node.getStart(), node.getEnd()]; 1628 } 1629 1630 static getVoidOperatorHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1631 return this.getKeywordHighlightRange(nodeOrComment, 'void'); 1632 } 1633 1634 static getImportLazyHighlightRange(nodeOrComment: ts.Node | ts.CommentRange): [number, number] | undefined { 1635 return this.getKeywordHighlightRange(nodeOrComment, 'lazy'); 1636 } 1637 1638 isStdRecordType(type: ts.Type): boolean { 1639 1640 /* 1641 * In TypeScript, 'Record<K, T>' is defined as type alias to a mapped type. 1642 * Thus, it should have 'aliasSymbol' and 'target' properties. The 'target' 1643 * in this case will resolve to origin 'Record' symbol. 1644 */ 1645 if (type.aliasSymbol) { 1646 const target = (type as ts.TypeReference).target; 1647 if (target) { 1648 const sym = target.aliasSymbol; 1649 return !!sym && sym.getName() === 'Record' && this.isGlobalSymbol(sym); 1650 } 1651 } 1652 1653 return false; 1654 } 1655 1656 isStdErrorType(type: ts.Type): boolean { 1657 const symbol = type.symbol; 1658 if (!symbol) { 1659 return false; 1660 } 1661 const name = this.tsTypeChecker.getFullyQualifiedName(symbol); 1662 return name === 'Error' && this.isGlobalSymbol(symbol); 1663 } 1664 1665 isStdPartialType(type: ts.Type): boolean { 1666 const sym = type.aliasSymbol; 1667 return !!sym && sym.getName() === 'Partial' && this.isGlobalSymbol(sym); 1668 } 1669 1670 isStdRequiredType(type: ts.Type): boolean { 1671 const sym = type.aliasSymbol; 1672 return !!sym && sym.getName() === 'Required' && this.isGlobalSymbol(sym); 1673 } 1674 1675 isStdReadonlyType(type: ts.Type): boolean { 1676 const sym = type.aliasSymbol; 1677 return !!sym && sym.getName() === 'Readonly' && this.isGlobalSymbol(sym); 1678 } 1679 1680 isLibraryType(type: ts.Type): boolean { 1681 const nonNullableType = type.getNonNullableType(); 1682 if (nonNullableType.isUnion()) { 1683 for (const componentType of nonNullableType.types) { 1684 if (!this.isLibraryType(componentType)) { 1685 return false; 1686 } 1687 } 1688 return true; 1689 } 1690 return this.isLibrarySymbol(nonNullableType.aliasSymbol ?? nonNullableType.getSymbol()); 1691 } 1692 1693 hasLibraryType(node: ts.Node): boolean { 1694 return this.isLibraryType(this.tsTypeChecker.getTypeAtLocation(node)); 1695 } 1696 1697 isLibrarySymbol(sym: ts.Symbol | undefined): boolean { 1698 if (sym?.declarations && sym.declarations.length > 0) { 1699 const srcFile = sym.declarations[0].getSourceFile(); 1700 if (!srcFile) { 1701 return false; 1702 } 1703 const fileName = srcFile.fileName; 1704 1705 /* 1706 * Symbols from both *.ts and *.d.ts files should obey interop rules. 1707 * We disable such behavior for *.ts files in the test mode due to lack of 'ets' 1708 * extension support. 1709 */ 1710 const ext = path.extname(fileName).toLowerCase(); 1711 const isThirdPartyCode = 1712 ARKTS_IGNORE_DIRS.some((ignore) => { 1713 return srcFilePathContainsDirectory(srcFile, ignore); 1714 }) || 1715 ARKTS_IGNORE_FILES.some((ignore) => { 1716 return path.basename(fileName) === ignore; 1717 }); 1718 const isEts = ext === '.ets'; 1719 const isTs = ext === '.ts' && !srcFile.isDeclarationFile; 1720 const isStatic = (isEts || isTs && this.options.checkTsAsSource) && !isThirdPartyCode; 1721 const isStdLib = STANDARD_LIBRARIES.includes(path.basename(fileName).toLowerCase()); 1722 1723 /* 1724 * We still need to confirm support for certain API from the 1725 * TypeScript standard library in ArkTS. Thus, for now do not 1726 * count standard library modules as dynamic. 1727 */ 1728 return !isStatic && !isStdLib; 1729 } 1730 return false; 1731 } 1732 1733 static isOhModulesEtsSymbol(sym: ts.Symbol | undefined): boolean { 1734 const sourceFile = sym?.declarations?.[0]?.getSourceFile(); 1735 return ( 1736 !!sourceFile && 1737 path.extname(sourceFile.fileName).toLowerCase() === ETS && 1738 srcFilePathContainsDirectory(sourceFile, ARKTS_IGNORE_DIRS_OH_MODULES) 1739 ); 1740 } 1741 1742 isDynamicType(type: ts.Type | undefined): boolean | undefined { 1743 if (type === undefined) { 1744 return false; 1745 } 1746 1747 /* 1748 * Return 'true' if it is an object of library type initialization, otherwise 1749 * return 'false' if it is not an object of standard library type one. 1750 * In the case of standard library type we need to determine context. 1751 */ 1752 1753 /* 1754 * Check the non-nullable version of type to eliminate 'undefined' type 1755 * from the union type elements. 1756 */ 1757 // eslint-disable-next-line no-param-reassign 1758 type = type.getNonNullableType(); 1759 1760 if (type.isUnion()) { 1761 for (const compType of type.types) { 1762 const isDynamic = this.isDynamicType(compType); 1763 if (isDynamic || isDynamic === undefined) { 1764 return isDynamic; 1765 } 1766 } 1767 return false; 1768 } 1769 1770 if (this.isLibraryType(type)) { 1771 return true; 1772 } 1773 1774 if (!isStdLibraryType(type) && !isIntrinsicObjectType(type) && !TsUtils.isAnyType(type)) { 1775 return false; 1776 } 1777 1778 return undefined; 1779 } 1780 1781 static isObjectType(type: ts.Type): type is ts.ObjectType { 1782 return !!(type.flags & ts.TypeFlags.Object); 1783 } 1784 1785 private static isAnonymous(type: ts.Type): boolean { 1786 if (TsUtils.isObjectType(type)) { 1787 return !!(type.objectFlags & ts.ObjectFlags.Anonymous); 1788 } 1789 return false; 1790 } 1791 1792 private isDynamicLiteralInitializerHandleCallExpression(callExpr: ts.CallExpression): boolean { 1793 const type = this.tsTypeChecker.getTypeAtLocation(callExpr.expression); 1794 1795 if (TsUtils.isAnyType(type)) { 1796 return true; 1797 } 1798 1799 let sym: ts.Symbol | undefined = type.symbol; 1800 if (this.isLibrarySymbol(sym)) { 1801 return true; 1802 } 1803 1804 /* 1805 * #13483: 1806 * x.foo({ ... }), where 'x' is exported from some library: 1807 */ 1808 if (ts.isPropertyAccessExpression(callExpr.expression)) { 1809 sym = this.trueSymbolAtLocation(callExpr.expression.expression); 1810 if (sym && this.isLibrarySymbol(sym)) { 1811 return true; 1812 } 1813 } 1814 1815 return false; 1816 } 1817 1818 isDynamicLiteralInitializer(expr: ts.Expression): boolean { 1819 if (!ts.isObjectLiteralExpression(expr) && !ts.isArrayLiteralExpression(expr)) { 1820 return false; 1821 } 1822 1823 /* 1824 * Handle nested literals: 1825 * { f: { ... } } 1826 */ 1827 let curNode: ts.Node = expr; 1828 while (ts.isObjectLiteralExpression(curNode) || ts.isArrayLiteralExpression(curNode)) { 1829 const exprType = this.tsTypeChecker.getContextualType(curNode); 1830 if (exprType !== undefined && !TsUtils.isAnonymous(exprType)) { 1831 const res = this.isDynamicType(exprType); 1832 if (res !== undefined) { 1833 return res; 1834 } 1835 } 1836 1837 curNode = curNode.parent; 1838 if (ts.isPropertyAssignment(curNode)) { 1839 curNode = curNode.parent; 1840 } 1841 } 1842 1843 /* 1844 * Handle calls with literals: 1845 * foo({ ... }) 1846 */ 1847 if (ts.isCallExpression(curNode) && this.isDynamicLiteralInitializerHandleCallExpression(curNode)) { 1848 return true; 1849 } 1850 1851 /* 1852 * Handle property assignments with literals: 1853 * obj.f = { ... } 1854 */ 1855 if (ts.isBinaryExpression(curNode)) { 1856 const binExpr = curNode; 1857 if (ts.isPropertyAccessExpression(binExpr.left)) { 1858 const propAccessExpr = binExpr.left; 1859 const type = this.tsTypeChecker.getTypeAtLocation(propAccessExpr.expression); 1860 return this.isLibrarySymbol(type.symbol); 1861 } 1862 } 1863 1864 return false; 1865 } 1866 1867 static isEsValueType(typeNode: ts.TypeNode | undefined): boolean { 1868 return ( 1869 !!typeNode && 1870 ts.isTypeReferenceNode(typeNode) && 1871 ts.isIdentifier(typeNode.typeName) && 1872 typeNode.typeName.text === ES_VALUE 1873 ); 1874 } 1875 1876 static isInsideBlock(node: ts.Node): boolean { 1877 let par = node.parent; 1878 while (par) { 1879 if (ts.isBlock(par)) { 1880 return true; 1881 } 1882 par = par.parent; 1883 } 1884 return false; 1885 } 1886 1887 static isEsValuePossiblyAllowed(typeRef: ts.TypeReferenceNode): boolean { 1888 return ts.isVariableDeclaration(typeRef.parent); 1889 } 1890 1891 isValueAssignableToESValue(node: ts.Node): boolean { 1892 if (ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node)) { 1893 return false; 1894 } 1895 const valueType = this.tsTypeChecker.getTypeAtLocation(node); 1896 return TsUtils.isUnsupportedType(valueType) || TsUtils.isAnonymousType(valueType); 1897 } 1898 1899 getVariableDeclarationTypeNode(node: ts.Node): ts.TypeNode | undefined { 1900 const sym = this.trueSymbolAtLocation(node); 1901 if (sym === undefined) { 1902 return undefined; 1903 } 1904 return TsUtils.getVariableSymbolDeclarationTypeNode(sym); 1905 } 1906 1907 static getVariableSymbolDeclarationTypeNode(sym: ts.Symbol): ts.TypeNode | undefined { 1908 const decl = TsUtils.getDeclaration(sym); 1909 if (!!decl && ts.isVariableDeclaration(decl)) { 1910 return decl.type; 1911 } 1912 return undefined; 1913 } 1914 1915 getDeclarationTypeNode(node: ts.Node): ts.TypeNode | undefined { 1916 const sym = this.trueSymbolAtLocation(node); 1917 if (sym === undefined) { 1918 return undefined; 1919 } 1920 return TsUtils.getSymbolDeclarationTypeNode(sym); 1921 } 1922 1923 static getSymbolDeclarationTypeNode(sym: ts.Symbol): ts.TypeNode | undefined { 1924 const decl = TsUtils.getDeclaration(sym); 1925 if (!!decl && (ts.isVariableDeclaration(decl) || ts.isPropertyDeclaration(decl))) { 1926 return decl.type; 1927 } 1928 return undefined; 1929 } 1930 1931 hasEsObjectType(node: ts.Node): boolean { 1932 const typeNode = this.getVariableDeclarationTypeNode(node); 1933 return typeNode !== undefined && TsUtils.isEsValueType(typeNode); 1934 } 1935 1936 static symbolHasEsObjectType(sym: ts.Symbol): boolean { 1937 const typeNode = TsUtils.getVariableSymbolDeclarationTypeNode(sym); 1938 return typeNode !== undefined && TsUtils.isEsValueType(typeNode); 1939 } 1940 1941 static isEsObjectSymbol(sym: ts.Symbol): boolean { 1942 const decl = TsUtils.getDeclaration(sym); 1943 return ( 1944 !!decl && 1945 ts.isTypeAliasDeclaration(decl) && 1946 decl.name.escapedText === ES_VALUE && 1947 decl.type.kind === ts.SyntaxKind.AnyKeyword 1948 ); 1949 } 1950 1951 static isAnonymousType(type: ts.Type): boolean { 1952 if (type.isUnionOrIntersection()) { 1953 for (const compType of type.types) { 1954 if (TsUtils.isAnonymousType(compType)) { 1955 return true; 1956 } 1957 } 1958 return false; 1959 } 1960 1961 return ( 1962 (type.flags & ts.TypeFlags.Object) !== 0 && ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0 1963 ); 1964 } 1965 1966 getSymbolOfCallExpression(callExpr: ts.CallExpression): ts.Symbol | undefined { 1967 const signature = this.tsTypeChecker.getResolvedSignature(callExpr); 1968 const signDecl = signature?.getDeclaration(); 1969 if (signDecl?.name) { 1970 return this.trueSymbolAtLocation(signDecl.name); 1971 } 1972 return undefined; 1973 } 1974 1975 static isClassValueType(type: ts.Type): boolean { 1976 if ( 1977 (type.flags & ts.TypeFlags.Object) === 0 || 1978 ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) === 0 1979 ) { 1980 return false; 1981 } 1982 return type.symbol && (type.symbol.flags & ts.SymbolFlags.Class) !== 0; 1983 } 1984 1985 isClassObjectExpression(expr: ts.Expression): boolean { 1986 if (!TsUtils.isClassValueType(this.tsTypeChecker.getTypeAtLocation(expr))) { 1987 return false; 1988 } 1989 const symbol = this.trueSymbolAtLocation(expr); 1990 return !symbol || (symbol.flags & ts.SymbolFlags.Class) === 0; 1991 } 1992 1993 isClassTypeExpression(expr: ts.Expression): boolean { 1994 const sym = this.trueSymbolAtLocation(expr); 1995 return sym !== undefined && (sym.flags & ts.SymbolFlags.Class) !== 0; 1996 } 1997 1998 isFunctionCalledRecursively(funcExpr: ts.FunctionExpression): boolean { 1999 if (!funcExpr.name) { 2000 return false; 2001 } 2002 2003 const sym = this.tsTypeChecker.getSymbolAtLocation(funcExpr.name); 2004 if (!sym) { 2005 return false; 2006 } 2007 2008 let found = false; 2009 const callback = (node: ts.Node): void => { 2010 if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) { 2011 const callSym = this.tsTypeChecker.getSymbolAtLocation(node.expression); 2012 if (callSym && callSym === sym) { 2013 found = true; 2014 } 2015 } 2016 }; 2017 2018 const stopCondition = (node: ts.Node): boolean => { 2019 void node; 2020 return found; 2021 }; 2022 2023 forEachNodeInSubtree(funcExpr, callback, stopCondition); 2024 return found; 2025 } 2026 2027 getTypeOrTypeConstraintAtLocation(expr: ts.Expression): ts.Type { 2028 const type = this.tsTypeChecker.getTypeAtLocation(expr); 2029 if (type.isTypeParameter()) { 2030 const constraint = type.getConstraint(); 2031 if (constraint) { 2032 return constraint; 2033 } 2034 } 2035 return type; 2036 } 2037 2038 private areCompatibleFunctionals(lhsType: ts.Type, rhsType: ts.Type): boolean { 2039 return ( 2040 (this.isStdFunctionType(lhsType) || TsUtils.isFunctionalType(lhsType)) && 2041 (this.isStdFunctionType(rhsType) || TsUtils.isFunctionalType(rhsType)) 2042 ); 2043 } 2044 2045 isIncompatibleFunctionals(lhsTypeNode: ts.TypeNode, rhsExpr: ts.Expression): boolean { 2046 if (ts.isUnionTypeNode(lhsTypeNode)) { 2047 for (let i = 0; i < lhsTypeNode.types.length; i++) { 2048 if (!this.isIncompatibleFunctional(lhsTypeNode.types[i], rhsExpr)) { 2049 return false; 2050 } 2051 } 2052 return true; 2053 } 2054 return this.isIncompatibleFunctional(lhsTypeNode, rhsExpr); 2055 } 2056 2057 private isIncompatibleFunctional(lhsTypeNode: ts.TypeNode, rhsExpr: ts.Expression): boolean { 2058 const lhsType = this.tsTypeChecker.getTypeAtLocation(lhsTypeNode); 2059 const rhsType = this.tsTypeChecker.getTypeAtLocation(rhsExpr); 2060 const lhsParams = this.getLhsFunctionParameters(lhsTypeNode); 2061 const rhsParams = this.getRhsFunctionParameters(rhsExpr); 2062 2063 if (lhsParams !== rhsParams) { 2064 return false; 2065 } 2066 2067 if (TsUtils.isFunctionalType(lhsType)) { 2068 const lhsFunctionReturnType = this.getFunctionType(lhsTypeNode); 2069 const rhsReturnType = this.getReturnTypeFromExpression(rhsType); 2070 if (lhsFunctionReturnType && rhsReturnType) { 2071 return TsUtils.isVoidType(lhsFunctionReturnType) && !TsUtils.isVoidType(rhsReturnType); 2072 } 2073 } 2074 return false; 2075 } 2076 2077 static isVoidType(tsType: ts.Type): boolean { 2078 return (tsType.getFlags() & ts.TypeFlags.Void) !== 0; 2079 } 2080 2081 private getRhsFunctionParameters(expr: ts.Expression): number { 2082 const type = this.tsTypeChecker.getTypeAtLocation(expr); 2083 const signatures = this.tsTypeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); 2084 if (signatures.length > 0) { 2085 const signature = signatures[0]; 2086 return signature.parameters.length; 2087 } 2088 return 0; 2089 } 2090 2091 private getLhsFunctionParameters(typeNode: ts.TypeNode): number { 2092 let current: ts.TypeNode = typeNode; 2093 while (ts.isTypeReferenceNode(current)) { 2094 const symbol = this.tsTypeChecker.getSymbolAtLocation(current.typeName); 2095 if (!symbol) { 2096 break; 2097 } 2098 2099 const declaration = symbol.declarations?.[0]; 2100 if (!declaration || !ts.isTypeAliasDeclaration(declaration)) { 2101 break; 2102 } 2103 2104 current = declaration.type; 2105 } 2106 if (ts.isFunctionTypeNode(current)) { 2107 return current.parameters.length; 2108 } 2109 return 0; 2110 } 2111 2112 private getFunctionType(typeNode: ts.TypeNode): ts.Type | undefined { 2113 let current: ts.TypeNode = typeNode; 2114 while (ts.isTypeReferenceNode(current)) { 2115 const symbol = this.tsTypeChecker.getSymbolAtLocation(current.typeName); 2116 if (!symbol) { 2117 break; 2118 } 2119 2120 const declaration = symbol.declarations?.[0]; 2121 if (!declaration || !ts.isTypeAliasDeclaration(declaration)) { 2122 break; 2123 } 2124 2125 current = declaration.type; 2126 } 2127 if (ts.isFunctionTypeNode(current)) { 2128 return this.tsTypeChecker.getTypeAtLocation(current.type); 2129 } 2130 return undefined; 2131 } 2132 2133 private getReturnTypeFromExpression(type: ts.Type): ts.Type | undefined { 2134 const signatures = this.tsTypeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); 2135 if (signatures.length > 0) { 2136 const returnType = this.tsTypeChecker.getReturnTypeOfSignature(signatures[0]); 2137 return returnType; 2138 } 2139 return undefined; 2140 } 2141 2142 static isFunctionalType(type: ts.Type): boolean { 2143 const callSigns = type.getCallSignatures(); 2144 return callSigns && callSigns.length > 0; 2145 } 2146 2147 static getFunctionReturnType(type: ts.Type): ts.Type | null { 2148 const signatures = type.getCallSignatures(); 2149 if (signatures.length === 0) { 2150 return null; 2151 } 2152 return signatures[0].getReturnType(); 2153 } 2154 2155 isStdFunctionType(type: ts.Type): boolean { 2156 const sym = type.getSymbol(); 2157 return !!sym && sym.getName() === 'Function' && this.isGlobalSymbol(sym); 2158 } 2159 2160 isStdBigIntType(type: ts.Type): boolean { 2161 const sym = type.symbol; 2162 return !!sym && sym.getName() === 'BigInt' && this.isGlobalSymbol(sym); 2163 } 2164 2165 isStdNumberType(type: ts.Type): boolean { 2166 const sym = type.symbol; 2167 return !!sym && sym.getName() === 'Number' && this.isGlobalSymbol(sym); 2168 } 2169 2170 isStdBooleanType(type: ts.Type): boolean { 2171 const sym = type.symbol; 2172 return !!sym && sym.getName() === 'Boolean' && this.isGlobalSymbol(sym); 2173 } 2174 2175 isEnumStringLiteral(expr: ts.Expression): boolean { 2176 const symbol = this.trueSymbolAtLocation(expr); 2177 const isEnumMember = !!symbol && !!(symbol.flags & ts.SymbolFlags.EnumMember); 2178 const type = this.tsTypeChecker.getTypeAtLocation(expr); 2179 const isStringEnumLiteral = TsUtils.isEnumType(type) && !!(type.flags & ts.TypeFlags.StringLiteral); 2180 return isEnumMember && isStringEnumLiteral; 2181 } 2182 2183 isValidComputedPropertyName(computedProperty: ts.ComputedPropertyName, isRecordObjectInitializer = false): boolean { 2184 const expr = computedProperty.expression; 2185 if (!isRecordObjectInitializer) { 2186 if (this.isSymbolIteratorExpression(expr)) { 2187 return true; 2188 } 2189 } 2190 // In ArkTS 1.0, the computed property names are allowed if expression is string literal or string Enum member. 2191 return ( 2192 !this.options.arkts2 && (ts.isStringLiteralLike(expr) || this.isEnumStringLiteral(computedProperty.expression)) 2193 ); 2194 } 2195 2196 skipPropertyInferredTypeCheck( 2197 decl: ts.PropertyDeclaration, 2198 sourceFile: ts.SourceFile | undefined, 2199 isEtsFileCb: IsEtsFileCallback | undefined 2200 ): boolean { 2201 if (!sourceFile) { 2202 return false; 2203 } 2204 2205 const isEts = this.options.useRtLogic ? 2206 !!isEtsFileCb && isEtsFileCb(sourceFile) : 2207 getScriptKind(sourceFile) === ts.ScriptKind.ETS; 2208 return ( 2209 isEts && 2210 sourceFile.isDeclarationFile && 2211 !!decl.modifiers?.some((m) => { 2212 return m.kind === ts.SyntaxKind.PrivateKeyword; 2213 }) 2214 ); 2215 } 2216 2217 hasAccessModifier(decl: ts.HasModifiers): boolean { 2218 const modifiers = ts.getModifiers(decl); 2219 return ( 2220 !!modifiers && 2221 (!this.options.useRtLogic && TsUtils.hasModifier(modifiers, ts.SyntaxKind.ReadonlyKeyword) || 2222 TsUtils.hasModifier(modifiers, ts.SyntaxKind.PublicKeyword) || 2223 TsUtils.hasModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) || 2224 TsUtils.hasModifier(modifiers, ts.SyntaxKind.PrivateKeyword) || 2225 !!this.options.arkts2 && 2226 (TsUtils.hasModifier(modifiers, ts.SyntaxKind.ReadonlyKeyword) || 2227 TsUtils.hasModifier(modifiers, ts.SyntaxKind.OverrideKeyword))) 2228 ); 2229 } 2230 2231 static getModifier( 2232 modifiers: readonly ts.Modifier[] | undefined, 2233 modifierKind: ts.SyntaxKind 2234 ): ts.Modifier | undefined { 2235 if (!modifiers) { 2236 return undefined; 2237 } 2238 return modifiers.find((x) => { 2239 return x.kind === modifierKind; 2240 }); 2241 } 2242 2243 static getAccessModifier(modifiers: readonly ts.Modifier[] | undefined): ts.Modifier | undefined { 2244 return ( 2245 TsUtils.getModifier(modifiers, ts.SyntaxKind.PublicKeyword) ?? 2246 TsUtils.getModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) ?? 2247 TsUtils.getModifier(modifiers, ts.SyntaxKind.PrivateKeyword) 2248 ); 2249 } 2250 2251 static getBaseClassType(type: ts.Type): ts.InterfaceType | undefined { 2252 const baseTypes = type.getBaseTypes(); 2253 if (baseTypes) { 2254 for (const baseType of baseTypes) { 2255 if (baseType.isClass()) { 2256 return baseType; 2257 } 2258 } 2259 } 2260 2261 return undefined; 2262 } 2263 2264 static destructuringAssignmentHasSpreadOperator(node: ts.AssignmentPattern): boolean { 2265 if (ts.isArrayLiteralExpression(node)) { 2266 return node.elements.some((x) => { 2267 if (ts.isSpreadElement(x)) { 2268 return true; 2269 } 2270 if (ts.isObjectLiteralExpression(x) || ts.isArrayLiteralExpression(x)) { 2271 return TsUtils.destructuringAssignmentHasSpreadOperator(x); 2272 } 2273 return false; 2274 }); 2275 } 2276 2277 return node.properties.some((x) => { 2278 if (ts.isSpreadAssignment(x)) { 2279 return true; 2280 } 2281 if ( 2282 ts.isPropertyAssignment(x) && 2283 (ts.isObjectLiteralExpression(x.initializer) || ts.isArrayLiteralExpression(x.initializer)) 2284 ) { 2285 return TsUtils.destructuringAssignmentHasSpreadOperator(x.initializer); 2286 } 2287 return false; 2288 }); 2289 } 2290 2291 static destructuringDeclarationHasSpreadOperator(node: ts.BindingPattern): boolean { 2292 return node.elements.some((x) => { 2293 if (ts.isBindingElement(x)) { 2294 if (x.dotDotDotToken) { 2295 return true; 2296 } 2297 if (ts.isArrayBindingPattern(x.name) || ts.isObjectBindingPattern(x.name)) { 2298 return TsUtils.destructuringDeclarationHasSpreadOperator(x.name); 2299 } 2300 } 2301 return false; 2302 }); 2303 } 2304 2305 // Check if the destructuring assignment has default values 2306 static destructuringAssignmentHasDefaultValue(node: ts.AssignmentPattern): boolean { 2307 if (ts.isArrayLiteralExpression(node)) { 2308 return node.elements.some((x) => { 2309 if (ts.isBinaryExpression(x) && x.right) { 2310 return true; 2311 } 2312 if (ts.isObjectLiteralExpression(x) || ts.isArrayLiteralExpression(x)) { 2313 return TsUtils.destructuringAssignmentHasDefaultValue(x); 2314 } 2315 return false; 2316 }); 2317 } 2318 2319 return node.properties.some((x) => { 2320 if (ts.isShorthandPropertyAssignment(x) && x.equalsToken) { 2321 return true; 2322 } 2323 if ( 2324 ts.isPropertyAssignment(x) && 2325 (ts.isObjectLiteralExpression(x.initializer) || ts.isArrayLiteralExpression(x.initializer)) 2326 ) { 2327 return TsUtils.destructuringAssignmentHasDefaultValue(x.initializer); 2328 } 2329 return false; 2330 }); 2331 } 2332 2333 // Check if the destructuring declaration has default values 2334 static destructuringDeclarationHasDefaultValue(node: ts.BindingPattern): boolean { 2335 return node.elements.some((x) => { 2336 if (ts.isBindingElement(x)) { 2337 if (x.initializer) { 2338 return true; 2339 } 2340 if (ts.isArrayBindingPattern(x.name) || ts.isObjectBindingPattern(x.name)) { 2341 return TsUtils.destructuringDeclarationHasDefaultValue(x.name); 2342 } 2343 } 2344 return false; 2345 }); 2346 } 2347 2348 // Check that the dimensions of the destruct array are the same 2349 static isSameDimension(arr: ts.Node): boolean | undefined { 2350 if (!ts.isArrayLiteralExpression(arr)) { 2351 return true; 2352 } 2353 let hasOneDimensional: boolean = false; 2354 let hasMultipleDimensions: boolean = false; 2355 const arrElements = arr.elements; 2356 let maxlen: number = 0; 2357 let minlen: number = 1000; 2358 for (let i = 0; i < arrElements.length; i++) { 2359 const e = arrElements[i] as ts.Node; 2360 if (!ts.isArrayLiteralExpression(e)) { 2361 hasOneDimensional = true; 2362 } else { 2363 hasMultipleDimensions = true; 2364 if (hasOneDimensional && hasMultipleDimensions) { 2365 return false; 2366 } 2367 if (!this.isSameDimension(e)) { 2368 return false; 2369 } 2370 maxlen = Math.max(e.elements.length, maxlen); 2371 minlen = Math.min(e.elements.length, minlen); 2372 } 2373 } 2374 if (hasOneDimensional && hasMultipleDimensions) { 2375 return false; 2376 } else if (hasOneDimensional && !hasMultipleDimensions) { 2377 return true; 2378 } else if (!hasOneDimensional && hasMultipleDimensions && maxlen === minlen) { 2379 return true; 2380 } 2381 return false; 2382 } 2383 2384 // if ArrayLiteralExpression has empty element, will be marked as not fixable 2385 static checkArrayLiteralHasEmptyElement(node: ts.ArrayLiteralExpression): boolean { 2386 return node.elements.some((x) => { 2387 if (ts.isOmittedExpression(x)) { 2388 return true; 2389 } 2390 if (ts.isArrayLiteralExpression(x)) { 2391 if (x.elements.length === 0) { 2392 return true; 2393 } 2394 return this.checkArrayLiteralHasEmptyElement(x); 2395 } 2396 return false; 2397 }); 2398 } 2399 2400 // if ArrayBindingPattern has empty element, will be marked as not fixable 2401 static checkArrayBindingHasEmptyElement(node: ts.ArrayBindingPattern): boolean { 2402 return node.elements.some((x) => { 2403 if (ts.isOmittedExpression(x)) { 2404 return true; 2405 } 2406 if (ts.isBindingElement(x) && ts.isArrayBindingPattern(x.name)) { 2407 if (x.name.elements.length === 0) { 2408 return true; 2409 } 2410 return this.checkArrayBindingHasEmptyElement(x.name); 2411 } 2412 return false; 2413 }); 2414 } 2415 2416 static hasNestedObjectDestructuring(node: ts.ArrayBindingOrAssignmentPattern): boolean { 2417 if (ts.isArrayLiteralExpression(node)) { 2418 return node.elements.some((x) => { 2419 const elem = ts.isSpreadElement(x) ? x.expression : x; 2420 if (ts.isArrayLiteralExpression(elem)) { 2421 return TsUtils.hasNestedObjectDestructuring(elem); 2422 } 2423 return ts.isObjectLiteralExpression(elem); 2424 }); 2425 } 2426 2427 return node.elements.some((x) => { 2428 if (ts.isBindingElement(x)) { 2429 if (ts.isArrayBindingPattern(x.name)) { 2430 return TsUtils.hasNestedObjectDestructuring(x.name); 2431 } 2432 return ts.isObjectBindingPattern(x.name); 2433 } 2434 return false; 2435 }); 2436 } 2437 2438 static getDecoratorName(decorator: ts.Decorator): string { 2439 let decoratorName = ''; 2440 if (ts.isIdentifier(decorator.expression)) { 2441 decoratorName = decorator.expression.text; 2442 } else if (ts.isCallExpression(decorator.expression) && ts.isIdentifier(decorator.expression.expression)) { 2443 decoratorName = decorator.expression.expression.text; 2444 } 2445 return decoratorName; 2446 } 2447 2448 static unwrapParenthesizedTypeNode(typeNode: ts.TypeNode): ts.TypeNode { 2449 let unwrappedTypeNode = typeNode; 2450 while (ts.isParenthesizedTypeNode(unwrappedTypeNode)) { 2451 unwrappedTypeNode = unwrappedTypeNode.type; 2452 } 2453 2454 return unwrappedTypeNode; 2455 } 2456 2457 isSendableTypeNode(typeNode: ts.TypeNode, isShared: boolean = false): boolean { 2458 2459 /* 2460 * In order to correctly identify the usage of the enum member or 2461 * const enum in type annotation, we need to handle union type and 2462 * type alias cases by processing the type node and checking the 2463 * symbol in case of type reference node. 2464 */ 2465 2466 // eslint-disable-next-line no-param-reassign 2467 typeNode = TsUtils.unwrapParenthesizedTypeNode(typeNode); 2468 2469 // Only a sendable union type is supported 2470 if (ts.isUnionTypeNode(typeNode)) { 2471 return typeNode.types.every((elemType) => { 2472 return this.isSendableTypeNode(elemType, isShared); 2473 }); 2474 } 2475 2476 const sym = ts.isTypeReferenceNode(typeNode) ? this.trueSymbolAtLocation(typeNode.typeName) : undefined; 2477 2478 if (sym && sym.getFlags() & ts.SymbolFlags.TypeAlias) { 2479 const typeDecl = TsUtils.getDeclaration(sym); 2480 if (typeDecl && ts.isTypeAliasDeclaration(typeDecl)) { 2481 const typeArgs = (typeNode as ts.TypeReferenceNode).typeArguments; 2482 if ( 2483 typeArgs && 2484 !typeArgs.every((typeArg) => { 2485 return this.isSendableTypeNode(typeArg); 2486 }) 2487 ) { 2488 return false; 2489 } 2490 return this.isSendableTypeNode(typeDecl.type, isShared); 2491 } 2492 } 2493 2494 // Const enum type is supported 2495 if (TsUtils.isConstEnum(sym)) { 2496 return true; 2497 } 2498 const type: ts.Type = this.tsTypeChecker.getTypeFromTypeNode(typeNode); 2499 2500 // In shared module, literal forms of primitive data types can be exported 2501 if (isShared && TsUtils.isPurePrimitiveLiteralType(type)) { 2502 return true; 2503 } 2504 2505 return this.isSendableType(type); 2506 } 2507 2508 isSendableType(type: ts.Type): boolean { 2509 if ( 2510 (type.flags & 2511 (ts.TypeFlags.Boolean | 2512 ts.TypeFlags.Number | 2513 ts.TypeFlags.String | 2514 ts.TypeFlags.BigInt | 2515 ts.TypeFlags.Null | 2516 ts.TypeFlags.Undefined | 2517 ts.TypeFlags.TypeParameter)) !== 2518 0 2519 ) { 2520 return true; 2521 } 2522 if (this.isSendableTypeAlias(type)) { 2523 return true; 2524 } 2525 if (TsUtils.isSendableFunction(type)) { 2526 return true; 2527 } 2528 2529 return this.isSendableClassOrInterface(type); 2530 } 2531 2532 isShareableType(tsType: ts.Type): boolean { 2533 const sym = tsType.getSymbol(); 2534 if (TsUtils.isConstEnum(sym)) { 2535 return true; 2536 } 2537 2538 if (tsType.isUnion()) { 2539 return tsType.types.every((elemType) => { 2540 return this.isShareableType(elemType); 2541 }); 2542 } 2543 2544 if (TsUtils.isPurePrimitiveLiteralType(tsType)) { 2545 return true; 2546 } 2547 2548 return this.isSendableType(tsType); 2549 } 2550 2551 isSendableClassOrInterface(type: ts.Type): boolean { 2552 const sym = type.getSymbol(); 2553 if (!sym) { 2554 return false; 2555 } 2556 2557 const targetType = TsUtils.reduceReference(type); 2558 2559 // class with @Sendable decorator 2560 if (targetType.isClass()) { 2561 if (sym.declarations?.length) { 2562 const decl = sym.declarations[0]; 2563 if (ts.isClassDeclaration(decl)) { 2564 return TsUtils.hasSendableDecorator(decl); 2565 } 2566 } 2567 } 2568 // ISendable interface, or a class/interface that implements/extends ISendable interface 2569 return this.isOrDerivedFrom(type, TsUtils.isISendableInterface); 2570 } 2571 2572 typeContainsSendableClassOrInterface(type: ts.Type): boolean { 2573 // Only check type contains sendable class / interface 2574 if ((type.flags & ts.TypeFlags.Union) !== 0) { 2575 return !!(type as ts.UnionType)?.types?.some((type) => { 2576 return this.typeContainsSendableClassOrInterface(type); 2577 }); 2578 } 2579 2580 return this.isSendableClassOrInterface(type); 2581 } 2582 2583 typeContainsNonSendableClassOrInterface(type: ts.Type): boolean { 2584 if (type.isUnion()) { 2585 return type.types.some((compType) => { 2586 return this.typeContainsNonSendableClassOrInterface(compType); 2587 }); 2588 } 2589 // eslint-disable-next-line no-param-reassign 2590 type = TsUtils.reduceReference(type); 2591 return type.isClassOrInterface() && !this.isSendableClassOrInterface(type); 2592 } 2593 2594 static isConstEnum(sym: ts.Symbol | undefined): boolean { 2595 return !!sym && sym.flags === ts.SymbolFlags.ConstEnum; 2596 } 2597 2598 isSendableUnionType(type: ts.UnionType): boolean { 2599 const types = type?.types; 2600 if (!types) { 2601 return false; 2602 } 2603 2604 return types.every((type) => { 2605 return this.isSendableType(type); 2606 }); 2607 } 2608 2609 static hasSendableDecorator(decl: ts.ClassDeclaration | ts.FunctionDeclaration | ts.TypeAliasDeclaration): boolean { 2610 return !!TsUtils.getSendableDecorator(decl); 2611 } 2612 2613 static getNonSendableDecorators( 2614 decl: ts.ClassDeclaration | ts.FunctionDeclaration | ts.TypeAliasDeclaration 2615 ): ts.Decorator[] | undefined { 2616 const decorators = ts.getAllDecorators(decl); 2617 return decorators?.filter((x) => { 2618 return TsUtils.getDecoratorName(x) !== SENDABLE_DECORATOR; 2619 }); 2620 } 2621 2622 static getSendableDecorator( 2623 decl: ts.ClassDeclaration | ts.FunctionDeclaration | ts.TypeAliasDeclaration 2624 ): ts.Decorator | undefined { 2625 const decorators = ts.getAllDecorators(decl); 2626 return decorators?.find((x) => { 2627 return TsUtils.getDecoratorName(x) === SENDABLE_DECORATOR; 2628 }); 2629 } 2630 2631 static getDecoratorsIfInSendableClass(declaration: ts.HasDecorators): readonly ts.Decorator[] | undefined { 2632 const classNode = TsUtils.getClassNodeFromDeclaration(declaration); 2633 if (classNode === undefined || !TsUtils.hasSendableDecorator(classNode)) { 2634 return undefined; 2635 } 2636 return ts.getDecorators(declaration); 2637 } 2638 2639 private static getClassNodeFromDeclaration(declaration: ts.HasDecorators): ts.ClassDeclaration | undefined { 2640 if (declaration.kind === ts.SyntaxKind.Parameter) { 2641 return ts.isClassDeclaration(declaration.parent.parent) ? declaration.parent.parent : undefined; 2642 } 2643 return ts.isClassDeclaration(declaration.parent) ? declaration.parent : undefined; 2644 } 2645 2646 static isISendableInterface(type: ts.Type): boolean { 2647 const symbol = type.aliasSymbol ?? type.getSymbol(); 2648 if (symbol?.declarations === undefined || symbol.declarations.length < 1) { 2649 return false; 2650 } 2651 2652 return TsUtils.isArkTSISendableDeclaration(symbol.declarations[0]); 2653 } 2654 2655 private static isArkTSISendableDeclaration(decl: ts.Declaration): boolean { 2656 if (!ts.isInterfaceDeclaration(decl) || !decl.name || decl.name.text !== ISENDABLE_TYPE) { 2657 return false; 2658 } 2659 2660 if (!ts.isModuleBlock(decl.parent) || decl.parent.parent.name.text !== LANG_NAMESPACE) { 2661 return false; 2662 } 2663 2664 if (path.basename(decl.getSourceFile().fileName).toLowerCase() !== ARKTS_LANG_D_ETS) { 2665 return false; 2666 } 2667 2668 return true; 2669 } 2670 2671 isAllowedIndexSignature(node: ts.IndexSignatureDeclaration): boolean { 2672 2673 /* 2674 * For now, relax index signature only for specific array-like types 2675 * with the following signature: 'collections.Array<T>.[_: number]: T'. 2676 */ 2677 2678 if (node.parameters.length !== 1) { 2679 return false; 2680 } 2681 2682 const paramType = this.tsTypeChecker.getTypeAtLocation(node.parameters[0]); 2683 if ((paramType.flags & ts.TypeFlags.Number) === 0) { 2684 return false; 2685 } 2686 2687 return this.isArkTSCollectionsArrayLikeDeclaration(node.parent); 2688 } 2689 2690 isArkTSCollectionsArrayLikeType(type: ts.Type): boolean { 2691 const symbol = type.aliasSymbol ?? type.getSymbol(); 2692 if (symbol?.declarations === undefined || symbol.declarations.length < 1) { 2693 return false; 2694 } 2695 2696 return this.isArkTSCollectionsArrayLikeDeclaration(symbol.declarations[0]); 2697 } 2698 2699 private isArkTSCollectionsArrayLikeDeclaration(decl: ts.Declaration): boolean { 2700 if (!TsUtils.isArkTSCollectionsClassOrInterfaceDeclaration(decl)) { 2701 return false; 2702 } 2703 if (!this.tsTypeChecker.getTypeAtLocation(decl).getNumberIndexType()) { 2704 return false; 2705 } 2706 return true; 2707 } 2708 2709 static isArkTSCollectionsClassOrInterfaceDeclaration(decl: ts.Node): boolean { 2710 if (!ts.isClassDeclaration(decl) && !ts.isInterfaceDeclaration(decl) || !decl.name) { 2711 return false; 2712 } 2713 if (!ts.isModuleBlock(decl.parent) || decl.parent.parent.name.text !== COLLECTIONS_NAMESPACE) { 2714 return false; 2715 } 2716 if (path.basename(decl.getSourceFile().fileName).toLowerCase() !== ARKTS_COLLECTIONS_D_ETS) { 2717 return false; 2718 } 2719 return true; 2720 } 2721 2722 private proceedConstructorDeclaration( 2723 isFromPrivateIdentifierOrSdk: boolean, 2724 targetMember: ts.ClassElement, 2725 classMember: ts.ClassElement, 2726 isFromPrivateIdentifier: boolean 2727 ): boolean | undefined { 2728 if ( 2729 isFromPrivateIdentifierOrSdk && 2730 ts.isConstructorDeclaration(classMember) && 2731 classMember.parameters.some((x) => { 2732 return ( 2733 ts.isIdentifier(x.name) && 2734 this.hasAccessModifier(x) && 2735 this.isPrivateIdentifierDuplicateOfIdentifier( 2736 targetMember.name as ts.Identifier, 2737 x.name, 2738 isFromPrivateIdentifier 2739 ) 2740 ); 2741 }) 2742 ) { 2743 return true; 2744 } 2745 return undefined; 2746 } 2747 2748 private proceedClassType( 2749 targetMember: ts.ClassElement, 2750 classType: ts.Type, 2751 isFromPrivateIdentifier: boolean 2752 ): boolean | undefined { 2753 if (classType) { 2754 const baseType = TsUtils.getBaseClassType(classType); 2755 if (baseType) { 2756 const baseDecl = baseType.getSymbol()?.valueDeclaration as ts.ClassLikeDeclaration; 2757 if (baseDecl) { 2758 return this.classMemberHasDuplicateName(targetMember, baseDecl, isFromPrivateIdentifier); 2759 } 2760 } 2761 } 2762 return undefined; 2763 } 2764 2765 classMemberHasDuplicateName( 2766 targetMember: ts.ClassElement, 2767 tsClassLikeDecl: ts.ClassLikeDeclaration, 2768 isFromPrivateIdentifier: boolean, 2769 classType?: ts.Type 2770 ): boolean { 2771 2772 /* 2773 * If two class members have the same name where one is a private identifer, 2774 * then such members are considered to have duplicate names. 2775 */ 2776 if (!TsUtils.isIdentifierOrPrivateIdentifier(targetMember.name)) { 2777 return false; 2778 } 2779 2780 const isFromPrivateIdentifierOrSdk = this.isFromPrivateIdentifierOrSdk(isFromPrivateIdentifier); 2781 for (const classMember of tsClassLikeDecl.members) { 2782 if (targetMember === classMember) { 2783 continue; 2784 } 2785 2786 // Check constructor parameter properties. 2787 const constructorDeclarationProceedResult = this.proceedConstructorDeclaration( 2788 isFromPrivateIdentifierOrSdk, 2789 targetMember, 2790 classMember, 2791 isFromPrivateIdentifier 2792 ); 2793 if (constructorDeclarationProceedResult) { 2794 return constructorDeclarationProceedResult; 2795 } 2796 if (!TsUtils.isIdentifierOrPrivateIdentifier(classMember.name)) { 2797 continue; 2798 } 2799 if (this.isPrivateIdentifierDuplicateOfIdentifier(targetMember.name, classMember.name, isFromPrivateIdentifier)) { 2800 return true; 2801 } 2802 } 2803 2804 if (isFromPrivateIdentifierOrSdk) { 2805 // eslint-disable-next-line no-param-reassign 2806 classType = classType ?? this.tsTypeChecker.getTypeAtLocation(tsClassLikeDecl); 2807 const proceedClassTypeResult = this.proceedClassType(targetMember, classType, isFromPrivateIdentifier); 2808 if (proceedClassTypeResult) { 2809 return proceedClassTypeResult; 2810 } 2811 } 2812 2813 return false; 2814 } 2815 2816 private isFromPrivateIdentifierOrSdk(isFromPrivateIdentifier: boolean): boolean { 2817 return this.options.useRtLogic || isFromPrivateIdentifier; 2818 } 2819 2820 private static isIdentifierOrPrivateIdentifier(node?: ts.PropertyName): node is ts.Identifier | ts.PrivateIdentifier { 2821 if (!node) { 2822 return false; 2823 } 2824 return ts.isIdentifier(node) || ts.isPrivateIdentifier(node); 2825 } 2826 2827 private isPrivateIdentifierDuplicateOfIdentifier( 2828 ident1: ts.Identifier | ts.PrivateIdentifier, 2829 ident2: ts.Identifier | ts.PrivateIdentifier, 2830 isFromPrivateIdentifier: boolean 2831 ): boolean { 2832 if (ts.isIdentifier(ident1) && ts.isPrivateIdentifier(ident2)) { 2833 return ident1.text === ident2.text.substring(1); 2834 } 2835 if (ts.isIdentifier(ident2) && ts.isPrivateIdentifier(ident1)) { 2836 return ident2.text === ident1.text.substring(1); 2837 } 2838 if ( 2839 this.isFromPrivateIdentifierOrSdk(isFromPrivateIdentifier) && 2840 ts.isPrivateIdentifier(ident1) && 2841 ts.isPrivateIdentifier(ident2) 2842 ) { 2843 return ident1.text.substring(1) === ident2.text.substring(1); 2844 } 2845 return false; 2846 } 2847 2848 findIdentifierNameForSymbol(symbol: ts.Symbol, enumMember?: ts.EnumMember): string | undefined { 2849 let name = TsUtils.getIdentifierNameFromString(symbol.name); 2850 if (name === undefined || name === symbol.name) { 2851 return name; 2852 } 2853 2854 const enumExpress = enumMember?.parent; 2855 const parentType = this.getTypeByProperty(symbol); 2856 if (parentType === undefined) { 2857 return undefined; 2858 } 2859 if (enumExpress) { 2860 while (this.findEnumMember(enumExpress, name)) { 2861 name = '_' + name; 2862 } 2863 } 2864 while (this.findProperty(parentType, name) !== undefined) { 2865 name = '_' + name; 2866 } 2867 2868 return name; 2869 } 2870 2871 private static getIdentifierNameFromString(str: string): string | undefined { 2872 if (str === '') { 2873 return '__empty'; 2874 } 2875 let result: string = ''; 2876 2877 let offset = 0; 2878 while (offset < str.length) { 2879 const codePoint = str.codePointAt(offset); 2880 if (!codePoint) { 2881 return undefined; 2882 } 2883 2884 const charSize = TsUtils.charSize(codePoint); 2885 2886 if (offset === 0 && !ts.isIdentifierStart(codePoint, undefined)) { 2887 result = '__'; 2888 } 2889 2890 if (!ts.isIdentifierPart(codePoint, undefined)) { 2891 if (codePoint === 0x20) { 2892 result += '_'; 2893 } else { 2894 result += 'x' + codePoint.toString(16); 2895 } 2896 } else { 2897 for (let i = 0; i < charSize; i++) { 2898 result += str.charAt(offset + i); 2899 } 2900 } 2901 2902 offset += charSize; 2903 } 2904 2905 return result; 2906 } 2907 2908 private static charSize(codePoint: number): number { 2909 return codePoint >= 0x10000 ? 2 : 1; 2910 } 2911 2912 private getTypeByProperty(symbol: ts.Symbol): ts.Type | undefined { 2913 if (symbol.declarations === undefined) { 2914 return undefined; 2915 } 2916 2917 for (const propDecl of symbol.declarations) { 2918 if ( 2919 !ts.isPropertyDeclaration(propDecl) && 2920 !ts.isPropertyAssignment(propDecl) && 2921 !ts.isPropertySignature(propDecl) && 2922 !ts.isEnumMember(propDecl) 2923 ) { 2924 return undefined; 2925 } 2926 2927 const type = this.tsTypeChecker.getTypeAtLocation(propDecl.parent); 2928 if (type !== undefined) { 2929 return type; 2930 } 2931 } 2932 2933 return undefined; 2934 } 2935 2936 static isPropertyOfInternalClassOrInterface(symbol: ts.Symbol): boolean { 2937 if (symbol.declarations === undefined) { 2938 return false; 2939 } 2940 2941 for (const propDecl of symbol.declarations) { 2942 if (!ts.isPropertyDeclaration(propDecl) && !ts.isPropertySignature(propDecl) && !ts.isEnumMember(propDecl)) { 2943 return false; 2944 } 2945 2946 if ( 2947 !ts.isClassDeclaration(propDecl.parent) && 2948 !ts.isInterfaceDeclaration(propDecl.parent) && 2949 !ts.isEnumDeclaration(propDecl.parent) 2950 ) { 2951 return false; 2952 } 2953 2954 if (TsUtils.hasModifier(ts.getModifiers(propDecl.parent), ts.SyntaxKind.ExportKeyword)) { 2955 return false; 2956 } 2957 } 2958 2959 return true; 2960 } 2961 2962 static isIntrinsicObjectType(type: ts.Type): boolean { 2963 return !!(type.flags & ts.TypeFlags.NonPrimitive); 2964 } 2965 2966 isStringType(tsType: ts.Type): boolean { 2967 if ((tsType.getFlags() & ts.TypeFlags.String) !== 0) { 2968 return true; 2969 } 2970 2971 if (!TsUtils.isTypeReference(tsType)) { 2972 return false; 2973 } 2974 2975 const symbol = tsType.symbol; 2976 if (symbol === undefined) { 2977 return false; 2978 } 2979 const name = this.tsTypeChecker.getFullyQualifiedName(symbol); 2980 return name === 'String' && this.isGlobalSymbol(symbol); 2981 } 2982 2983 isStdMapType(type: ts.Type): boolean { 2984 const sym = type.symbol; 2985 return !!sym && sym.getName() === 'Map' && this.isGlobalSymbol(sym); 2986 } 2987 2988 hasGenericTypeParameter(type: ts.Type): boolean { 2989 if (type.isUnionOrIntersection()) { 2990 return type.types.some((x) => { 2991 return this.hasGenericTypeParameter(x); 2992 }); 2993 } 2994 if (TsUtils.isTypeReference(type)) { 2995 const typeArgs = this.tsTypeChecker.getTypeArguments(type); 2996 return typeArgs.some((x) => { 2997 return this.hasGenericTypeParameter(x); 2998 }); 2999 } 3000 return type.isTypeParameter(); 3001 } 3002 3003 static getEnclosingTopLevelStatement(node: ts.Node): ts.Node | undefined { 3004 return ts.findAncestor(node, (ancestor) => { 3005 return ts.isSourceFile(ancestor.parent); 3006 }); 3007 } 3008 3009 static isDeclarationStatement(node: ts.Node): node is ts.DeclarationStatement { 3010 const kind = node.kind; 3011 return ( 3012 kind === ts.SyntaxKind.FunctionDeclaration || 3013 kind === ts.SyntaxKind.ModuleDeclaration || 3014 kind === ts.SyntaxKind.ClassDeclaration || 3015 kind === ts.SyntaxKind.StructDeclaration || 3016 kind === ts.SyntaxKind.TypeAliasDeclaration || 3017 kind === ts.SyntaxKind.InterfaceDeclaration || 3018 kind === ts.SyntaxKind.EnumDeclaration || 3019 kind === ts.SyntaxKind.MissingDeclaration || 3020 kind === ts.SyntaxKind.ImportEqualsDeclaration || 3021 kind === ts.SyntaxKind.ImportDeclaration || 3022 kind === ts.SyntaxKind.NamespaceExportDeclaration 3023 ); 3024 } 3025 3026 static declarationNameExists(srcFile: ts.SourceFile, name: string): boolean { 3027 return srcFile.statements.some((stmt) => { 3028 return ( 3029 TsUtils.checkDeclarationInBlockStatement(stmt, name) || 3030 TsUtils.checkImportDeclaration(stmt, name) || 3031 TsUtils.checkDeclarationInLoopStatement(stmt, name) || 3032 TsUtils.checkDeclarationInVariableStatement(stmt, name) || 3033 TsUtils.checkGeneralDeclaration(stmt, name) 3034 ); 3035 }); 3036 } 3037 3038 static checkDeclarationInBlockStatement(stmt: ts.Statement, name: string): boolean { 3039 if (ts.isBlock(stmt)) { 3040 return stmt.statements.some((innerStmt) => { 3041 return ( 3042 TsUtils.checkDeclarationInVariableStatement(innerStmt, name) || 3043 TsUtils.checkGeneralDeclaration(innerStmt, name) 3044 ); 3045 }); 3046 } 3047 return false; 3048 } 3049 3050 static checkImportDeclaration(stmt: ts.Statement, name: string): boolean { 3051 if (ts.isImportDeclaration(stmt)) { 3052 if (!stmt.importClause) { 3053 return false; 3054 } 3055 if (stmt.importClause.namedBindings) { 3056 if (ts.isNamespaceImport(stmt.importClause.namedBindings)) { 3057 return stmt.importClause.namedBindings.name.text === name; 3058 } 3059 return stmt.importClause.namedBindings.elements.some((x) => { 3060 return x.name.text === name; 3061 }); 3062 } 3063 return stmt.importClause.name?.text === name; 3064 } 3065 return false; 3066 } 3067 3068 static checkDeclarationInLoopStatement(stmt: ts.Statement, name: string): boolean { 3069 if ( 3070 ts.isForStatement(stmt) || 3071 ts.isWhileStatement(stmt) || 3072 ts.isDoStatement(stmt) || 3073 ts.isForOfStatement(stmt) || 3074 ts.isForInStatement(stmt) 3075 ) { 3076 if (ts.isBlock(stmt.statement)) { 3077 return stmt.statement.statements.some((innerStmt) => { 3078 return ( 3079 TsUtils.checkDeclarationInVariableStatement(innerStmt, name) || 3080 TsUtils.checkGeneralDeclaration(innerStmt, name) 3081 ); 3082 }); 3083 } 3084 } 3085 return false; 3086 } 3087 3088 static checkDeclarationInVariableStatement(stmt: ts.Statement, name: string): boolean { 3089 if (ts.isVariableStatement(stmt)) { 3090 return stmt.declarationList.declarations.some((decl) => { 3091 return decl.name.getText() === name; 3092 }); 3093 } 3094 return false; 3095 } 3096 3097 static checkGeneralDeclaration(stmt: ts.Statement, name: string): boolean { 3098 if (ts.isFunctionDeclaration(stmt) && stmt.body !== undefined) { 3099 return ( 3100 stmt.body.statements.some((innerStmt) => { 3101 return TsUtils.checkDeclarationInVariableStatement(innerStmt, name); 3102 }) || 3103 stmt.name !== undefined && ts.isIdentifier(stmt.name) && stmt.name.text === name 3104 ); 3105 } 3106 return ( 3107 TsUtils.isDeclarationStatement(stmt) && 3108 stmt.name !== undefined && 3109 ts.isIdentifier(stmt.name) && 3110 stmt.name.text === name 3111 ); 3112 } 3113 3114 static generateUniqueName(nameGenerator: NameGenerator, srcFile: ts.SourceFile): string | undefined { 3115 let newName: string | undefined; 3116 3117 do { 3118 newName = nameGenerator.getName(); 3119 if (newName !== undefined && TsUtils.declarationNameExists(srcFile, newName)) { 3120 continue; 3121 } 3122 break; 3123 } while (newName !== undefined); 3124 3125 return newName; 3126 } 3127 3128 static isSharedModule(sourceFile: ts.SourceFile): boolean { 3129 const statements = sourceFile.statements; 3130 for (const statement of statements) { 3131 if (ts.isImportDeclaration(statement)) { 3132 continue; 3133 } 3134 3135 return ( 3136 ts.isExpressionStatement(statement) && 3137 ts.isStringLiteral(statement.expression) && 3138 statement.expression.text === USE_SHARED 3139 ); 3140 } 3141 return false; 3142 } 3143 3144 getDeclarationNode(node: ts.Node): ts.Declaration | undefined { 3145 const sym = this.trueSymbolAtLocation(node); 3146 return TsUtils.getDeclaration(sym); 3147 } 3148 3149 static isFunctionLikeDeclaration(node: ts.Declaration): boolean { 3150 return ( 3151 ts.isFunctionDeclaration(node) || 3152 ts.isMethodDeclaration(node) || 3153 ts.isGetAccessorDeclaration(node) || 3154 ts.isSetAccessorDeclaration(node) || 3155 ts.isConstructorDeclaration(node) || 3156 ts.isFunctionExpression(node) || 3157 ts.isArrowFunction(node) 3158 ); 3159 } 3160 3161 isShareableEntity(node: ts.Node): boolean { 3162 const decl = this.getDeclarationNode(node); 3163 // CC-OFFNXT(no_explicit_any) std lib 3164 const typeNode = (decl as any)?.type; 3165 return typeNode && !TsUtils.isFunctionLikeDeclaration(decl!) ? 3166 this.isSendableTypeNode(typeNode, true) : 3167 this.isShareableType(this.tsTypeChecker.getTypeAtLocation(decl ? decl : node)); 3168 } 3169 3170 isSendableClassOrInterfaceEntity(node: ts.Node): boolean { 3171 const decl = this.getDeclarationNode(node); 3172 if (!decl) { 3173 return false; 3174 } 3175 if (ts.isClassDeclaration(decl)) { 3176 return TsUtils.hasSendableDecorator(decl); 3177 } 3178 if (ts.isInterfaceDeclaration(decl)) { 3179 return this.isOrDerivedFrom(this.tsTypeChecker.getTypeAtLocation(decl), TsUtils.isISendableInterface); 3180 } 3181 return false; 3182 } 3183 3184 static isInImportWhiteList(resolvedModule: ts.ResolvedModuleFull): boolean { 3185 if ( 3186 !resolvedModule.resolvedFileName || 3187 path.basename(resolvedModule.resolvedFileName).toLowerCase() !== ARKTS_LANG_D_ETS && 3188 path.basename(resolvedModule.resolvedFileName).toLowerCase() !== ARKTS_COLLECTIONS_D_ETS 3189 ) { 3190 return false; 3191 } 3192 return true; 3193 } 3194 3195 // If it is an overloaded function, all declarations for that function are found 3196 static hasSendableDecoratorFunctionOverload(decl: ts.FunctionDeclaration): boolean { 3197 const decorators = TsUtils.getFunctionOverloadDecorators(decl); 3198 return !!decorators?.some((x) => { 3199 return TsUtils.getDecoratorName(x) === SENDABLE_DECORATOR; 3200 }); 3201 } 3202 3203 static getFunctionOverloadDecorators(funcDecl: ts.FunctionDeclaration): readonly ts.Decorator[] | undefined { 3204 const decls = funcDecl.symbol.getDeclarations(); 3205 if (!decls?.length) { 3206 return undefined; 3207 } 3208 let result: ts.Decorator[] = []; 3209 decls.forEach((decl) => { 3210 if (!ts.isFunctionDeclaration(decl)) { 3211 return; 3212 } 3213 const decorators = ts.getAllDecorators(decl); 3214 if (decorators?.length) { 3215 result = result.concat(decorators); 3216 } 3217 }); 3218 return result.length ? result : undefined; 3219 } 3220 3221 static isSendableFunction(type: ts.Type): boolean { 3222 const callSigns = type.getCallSignatures(); 3223 if (!callSigns?.length) { 3224 return false; 3225 } 3226 const decl = callSigns[0].declaration; 3227 if (!decl || !ts.isFunctionDeclaration(decl)) { 3228 return false; 3229 } 3230 return TsUtils.hasSendableDecoratorFunctionOverload(decl); 3231 } 3232 3233 isSendableTypeAlias(type: ts.Type): boolean { 3234 const decl = this.getTypsAliasOriginalDecl(type); 3235 return !!decl && TsUtils.hasSendableDecorator(decl); 3236 } 3237 3238 hasSendableTypeAlias(type: ts.Type): boolean { 3239 if (type.isUnion()) { 3240 return type.types.some((compType) => { 3241 return this.hasSendableTypeAlias(compType); 3242 }); 3243 } 3244 return this.isSendableTypeAlias(type); 3245 } 3246 3247 isNonSendableFunctionTypeAlias(type: ts.Type): boolean { 3248 const decl = this.getTypsAliasOriginalDecl(type); 3249 return !!decl && ts.isFunctionTypeNode(decl.type) && !TsUtils.hasSendableDecorator(decl); 3250 } 3251 3252 // If the alias refers to another alias, the search continues 3253 private getTypsAliasOriginalDecl(type: ts.Type): ts.TypeAliasDeclaration | undefined { 3254 if (!type.aliasSymbol) { 3255 return undefined; 3256 } 3257 const decl = TsUtils.getDeclaration(type.aliasSymbol); 3258 if (!decl || !ts.isTypeAliasDeclaration(decl)) { 3259 return undefined; 3260 } 3261 if (ts.isTypeReferenceNode(decl.type)) { 3262 const targetType = this.tsTypeChecker.getTypeAtLocation(decl.type.typeName); 3263 if (targetType.aliasSymbol && targetType.aliasSymbol.getFlags() & ts.SymbolFlags.TypeAlias) { 3264 return this.getTypsAliasOriginalDecl(targetType); 3265 } 3266 } 3267 return decl; 3268 } 3269 3270 // not allow 'lhsType' contains 'sendable typeAlias' && 'rhsType' contains 'non-sendable function/non-sendable function typeAlias' 3271 isWrongSendableFunctionAssignment(lhsType: ts.Type, rhsType: ts.Type): boolean { 3272 // eslint-disable-next-line no-param-reassign 3273 lhsType = this.getNonNullableType(lhsType); 3274 // eslint-disable-next-line no-param-reassign 3275 rhsType = this.getNonNullableType(rhsType); 3276 if (!this.hasSendableTypeAlias(lhsType)) { 3277 return false; 3278 } 3279 3280 if (rhsType.isUnion()) { 3281 return rhsType.types.some((compType) => { 3282 return this.isInvalidSendableFunctionAssignmentType(compType); 3283 }); 3284 } 3285 return this.isInvalidSendableFunctionAssignmentType(rhsType); 3286 } 3287 3288 private isInvalidSendableFunctionAssignmentType(type: ts.Type): boolean { 3289 if (type.aliasSymbol) { 3290 return this.isNonSendableFunctionTypeAlias(type); 3291 } 3292 if (TsUtils.isFunctionalType(type)) { 3293 return !TsUtils.isSendableFunction(type); 3294 } 3295 return false; 3296 } 3297 3298 static isSetExpression(accessExpr: ts.ElementAccessExpression): boolean { 3299 if (!ts.isBinaryExpression(accessExpr.parent)) { 3300 return false; 3301 } 3302 const binaryExpr = accessExpr.parent; 3303 return binaryExpr.operatorToken.kind === ts.SyntaxKind.EqualsToken && binaryExpr.left === accessExpr; 3304 } 3305 3306 haveSameBaseType(type1: ts.Type, type2: ts.Type): boolean { 3307 return this.tsTypeChecker.getBaseTypeOfLiteralType(type1) === this.tsTypeChecker.getBaseTypeOfLiteralType(type2); 3308 } 3309 3310 isGetIndexableType(type: ts.Type, indexType: ts.Type): boolean { 3311 const getDecls = type.getProperty('$_get')?.getDeclarations(); 3312 if (getDecls?.length !== 1 || getDecls[0].kind !== ts.SyntaxKind.MethodDeclaration) { 3313 return false; 3314 } 3315 const getMethodDecl = getDecls[0] as ts.MethodDeclaration; 3316 const getParams = getMethodDecl.parameters; 3317 if (getMethodDecl.type === undefined || getParams.length !== 1 || getParams[0].type === undefined) { 3318 return false; 3319 } 3320 3321 return this.haveSameBaseType(this.tsTypeChecker.getTypeFromTypeNode(getParams[0].type), indexType); 3322 } 3323 3324 isSetIndexableType(type: ts.Type, indexType: ts.Type, valueType: ts.Type): boolean { 3325 const setProp = type.getProperty('$_set'); 3326 const setDecls = setProp?.getDeclarations(); 3327 if (setDecls?.length !== 1 || setDecls[0].kind !== ts.SyntaxKind.MethodDeclaration) { 3328 return false; 3329 } 3330 const setMethodDecl = setDecls[0] as ts.MethodDeclaration; 3331 const setParams = setMethodDecl.parameters; 3332 if ( 3333 setMethodDecl.type !== undefined || 3334 setParams.length !== 2 || 3335 setParams[0].type === undefined || 3336 setParams[1].type === undefined 3337 ) { 3338 return false; 3339 } 3340 3341 return ( 3342 this.haveSameBaseType(this.tsTypeChecker.getTypeFromTypeNode(setParams[0].type), indexType) && 3343 this.haveSameBaseType(this.tsTypeChecker.getTypeFromTypeNode(setParams[1].type), valueType) 3344 ); 3345 } 3346 3347 // Search for and save the exported declaration in the specified file, re-exporting another module will not be included. 3348 searchFileExportDecl(sourceFile: ts.SourceFile, targetDecls?: ts.SyntaxKind[]): Set<ts.Node> { 3349 const exportDeclSet = new Set<ts.Node>(); 3350 const appendDecl = (decl: ts.Node | undefined): void => { 3351 if (!decl || targetDecls && !targetDecls.includes(decl.kind)) { 3352 return; 3353 } 3354 exportDeclSet.add(decl); 3355 }; 3356 3357 sourceFile.statements.forEach((statement: ts.Statement) => { 3358 if (ts.isExportAssignment(statement)) { 3359 // handle the case:"export default declName;" 3360 if (statement.isExportEquals) { 3361 return; 3362 } 3363 appendDecl(this.getDeclarationNode(statement.expression)); 3364 } else if (ts.isExportDeclaration(statement)) { 3365 // handle the case:"export { declName1, declName2 };" 3366 if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) { 3367 return; 3368 } 3369 statement.exportClause.elements.forEach((specifier) => { 3370 appendDecl(this.getDeclarationNode(specifier.propertyName ?? specifier.name)); 3371 }); 3372 } else if (ts.canHaveModifiers(statement)) { 3373 // handle the case:"export const/class/function... decalName;" 3374 if (!TsUtils.hasModifier(ts.getModifiers(statement), ts.SyntaxKind.ExportKeyword)) { 3375 return; 3376 } 3377 if (!ts.isVariableStatement(statement)) { 3378 appendDecl(statement); 3379 return; 3380 } 3381 for (const exportDecl of statement.declarationList.declarations) { 3382 appendDecl(exportDecl); 3383 } 3384 } 3385 }); 3386 return exportDeclSet; 3387 } 3388 3389 static isAmbientNode(node: ts.Node): boolean { 3390 3391 /* CC-OFFNXT(no_explicit_any) std lib */ 3392 // Ambient flag is not exposed, so we apply dirty hack to make it visible 3393 return !!(node.flags & (ts.NodeFlags as any).Ambient); 3394 } 3395 3396 isValidSendableClassExtends(tsTypeExpr: ts.ExpressionWithTypeArguments): boolean { 3397 const expr = tsTypeExpr.expression; 3398 const sym = this.tsTypeChecker.getSymbolAtLocation(expr); 3399 if (sym && (sym.flags & ts.SymbolFlags.Class) === 0) { 3400 // handle non-class situation(local / import) 3401 if ((sym.flags & ts.SymbolFlags.Alias) !== 0) { 3402 3403 /* 3404 * Sendable class can not extends imported sendable class variable 3405 * Sendable class can extends imported sendable class 3406 */ 3407 const realSym = this.tsTypeChecker.getAliasedSymbol(sym); 3408 if (realSym && (realSym.flags & ts.SymbolFlags.Class) === 0) { 3409 return false; 3410 } 3411 return true; 3412 } 3413 return false; 3414 } 3415 return true; 3416 } 3417 3418 isBuiltinClassHeritageClause(node: ts.HeritageClause): boolean { 3419 const typeNode = node.types[0].expression; 3420 if (!typeNode) { 3421 return false; 3422 } 3423 3424 const symbol = this.tsTypeChecker.getSymbolAtLocation(typeNode); 3425 return !!symbol && !isStdLibrarySymbol(symbol); 3426 } 3427 3428 isIntegerVariable(sym: ts.Symbol | undefined): boolean { 3429 if (!sym) { 3430 return false; 3431 } 3432 const decl = TsUtils.getDeclaration(sym); 3433 if (!decl || !ts.isVariableDeclaration(decl) && !ts.isPropertyDeclaration(decl) || decl.type) { 3434 return false; 3435 } 3436 const init = decl.initializer; 3437 return !!init && ts.isNumericLiteral(init) && this.isIntegerConstantValue(init); 3438 } 3439 3440 isIntegerValue(expr: ts.Expression): boolean { 3441 if (ts.isParenthesizedExpression(expr)) { 3442 return this.isIntegerValue(expr.expression); 3443 } else if (ts.isBinaryExpression(expr) && TsUtils.isArithmeticOperator(expr.operatorToken)) { 3444 return this.isIntegerValue(expr.left) && this.isIntegerValue(expr.right); 3445 } else if ( 3446 (ts.isPrefixUnaryExpression(expr) || ts.isPostfixUnaryExpression(expr)) && 3447 TsUtils.isUnaryArithmeticOperator(expr.operator) 3448 ) { 3449 return this.isIntegerValue(expr.operand); 3450 } 3451 3452 return ( 3453 ts.isNumericLiteral(expr) && this.isIntegerConstantValue(expr) || 3454 this.isIntegerVariable(this.tsTypeChecker.getSymbolAtLocation(expr)) 3455 ); 3456 } 3457 3458 static isAppStorageAccess(tsCallExpr: ts.CallExpression): boolean { 3459 const propertyAccessExpr = tsCallExpr.expression; 3460 if (!ts.isPropertyAccessExpression(propertyAccessExpr)) { 3461 return false; 3462 } 3463 const accessedExpr = propertyAccessExpr.expression; 3464 if (!ts.isIdentifier(accessedExpr)) { 3465 return false; 3466 } 3467 3468 if (accessedExpr.text !== 'AppStorage') { 3469 return false; 3470 } 3471 3472 return true; 3473 } 3474 3475 static isArithmeticOperator(op: ts.BinaryOperatorToken): boolean { 3476 switch (op.kind) { 3477 case ts.SyntaxKind.PlusToken: 3478 case ts.SyntaxKind.MinusToken: 3479 case ts.SyntaxKind.AsteriskToken: 3480 case ts.SyntaxKind.SlashToken: 3481 return true; 3482 default: 3483 return false; 3484 } 3485 } 3486 3487 static isUnaryArithmeticOperator(op: ts.SyntaxKind): boolean { 3488 switch (op) { 3489 case ts.SyntaxKind.PlusToken: 3490 case ts.SyntaxKind.MinusToken: 3491 case ts.SyntaxKind.PlusPlusToken: 3492 case ts.SyntaxKind.MinusMinusToken: 3493 case ts.SyntaxKind.TildeToken: 3494 return true; 3495 default: 3496 return false; 3497 } 3498 } 3499 3500 static isNumberLike(type: ts.Type, typeText: string, isEnum: boolean): boolean { 3501 const typeFlags = type.flags; 3502 3503 const isNumberLike = 3504 typeText === STRINGLITERAL_NUMBER || 3505 typeText === STRINGLITERAL_NUMBER_ARRAY || 3506 (typeFlags & ts.TypeFlags.NumberLiteral) !== 0 || 3507 isEnum; 3508 return isNumberLike; 3509 } 3510 3511 static getModuleName(node: ts.Node): string | undefined { 3512 const currentFilePath = node.getSourceFile().fileName; 3513 if (!currentFilePath.includes('src')) { 3514 return undefined; 3515 } 3516 3517 /* 3518 * Assuming we are working with a path like "entry/src/main/ets/pages/test1.ets" 3519 * we are splitting at "/src" and get the first item in the list 3520 * in order to capture the "entry" group 3521 * we could have worked with an absolute path like 3522 * "home/user/projects/oh_project/entry/src/main/ets/pages/test1.sts" 3523 * in that case, after the fist split we would have 3524 * "home/user/projects/entry" 3525 * so we split once again with "/" to separate out the directories 3526 * 3527 * and get the last item which is entry or our module 3528 */ 3529 const getPossibleModule = currentFilePath.split('/src')[0]; 3530 if (getPossibleModule.length === 0) { 3531 return undefined; 3532 } 3533 const sanitizedDirectories = getPossibleModule.split('/'); 3534 return sanitizedDirectories[sanitizedDirectories.length - 1]; 3535 } 3536 3537 static checkFileExists( 3538 importIncludesModule: boolean, 3539 currentNode: ts.Node, 3540 importFilePath: string, 3541 projectPath: string, 3542 extension: string = EXTNAME_ETS 3543 ): boolean { 3544 const currentModule = TsUtils.getModuleName(currentNode); 3545 3546 /* 3547 * some imports are like this 3548 * ets/pages/test1 3549 * in this case, they are in the same module as the file we are trying to import to 3550 * so we add the current module back in 3551 */ 3552 if (!importIncludesModule) { 3553 if (!currentModule) { 3554 return false; 3555 } 3556 3557 projectPath.concat(PATH_SEPARATOR + currentModule); 3558 } 3559 3560 const importedFile = path.resolve(projectPath, importFilePath + extension); 3561 3562 return fs.existsSync(importedFile); 3563 } 3564 3565 isImportedFromJS(identifier: ts.Identifier): boolean { 3566 const sym = this.trueSymbolAtLocation(identifier); 3567 const declaration = sym?.declarations?.[0]; 3568 return !!declaration?.getSourceFile().fileName.endsWith(EXTNAME_JS); 3569 } 3570 3571 isPossiblyImportedFromJS(node: ts.Node): boolean { 3572 const identifier = this.findIdentifierInExpression(node); 3573 if (!identifier) { 3574 return false; 3575 } 3576 3577 // Direct import check 3578 if (this.isImportedFromJS(identifier)) { 3579 return true; 3580 } 3581 3582 // Indirect import check 3583 const originalIdentifier = this.findOriginalIdentifier(identifier); 3584 return !!originalIdentifier && this.isImportedFromJS(originalIdentifier); 3585 } 3586 3587 /** 3588 * Extracts the Identifier from the given node. returns undefined if no Identifier is found. 3589 * 3590 * Direct Identifier (foo) → Returns it. 3591 * Binary Expressions (foo + bar) → Recursively checks left and right. 3592 * Property Access (obj.foo) → Extracts obj. 3593 * Function Calls (foo()) → Extracts foo. 3594 * Array Access (arr[foo]) → Extracts foo. 3595 * Variable Declaration (let foo = ...) → Extracts foo. 3596 * Assignments (foo = ...) → Extracts foo. 3597 */ 3598 findIdentifierInExpression(node: ts.Node): ts.Identifier | undefined { 3599 if (ts.isIdentifier(node)) { 3600 return node; 3601 } else if (ts.isBinaryExpression(node)) { 3602 return this.findIdentifierInExpression(node.left) || this.findIdentifierInExpression(node.right); 3603 } else if (ts.isPropertyAccessExpression(node)) { 3604 return this.findIdentifierInExpression(node.expression); 3605 } else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { 3606 return this.findIdentifierInExpression(node.expression); 3607 } else if (ts.isElementAccessExpression(node)) { 3608 return this.findIdentifierInExpression(node.expression); 3609 } else if (ts.isAsExpression(node)) { 3610 return this.findIdentifierInExpression(node.expression); 3611 } else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { 3612 return node.name; 3613 } 3614 return undefined; 3615 } 3616 3617 // Helper function: Find the variable declaration of an identifier -> let arr = foo.arr -> returns foo 3618 findVariableDeclaration(identifier: ts.Identifier): ts.VariableDeclaration | undefined { 3619 const sym = this.tsTypeChecker.getSymbolAtLocation(identifier); 3620 const decl = TsUtils.getDeclaration(sym); 3621 if (decl && ts.isVariableDeclaration(decl)) { 3622 return decl; 3623 } 3624 return undefined; 3625 } 3626 3627 findOriginalIdentifier(identifier: ts.Identifier): ts.Identifier | undefined { 3628 const variableDeclaration = this.findVariableDeclaration(identifier); 3629 if (!variableDeclaration?.initializer) { 3630 return undefined; 3631 } 3632 3633 const originalIdentifier = ts.isPropertyAccessExpression(variableDeclaration.initializer) ? 3634 (variableDeclaration.initializer.expression as ts.Identifier) : 3635 undefined; 3636 3637 if (!originalIdentifier) { 3638 return undefined; 3639 } 3640 3641 return originalIdentifier; 3642 } 3643 3644 findTypeOfNodeForConversion(node: ts.Node): string { 3645 // Get type of the property 3646 const type = this.tsTypeChecker.getTypeAtLocation(node); 3647 // Default 3648 let conversionMethod = ''; 3649 3650 if (this.tsTypeChecker.typeToString(type) === 'boolean') { 3651 conversionMethod = '.toBoolean()'; 3652 } else if (this.tsTypeChecker.typeToString(type) === 'number') { 3653 conversionMethod = '.toNumber()'; 3654 } else if (this.tsTypeChecker.typeToString(type) === 'string') { 3655 conversionMethod = '.toString()'; 3656 } 3657 3658 return conversionMethod; 3659 } 3660 3661 static isInsideIfCondition(node: ts.Node): boolean { 3662 let current: ts.Node | undefined = node; 3663 while (current) { 3664 if (ts.isIfStatement(current)) { 3665 if ( 3666 ts.isBinaryExpression(node.parent) && 3667 node.parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken 3668 ) { 3669 return false; 3670 } 3671 3672 return true; 3673 } 3674 current = current.parent; 3675 } 3676 return false; 3677 } 3678 3679 static isOhModule(path: string): boolean { 3680 return path.includes(ETS_MODULE); 3681 } 3682 3683 static isValidOhModulePath(path: string): boolean { 3684 return path.includes(VALID_OHM_COMPONENTS_MODULE_PATH); 3685 } 3686 3687 findEnumMember(enumDecl: ts.EnumDeclaration | undefined, name: string): ts.Symbol | undefined { 3688 if (!enumDecl) { 3689 return undefined; 3690 } 3691 3692 const syms: ts.Symbol[] = []; 3693 for (const enumMember of enumDecl.members) { 3694 const sym = this.trueSymbolAtLocation(enumMember.name); 3695 if (sym) { 3696 syms.push(sym); 3697 } 3698 } 3699 3700 for (const sym of syms) { 3701 if (sym.name === name) { 3702 return sym; 3703 } 3704 } 3705 3706 return undefined; 3707 } 3708 3709 isArkts12File(sourceFile: ts.SourceFile): boolean { 3710 if (!sourceFile?.fileName) { 3711 return false; 3712 } 3713 if (sourceFile.fileName.endsWith(EXTNAME_D_ETS)) { 3714 return true; 3715 } 3716 return !!this.options.inputFiles?.includes(path.normalize(sourceFile.fileName)); 3717 } 3718 3719 static removeOrReplaceQuotes(str: string, isReplace: boolean): string { 3720 if (isReplace) { 3721 const charArray = new Array(str.length); 3722 for (let i = 0; i < str.length; i++) { 3723 charArray[i] = str[i] === '"' ? '\'' : str[i]; 3724 } 3725 return charArray.join(''); 3726 } 3727 if (str.startsWith('\'') && str.endsWith('\'') || str.startsWith('"') && str.endsWith('"')) { 3728 return str.slice(1, -1); 3729 } 3730 return str; 3731 } 3732 3733 isJsImport(node: ts.Node): boolean { 3734 const symbol = this.trueSymbolAtLocation(node); 3735 if (symbol) { 3736 const declaration = symbol.declarations?.[0]; 3737 if (declaration) { 3738 const sourceFile = declaration.getSourceFile(); 3739 return sourceFile.fileName.endsWith(EXTNAME_JS); 3740 } 3741 } 3742 return false; 3743 } 3744 3745 static typeIsCapturedFromEnclosingLocalScope(type: ts.Type, enclosingStmt: ts.Node): boolean { 3746 let symNode: ts.Node | undefined = TsUtils.getDeclaration(type.getSymbol()); 3747 3748 while (symNode) { 3749 if (symNode === enclosingStmt) { 3750 return true; 3751 } 3752 symNode = symNode.parent; 3753 } 3754 3755 return false; 3756 } 3757 3758 nodeCapturesValueFromEnclosingLocalScope(targetNode: ts.Node, enclosingStmt: ts.Node): boolean { 3759 let found = false; 3760 3761 const callback = (node: ts.Node): void => { 3762 if (!ts.isIdentifier(node)) { 3763 return; 3764 } 3765 const sym = this.tsTypeChecker.getSymbolAtLocation(node); 3766 let symNode: ts.Node | undefined = TsUtils.getDeclaration(sym); 3767 while (symNode) { 3768 if (symNode === targetNode) { 3769 // Symbol originated from the target node. Skip and continue to search 3770 return; 3771 } 3772 if (symNode === enclosingStmt) { 3773 found = true; 3774 return; 3775 } 3776 symNode = symNode.parent; 3777 } 3778 }; 3779 3780 const stopCondition = (node: ts.Node): boolean => { 3781 void node; 3782 return found; 3783 }; 3784 3785 forEachNodeInSubtree(targetNode, callback, stopCondition); 3786 return found; 3787 } 3788 3789 static typeContainsVoid(typeNode: ts.TypeNode): boolean { 3790 if (ts.isUnionTypeNode(typeNode)) { 3791 return typeNode.types.some((t) => { 3792 return t.kind === ts.SyntaxKind.VoidKeyword; 3793 }); 3794 } 3795 return typeNode.kind === ts.SyntaxKind.VoidKeyword; 3796 } 3797} 3798