1/* 2 * Copyright (c) 2022-2023 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 16namespace ts { 17export namespace ArkTSLinter_1_1 { 18 19import FaultID = Problems.FaultID; 20import AutofixInfo = Common.AutofixInfo; 21 22export namespace Utils { 23//import * as path from 'node:path'; 24//import * as ts from 'typescript'; 25//import ProblemInfo = ts.ProblemInfo; 26//import TypeScriptLinter = TypeScriptLinter; 27 28export const PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE = 2564; 29 30export const NON_INITIALIZABLE_PROPERTY_DECORATORS = ["Link", "Consume", "ObjectLink", "Prop", "BuilderParam"]; 31 32export const NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS = ['CustomDialog'] 33 34export const LIMITED_STANDARD_UTILITY_TYPES = [ 35 "Awaited", "Pick", "Omit", "Exclude", "Extract", "NonNullable", "Parameters", 36 "ConstructorParameters", "ReturnType", "InstanceType", "ThisParameterType", "OmitThisParameter", 37 "ThisType", "Uppercase", "Lowercase", "Capitalize", "Uncapitalize", 38]; 39 40export const ALLOWED_STD_SYMBOL_API = ["iterator"] 41 42export const ARKTS_IGNORE_DIRS = ['node_modules', 'oh_modules', 'build', '.preview']; 43export const ARKTS_IGNORE_FILES = ['hvigorfile.ts']; 44 45let typeChecker: TypeChecker; 46export function setTypeChecker(tsTypeChecker: TypeChecker): void { 47 typeChecker = tsTypeChecker; 48} 49 50export function clearTypeChecker(): void { 51 typeChecker = {} as TypeChecker; 52} 53 54let testMode = false; 55export function setTestMode(tsTestMode: boolean): void { 56 testMode = tsTestMode; 57} 58 59export function getStartPos(nodeOrComment: Node | CommentRange): number { 60 return (nodeOrComment.kind === SyntaxKind.SingleLineCommentTrivia || nodeOrComment.kind === SyntaxKind.MultiLineCommentTrivia) 61 ? (nodeOrComment as CommentRange).pos 62 : (nodeOrComment as Node).getStart(); 63} 64 65export function getEndPos(nodeOrComment: Node | CommentRange): number { 66 return (nodeOrComment.kind === SyntaxKind.SingleLineCommentTrivia || nodeOrComment.kind === SyntaxKind.MultiLineCommentTrivia) 67 ? (nodeOrComment as CommentRange).end 68 : (nodeOrComment as Node).getEnd(); 69} 70 71export function getHighlightRange(nodeOrComment: Node | CommentRange, faultId: number): [number, number] { 72 return ( 73 highlightRangeHandlers.get(faultId)?.call(undefined, nodeOrComment) ?? [ 74 getStartPos(nodeOrComment), 75 getEndPos(nodeOrComment) 76 ] 77 ); 78} 79 80const highlightRangeHandlers = new Map([ 81 [FaultID.VarDeclaration, getVarDeclarationHighlightRange], 82 [FaultID.CatchWithUnsupportedType, getCatchWithUnsupportedTypeHighlightRange], 83 [FaultID.ForInStatement, getForInStatementHighlightRange], 84 [FaultID.WithStatement, getWithStatementHighlightRange], 85 [FaultID.DeleteOperator, getDeleteOperatorHighlightRange], 86 [FaultID.TypeQuery, getTypeQueryHighlightRange], 87 [FaultID.InstanceofUnsupported, getInstanceofUnsupportedHighlightRange], 88 [FaultID.ConstAssertion, getConstAssertionHighlightRange], 89 [FaultID.LimitedReturnTypeInference, getLimitedReturnTypeInferenceHighlightRange], 90 [FaultID.LocalFunction, getLocalFunctionHighlightRange], 91 [FaultID.FunctionBind, getFunctionApplyCallHighlightRange], 92 [FaultID.FunctionApplyCall, getFunctionApplyCallHighlightRange], 93 [FaultID.DeclWithDuplicateName, getDeclWithDuplicateNameHighlightRange], 94 [FaultID.ObjectLiteralNoContextType, getObjectLiteralNoContextTypeHighlightRange], 95 [FaultID.ClassExpression, getClassExpressionHighlightRange] 96]); 97 98export function getVarDeclarationHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 99 return getKeywordHighlightRange(nodeOrComment, 'var'); 100} 101 102export function getCatchWithUnsupportedTypeHighlightRange( 103 nodeOrComment: Node | CommentRange 104): [number, number] | undefined { 105 const catchClauseNode = (nodeOrComment as CatchClause).variableDeclaration; 106 if (catchClauseNode !== undefined) { 107 return [catchClauseNode.getStart(), catchClauseNode.getEnd()]; 108 } 109 110 return undefined; 111} 112 113export function getForInStatementHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 114 return [ 115 getEndPos((nodeOrComment as ForInStatement).initializer) + 1, 116 getStartPos((nodeOrComment as ForInStatement).expression) - 1 117 ]; 118} 119 120export function getWithStatementHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 121 return [getStartPos(nodeOrComment), (nodeOrComment as WithStatement).statement.getStart() - 1]; 122} 123 124export function getDeleteOperatorHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 125 return getKeywordHighlightRange(nodeOrComment, 'delete'); 126} 127 128export function getTypeQueryHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 129 return getKeywordHighlightRange(nodeOrComment, 'typeof'); 130} 131 132export function getInstanceofUnsupportedHighlightRange( 133 nodeOrComment: Node | CommentRange 134): [number, number] | undefined { 135 return getKeywordHighlightRange((nodeOrComment as BinaryExpression).operatorToken, 'instanceof'); 136} 137 138export function getConstAssertionHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 139 if (nodeOrComment.kind === SyntaxKind.AsExpression) { 140 return [ 141 (nodeOrComment as AsExpression).expression.getEnd() + 1, 142 (nodeOrComment as AsExpression).type.getStart() - 1 143 ]; 144 } 145 return [ 146 (nodeOrComment as TypeAssertion).expression.getEnd() + 1, 147 (nodeOrComment as TypeAssertion).type.getEnd() + 1 148 ]; 149} 150 151export function getLimitedReturnTypeInferenceHighlightRange( 152 nodeOrComment: Node | CommentRange 153): [number, number] | undefined { 154 let node: Node | undefined; 155 if (nodeOrComment.kind === SyntaxKind.FunctionExpression) { 156 // we got error about return type so it should be present 157 node = (nodeOrComment as FunctionExpression).type; 158 } else if (nodeOrComment.kind === SyntaxKind.FunctionDeclaration) { 159 node = (nodeOrComment as FunctionDeclaration).name; 160 } else if (nodeOrComment.kind === SyntaxKind.MethodDeclaration) { 161 node = (nodeOrComment as MethodDeclaration).name; 162 } 163 if (node !== undefined) { 164 return [node.getStart(), node.getEnd()]; 165 } 166 167 return undefined; 168} 169 170export function getLocalFunctionHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 171 return getKeywordHighlightRange(nodeOrComment, 'function'); 172} 173 174export function getFunctionApplyCallHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 175 const pointPos = (nodeOrComment as Node).getText().lastIndexOf('.'); 176 return [getStartPos(nodeOrComment) + pointPos + 1, getEndPos(nodeOrComment)]; 177} 178 179export function getDeclWithDuplicateNameHighlightRange( 180 nodeOrComment: Node | CommentRange 181): [number, number] | undefined { 182 // in case of private identifier no range update is needed 183 const nameNode: Node | undefined = (nodeOrComment as NamedDeclaration).name; 184 if (nameNode !== undefined) { 185 return [nameNode.getStart(), nameNode.getEnd()]; 186 } 187 188 return undefined; 189} 190 191export function getObjectLiteralNoContextTypeHighlightRange( 192 nodeOrComment: Node | CommentRange 193): [number, number] | undefined { 194 return getKeywordHighlightRange(nodeOrComment, '{'); 195} 196 197export function getClassExpressionHighlightRange(nodeOrComment: Node | CommentRange): [number, number] | undefined { 198 return getKeywordHighlightRange(nodeOrComment, 'class'); 199} 200 201export function getKeywordHighlightRange(nodeOrComment: Node | CommentRange, keyword: string): [number, number] { 202 const start = getStartPos(nodeOrComment); 203 return [start, start + keyword.length]; 204} 205 206export function isAssignmentOperator(tsBinOp: BinaryOperatorToken): boolean { 207 return tsBinOp.kind >= SyntaxKind.FirstAssignment && tsBinOp.kind <= SyntaxKind.LastAssignment; 208} 209 210export function isType(tsType: TypeNode | undefined, checkType: string): boolean { 211 if (tsType === undefined || !isTypeReferenceNode(tsType)) { 212 return false; 213 } 214 return entityNameToString(tsType.typeName) === checkType; 215} 216 217export function entityNameToString(name: EntityName): string { 218 if (isIdentifier(name)) { 219 return name.escapedText.toString(); 220 } 221 else { 222 return entityNameToString(name.left) + entityNameToString(name.right); 223 } 224} 225 226export function isNumberLikeType(tsType: Type): boolean { 227 return (tsType.getFlags() & TypeFlags.NumberLike) !== 0; 228} 229 230export function isBooleanLikeType(tsType: Type): boolean { 231 return (tsType.getFlags() & TypeFlags.BooleanLike) !== 0; 232} 233 234export function isStringLikeType(tsType: Type): boolean { 235 if (tsType.isUnion()) { 236 for (const tsCompType of tsType.types) { 237 if ((tsCompType.flags & TypeFlags.StringLike) === 0) return false; 238 } 239 return true; 240 } 241 return (tsType.getFlags() & TypeFlags.StringLike) !== 0; 242} 243 244export function isStringType(tsType: ts.Type): boolean { 245 if ((tsType.getFlags() & ts.TypeFlags.String) !== 0) { 246 return true; 247 } 248 249 if (!isTypeReference(tsType)) { 250 return false; 251 } 252 253 const symbol = tsType.symbol; 254 const name = typeChecker.getFullyQualifiedName(symbol); 255 return name === 'String' && isGlobalSymbol(symbol); 256} 257 258export function isPrimitiveEnumMemberType(type: Type, primitiveType: TypeFlags): boolean { 259 const isNonPrimitive = (type.flags & TypeFlags.NonPrimitive) !== 0; 260 if (!isEnumMemberType(type) || isNonPrimitive) { 261 return false; 262 } 263 return (type.flags & primitiveType) !== 0; 264} 265 266export function unwrapParenthesizedType(tsType: TypeNode): TypeNode { 267 while (isParenthesizedTypeNode(tsType)) { 268 tsType = tsType.type; 269 } 270 return tsType; 271} 272 273export function findParentIf(asExpr: AsExpression): IfStatement | null { 274 let node = asExpr.parent; 275 while (node) { 276 if (node.kind === SyntaxKind.IfStatement) { 277 return node as IfStatement; 278 } 279 node = node.parent; 280 } 281 282 return null; 283} 284 285export function isDestructuringAssignmentLHS( 286 tsExpr: ArrayLiteralExpression | ObjectLiteralExpression 287): boolean { 288 // Check whether given expression is the LHS part of the destructuring 289 // assignment (or is a nested element of destructuring pattern). 290 let tsParent = tsExpr.parent; 291 let tsCurrentExpr: Node = tsExpr; 292 while (tsParent) { 293 if ( 294 isBinaryExpression(tsParent) && isAssignmentOperator(tsParent.operatorToken) && 295 tsParent.left === tsCurrentExpr 296 ) { 297 return true; 298 } 299 if ( 300 (isForStatement(tsParent) || isForInStatement(tsParent) || isForOfStatement(tsParent)) && 301 tsParent.initializer && tsParent.initializer === tsCurrentExpr 302 ) { 303 return true; 304 } 305 tsCurrentExpr = tsParent; 306 tsParent = tsParent.parent; 307 } 308 309 return false; 310} 311 312export function isEnumType(tsType: ts.Type): boolean { 313 // when type equals `typeof <Enum>`, only symbol contains information about it's type. 314 const isEnumSymbol = tsType.symbol && isEnum(tsType.symbol); 315 // otherwise, we should analyze flags of the type itself 316 const isEnumType = !!(tsType.flags & ts.TypeFlags.Enum) || !!(tsType.flags & ts.TypeFlags.EnumLiteral); 317 return isEnumSymbol || isEnumType; 318} 319 320export function isEnum(tsSymbol: ts.Symbol): boolean { 321 return !!(tsSymbol.flags & ts.SymbolFlags.Enum); 322} 323 324export function isEnumMemberType(tsType: Type): boolean { 325 // Note: For some reason, test (tsType.flags & TypeFlags.Enum) != 0 doesn't work here. 326 // Must use SymbolFlags to figure out if this is an enum type. 327 return tsType.symbol && (tsType.symbol.flags & SymbolFlags.EnumMember) !== 0; 328} 329 330export function isObjectLiteralType(tsType: Type): boolean { 331 return tsType.symbol && (tsType.symbol.flags & SymbolFlags.ObjectLiteral) !== 0; 332} 333 334export function hasModifier(tsModifiers: readonly Modifier[] | undefined, tsModifierKind: number): boolean { 335 // Sanity check. 336 if (!tsModifiers) return false; 337 338 for (const tsModifier of tsModifiers) { 339 if (tsModifier.kind === tsModifierKind) return true; 340 } 341 342 return false; 343} 344 345export function unwrapParenthesized(tsExpr: Expression): Expression { 346 let unwrappedExpr = tsExpr; 347 while (isParenthesizedExpression(unwrappedExpr)) { 348 unwrappedExpr = unwrappedExpr.expression; 349 } 350 return unwrappedExpr; 351} 352 353export function followIfAliased(sym: Symbol): Symbol { 354 if ((sym.getFlags() & SymbolFlags.Alias) !== 0) { 355 return typeChecker.getAliasedSymbol(sym); 356 } 357 return sym; 358} 359 360let trueSymbolAtLocationCache = new Map<ts.Node, ts.Symbol | null>(); 361 362export function trueSymbolAtLocation(node: Node): Symbol | undefined { 363 let cache = trueSymbolAtLocationCache; 364 let val = cache.get(node); 365 if (val !== undefined) { 366 return val !== null ? val : undefined; 367 } 368 let sym = typeChecker.getSymbolAtLocation(node); 369 if (sym === undefined) { 370 cache.set(node, null); 371 return undefined; 372 } 373 sym = followIfAliased(sym); 374 cache.set(node, sym); 375 return sym; 376} 377 378export function clearTrueSymbolAtLocationCache(): void { 379 trueSymbolAtLocationCache.clear(); 380} 381 382export function isTypeDeclSyntaxKind(kind: SyntaxKind) { 383 return isStructDeclarationKind(kind) || 384 kind === SyntaxKind.EnumDeclaration || 385 kind === SyntaxKind.ClassDeclaration || 386 kind === SyntaxKind.InterfaceDeclaration || 387 kind === SyntaxKind.TypeAliasDeclaration; 388} 389 390export function symbolHasDuplicateName(symbol: Symbol, tsDeclKind: SyntaxKind): boolean { 391 // Type Checker merges all declarations with the same name in one scope into one symbol. 392 // Thus, check whether the symbol of certain declaration has any declaration with 393 // different syntax kind. 394 const symbolDecls = symbol?.getDeclarations(); 395 if (symbolDecls) { 396 for (const symDecl of symbolDecls) { 397 const declKind = symDecl.kind; 398 // we relax arkts-unique-names for namespace collision with class/interface/enum/type/struct 399 const isNamespaceTypeCollision = 400 (isTypeDeclSyntaxKind(declKind) && tsDeclKind === SyntaxKind.ModuleDeclaration) || 401 (isTypeDeclSyntaxKind(tsDeclKind) && declKind === SyntaxKind.ModuleDeclaration); 402 403 // Don't count declarations with 'Identifier' syntax kind as those 404 // usually depict declaring an object's property through assignment. 405 if (declKind !== SyntaxKind.Identifier && declKind !== tsDeclKind && !isNamespaceTypeCollision) return true; 406 } 407 } 408 409 return false; 410} 411 412export function isReferenceType(tsType: Type): boolean { 413 const f = tsType.getFlags(); 414 return ( 415 (f & TypeFlags.InstantiableNonPrimitive) !== 0 || (f & TypeFlags.Object) !== 0 || 416 (f & TypeFlags.Boolean) !== 0 || (f & TypeFlags.Enum) !== 0 || (f & TypeFlags.NonPrimitive) !== 0 || 417 (f & TypeFlags.Number) !== 0 || (f & TypeFlags.String) !== 0 418 ); 419} 420 421export function isPrimitiveType(type: Type): boolean { 422 const f = type.getFlags(); 423 return ( 424 (f & TypeFlags.Boolean) !== 0 || (f & TypeFlags.BooleanLiteral) !== 0 || 425 (f & TypeFlags.Number) !== 0 || (f & TypeFlags.NumberLiteral) !== 0 426 // In ArkTS 'string' is not a primitive type. So for the common subset 'string' 427 // should be considered as a reference type. That is why next line is commented out. 428 //(f & TypeFlags.String) != 0 || (f & TypeFlags.StringLiteral) != 0 429 ); 430} 431 432export function isTypeSymbol(symbol: Symbol | undefined): boolean { 433 return ( 434 !!symbol && !!symbol.flags && 435 ((symbol.flags & SymbolFlags.Class) !== 0 || (symbol.flags & SymbolFlags.Interface) !== 0) 436 ); 437} 438 439// Check whether type is generic 'Array<T>' type defined in TypeScript standard library. 440export function isGenericArrayType(tsType: Type): tsType is TypeReference { 441 return ( 442 isTypeReference(tsType) && tsType.typeArguments?.length === 1 && tsType.target.typeParameters?.length === 1 && 443 tsType.getSymbol()?.getName() === "Array" 444 ); 445} 446 447export function isReadonlyArrayType(tsType: Type): boolean { 448 return ( 449 isTypeReference(tsType) && tsType.typeArguments?.length === 1 && tsType.target.typeParameters?.length === 1 && 450 (tsType.getSymbol()?.getName() === "ReadonlyArray") 451 ); 452} 453 454export function isTypedArray(tsType: ts.Type): boolean { 455 const symbol = tsType.symbol; 456 if (!symbol) { 457 return false; 458 } 459 const name = typeChecker.getFullyQualifiedName(symbol); 460 return isGlobalSymbol(symbol) && TYPED_ARRAYS.includes(name); 461} 462 463export function isArray(tsType: ts.Type): boolean { 464 return isGenericArrayType(tsType) || isReadonlyArrayType(tsType) || isTypedArray(tsType); 465} 466 467export function isTuple(tsType: ts.Type): boolean { 468 return isTypeReference(tsType) && !!(tsType.objectFlags & ts.ObjectFlags.Tuple); 469} 470 471// does something similar to relatedByInheritanceOrIdentical function 472export function isOrDerivedFrom(tsType: ts.Type, checkType: CheckType): boolean { 473 if (isTypeReference(tsType) && tsType.target !== tsType) { 474 tsType = tsType.target; 475 } 476 if (checkType(tsType)) { 477 return true; 478 } 479 if (!tsType.symbol || !tsType.symbol.declarations) { 480 return false; 481 } 482 483 for (const tsTypeDecl of tsType.symbol.declarations) { 484 const isClassOrInterfaceDecl = ts.isClassDeclaration(tsTypeDecl) || ts.isInterfaceDeclaration(tsTypeDecl); 485 const isDerived = isClassOrInterfaceDecl && !!tsTypeDecl.heritageClauses; 486 if (!isDerived) { 487 continue; 488 } 489 for (const heritageClause of tsTypeDecl.heritageClauses) { 490 if (processParentTypesCheck(heritageClause.types, checkType)) { 491 return true; 492 } 493 } 494 } 495 496 return false; 497} 498 499export function isTypeReference(tsType: Type): tsType is TypeReference { 500 return ( 501 (tsType.getFlags() & TypeFlags.Object) !== 0 && 502 ((tsType as ObjectType).objectFlags & ObjectFlags.Reference) !== 0 503 ); 504} 505 506export function isNullType(tsTypeNode: TypeNode): boolean { 507 return (isLiteralTypeNode(tsTypeNode) && tsTypeNode.literal.kind === SyntaxKind.NullKeyword); 508} 509 510export function isThisOrSuperExpr(tsExpr: Expression): boolean { 511 return (tsExpr.kind === SyntaxKind.ThisKeyword || tsExpr.kind === SyntaxKind.SuperKeyword); 512} 513 514export function isPrototypeSymbol(symbol: Symbol | undefined): boolean { 515 return (!!symbol && !!symbol.flags && (symbol.flags & SymbolFlags.Prototype) !== 0); 516} 517 518export function isFunctionSymbol(symbol: Symbol | undefined): boolean { 519 return (!!symbol && !!symbol.flags && (symbol.flags & SymbolFlags.Function) !== 0); 520} 521 522export function isInterfaceType(tsType: Type | undefined): boolean { 523 return ( 524 !!tsType && !!tsType.symbol && !!tsType.symbol.flags && 525 (tsType.symbol.flags & SymbolFlags.Interface) !== 0 526 ); 527} 528 529export function isAnyType(tsType: Type): tsType is TypeReference { 530 return (tsType.getFlags() & TypeFlags.Any) !== 0; 531} 532 533export function isUnknownType(tsType: Type): boolean { 534 return (tsType.getFlags() & TypeFlags.Unknown) !== 0; 535} 536 537export function isUnsupportedType(tsType: Type): boolean { 538 return ( 539 !!tsType.flags && ((tsType.flags & TypeFlags.Any) !== 0 || (tsType.flags & TypeFlags.Unknown) !== 0 || 540 (tsType.flags & TypeFlags.Intersection) !== 0) 541 ); 542} 543 544export function isUnsupportedUnionType(tsType: Type): boolean { 545 if (tsType.isUnion()) { 546 return !isNullableUnionType(tsType) && !isBooleanUnionType(tsType); 547 } 548 return false; 549} 550 551function isNullableUnionType(tsUnionType: UnionType): boolean { 552 for (const t of tsUnionType.types) { 553 if (!!(t.flags & ts.TypeFlags.Undefined) || !!(t.flags & ts.TypeFlags.Null)) { 554 return true; 555 } 556 } 557 return false; 558} 559 560function isBooleanUnionType(tsUnionType: UnionType): boolean { 561 // For some reason, 'boolean' type is also represented as as union 562 // of 'true' and 'false' literal types. This form of 'union' type 563 // should be considered as supported. 564 const tsCompTypes = tsUnionType.types; 565 return ( 566 tsUnionType.flags === (TypeFlags.Boolean | TypeFlags.Union) && tsCompTypes.length === 2 && 567 tsCompTypes[0].flags === TypeFlags.BooleanLiteral && (tsCompTypes[1].flags === TypeFlags.BooleanLiteral) 568 ); 569} 570 571export function isFunctionOrMethod(tsSymbol: Symbol | undefined): boolean { 572 return ( 573 !!tsSymbol && 574 ((tsSymbol.flags & SymbolFlags.Function) !== 0 || (tsSymbol.flags & SymbolFlags.Method) !== 0) 575 ); 576} 577 578export function isMethodAssignment(tsSymbol: Symbol | undefined): boolean { 579 return ( 580 !!tsSymbol && 581 ((tsSymbol.flags & SymbolFlags.Method) !== 0 && (tsSymbol.flags & SymbolFlags.Assignment) !== 0) 582 ); 583} 584 585export function getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | undefined { 586 if (tsSymbol && tsSymbol.declarations && tsSymbol.declarations.length > 0) { 587 return tsSymbol.declarations[0]; 588 } 589 return undefined; 590} 591 592function isVarDeclaration(tsDecl: Node): boolean { 593 return isVariableDeclaration(tsDecl) && isVariableDeclarationList(tsDecl.parent); 594} 595 596export function isValidEnumMemberInit(tsExpr: Expression): boolean { 597 if (isNumberConstantValue(tsExpr.parent as EnumMember)) { 598 return true; 599 } 600 if (isStringConstantValue(tsExpr.parent as EnumMember)) { 601 return true; 602 } 603 return isCompileTimeExpression(tsExpr); 604} 605 606export function isCompileTimeExpression(tsExpr: Expression): boolean { 607 if ( 608 isParenthesizedExpression(tsExpr) || 609 (isAsExpression(tsExpr) && tsExpr.type.kind === SyntaxKind.NumberKeyword)) { 610 return isCompileTimeExpression(tsExpr.expression); 611 } 612 switch (tsExpr.kind) { 613 case SyntaxKind.PrefixUnaryExpression: 614 return isPrefixUnaryExprValidEnumMemberInit(tsExpr as PrefixUnaryExpression); 615 case SyntaxKind.ParenthesizedExpression: 616 case SyntaxKind.BinaryExpression: 617 return isBinaryExprValidEnumMemberInit(tsExpr as BinaryExpression); 618 case SyntaxKind.ConditionalExpression: 619 return isConditionalExprValidEnumMemberInit(tsExpr as ConditionalExpression); 620 case SyntaxKind.Identifier: 621 return isIdentifierValidEnumMemberInit(tsExpr as Identifier); 622 case SyntaxKind.NumericLiteral: 623 return true; 624 case SyntaxKind.StringLiteral: 625 return true; 626 case SyntaxKind.PropertyAccessExpression: { 627 // if enum member is in current enum declaration try to get value 628 // if it comes from another enum consider as constant 629 const propertyAccess = tsExpr as PropertyAccessExpression; 630 if(isNumberConstantValue(propertyAccess)) { 631 return true; 632 } 633 const leftHandSymbol = typeChecker.getSymbolAtLocation(propertyAccess.expression); 634 if(!leftHandSymbol) { 635 return false; 636 } 637 const decls = leftHandSymbol.getDeclarations(); 638 if (!decls || decls.length !== 1) { 639 return false; 640 } 641 return isEnumDeclaration(decls[0]); 642 } 643 default: 644 return false; 645 } 646} 647 648function isPrefixUnaryExprValidEnumMemberInit(tsExpr: PrefixUnaryExpression): boolean { 649 return (isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && isCompileTimeExpression(tsExpr.operand)); 650} 651 652function isBinaryExprValidEnumMemberInit(tsExpr: BinaryExpression): boolean { 653 return ( 654 isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) && isCompileTimeExpression(tsExpr.left) && 655 isCompileTimeExpression(tsExpr.right) 656 ); 657} 658 659function isConditionalExprValidEnumMemberInit(tsExpr: ConditionalExpression): boolean { 660 return (isCompileTimeExpression(tsExpr.whenTrue) && isCompileTimeExpression(tsExpr.whenFalse)); 661} 662 663function isIdentifierValidEnumMemberInit(tsExpr: Identifier): boolean { 664 const tsSymbol = typeChecker.getSymbolAtLocation(tsExpr); 665 const tsDecl = getDeclaration(tsSymbol); 666 return (!!tsDecl && 667 ((isVarDeclaration(tsDecl) && isConst(tsDecl.parent)) || 668 (tsDecl.kind === SyntaxKind.EnumMember) 669 ) 670 ); 671} 672 673function isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: PrefixUnaryOperator): boolean { 674 return ( 675 tsPrefixUnaryOp === SyntaxKind.PlusToken || tsPrefixUnaryOp === SyntaxKind.MinusToken || 676 tsPrefixUnaryOp === SyntaxKind.TildeToken 677 ); 678} 679 680function isBinaryOpAllowedForEnumMemberInit(tsBinaryOp: BinaryOperatorToken): boolean { 681 return ( 682 tsBinaryOp.kind === SyntaxKind.AsteriskToken || tsBinaryOp.kind === SyntaxKind.SlashToken || 683 tsBinaryOp.kind === SyntaxKind.PercentToken || tsBinaryOp.kind === SyntaxKind.MinusToken || 684 tsBinaryOp.kind === SyntaxKind.PlusToken || tsBinaryOp.kind === SyntaxKind.LessThanLessThanToken || 685 tsBinaryOp.kind === SyntaxKind.GreaterThanGreaterThanToken || tsBinaryOp.kind === SyntaxKind.BarBarToken || 686 tsBinaryOp.kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken || 687 tsBinaryOp.kind === SyntaxKind.AmpersandToken || tsBinaryOp.kind === SyntaxKind.CaretToken || 688 tsBinaryOp.kind === SyntaxKind.BarToken || tsBinaryOp.kind === SyntaxKind.AmpersandAmpersandToken 689 ); 690} 691 692export function isConst(tsNode: Node): boolean { 693 return !!(getCombinedNodeFlags(tsNode) & NodeFlags.Const); 694} 695 696export function isNumberConstantValue( 697 tsExpr: EnumMember | PropertyAccessExpression | ElementAccessExpression | NumericLiteral 698): boolean { 699 700 const tsConstValue = (tsExpr.kind === SyntaxKind.NumericLiteral) ? 701 Number(tsExpr.getText()) : 702 typeChecker.getConstantValue(tsExpr); 703 704 return tsConstValue !== undefined && typeof tsConstValue === "number"; 705} 706 707export function isIntegerConstantValue( 708 tsExpr: EnumMember | PropertyAccessExpression | ElementAccessExpression | NumericLiteral 709): boolean { 710 711 const tsConstValue = (tsExpr.kind === SyntaxKind.NumericLiteral) ? 712 Number(tsExpr.getText()) : 713 typeChecker.getConstantValue(tsExpr); 714 return ( 715 tsConstValue !== undefined && typeof tsConstValue === "number" && 716 tsConstValue.toFixed(0) === tsConstValue.toString() 717 ); 718} 719 720export function isStringConstantValue( 721 tsExpr: EnumMember | PropertyAccessExpression | ElementAccessExpression 722): boolean { 723 const tsConstValue = typeChecker.getConstantValue(tsExpr); 724 return ( 725 tsConstValue !== undefined && typeof tsConstValue === "string" 726 ); 727} 728 729// Returns true if typeA is a subtype of typeB 730export function relatedByInheritanceOrIdentical(typeA: Type, typeB: Type): boolean { 731 if (isTypeReference(typeA) && typeA.target !== typeA) { typeA = typeA.target; } 732 if (isTypeReference(typeB) && typeB.target !== typeB) { typeB = typeB.target; } 733 734 if (typeA === typeB || isObject(typeB)) { return true; } 735 if (!typeA.symbol || !typeA.symbol.declarations) { return false; } 736 737 for (const typeADecl of typeA.symbol.declarations) { 738 if ( 739 (!isClassDeclaration(typeADecl) && !isInterfaceDeclaration(typeADecl)) || 740 !typeADecl.heritageClauses 741 ) { continue; } 742 for (const heritageClause of typeADecl.heritageClauses) { 743 const processInterfaces = typeA.isClass() ? (heritageClause.token !== SyntaxKind.ExtendsKeyword) : true; 744 if (processParentTypes(heritageClause.types, typeB, processInterfaces)) return true; 745 } 746 } 747 748 return false; 749} 750 751export function reduceReference(t: ts.Type): ts.Type { 752 return isTypeReference(t) && t.target !== t ? t.target : t; 753} 754 755function needToDeduceStructuralIdentityHandleUnions( 756 lhsType: Type, 757 rhsType: Type, 758 rhsExpr: Expression 759): boolean { 760 if (rhsType.isUnion()) { 761 // Each Class/Interface of the RHS union type must be compatible with LHS type. 762 for (const compType of rhsType.types) { 763 if (needToDeduceStructuralIdentity(lhsType, compType, rhsExpr)) { 764 return true; 765 } 766 } 767 return false; 768 } 769 770 if (lhsType.isUnion()) { 771 // RHS type needs to be compatible with at least one type of the LHS union. 772 for (const compType of lhsType.types) { 773 if (!needToDeduceStructuralIdentity(compType, rhsType, rhsExpr)) { 774 return false; 775 } 776 } 777 return true; 778 } 779 // should be unreachable 780 return false; 781} 782 783// return true if two class types are not related by inheritance and structural identity check is needed 784export function needToDeduceStructuralIdentity( 785 lhsType: Type, 786 rhsType: Type, 787 rhsExpr: Expression 788): boolean { 789 lhsType = getNonNullableType(lhsType); 790 rhsType = getNonNullableType(rhsType); 791 if (isLibraryType(lhsType)) { 792 return false; 793 } 794 if (isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) { 795 return false; 796 } 797 // #14569: Check for Function type. 798 if (areCompatibleFunctionals(lhsType, rhsType)) { 799 return false; 800 } 801 if (rhsType.isUnion() || lhsType.isUnion()) { 802 return needToDeduceStructuralIdentityHandleUnions(lhsType, rhsType, rhsExpr); 803 } 804 return lhsType.isClassOrInterface() && rhsType.isClassOrInterface() && 805 !relatedByInheritanceOrIdentical(rhsType, lhsType); 806} 807 808export function hasPredecessor(node: Node, predicate: (node: Node) => boolean): boolean { 809 let parent = node.parent; 810 while (parent !== undefined) { 811 if (predicate(parent)) { 812 return true; 813 } 814 parent = parent.parent; 815 } 816 return false; 817} 818 819export function processParentTypes(parentTypes: NodeArray<ExpressionWithTypeArguments>, typeB: Type, processInterfaces: boolean): boolean { 820 for (const baseTypeExpr of parentTypes) { 821 let baseType = typeChecker.getTypeAtLocation(baseTypeExpr); 822 if (isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; 823 if (baseType && (baseType.isClass() !== processInterfaces) && relatedByInheritanceOrIdentical(baseType, typeB)) return true; 824 } 825 return false; 826} 827 828function processParentTypesCheck(parentTypes: NodeArray<ExpressionWithTypeArguments>, checkType: CheckType): boolean { 829 for (const baseTypeExpr of parentTypes) { 830 let baseType = typeChecker.getTypeAtLocation(baseTypeExpr); 831 if (isTypeReference(baseType) && baseType.target !== baseType) { 832 baseType = baseType.target; 833 } 834 if (baseType && isOrDerivedFrom(baseType, checkType)) { 835 return true; 836 } 837 } 838 return false; 839} 840 841 842export function isObject(tsType: Type): boolean { 843 if (!tsType) { 844 return false; 845 } 846 if (tsType.symbol && (tsType.isClassOrInterface() && tsType.symbol.name === "Object")) { 847 return true; 848 } 849 const node = typeChecker.typeToTypeNode(tsType, undefined, undefined); 850 return node !== undefined && node.kind === SyntaxKind.ObjectKeyword; 851} 852 853export function logTscDiagnostic(diagnostics: readonly Diagnostic[], log: (message: any, ...args: any[]) => void): void { 854 diagnostics.forEach((diagnostic) => { 855 let message = flattenDiagnosticMessageText(diagnostic.messageText, "\n"); 856 857 if (diagnostic.file && diagnostic.start) { 858 const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); 859 message = `${diagnostic.file.fileName} (${line + 1}, ${character + 1}): ${message}`; 860 } 861 862 log(message); 863 }); 864} 865 866export function encodeProblemInfo(problem: ProblemInfo): string { 867 return `${problem.problem}%${problem.start}%${problem.end}`; 868} 869 870export function decodeAutofixInfo(info: string): AutofixInfo { 871 const infos = info.split("%"); 872 return { problemID: infos[0], start: Number.parseInt(infos[1]), end: Number.parseInt(infos[2]) }; 873} 874 875export function isCallToFunctionWithOmittedReturnType(tsExpr: Expression): boolean { 876 if (isCallExpression(tsExpr)) { 877 const tsCallSignature = typeChecker.getResolvedSignature(tsExpr); 878 if (tsCallSignature) { 879 const tsSignDecl = tsCallSignature.getDeclaration(); 880 // `tsSignDecl` is undefined when `getResolvedSignature` returns `unknownSignature` 881 if (!tsSignDecl || !tsSignDecl.type) return true; 882 } 883 } 884 885 return false; 886} 887 888function hasReadonlyFields(type: Type): boolean { 889 if (type.symbol.members === undefined) return false; // No members -> no readonly fields 890 891 let result = false; 892 893 type.symbol.members.forEach((value /*, key*/) => { 894 if ( 895 value.declarations !== undefined && value.declarations.length > 0 && 896 isPropertyDeclaration(value.declarations[0]) 897 ) { 898 const propmMods = ts.getModifiers(value.declarations[0] as ts.PropertyDeclaration);//value.declarations[0].modifiers; // TSC 4.2 doesn't have 'getModifiers()' method 899 if (hasModifier(propmMods, SyntaxKind.ReadonlyKeyword)) { 900 result = true; 901 return; 902 } 903 } 904 }); 905 906 return result; 907} 908 909function hasDefaultCtor(type: Type): boolean { 910 if (type.symbol.members === undefined) return true; // No members -> no explicite constructors -> there is default ctor 911 912 let hasCtor = false; // has any constructor 913 let hasDefaultCtor = false; // has default constructor 914 915 type.symbol.members.forEach((value /*, key*/) => { 916 if ((value.flags & SymbolFlags.Constructor) !== 0) { 917 hasCtor = true; 918 919 if (value.declarations !== undefined && value.declarations.length > 0) { 920 const declCtor = value.declarations[0] as ConstructorDeclaration; 921 if (declCtor.parameters.length === 0) { 922 hasDefaultCtor = true; 923 return; 924 } 925 } 926 } 927 }); 928 929 return !hasCtor || hasDefaultCtor; // Has no any explicite constructor -> has implicite default constructor. 930} 931 932function isAbstractClass(type: Type): boolean { 933 if (type.isClass() && type.symbol.declarations && type.symbol.declarations.length > 0) { 934 const declClass = type.symbol.declarations[0] as ClassDeclaration; 935 const classMods = ts.getModifiers(declClass); //declClass.modifiers; // TSC 4.2 doesn't have 'getModifiers()' method 936 if (hasModifier(classMods, SyntaxKind.AbstractKeyword)) { 937 return true; 938 } 939 } 940 941 return false; 942} 943 944export function validateObjectLiteralType(type: Type | undefined): boolean { 945 if (!type) return false; 946 947 type = getTargetType(type); 948 return ( 949 type !== undefined && type.isClassOrInterface() && hasDefaultCtor(type) && 950 !hasReadonlyFields(type) && !isAbstractClass(type) 951 ); 952} 953 954export function isStructDeclarationKind(kind: SyntaxKind) { 955 return kind === SyntaxKind.StructDeclaration; 956} 957 958export function isStructDeclaration(node: Node) { 959 return isStructDeclarationKind(node.kind); 960} 961export function isStructObjectInitializer(objectLiteral: ObjectLiteralExpression): boolean { 962 if(isCallLikeExpression(objectLiteral.parent)) { 963 const signature = typeChecker.getResolvedSignature(objectLiteral.parent); 964 const signDecl = signature?.declaration; 965 return !!signDecl && isConstructorDeclaration(signDecl) && isStructDeclaration(signDecl.parent); 966 } 967 return false; 968} 969 970export function hasMethods(type: Type): boolean { 971 const properties = typeChecker.getPropertiesOfType(type); 972 if (properties?.length) { 973 for (const prop of properties) { 974 if (prop.getFlags() & SymbolFlags.Method) return true; 975 } 976 }; 977 978 return false; 979} 980 981function findProperty(type: Type, name: string): Symbol | undefined { 982 const properties = typeChecker.getPropertiesOfType(type); 983 if(properties.length) { 984 for (const prop of properties) { 985 if (prop.name === name) return prop; 986 } 987 } 988 return undefined; 989} 990 991export function checkTypeSet(typeSet: ts.Type, predicate: CheckType): boolean { 992 if (!typeSet.isUnionOrIntersection()) { 993 return predicate(typeSet); 994 } 995 for (let elemType of typeSet.types) { 996 if (checkTypeSet(elemType, predicate)) { 997 return true; 998 } 999 } 1000 return false; 1001} 1002 1003export function getNonNullableType(t: ts.Type): ts.Type { 1004 if (t.isUnion()) { 1005 return t.getNonNullableType(); 1006 } 1007 return t; 1008} 1009 1010export function isObjectLiteralAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.ObjectLiteralExpression): boolean { 1011 if (lhsType === undefined) { 1012 return false; 1013 } 1014 1015 // Always check with the non-nullable variant of lhs type. 1016 lhsType = getNonNullableType(lhsType); 1017 1018 if (lhsType.isUnion()) { 1019 for (const compType of lhsType.types) { 1020 if (isObjectLiteralAssignable(compType, rhsExpr)) { 1021 return true; 1022 } 1023 } 1024 } 1025 1026 // Allow initializing with anything when the type 1027 // originates from the library. 1028 if (isAnyType(lhsType) || isLibraryType(lhsType)) { 1029 return true; 1030 } 1031 1032 // issue 13412: 1033 // Allow initializing with a dynamic object when the LHS type 1034 // is primitive or defined in standard library. 1035 if (isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) { 1036 return true; 1037 } 1038 1039 // For Partial<T>, Required<T>, Readonly<T> types, validate their argument type. 1040 if (isStdPartialType(lhsType) || isStdRequiredType(lhsType) || isStdReadonlyType(lhsType)) { 1041 if (lhsType.aliasTypeArguments && lhsType.aliasTypeArguments.length === 1) { 1042 lhsType = lhsType.aliasTypeArguments[0]; 1043 } else { 1044 return false; 1045 } 1046 } 1047 1048 // Allow initializing Record objects with object initializer. 1049 // Record supports any type for a its value, but the key value 1050 // must be either a string or number literal. 1051 if (isStdRecordType(lhsType)) { 1052 return validateRecordObjectKeys(rhsExpr); 1053 } 1054 1055 return validateObjectLiteralType(lhsType) && !hasMethods(lhsType) && validateFields(lhsType, rhsExpr); 1056} 1057 1058function isDynamicObjectAssignedToStdType(lhsType: Type, rhsExpr: Expression): boolean { 1059 if (isStdLibraryType(lhsType) || isPrimitiveType(lhsType)) { 1060 const rhsSym = isCallExpression(rhsExpr) 1061 ? getSymbolOfCallExpression(rhsExpr) 1062 : typeChecker.getSymbolAtLocation(rhsExpr); 1063 1064 if (rhsSym && isLibrarySymbol(rhsSym)) return true; 1065 } 1066 return false; 1067} 1068 1069function getTargetType(type: Type): Type { 1070 return (type.getFlags() & TypeFlags.Object) && 1071 (type as ObjectType).objectFlags & ObjectFlags.Reference ? (type as TypeReference).target : type; 1072} 1073 1074export function isLiteralType(type: Type): boolean { 1075 return type.isLiteral() || (type.flags & TypeFlags.BooleanLiteral) !== 0; 1076} 1077 1078export function validateFields(objectType: Type, objectLiteral: ObjectLiteralExpression): boolean { 1079 for (const prop of objectLiteral.properties) { 1080 if (isPropertyAssignment(prop)) { 1081 if (!validateField(objectType, prop)) { 1082 return false; 1083 } 1084 } 1085 }; 1086 1087 return true; 1088} 1089 1090function validateField(type: ts.Type, prop: ts.PropertyAssignment): boolean { 1091 // Issue 15497: Use unescaped property name to find correpsponding property. 1092 const propNameSymbol = typeChecker.getSymbolAtLocation(prop.name); 1093 const propName = propNameSymbol ? 1094 ts.symbolName(propNameSymbol) : 1095 ts.isMemberName(prop.name) ? 1096 ts.idText(prop.name) : 1097 prop.name.getText(); 1098 const propSym = findProperty(type, propName); 1099 if (!propSym || !propSym.declarations?.length) { 1100 return false; 1101 } 1102 1103 const propType = TypeScriptLinter.tsTypeChecker.getTypeOfSymbolAtLocation(propSym, propSym.declarations[0]); 1104 const initExpr = unwrapParenthesized(prop.initializer); 1105 if (ts.isObjectLiteralExpression(initExpr)) { 1106 if (!isObjectLiteralAssignable(propType, initExpr)) { 1107 return false; 1108 } 1109 } else { 1110 // Only check for structural sub-typing. 1111 if (needToDeduceStructuralIdentity(propType, TypeScriptLinter.tsTypeChecker.getTypeAtLocation(initExpr), initExpr)) { 1112 return false; 1113 } 1114 } 1115 1116 return true; 1117} 1118 1119function isSupportedTypeNodeKind(kind: SyntaxKind): boolean { 1120 return kind !== SyntaxKind.AnyKeyword && kind !== SyntaxKind.UnknownKeyword && 1121 kind !== SyntaxKind.SymbolKeyword && kind !== SyntaxKind.IndexedAccessType && 1122 kind !== SyntaxKind.ConditionalType && kind !== SyntaxKind.MappedType && 1123 kind !== SyntaxKind.InferType; 1124 1125} 1126 1127export function isSupportedType(typeNode: TypeNode): boolean { 1128 if (isParenthesizedTypeNode(typeNode)) return isSupportedType(typeNode.type); 1129 1130 if (isArrayTypeNode(typeNode)) return isSupportedType(typeNode.elementType); 1131 1132 if (isTypeReferenceNode(typeNode) && typeNode.typeArguments) { 1133 for (const typeArg of typeNode.typeArguments) { 1134 if (!isSupportedType(typeArg)) { return false; } 1135 } 1136 return true; 1137 } 1138 1139 if (isUnionTypeNode(typeNode)) { 1140 for (const unionTypeElem of typeNode.types) { 1141 if (!isSupportedType(unionTypeElem)) { return false; } 1142 } 1143 return true; 1144 } 1145 1146 if (isTupleTypeNode(typeNode)) { 1147 for (const elem of typeNode.elements) { 1148 if (isTypeNode(elem) && !isSupportedType(elem)) return false; 1149 if (isNamedTupleMember(elem) && !isSupportedType(elem.type)) return false; 1150 } 1151 return true; 1152 } 1153 1154 return !isTypeLiteralNode(typeNode) && !isTypeQueryNode(typeNode) && 1155 !isIntersectionTypeNode(typeNode) && isSupportedTypeNodeKind(typeNode.kind); 1156} 1157 1158export function isStruct(symbol: Symbol) { 1159 if (!symbol.declarations) { 1160 return false; 1161 } 1162 for (const decl of symbol.declarations) { 1163 if (isStructDeclaration(decl)) { 1164 return true; 1165 } 1166 } 1167 return false; 1168} 1169 1170function validateRecordObjectKeys(objectLiteral: ObjectLiteralExpression): boolean { 1171 for (const prop of objectLiteral.properties) { 1172 if (!prop.name) { 1173 return false; 1174 } 1175 const isValidComputedProperty = isComputedPropertyName(prop.name) && isValidComputedPropertyName(prop.name, true); 1176 if (!isStringLiteral(prop.name) && !isNumericLiteral(prop.name) && !isValidComputedProperty) { 1177 return false; 1178 } 1179 } 1180 return true; 1181} 1182 1183/* Not need in Tsc 4.9 1184export function getDecorators(node: Node): readonly Decorator[] | undefined { 1185 if (node.decorators) { 1186 return filter(node.decorators, isDecorator); 1187 } 1188} 1189*/ 1190 1191export type CheckType = ((t: Type) => boolean); 1192 1193export const ES_OBJECT = "ESObject"; 1194 1195export const LIMITED_STD_GLOBAL_FUNC = [ 1196 "eval" 1197]; 1198export const LIMITED_STD_OBJECT_API = [ 1199 "__proto__", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "assign", "create", 1200 "defineProperties", "defineProperty", "freeze", "fromEntries", "getOwnPropertyDescriptor", 1201 "getOwnPropertyDescriptors", "getOwnPropertySymbols", "getPrototypeOf", "hasOwnProperty", "is", 1202 "isExtensible", "isFrozen", "isPrototypeOf", "isSealed", "preventExtensions", "propertyIsEnumerable", 1203 "seal", "setPrototypeOf" 1204]; 1205export const LIMITED_STD_REFLECT_API = [ 1206 "apply", "construct", "defineProperty", "deleteProperty", "getOwnPropertyDescriptor", "getPrototypeOf", 1207 "isExtensible", "preventExtensions", "setPrototypeOf" 1208]; 1209export const LIMITED_STD_PROXYHANDLER_API = [ 1210 "apply", "construct", "defineProperty", "deleteProperty", "get", "getOwnPropertyDescriptor", "getPrototypeOf", 1211 "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf" 1212]; 1213 1214export const FUNCTION_HAS_NO_RETURN_ERROR_CODE = 2366; 1215export const NON_RETURN_FUNCTION_DECORATORS = ["AnimatableExtend", "Builder", "Extend", "Styles" ]; 1216 1217export const STANDARD_LIBRARIES = [ 1218 "lib.dom.d.ts", "lib.dom.iterable.d.ts", "lib.webworker.d.ts", "lib.webworker.importscripd.ts", 1219 "lib.webworker.iterable.d.ts", "lib.scripthost.d.ts", "lib.decorators.d.ts", "lib.decorators.legacy.d.ts", 1220 "lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.generator.d.ts", 1221 "lib.es2015.iterable.d.ts", "lib.es2015.promise.d.ts", "lib.es2015.proxy.d.ts", "lib.es2015.reflect.d.ts", 1222 "lib.es2015.symbol.d.ts", "lib.es2015.symbol.wellknown.d.ts", "lib.es2016.array.include.d.ts", 1223 "lib.es2017.object.d.ts", "lib.es2017.sharedmemory.d.ts", "lib.es2017.string.d.ts", "lib.es2017.intl.d.ts", 1224 "lib.es2017.typedarrays.d.ts", "lib.es2018.asyncgenerator.d.ts", "lib.es2018.asynciterable.d.ts", 1225 "lib.es2018.intl.d.ts", "lib.es2018.promise.d.ts", "lib.es2018.regexp.d.ts", "lib.es2019.array.d.ts", 1226 "lib.es2019.object.d.ts", "lib.es2019.string.d.ts", "lib.es2019.symbol.d.ts", "lib.es2019.intl.d.ts", 1227 "lib.es2020.bigint.d.ts", "lib.es2020.date.d.ts", "lib.es2020.promise.d.ts", "lib.es2020.sharedmemory.d.ts", 1228 "lib.es2020.string.d.ts", "lib.es2020.symbol.wellknown.d.ts", "lib.es2020.intl.d.ts", "lib.es2020.number.d.ts", 1229 "lib.es2021.promise.d.ts", "lib.es2021.string.d.ts", "lib.es2021.weakref.d.ts", "lib.es2021.intl.d.ts", 1230 "lib.es2022.array.d.ts", "lib.es2022.error.d.ts", "lib.es2022.intl.d.ts", "lib.es2022.object.d.ts", 1231 "lib.es2022.sharedmemory.d.ts", "lib.es2022.string.d.ts", "lib.es2022.regexp.d.ts", "lib.es2023.array.d.ts", 1232]; 1233 1234export const TYPED_ARRAYS = [ 1235 "Int8Array", 1236 "Uint8Array", 1237 "Uint8ClampedArray", 1238 "Int16Array", 1239 "Uint16Array", 1240 "Int32Array", 1241 "Uint32Array", 1242 "Float32Array", 1243 "Float64Array", 1244 "BigInt64Array", 1245 "BigUint64Array", 1246 ]; 1247 1248export function getParentSymbolName(symbol: Symbol): string | undefined { 1249 const name = typeChecker.getFullyQualifiedName(symbol); 1250 const dotPosition = name.lastIndexOf("."); 1251 return (dotPosition === -1) ? undefined : name.substring(0, dotPosition); 1252} 1253 1254export function isGlobalSymbol(symbol: Symbol): boolean { 1255 const parentName = getParentSymbolName(symbol); 1256 return !parentName || parentName === "global"; 1257} 1258 1259export function isSymbolAPI(symbol: Symbol): boolean { 1260 const parentName = getParentSymbolName(symbol); 1261 let name = parentName ? parentName : symbol.escapedName; 1262 return name === 'Symbol' || name === "SymbolConstructor"; 1263} 1264 1265export function isStdSymbol(symbol: ts.Symbol): boolean { 1266 const name = TypeScriptLinter.tsTypeChecker.getFullyQualifiedName(symbol) 1267 return name === 'Symbol' && isGlobalSymbol(symbol); 1268} 1269 1270export function isSymbolIterator(symbol: ts.Symbol): boolean { 1271 const name = symbol.name; 1272 const parName = getParentSymbolName(symbol); 1273 return (parName === 'Symbol' || parName === 'SymbolConstructor') && name === 'iterator' 1274} 1275 1276export function isDefaultImport(importSpec: ImportSpecifier): boolean { 1277 return importSpec?.propertyName?.text === "default"; 1278} 1279export function hasAccessModifier(decl: Declaration): boolean { 1280 const modifiers = ts.getModifiers(decl as HasModifiers); //decl.modifiers; // TSC 4.2 doesn't have 'getModifiers()' method 1281 return ( 1282 !!modifiers && 1283 (hasModifier(modifiers, SyntaxKind.PublicKeyword) || 1284 hasModifier(modifiers, SyntaxKind.ProtectedKeyword) || 1285 hasModifier(modifiers, SyntaxKind.PrivateKeyword)) 1286 ); 1287} 1288 1289export function getModifier(modifiers: readonly Modifier[] | undefined, modifierKind: SyntaxKind): Modifier | undefined { 1290 if (!modifiers) return undefined; 1291 return modifiers.find(x => x.kind === modifierKind); 1292} 1293 1294export function getAccessModifier(modifiers: readonly Modifier[] | undefined): Modifier | undefined { 1295 return getModifier(modifiers, SyntaxKind.PublicKeyword) ?? 1296 getModifier(modifiers, SyntaxKind.ProtectedKeyword) ?? 1297 getModifier(modifiers, SyntaxKind.PrivateKeyword); 1298} 1299 1300export function isStdRecordType(type: Type): boolean { 1301 // In TypeScript, 'Record<K, T>' is defined as type alias to a mapped type. 1302 // Thus, it should have 'aliasSymbol' and 'target' properties. The 'target' 1303 // in this case will resolve to origin 'Record' symbol. 1304 if (type.aliasSymbol) { 1305 const target = (type as TypeReference).target; 1306 if (target) { 1307 const sym = target.aliasSymbol; 1308 return !!sym && sym.getName() === "Record" && isGlobalSymbol(sym); 1309 } 1310 } 1311 1312 return false; 1313} 1314 1315export function isStdMapType(type: Type): boolean { 1316 const sym = type.symbol; 1317 return !!sym && sym.getName() === "Map" && isGlobalSymbol(sym); 1318} 1319 1320export function isStdErrorType(type: ts.Type): boolean { 1321 const symbol = type.symbol; 1322 if (!symbol) { 1323 return false; 1324 } 1325 const name = typeChecker.getFullyQualifiedName(symbol); 1326 return name === 'Error' && isGlobalSymbol(symbol); 1327} 1328 1329export function isStdPartialType(type: Type): boolean { 1330 const sym = type.aliasSymbol; 1331 return !!sym && sym.getName() === "Partial" && isGlobalSymbol(sym); 1332} 1333 1334export function isStdRequiredType(type: Type): boolean { 1335 const sym = type.aliasSymbol; 1336 return !!sym && sym.getName() === "Required" && isGlobalSymbol(sym); 1337} 1338 1339export function isStdReadonlyType(type: Type): boolean { 1340 const sym = type.aliasSymbol; 1341 return !!sym && sym.getName() === "Readonly" && isGlobalSymbol(sym); 1342} 1343 1344export function isLibraryType(type: Type): boolean { 1345 const nonNullableType = type.getNonNullableType(); 1346 if (nonNullableType.isUnion()) { 1347 for (const componentType of nonNullableType.types) { 1348 if (!isLibraryType(componentType)) { 1349 return false; 1350 } 1351 } 1352 return true; 1353 } 1354 return isLibrarySymbol(nonNullableType.aliasSymbol ?? nonNullableType.getSymbol()); 1355} 1356 1357export function hasLibraryType(node: Node): boolean { 1358 return isLibraryType(typeChecker.getTypeAtLocation(node)); 1359} 1360 1361export function isLibrarySymbol(sym: Symbol | undefined) { 1362 if (sym && sym.declarations && sym.declarations.length > 0) { 1363 const srcFile = sym.declarations[0].getSourceFile(); 1364 if (!srcFile) { 1365 return false; 1366 } 1367 const fileName = srcFile.fileName; 1368 1369 // Symbols from both *.ts and *.d.ts files should obey interop rules. 1370 // We disable such behavior for *.ts files in the test mode due to lack of 'ets' 1371 // extension support. 1372 const ext = getAnyExtensionFromPath(fileName); 1373 const isThirdPartyCode = 1374 ARKTS_IGNORE_DIRS.some(ignore => pathContainsDirectory(normalizePath(fileName), ignore)) || 1375 ARKTS_IGNORE_FILES.some(ignore => getBaseFileName(fileName) === ignore); 1376 const isEts = (ext === '.ets'); 1377 const isTs = (ext === '.ts' && !srcFile.isDeclarationFile); 1378 const isStatic = (isEts || (isTs && testMode)) && !isThirdPartyCode; 1379 const isStdLib = STANDARD_LIBRARIES.includes(getBaseFileName(fileName).toLowerCase()); 1380 // We still need to confirm support for certain API from the 1381 // TypeScript standard library in ArkTS. Thus, for now do not 1382 // count standard library modules as dynamic. 1383 return !isStatic && !isStdLib; 1384 } 1385 1386 return false; 1387} 1388 1389export function pathContainsDirectory(targetPath: string, dir: string): boolean { 1390 for (const subdir of getPathComponents(targetPath)) { 1391 if (subdir === dir) { 1392 return true; 1393 } 1394 } 1395 return false; 1396} 1397 1398export function getScriptKind(srcFile: SourceFile): ScriptKind { 1399 const fileName = srcFile.fileName; 1400 const ext = getAnyExtensionFromPath(fileName); 1401 switch (ext.toLowerCase()) { 1402 case Extension.Js: 1403 return ScriptKind.JS; 1404 case Extension.Jsx: 1405 return ScriptKind.JSX; 1406 case Extension.Ts: 1407 return ScriptKind.TS; 1408 case Extension.Tsx: 1409 return ScriptKind.TSX; 1410 case Extension.Json: 1411 return ScriptKind.JSON; 1412 default: 1413 return ScriptKind.Unknown; 1414 } 1415} 1416 1417export function isStdLibraryType(type: Type): boolean { 1418 return isStdLibrarySymbol(type.aliasSymbol ?? type.getSymbol()); 1419} 1420 1421export function isStdLibrarySymbol(sym: Symbol | undefined) { 1422 if (sym && sym.declarations && sym.declarations.length > 0) { 1423 const srcFile = sym.declarations[0].getSourceFile(); 1424 return srcFile && 1425 STANDARD_LIBRARIES.includes(getBaseFileName(srcFile.fileName).toLowerCase()); 1426 } 1427 1428 return false; 1429} 1430 1431export function isIntrinsicObjectType(type: Type): boolean { 1432 return !!(type.flags & TypeFlags.NonPrimitive); 1433} 1434 1435export function isDynamicType(type: Type | undefined): boolean | undefined { 1436 if (type === undefined) { 1437 return false; 1438 } 1439 1440 // Return 'true' if it is an object of library type initialization, otherwise 1441 // return 'false' if it is not an object of standard library type one. 1442 // In the case of standard library type we need to determine context. 1443 1444 // Check the non-nullable version of type to eliminate 'undefined' type 1445 // from the union type elements. 1446 type = type.getNonNullableType(); 1447 1448 1449 if (type.isUnion()) { 1450 for (const compType of type.types) { 1451 const isDynamic = isDynamicType(compType); 1452 if (isDynamic || isDynamic === undefined) { 1453 return isDynamic; 1454 } 1455 } 1456 return false; 1457 } 1458 1459 if (isLibraryType(type)) { 1460 return true; 1461 } 1462 1463 if (!isStdLibraryType(type) && !isIntrinsicObjectType(type) && !isAnyType(type)) { 1464 return false; 1465 } 1466 1467 return undefined; 1468} 1469 1470export function isObjectType(type: ts.Type): type is ts.ObjectType { 1471 return !!(type.flags & ts.TypeFlags.Object); 1472} 1473 1474export function isAnonymous(type: ts.Type): boolean { 1475 if (isObjectType(type)) { 1476 return !!(type.objectFlags & ts.ObjectFlags.Anonymous); 1477 } 1478 return false; 1479} 1480 1481 1482export function isDynamicLiteralInitializer(expr: Expression): boolean { 1483 if (!isObjectLiteralExpression(expr) && !isArrayLiteralExpression(expr)) { 1484 return false; 1485 } 1486 1487 // Handle nested literals: 1488 // { f: { ... } } 1489 let curNode: Node = expr; 1490 while (isObjectLiteralExpression(curNode) || isArrayLiteralExpression(curNode)) { 1491 const exprType = typeChecker.getContextualType(curNode); 1492 if (exprType !== undefined && !isAnonymous(exprType)) { 1493 const res = isDynamicType(exprType); 1494 if (res !== undefined) { 1495 return res; 1496 } 1497 } 1498 1499 curNode = curNode.parent; 1500 if (isPropertyAssignment(curNode)) { 1501 curNode = curNode.parent; 1502 } 1503 } 1504 1505 // Handle calls with literals: 1506 // foo({ ... }) 1507 if (isCallExpression(curNode)) { 1508 const callExpr = curNode; 1509 const type = typeChecker.getTypeAtLocation(callExpr.expression); 1510 1511 // this check is a hack to fix #13474, only for tac 4.2 1512 if (isAnyType(type)) return true; 1513 1514 let sym: Symbol | undefined = type.symbol; 1515 if(isLibrarySymbol(sym)) { 1516 return true; 1517 } 1518 1519 // #13483: 1520 // x.foo({ ... }), where 'x' is a variable exported from some library: 1521 if (isPropertyAccessExpression(callExpr.expression)) { 1522 sym = typeChecker.getSymbolAtLocation(callExpr.expression.expression); 1523 if (sym && sym.getFlags() & SymbolFlags.Alias) { 1524 sym = typeChecker.getAliasedSymbol(sym); 1525 if (isLibrarySymbol(sym)) { 1526 return true; 1527 } 1528 } 1529 } 1530 } 1531 1532 // Handle property assignments with literals: 1533 // obj.f = { ... } 1534 if (isBinaryExpression(curNode)) { 1535 const binExpr = curNode; 1536 if (isPropertyAccessExpression(binExpr.left)) { 1537 const propAccessExpr = binExpr.left; 1538 const type = typeChecker.getTypeAtLocation(propAccessExpr.expression); 1539 return isLibrarySymbol(type.symbol); 1540 } 1541 } 1542 1543 return false; 1544} 1545 1546export function isEsObjectType(typeNode: ts.TypeNode | undefined): boolean { 1547 return !!typeNode && ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName) && 1548 typeNode.typeName.text === ES_OBJECT; 1549} 1550 1551export function isInsideBlock(node: ts.Node): boolean { 1552 let par = node.parent 1553 while (par) { 1554 if (ts.isBlock(par)) { 1555 return true; 1556 } 1557 par = par.parent; 1558 } 1559 return false; 1560} 1561 1562export function isEsObjectPossiblyAllowed(typeRef: ts.TypeReferenceNode): boolean { 1563 return ts.isVariableDeclaration(typeRef.parent); 1564} 1565 1566export function isValueAssignableToESObject(node: ts.Node): boolean { 1567 if (ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node)) { 1568 return false; 1569 } 1570 const valueType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(node); 1571 return isUnsupportedType(valueType) || isAnonymousType(valueType) 1572} 1573 1574export function getVariableDeclarationTypeNode(node: Node): TypeNode | undefined { 1575 let sym = trueSymbolAtLocation(node); 1576 if (sym === undefined) { 1577 return undefined; 1578 } 1579 return getSymbolDeclarationTypeNode(sym); 1580 } 1581 1582export function getSymbolDeclarationTypeNode(sym: ts.Symbol): ts.TypeNode | undefined { 1583 const decl = getDeclaration(sym); 1584 if (!!decl && isVariableDeclaration(decl)) { 1585 return decl.type; 1586 } 1587 return undefined; 1588} 1589 1590export function hasEsObjectType(node: Node): boolean { 1591 const typeNode = getVariableDeclarationTypeNode(node); 1592 return typeNode !== undefined && isEsObjectType(typeNode); 1593} 1594 1595export function symbolHasEsObjectType(sym: ts.Symbol): boolean { 1596 const typeNode = getSymbolDeclarationTypeNode(sym); 1597 return typeNode !== undefined && isEsObjectType(typeNode); 1598} 1599 1600export function isEsObjectSymbol(sym: Symbol): boolean { 1601 const decl = getDeclaration(sym); 1602 return !!decl && isTypeAliasDeclaration(decl) && decl.name.escapedText === ES_OBJECT && 1603 decl.type.kind === SyntaxKind.AnyKeyword; 1604} 1605 1606export function isAnonymousType(type: Type): boolean { 1607 if (type.isUnionOrIntersection()) { 1608 for (const compType of type.types) { 1609 if (isAnonymousType(compType)) { 1610 return true; 1611 } 1612 } 1613 return false; 1614 } 1615 1616 return (type.flags & TypeFlags.Object) !== 0 && 1617 ((type as ObjectType).objectFlags & ObjectFlags.Anonymous) !== 0; 1618} 1619 1620export function getSymbolOfCallExpression(callExpr: CallExpression): Symbol | undefined { 1621 const signature = typeChecker.getResolvedSignature(callExpr); 1622 const signDecl = signature?.getDeclaration(); 1623 if (signDecl && signDecl.name) { 1624 return typeChecker.getSymbolAtLocation(signDecl.name); 1625 } 1626 return undefined; 1627} 1628 1629export function typeIsRecursive(topType: Type, type: Type | undefined = undefined): boolean { 1630 if (type === undefined) { 1631 type = topType; 1632 } 1633 else if (type === topType) { 1634 return true; 1635 } 1636 else if (type.aliasSymbol) { 1637 return false; 1638 } 1639 1640 if (type.isUnion()) { 1641 for (const unionElem of type.types) { 1642 if (typeIsRecursive(topType, unionElem)) { 1643 return true; 1644 } 1645 } 1646 } 1647 if (type.flags & TypeFlags.Object && (type as ObjectType).objectFlags & ObjectFlags.Reference) { 1648 const typeArgs = typeChecker.getTypeArguments(type as TypeReference); 1649 if (typeArgs) { 1650 for (const typeArg of typeArgs) { 1651 if (typeIsRecursive(topType, typeArg)) { 1652 return true; 1653 } 1654 } 1655 } 1656 } 1657 return false; 1658} 1659 1660export function getTypeOrTypeConstraintAtLocation(expr: ts.Expression): ts.Type { 1661 let type = typeChecker.getTypeAtLocation(expr); 1662 if (type.isTypeParameter()) { 1663 let constraint = type.getConstraint(); 1664 if (constraint) { 1665 return constraint; 1666 } 1667 } 1668 return type; 1669} 1670 1671function areCompatibleFunctionals(lhsType: ts.Type, rhsType: ts.Type): boolean { 1672 return ( 1673 (isStdFunctionType(lhsType) || isFunctionalType(lhsType)) && 1674 (isStdFunctionType(rhsType) || isFunctionalType(rhsType)) 1675 ); 1676} 1677 1678function isFunctionalType(type: ts.Type): boolean { 1679 const callSigns = type.getCallSignatures(); 1680 return callSigns && callSigns.length > 0; 1681} 1682 1683function isStdFunctionType(type: ts.Type): boolean { 1684 const sym = type.getSymbol(); 1685 return !!sym && sym.getName() === 'Function' && isGlobalSymbol(sym); 1686} 1687 1688export function isStdBigIntType(type: ts.Type): boolean { 1689 const sym = type.symbol; 1690 return !!sym && sym.getName() === 'BigInt' && isGlobalSymbol(sym); 1691} 1692 1693export function isStdNumberType(type: ts.Type): boolean { 1694 const sym = type.symbol; 1695 return !!sym && sym.getName() === 'Number' && isGlobalSymbol(sym); 1696} 1697 1698export function isStdBooleanType(type: ts.Type): boolean { 1699 const sym = type.symbol; 1700 return !!sym && sym.getName() === 'Boolean' && isGlobalSymbol(sym); 1701} 1702 1703export function isEnumStringLiteral(expr: ts.Expression): boolean { 1704 const symbol = trueSymbolAtLocation(expr); 1705 const isEnumMember = !!symbol && !!(symbol.flags & ts.SymbolFlags.EnumMember); 1706 const type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(expr); 1707 const isStringEnumLiteral = isEnumType(type) && !!(type.flags & ts.TypeFlags.StringLiteral); 1708 return isEnumMember && isStringEnumLiteral; 1709} 1710 1711export function isValidComputedPropertyName(computedProperty: ComputedPropertyName, isRecordObjectInitializer = false): boolean { 1712 const expr = computedProperty.expression; 1713 if (!isRecordObjectInitializer) { 1714 const symbol = trueSymbolAtLocation(expr); 1715 if (!!symbol && isSymbolIterator(symbol)) { 1716 return true; 1717 } 1718 } 1719 return isStringLiteralLike(expr) || isEnumStringLiteral(computedProperty.expression); 1720} 1721 1722} 1723} 1724} 1725