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 faultsAttrs = Problems.faultsAttrs; 21 22//import cookBookMsg = cookBookMsg; 23//import cookBookTag = ts.cookBookTag; 24 25//import LinterConfig = ts.LinterConfig; 26 27import Autofix = Autofixer.Autofix; 28//import Autofixer = ts.Autofixer; 29 30import Logger = ts.perfLogger; 31 32import DiagnosticChecker = DiagnosticCheckerNamespace.DiagnosticChecker; 33import ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 34 LibraryTypeCallDiagnosticCheckerNamespace.ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE; 35import TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 36 LibraryTypeCallDiagnosticCheckerNamespace.TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE; 37import NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE = 38 LibraryTypeCallDiagnosticCheckerNamespace.NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE; 39import LibraryTypeCallDiagnosticChecker = 40 LibraryTypeCallDiagnosticCheckerNamespace.LibraryTypeCallDiagnosticChecker; 41 42//const logger = Logger.getLogger(); 43 44export interface ProblemInfo { 45 line: number; 46 column: number; 47 start: number; 48 end: number; 49 type: string; 50 severity: number; 51 problem: string; 52 suggest: string; 53 rule: string; 54 ruleTag: number; 55 autofixable: boolean; 56 autofix?: Autofix[]; 57} 58 59export class TypeScriptLinter { 60 static ideMode: boolean; 61 static strictMode: boolean; 62 static logTscErrors: boolean; 63 static warningsAsErrors: boolean; 64 static lintEtsOnly: boolean; 65 static totalVisitedNodes: number; 66 static nodeCounters: number[]; 67 static lineCounters: number[]; 68 69 static totalErrorLines: number; 70 static errorLineNumbersString: string; 71 static totalWarningLines: number; 72 static warningLineNumbersString: string; 73 static reportDiagnostics = true; 74 75 // The SyntaxKind enum defines additional elements at the end of the enum 76 // that serve as markers (FirstX/LastX). Those elements are initialized 77 // with indices of the previously defined elements. As result, the enum 78 // may return incorrect name for a certain kind index (e.g. 'FirstStatement' 79 // instead of 'VariableStatement'). 80 // The following code creates a map with correct syntax kind names. 81 // It can be used when need to print name of syntax kind of certain 82 // AST node in diagnostic messages. 83 //private static tsSyntaxKindNames: string[]; 84 85 static problemsInfos: ProblemInfo[] = []; 86 87 static filteredDiagnosticMessages: DiagnosticMessageChain[] = []; 88 static sharedModulesCache: ESMap<string, boolean>; 89 static strictDiagnosticCache: Set<Diagnostic>; 90 static unknowDiagnosticCache: Set<Diagnostic>; 91 92 public static initGlobals(): void { 93 TypeScriptLinter.filteredDiagnosticMessages = [] 94 TypeScriptLinter.sharedModulesCache = new Map<string, boolean>(); 95 TypeScriptLinter.strictDiagnosticCache = new Set<Diagnostic>(); 96 TypeScriptLinter.unknowDiagnosticCache = new Set<Diagnostic>(); 97 } 98 99 public static initStatic(): void { 100 TypeScriptLinter.strictMode = true; 101 TypeScriptLinter.logTscErrors = false; 102 TypeScriptLinter.warningsAsErrors = false; 103 TypeScriptLinter.lintEtsOnly = true; 104 TypeScriptLinter.totalVisitedNodes = 0; 105 TypeScriptLinter.nodeCounters = []; 106 TypeScriptLinter.lineCounters = []; 107 108 TypeScriptLinter.totalErrorLines = 0; 109 TypeScriptLinter.totalWarningLines = 0; 110 TypeScriptLinter.errorLineNumbersString = ""; 111 TypeScriptLinter.warningLineNumbersString = ""; 112 113 Autofixer.autofixInfo.length = 0; 114 115 //TypeScriptLinter.tsSyntaxKindNames = []; 116 //const keys = Object.keys(ts.SyntaxKind); 117 //const keys: string[] = []; 118 //const values = Object.values(ts.SyntaxKind); 119 //const values: string[] = []; 120 121 /* 122 for (let i = 0; i < values.length; i++) { 123 const val = values[i]; 124 const kindNum = typeof val === "string" ? parseInt(val) : val; 125 if (kindNum && !TypeScriptLinter.tsSyntaxKindNames[kindNum]) 126 TypeScriptLinter.tsSyntaxKindNames[kindNum] = keys[i]; 127 } 128 */ 129 130 for (let i = 0; i < FaultID.LAST_ID; i++) { 131 TypeScriptLinter.nodeCounters[i] = 0; 132 TypeScriptLinter.lineCounters[i] = 0; 133 } 134 135 TypeScriptLinter.problemsInfos = []; 136 } 137 138 public static tsTypeChecker: TypeChecker; 139 140 currentErrorLine: number; 141 currentWarningLine: number; 142 staticBlocks: Set<string>; 143 libraryTypeCallDiagnosticChecker: LibraryTypeCallDiagnosticChecker; 144 skipArkTSStaticBlocksCheck: boolean; 145 private fileExportSendableDeclCaches?: Set<ts.Node>; 146 private compatibleSdkVersionStage: string = 'beta1'; 147 private compatibleSdkVersion: number = 12; 148 149 constructor(private sourceFile: SourceFile, 150 /* private */ tsProgram: Program, 151 private tscStrictDiagnostics?: Map<Diagnostic[]>) { 152 TypeScriptLinter.tsTypeChecker = tsProgram.getLinterTypeChecker(); 153 this.currentErrorLine = 0; 154 this.currentWarningLine = 0; 155 this.staticBlocks = new Set<string>(); 156 this.libraryTypeCallDiagnosticChecker = new LibraryTypeCallDiagnosticChecker(TypeScriptLinter.filteredDiagnosticMessages); 157 158 const options = tsProgram.getCompilerOptions(); 159 this.skipArkTSStaticBlocksCheck = false; 160 if (options.skipArkTSStaticBlocksCheck) { 161 this.skipArkTSStaticBlocksCheck = options.skipArkTSStaticBlocksCheck as boolean; 162 } 163 if (options.compatibleSdkVersion) { 164 this.compatibleSdkVersion = options.compatibleSdkVersion; 165 } 166 if (options.compatibleSdkVersionStage) { 167 this.compatibleSdkVersionStage = options.compatibleSdkVersionStage; 168 } 169 } 170 171 public static clearTsTypeChecker(): void { 172 TypeScriptLinter.tsTypeChecker = {} as TypeChecker; 173 } 174 175 readonly handlersMap = new Map([ 176 [SyntaxKind.ObjectLiteralExpression, this.handleObjectLiteralExpression], 177 [SyntaxKind.ArrayLiteralExpression, this.handleArrayLiteralExpression], 178 [SyntaxKind.Parameter, this.handleParameter], 179 [SyntaxKind.EnumDeclaration, this.handleEnumDeclaration], 180 [SyntaxKind.InterfaceDeclaration, this.handleInterfaceDeclaration], 181 [SyntaxKind.ThrowStatement, this.handleThrowStatement], [SyntaxKind.ImportClause, this.handleImportClause], 182 [SyntaxKind.ForStatement, this.handleForStatement], 183 [SyntaxKind.ForInStatement, this.handleForInStatement], 184 [SyntaxKind.ForOfStatement, this.handleForOfStatement], 185 [SyntaxKind.ImportDeclaration, this.handleImportDeclaration], 186 [SyntaxKind.PropertyAccessExpression, this.handlePropertyAccessExpression], 187 [SyntaxKind.PropertyDeclaration, this.handlePropertyDeclaration], 188 [SyntaxKind.PropertyAssignment, this.handlePropertyAssignment], 189 [SyntaxKind.PropertySignature, this.handlePropertySignature], 190 [SyntaxKind.FunctionExpression, this.handleFunctionExpression], 191 [SyntaxKind.ArrowFunction, this.handleArrowFunction], 192 [SyntaxKind.CatchClause, this.handleCatchClause], 193 [SyntaxKind.FunctionDeclaration, this.handleFunctionDeclaration], 194 [SyntaxKind.PrefixUnaryExpression, this.handlePrefixUnaryExpression], 195 [SyntaxKind.BinaryExpression, this.handleBinaryExpression], 196 [SyntaxKind.VariableDeclarationList, this.handleVariableDeclarationList], 197 [SyntaxKind.VariableDeclaration, this.handleVariableDeclaration], 198 [SyntaxKind.ClassDeclaration, this.handleClassDeclaration], 199 [SyntaxKind.ModuleDeclaration, this.handleModuleDeclaration], 200 [SyntaxKind.TypeAliasDeclaration, this.handleTypeAliasDeclaration], 201 [SyntaxKind.ImportSpecifier, this.handleImportSpecifier], 202 [SyntaxKind.NamespaceImport, this.handleNamespaceImport], 203 [SyntaxKind.TypeAssertionExpression, this.handleTypeAssertionExpression], 204 [SyntaxKind.MethodDeclaration, this.handleMethodDeclaration], 205 [SyntaxKind.MethodSignature, this.handleMethodSignature], 206 [SyntaxKind.Identifier, this.handleIdentifier], 207 [SyntaxKind.ElementAccessExpression, this.handleElementAccessExpression], 208 [SyntaxKind.EnumMember, this.handleEnumMember], [SyntaxKind.TypeReference, this.handleTypeReference], 209 [SyntaxKind.ExportAssignment, this.handleExportAssignment], 210 [SyntaxKind.CallExpression, this.handleCallExpression], [SyntaxKind.MetaProperty, this.handleMetaProperty], 211 [SyntaxKind.NewExpression, this.handleNewExpression], [SyntaxKind.AsExpression, this.handleAsExpression], 212 [SyntaxKind.SpreadElement, this.handleSpreadOp], [SyntaxKind.SpreadAssignment, this.handleSpreadOp], 213 [SyntaxKind.GetAccessor, this.handleGetAccessor], [SyntaxKind.SetAccessor, this.handleSetAccessor], 214 [SyntaxKind.ConstructSignature, this.handleConstructSignature], 215 [SyntaxKind.ExpressionWithTypeArguments, this.handleExpressionWithTypeArguments], 216 [SyntaxKind.ComputedPropertyName, this.handleComputedPropertyName], 217 [SyntaxKind.EtsComponentExpression, this.handleEtsComponentExpression], 218 [SyntaxKind.ClassStaticBlockDeclaration, this.handleClassStaticBlockDeclaration], 219 [ts.SyntaxKind.IndexSignature, this.handleIndexSignature], 220 [ts.SyntaxKind.ExportKeyword, this.handleExportKeyword], 221 [ts.SyntaxKind.ExportDeclaration, this.handleExportDeclaration], 222 [ts.SyntaxKind.ReturnStatement, this.handleReturnStatement], 223 [ts.SyntaxKind.Decorator, this.handleDecorator] 224 ]); 225 226 public incrementCounters(node: Node | CommentRange, faultId: number, autofixable = false, autofix?: Autofix[]): void { 227 if (!TypeScriptLinter.strictMode && faultsAttrs[faultId].migratable) { return; } // In relax mode skip migratable 228 229 const [startOffset, endOffset] = Utils.getHighlightRange(node, faultId); 230 const startPos = this.sourceFile!.getLineAndCharacterOfPosition(startOffset); 231 const line = startPos.line + 1; 232 const character = startPos.character + 1; 233 234 TypeScriptLinter.nodeCounters[faultId]++; 235 236 const faultDescr = LinterConfig.nodeDesc[faultId]; 237 const faultType = "unknown"; //TypeScriptLinter.tsSyntaxKindNames[node.kind]; 238 239 const cookBookMsgNum = faultsAttrs[faultId] ? faultsAttrs[faultId].cookBookRef : 0; 240 const cookBookTg = cookBookTag[cookBookMsgNum]; 241 const severity = faultsAttrs[faultId]?.severity ?? Common.ProblemSeverity.ERROR; 242 const badNodeInfo: ProblemInfo = { 243 line: line, 244 column: character, 245 start: startOffset, 246 end: endOffset, 247 type: faultType, 248 severity: severity, 249 problem: FaultID[faultId], 250 suggest: cookBookMsgNum > 0 ? cookBookMsg[cookBookMsgNum] : "", 251 rule: cookBookMsgNum > 0 && cookBookTg !== "" ? cookBookTg : faultDescr ? faultDescr : faultType, 252 ruleTag: cookBookMsgNum, 253 autofixable: autofixable, 254 autofix: autofix 255 }; 256 257 TypeScriptLinter.problemsInfos.push(badNodeInfo); 258 259 if (!TypeScriptLinter.reportDiagnostics) { 260 //logger.info( 261 Logger.logEvent( 262 `Warning: ${this.sourceFile.fileName} (${line}, ${character}): ${faultDescr ? faultDescr : faultType}` 263 ); 264 } 265 266 TypeScriptLinter.lineCounters[faultId]++; 267 268 switch (faultsAttrs[faultId].severity) { 269 case Common.ProblemSeverity.ERROR: { 270 this.currentErrorLine = line; 271 ++TypeScriptLinter.totalErrorLines; 272 TypeScriptLinter.errorLineNumbersString += line + ', '; 273 break; 274 } 275 case Common.ProblemSeverity.WARNING: { 276 if (line === this.currentWarningLine) { 277 break; 278 } 279 this.currentWarningLine = line; 280 ++TypeScriptLinter.totalWarningLines; 281 TypeScriptLinter.warningLineNumbersString += line + ', '; 282 break; 283 } 284 } 285 } 286 287 private forEachNodeInSubtree(node: ts.Node, cb: (n: ts.Node) => void, stopCond?: (n: ts.Node) => boolean): void { 288 cb.call(this, node); 289 if (stopCond?.call(this, node)) { 290 return; 291 } 292 ts.forEachChild(node, (child) => { 293 this.forEachNodeInSubtree(child, cb, stopCond); 294 }); 295 } 296 297 private visitSourceFile(sf: ts.SourceFile): void { 298 const callback = (node: ts.Node): void => { 299 TypeScriptLinter.totalVisitedNodes++; 300 const incrementedType = LinterConfig.incrementOnlyTokens.get(node.kind); 301 if (incrementedType !== undefined) { 302 this.incrementCounters(node, incrementedType); 303 } 304 else { 305 const handler = this.handlersMap.get(node.kind); 306 if (handler !== undefined) { 307 handler.call(this, node); 308 } 309 } 310 }; 311 const stopCondition = (node: ts.Node): boolean => { 312 if (node === null || node.kind === null) { 313 return true; 314 } 315 // Skip synthetic constructor in Struct declaration. 316 if (node.parent && Utils.isStructDeclaration(node.parent) && ts.isConstructorDeclaration(node)) { 317 return true; 318 } 319 if (LinterConfig.terminalTokens.has(node.kind)) { 320 return true; 321 } 322 return false; 323 }; 324 this.forEachNodeInSubtree(sf, callback, stopCondition); 325 } 326 327 private countInterfaceExtendsDifferentPropertyTypes( 328 node: Node, 329 prop2type: Map<string /*, string*/>, 330 propName: string, 331 type: TypeNode | undefined 332 ): void { 333 if (type) { 334 const methodType = type.getText(); 335 const propType = prop2type.get(propName); 336 if (!propType) { 337 prop2type.set(propName, methodType); 338 } 339 else if (propType !== methodType) { 340 this.incrementCounters(node, FaultID.IntefaceExtendDifProps); 341 } 342 } 343 } 344 345 private countDeclarationsWithDuplicateName( 346 tsNode: Node, tsDeclNode: Node, tsDeclKind?: SyntaxKind 347 ): void { 348 const symbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsNode); 349 350 // If specific declaration kind is provided, check against it. 351 // Otherwise, use syntax kind of corresponding declaration node. 352 if (!!symbol && Utils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)) { 353 this.incrementCounters(tsDeclNode, FaultID.DeclWithDuplicateName); 354 } 355 } 356 357 private countClassMembersWithDuplicateName(tsClassDecl: ClassDeclaration): void { 358 for (const tsCurrentMember of tsClassDecl.members) { 359 if ( 360 !tsCurrentMember.name || 361 !(isIdentifier(tsCurrentMember.name) || isPrivateIdentifier(tsCurrentMember.name)) 362 ) { 363 continue; 364 } 365 for (const tsClassMember of tsClassDecl.members) { 366 if (tsCurrentMember === tsClassMember) continue; 367 368 if ( 369 !tsClassMember.name || 370 !(isIdentifier(tsClassMember.name) || isPrivateIdentifier(tsClassMember.name)) 371 ) { 372 continue; 373 } 374 if ( 375 isIdentifier(tsCurrentMember.name) && 376 isPrivateIdentifier(tsClassMember.name) && 377 tsCurrentMember.name.text === tsClassMember.name.text.substring(1) 378 ) { 379 this.incrementCounters(tsCurrentMember, FaultID.DeclWithDuplicateName); 380 break; 381 } 382 383 if ( 384 isPrivateIdentifier(tsCurrentMember.name) && 385 isIdentifier(tsClassMember.name) && 386 tsCurrentMember.name.text.substring(1) === tsClassMember.name.text 387 ) { 388 this.incrementCounters(tsCurrentMember, FaultID.DeclWithDuplicateName); 389 break; 390 } 391 } 392 } 393 } 394 395 private static scopeContainsThis(tsNode: ts.Expression | ts.Block): boolean { 396 function scopeContainsThisVisitor(tsNode: ts.Node): boolean { 397 if (tsNode.kind === SyntaxKind.ThisKeyword) { 398 return true; 399 } 400 401 // Visit children nodes. Skip any local declaration that defines 402 // its own scope as it needs to be checked separately. 403 const isClassLike = ts.isClassDeclaration(tsNode) || ts.isClassExpression(tsNode); 404 const isFunctionLike = ts.isFunctionDeclaration(tsNode) || ts.isFunctionExpression(tsNode); 405 const isModuleDecl = ts.isModuleDeclaration(tsNode); 406 if (isClassLike || isFunctionLike || isModuleDecl) { 407 return false; 408 } 409 410 for (const child of tsNode.getChildren()) { 411 if (scopeContainsThisVisitor(child)) { 412 return true; 413 } 414 } 415 return false; 416 } 417 418 return scopeContainsThisVisitor(tsNode); 419 } 420 421 private isPrototypePropertyAccess(tsPropertyAccess: ts.PropertyAccessExpression, propAccessSym: ts.Symbol | undefined, baseExprSym: ts.Symbol | undefined, baseExprType: ts.Type): boolean { 422 if (!(isIdentifier(tsPropertyAccess.name) && tsPropertyAccess.name.text === "prototype")) { 423 return false; 424 } 425 // #13600: Relax prototype check when expression comes from interop. 426 let curPropAccess: Node = tsPropertyAccess; 427 while (curPropAccess && isPropertyAccessExpression(curPropAccess)) { 428 const baseExprSym = Utils.trueSymbolAtLocation(curPropAccess.expression); 429 if (Utils.isLibrarySymbol(baseExprSym)) { 430 return false; 431 } 432 curPropAccess = curPropAccess.expression; 433 } 434 if (ts.isIdentifier(curPropAccess) && curPropAccess.text !== 'prototype') { 435 const type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(curPropAccess); 436 if (Utils.isAnyType(type)) { 437 return false; 438 } 439 } 440 // Check if property symbol is "Prototype" 441 if (Utils.isPrototypeSymbol(propAccessSym)) return true; 442 443 // Check if symbol of LHS-expression is Class or Function. 444 if (Utils.isTypeSymbol(baseExprSym) || Utils.isFunctionSymbol(baseExprSym)) { 445 return true; 446 } 447 // Check if type of LHS expression Function type or Any type. 448 // The latter check is to cover cases with multiple prototype 449 // chain (as the 'Prototype' property should be 'Any' type): 450 // X.prototype.prototype.prototype = ... 451 const baseExprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode( 452 baseExprType, undefined, NodeBuilderFlags.None 453 ); 454 455 return ((baseExprTypeNode && isFunctionTypeNode(baseExprTypeNode)) || Utils.isAnyType(baseExprType)); 456 } 457 458 private interfaceInheritanceLint(node: Node, heritageClauses: NodeArray<HeritageClause>): void { 459 for (const hClause of heritageClauses) { 460 if (hClause.token !== SyntaxKind.ExtendsKeyword) continue; 461 462 const prop2type = new Map<string, string>(); 463 for (const tsTypeExpr of hClause.types) { 464 const tsExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); 465 if (tsExprType.isClass()) { 466 this.incrementCounters(tsTypeExpr, FaultID.InterfaceExtendsClass); 467 } 468 else if (tsExprType.isClassOrInterface()) { 469 this.lintForInterfaceExtendsDifferentPorpertyTypes(node, tsExprType, prop2type); 470 } 471 } 472 } 473 } 474 475 private lintForInterfaceExtendsDifferentPorpertyTypes( 476 node: Node, tsExprType: Type, prop2type: Map</*string,*/ string> 477 ): void { 478 const props = tsExprType.getProperties(); 479 for (const p of props) { 480 if (!p.declarations) continue; 481 482 const decl: Declaration = p.declarations[0]; 483 if (decl.kind === SyntaxKind.MethodSignature) { 484 this.countInterfaceExtendsDifferentPropertyTypes( 485 node, prop2type, p.name, (decl as MethodSignature).type 486 ); 487 } 488 else if (decl.kind === SyntaxKind.MethodDeclaration) { 489 this.countInterfaceExtendsDifferentPropertyTypes( 490 node, prop2type, p.name, (decl as MethodDeclaration).type 491 ); 492 } 493 else if (decl.kind === SyntaxKind.PropertyDeclaration) { 494 this.countInterfaceExtendsDifferentPropertyTypes( 495 node, prop2type, p.name, (decl as PropertyDeclaration).type 496 ); 497 } 498 else if (decl.kind === SyntaxKind.PropertySignature) { 499 this.countInterfaceExtendsDifferentPropertyTypes( 500 node, prop2type, p.name, (decl as PropertySignature).type 501 ); 502 } 503 } 504 } 505 506 private handleObjectLiteralExpression(node: Node): void { 507 const objectLiteralExpr = node as ObjectLiteralExpression; 508 509 // If object literal is a part of destructuring assignment, then don't process it further. 510 if (Utils.isDestructuringAssignmentLHS(objectLiteralExpr)) { 511 return; 512 } 513 const objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(objectLiteralExpr); 514 if (objectLiteralType && Utils.typeContainsSendableClassOrInterface(objectLiteralType)) { 515 this.incrementCounters(node, FaultID.SendableObjectInitialization); 516 } else if ( 517 // issue 13082: Allow initializing struct instances with object literal. 518 !Utils.isStructObjectInitializer(objectLiteralExpr) && 519 !Utils.isDynamicLiteralInitializer(objectLiteralExpr) && 520 !Utils.isObjectLiteralAssignable(objectLiteralType, objectLiteralExpr) 521 ) { 522 this.incrementCounters(node, FaultID.ObjectLiteralNoContextType); 523 } 524 } 525 526 private handleArrayLiteralExpression(node: Node): void { 527 // If array literal is a part of destructuring assignment, then 528 // don't process it further. 529 if (Utils.isDestructuringAssignmentLHS(node as ArrayLiteralExpression)) return; 530 531 const arrayLitNode = node as ArrayLiteralExpression; 532 let noContextTypeForArrayLiteral = false; 533 534 const arrayLitType = TypeScriptLinter.tsTypeChecker.getContextualType(arrayLitNode); 535 if (arrayLitType && Utils.typeContainsSendableClassOrInterface(arrayLitType)) { 536 this.incrementCounters(node, FaultID.SendableObjectInitialization); 537 return; 538 } 539 540 // check that array literal consists of inferrable types 541 // e.g. there is no element which is untyped object literals 542 const arrayLitElements = arrayLitNode.elements; 543 for(const element of arrayLitElements) { 544 const elementContextType = TypeScriptLinter.tsTypeChecker.getContextualType(element); 545 if(element.kind === SyntaxKind.ObjectLiteralExpression) { 546 if (!Utils.isDynamicLiteralInitializer(arrayLitNode) && 547 !Utils.isObjectLiteralAssignable(elementContextType, element as ts.ObjectLiteralExpression)) { 548 noContextTypeForArrayLiteral = true; 549 break; 550 } 551 } 552 if (elementContextType) { 553 this.checkAssignmentMatching(element, elementContextType, element, true); 554 } 555 } 556 557 if (noContextTypeForArrayLiteral) { 558 this.incrementCounters(node, FaultID.ArrayLiteralNoContextType); 559 } 560 } 561 562 private handleParameter(node: Node): void { 563 const tsParam = node as ParameterDeclaration; 564 565 Utils.getDecoratorsIfInSendableClass(tsParam)?.forEach((decorator) => { 566 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 567 }); 568 569 if (isArrayBindingPattern(tsParam.name) || isObjectBindingPattern(tsParam.name)) { 570 this.incrementCounters(node, FaultID.DestructuringParameter); 571 } 572 const tsParamMods = ts.getModifiers(tsParam); //tsParam.modifiers; 573 if ( 574 tsParamMods && 575 (Utils.hasModifier(tsParamMods, SyntaxKind.PublicKeyword) || 576 Utils.hasModifier(tsParamMods, SyntaxKind.ProtectedKeyword) || 577 Utils.hasModifier(tsParamMods, SyntaxKind.ReadonlyKeyword) || 578 Utils.hasModifier(tsParamMods, SyntaxKind.PrivateKeyword)) 579 ) { 580 this.incrementCounters(node, FaultID.ParameterProperties); 581 } 582 583 this.handleDeclarationInferredType(tsParam); 584 } 585 586 private handleEnumDeclaration(node: Node): void { 587 const enumNode = node as EnumDeclaration; 588 this.countDeclarationsWithDuplicateName(enumNode.name, enumNode); 589 590 const enumSymbol = Utils.trueSymbolAtLocation(enumNode.name); 591 if (!enumSymbol) return; 592 593 const enumDecls = enumSymbol.getDeclarations(); 594 if (!enumDecls) return; 595 596 // Since type checker merges all declarations with the same name 597 // into one symbol, we need to check that there's more than one 598 // enum declaration related to that specific symbol. 599 // See 'countDeclarationsWithDuplicateName' method for details. 600 let enumDeclCount = 0; 601 for (const decl of enumDecls) { 602 if (decl.kind === SyntaxKind.EnumDeclaration) enumDeclCount++; 603 } 604 605 if (enumDeclCount > 1) this.incrementCounters(node, FaultID.EnumMerging); 606 } 607 608 private handleInterfaceDeclaration(node: Node): void { 609 const interfaceNode = node as InterfaceDeclaration; 610 const iSymbol = Utils.trueSymbolAtLocation(interfaceNode.name); 611 const iDecls = iSymbol ? iSymbol.getDeclarations() : null; 612 if (iDecls) { 613 // Since type checker merges all declarations with the same name 614 // into one symbol, we need to check that there's more than one 615 // interface declaration related to that specific symbol. 616 // See 'countDeclarationsWithDuplicateName' method for details. 617 let iDeclCount = 0; 618 for (const decl of iDecls) { 619 if (decl.kind === SyntaxKind.InterfaceDeclaration) iDeclCount++; 620 } 621 622 if (iDeclCount > 1) this.incrementCounters(node, FaultID.InterfaceMerging); 623 } 624 625 if (interfaceNode.heritageClauses) this.interfaceInheritanceLint(node, interfaceNode.heritageClauses); 626 627 this.countDeclarationsWithDuplicateName(interfaceNode.name, interfaceNode); 628 } 629 630 private handleThrowStatement(node: Node): void { 631 const throwStmt = node as ThrowStatement; 632 const throwExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(throwStmt.expression); 633 if (!throwExprType.isClassOrInterface() || !Utils.isOrDerivedFrom(throwExprType, Utils.isStdErrorType)) { 634 this.incrementCounters(node, FaultID.ThrowStatement); 635 } 636 } 637 638 private handleForStatement(node: Node): void { 639 const tsForStmt = node as ForStatement; 640 const tsForInit = tsForStmt.initializer; 641 if (tsForInit && (isArrayLiteralExpression(tsForInit) || isObjectLiteralExpression(tsForInit))) { 642 this.incrementCounters(tsForInit, FaultID.DestructuringAssignment); 643 } 644 } 645 646 private handleForInStatement(node: Node): void { 647 const tsForInStmt = node as ForInStatement; 648 const tsForInInit = tsForInStmt.initializer; 649 if (isArrayLiteralExpression(tsForInInit) || isObjectLiteralExpression(tsForInInit)) { 650 this.incrementCounters(tsForInInit, FaultID.DestructuringAssignment); 651 } 652 this.incrementCounters(node, FaultID.ForInStatement); 653 } 654 655 private handleForOfStatement(node: Node): void { 656 const tsForOfStmt = node as ForOfStatement; 657 const tsForOfInit = tsForOfStmt.initializer; 658 if (isArrayLiteralExpression(tsForOfInit) || isObjectLiteralExpression(tsForOfInit)) { 659 this.incrementCounters(tsForOfInit, FaultID.DestructuringAssignment); 660 } 661 } 662 663 private handleImportDeclaration(node: Node): void { 664 const importDeclNode = node as ImportDeclaration; 665 for (const stmt of importDeclNode.parent.statements) { 666 if (stmt === importDeclNode) { 667 break; 668 } 669 if (!isImportDeclaration(stmt)) { 670 this.incrementCounters(node, FaultID.ImportAfterStatement); 671 break; 672 } 673 } 674 675 const expr1 = importDeclNode.moduleSpecifier; 676 if (expr1.kind === ts.SyntaxKind.StringLiteral) { 677 if (importDeclNode.assertClause) { 678 this.incrementCounters(importDeclNode.assertClause, FaultID.ImportAssertion); 679 } 680 } 681 682 //handle no side effect import in sendable module 683 this.handleSharedModuleNoSideEffectImport(importDeclNode); 684 } 685 686 687 private handleSharedModuleNoSideEffectImport(node : ts.ImportDeclaration): void { 688 //check 'use shared' 689 if (TypeScriptLinter.inSharedModule(node) && !node.importClause) { 690 this.incrementCounters(node, FaultID.SharedNoSideEffectImport); 691 } 692 } 693 694 private static inSharedModule(node: ts.Node): boolean { 695 const sourceFile: ts.SourceFile = node.getSourceFile(); 696 const modulePath = normalizePath(sourceFile.fileName); 697 if (TypeScriptLinter.sharedModulesCache.has(modulePath)) { 698 return TypeScriptLinter.sharedModulesCache.get(modulePath)!; 699 } 700 const isSharedModule: boolean = Utils.isSharedModule(sourceFile); 701 TypeScriptLinter.sharedModulesCache.set(modulePath, isSharedModule); 702 return isSharedModule; 703 } 704 705 private handlePropertyAccessExpression(node: Node): void { 706 if (ts.isCallExpression(node.parent) && node == node.parent.expression) { 707 return; 708 } 709 710 const propertyAccessNode = node as PropertyAccessExpression; 711 const exprSym = Utils.trueSymbolAtLocation(propertyAccessNode); 712 const baseExprSym = Utils.trueSymbolAtLocation(propertyAccessNode.expression); 713 const baseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(propertyAccessNode.expression); 714 if (this.isPrototypePropertyAccess(propertyAccessNode, exprSym, baseExprSym, baseExprType)) { 715 this.incrementCounters(propertyAccessNode.name, FaultID.Prototype); 716 } 717 if (!!exprSym && Utils.isSymbolAPI(exprSym) && !Utils.ALLOWED_STD_SYMBOL_API.includes(exprSym.getName())) { 718 this.incrementCounters(propertyAccessNode, FaultID.SymbolType); 719 } 720 if (!!baseExprSym && Utils.symbolHasEsObjectType(baseExprSym)) { 721 this.incrementCounters(propertyAccessNode, FaultID.EsObjectType); 722 } 723 if (Utils.isSendableFunction(baseExprType) || Utils.hasSendableTypeAlias(baseExprType)) { 724 this.incrementCounters(propertyAccessNode, FaultID.SendableFunctionProperty); 725 } 726 } 727 728 private handlePropertyDeclaration(node: ts.PropertyDeclaration) { 729 const propName = node.name; 730 if (!!propName && ts.isNumericLiteral(propName)) { 731 let autofix: Autofix[] | undefined = Autofixer.fixLiteralAsPropertyName(node); 732 const autofixable = autofix !== undefined; 733 if (!Autofixer.shouldAutofix(node, FaultID.LiteralAsPropertyName)) { 734 autofix = undefined; 735 } 736 this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofixable, autofix); 737 } 738 const decorators = ts.getDecorators(node); 739 this.filterOutDecoratorsDiagnostics(decorators, Utils.NON_INITIALIZABLE_PROPERTY_DECORATORS, 740 {begin: propName.getStart(), end: propName.getStart()}, 741 Utils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE); 742 743 const classDecorators = ts.getDecorators(node.parent); 744 const propType = (node as ts.PropertyDeclaration).type?.getText(); 745 this.filterOutDecoratorsDiagnostics(classDecorators, Utils.NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS, 746 {begin: propName.getStart(), end: propName.getStart()}, Utils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE, propType); 747 if (node.type && node.initializer) { 748 this.checkAssignmentMatching(node, TypeScriptLinter.tsTypeChecker.getTypeAtLocation(node.type), node.initializer, true); 749 } 750 this.handleDeclarationInferredType(node); 751 this.handleDefiniteAssignmentAssertion(node); 752 this.handleSendableClassProperty(node); 753 } 754 755 private handleSendableClassProperty(node: ts.PropertyDeclaration): void { 756 const classNode = node.parent; 757 if (!ts.isClassDeclaration(classNode) || !Utils.hasSendableDecorator(classNode)) { 758 return; 759 } 760 const typeNode = node.type; 761 if (!typeNode) { 762 this.incrementCounters(node, FaultID.SendableExplicitFieldType); 763 return; 764 } 765 Utils.getDecoratorsIfInSendableClass(node)?.forEach((decorator) => { 766 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 767 }); 768 if (!Utils.isSendableTypeNode(typeNode)) { 769 this.incrementCounters(node, FaultID.SendablePropType); 770 } else { 771 this.checkTypeAliasInSendableScope(node); 772 } 773 } 774 775 private checkTypeAliasInSendableScope(node: ts.PropertyDeclaration | ts.PropertySignature): void { 776 if (!node.type) { 777 return; 778 } 779 780 const typeNode = Utils.unwrapParenthesizedTypeNode(node.type); 781 const needWarning = 782 (ts.isUnionTypeNode(typeNode) && typeNode.types.some((elemType) => this.isNoneSendableTypeAlias(elemType))) || 783 (ts.isTypeReferenceNode(typeNode) && this.isNoneSendableTypeAlias(typeNode)); 784 785 if (needWarning) { 786 this.incrementCounters(node.type, FaultID.SendablePropTypeWarning); 787 } 788 } 789 790 private isNoneSendableTypeAlias(typeNode: TypeNode): boolean { 791 if (!ts.isTypeReferenceNode(typeNode)) { 792 return false; 793 } 794 795 const sym = Utils.trueSymbolAtLocation(typeNode.typeName); 796 if (!sym || !(sym.getFlags() & ts.SymbolFlags.TypeAlias)) { 797 return false; 798 } 799 800 const typeDecl = Utils.getDeclaration(sym); 801 if (!typeDecl || !ts.isTypeAliasDeclaration(typeDecl)) { 802 return false; 803 } 804 805 const typeArgs = typeNode.typeArguments; 806 807 if (typeArgs && !typeArgs.every((typeArg) => Utils.isSendableTypeNode(typeArg))) { 808 return true; 809 } 810 return false; 811 } 812 813 private handlePropertyAssignment(node: ts.PropertyAssignment) { 814 const propName = node.name; 815 if (!(!!propName && ts.isNumericLiteral(propName))) { 816 return; 817 } 818 819 /* 820 * We can use literals as property names only when creating Record or any interop instances. 821 * We can also initialize with constant string literals. 822 * Assignment with string enum values is handled in handleComputedPropertyName 823 */ 824 let isRecordObjectInitializer = false; 825 let isLibraryType = false; 826 let isDynamic = false; 827 const objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(node.parent); 828 if (objectLiteralType) { 829 isRecordObjectInitializer = Utils.checkTypeSet(objectLiteralType, Utils.isStdRecordType); 830 isLibraryType = Utils.isLibraryType(objectLiteralType); 831 } 832 833 isDynamic = isLibraryType || Utils.isDynamicLiteralInitializer(node.parent); 834 if (!isRecordObjectInitializer && !isDynamic) { 835 let autofix: Autofix[] | undefined = Autofixer.fixLiteralAsPropertyName(node); 836 let autofixable = autofix != undefined; 837 if (!Autofixer.shouldAutofix(node, FaultID.LiteralAsPropertyName)) { 838 autofix = undefined; 839 } 840 this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofixable, autofix); 841 } 842 } 843 844 private handlePropertySignature(node: ts.PropertySignature): void { 845 const propName = node.name; 846 if (!!propName && ts.isNumericLiteral(propName)) { 847 let autofix: Autofix[] | undefined = Autofixer.fixLiteralAsPropertyName(node); 848 const autofixable = autofix !== undefined; 849 if (!Autofixer.shouldAutofix(node, FaultID.LiteralAsPropertyName)) { 850 autofix = undefined; 851 } 852 this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofixable, autofix); 853 } 854 this.handleSendableInterfaceProperty(node); 855 } 856 857 private handleSendableInterfaceProperty(node: ts.PropertySignature): void { 858 const typeNode = node.type; 859 if (!typeNode) { 860 return; 861 } 862 const interfaceNode = node.parent; 863 const interfaceNodeType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(interfaceNode); 864 if (!ts.isInterfaceDeclaration(interfaceNode) || !Utils.isSendableClassOrInterface(interfaceNodeType)) { 865 return; 866 } 867 if (!Utils.isSendableTypeNode(typeNode)) { 868 this.incrementCounters(node, FaultID.SendablePropType); 869 } else { 870 this.checkTypeAliasInSendableScope(node); 871 } 872 } 873 874 private filterOutDecoratorsDiagnostics(decorators: readonly Decorator[] | undefined, 875 expectedDecorators: readonly string[], 876 range: { begin: number, end: number}, 877 code: number, 878 prop_type?: string) { 879 // Filter out non-initializable property decorators from strict diagnostics. 880 if (this.tscStrictDiagnostics && this.sourceFile) { 881 if (decorators?.some((decorator) => { 882 const decoratorName = Utils.getDecoratorName(decorator); 883 // special case for property of type CustomDialogController of the @CustomDialog-decorated class 884 if (expectedDecorators.includes(Utils.NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS[0])) { 885 return expectedDecorators.includes(decoratorName) && prop_type === 'CustomDialogController'; 886 } 887 //return Utils.NON_INITIALIZABLE_PROPERTY_DECORATORS.includes(decoratorName); 888 return expectedDecorators.includes(decoratorName); 889 })) { 890 const file = normalizePath(this.sourceFile.fileName); 891 const tscDiagnostics = this.tscStrictDiagnostics.get(file); 892 if (tscDiagnostics) { 893 const filteredDiagnostics = tscDiagnostics.filter( 894 (val /*, idx, array */) => { 895 if (val.code !== code) return true; 896 if (val.start === undefined) return true; 897 if (val.start < range.begin) return true; 898 if (val.start > range.end) return true; 899 return false; 900 } 901 ); 902 this.tscStrictDiagnostics.set(file, filteredDiagnostics); 903 } 904 } 905 } 906 } 907 908 private checkInRange(rangesToFilter: { begin: number, end: number }[], pos: number): boolean { 909 for (let i = 0; i < rangesToFilter.length; i++) { 910 if (pos >= rangesToFilter[i].begin && pos < rangesToFilter[i].end) { 911 return false; 912 } 913 } 914 return true; 915 } 916 917 private filterStrictDiagnostics(filters: { [code: number]: (pos: number, length?: number) => boolean }, 918 diagnosticChecker: DiagnosticChecker, inLibCall: boolean): boolean { 919 if (!this.tscStrictDiagnostics || !this.sourceFile) { 920 return false; 921 } 922 const file = normalizePath(this.sourceFile.fileName); 923 const tscDiagnostics = this.tscStrictDiagnostics.get(file); 924 if (!tscDiagnostics) { 925 return false; 926 } 927 928 const checkDiagnostic = (val: Diagnostic): boolean => { 929 const checkInRange = filters[val.code]; 930 if (!checkInRange) { 931 return true; 932 } 933 if (val.start === undefined || checkInRange(val.start, val.length)) { 934 return true; 935 } 936 if (TypeScriptLinter.unknowDiagnosticCache.has(val)) { 937 TypeScriptLinter.filteredDiagnosticMessages.push(val.messageText as DiagnosticMessageChain); 938 return false; 939 } 940 941 /** 942 * When a fault is DiagnosticMessageChain, a filter error is reported when the node source is ts or a tripartite database. 943 * When a fault does not match TypeScriptLinter.strictDiagnosticCache and error type is not string,just return true directly. 944 */ 945 if (TypeScriptLinter.strictDiagnosticCache.has(val)) { 946 if (inLibCall) { 947 TypeScriptLinter.filteredDiagnosticMessages.push(val.messageText as DiagnosticMessageChain); 948 return false; 949 } 950 return true; 951 } 952 return diagnosticChecker.checkDiagnosticMessage(val.messageText); 953 }; 954 955 if (tscDiagnostics.every(checkDiagnostic)) { 956 return false; 957 } 958 this.tscStrictDiagnostics.set(file, tscDiagnostics.filter(checkDiagnostic)); 959 return true; 960 } 961 962 private static isClassLikeOrIface(node: ts.Node): boolean { 963 return ts.isClassLike(node) || ts.isInterfaceDeclaration(node); 964 } 965 966 private handleFunctionExpression(node: Node): void { 967 const funcExpr = node as FunctionExpression; 968 const isGeneric = funcExpr.typeParameters !== undefined && funcExpr.typeParameters.length > 0; 969 const isGenerator = funcExpr.asteriskToken !== undefined; 970 const hasThisKeyword = TypeScriptLinter.scopeContainsThis(funcExpr.body); 971 const [hasUnfixableReturnType, newRetTypeNode] = this.handleMissingReturnType(funcExpr); 972 const autofixable = !isGeneric && !isGenerator && !hasThisKeyword && !hasUnfixableReturnType; 973 974 let autofix: Autofix[] | undefined; 975 if (autofixable && Autofixer.shouldAutofix(node, FaultID.FunctionExpression)) { 976 autofix = [ Autofixer.fixFunctionExpression(funcExpr, funcExpr.parameters, newRetTypeNode) ]; 977 } 978 979 this.incrementCounters(node, FaultID.FunctionExpression, autofixable, autofix); 980 if (isGenerator) { 981 this.incrementCounters(funcExpr, FaultID.GeneratorFunction); 982 } 983 if (!Utils.hasPredecessor(funcExpr, TypeScriptLinter.isClassLikeOrIface)) { 984 this.reportThisKeywordsInScope(funcExpr.body); 985 } 986 if (hasUnfixableReturnType) { 987 this.incrementCounters(funcExpr, FaultID.LimitedReturnTypeInference); 988 } 989 } 990 991 private handleArrowFunction(node: Node): void { 992 const arrowFunc = node as ArrowFunction; 993 if (!Utils.hasPredecessor(arrowFunc, TypeScriptLinter.isClassLikeOrIface)) { 994 this.reportThisKeywordsInScope(arrowFunc.body); 995 } 996 997 const contextType = TypeScriptLinter.tsTypeChecker.getContextualType(arrowFunc); 998 if (!(contextType && Utils.isLibraryType(contextType))) { 999 if (!arrowFunc.type) { 1000 this.handleMissingReturnType(arrowFunc); 1001 } 1002 } 1003 } 1004 1005 private handleFunctionDeclaration(node: Node) { 1006 const tsFunctionDeclaration = node as ts.FunctionDeclaration; 1007 if (!tsFunctionDeclaration.type) { 1008 this.handleMissingReturnType(tsFunctionDeclaration); 1009 } 1010 if (tsFunctionDeclaration.name) { 1011 this.countDeclarationsWithDuplicateName(tsFunctionDeclaration.name, tsFunctionDeclaration); 1012 } 1013 if (tsFunctionDeclaration.body) { 1014 this.reportThisKeywordsInScope(tsFunctionDeclaration.body); 1015 } 1016 const parent = tsFunctionDeclaration.parent; 1017 if (!ts.isSourceFile(parent) && !ts.isModuleBlock(parent)) { 1018 this.incrementCounters(tsFunctionDeclaration, FaultID.LocalFunction); 1019 } 1020 if (tsFunctionDeclaration.asteriskToken) { 1021 this.incrementCounters(node, FaultID.GeneratorFunction); 1022 } 1023 if (Utils.hasSendableDecoratorFunctionOverload(tsFunctionDeclaration)) { 1024 if (!this.isSendableDecoratorValid(tsFunctionDeclaration)) { 1025 return; 1026 } 1027 Utils.getNonSendableDecorators(tsFunctionDeclaration)?.forEach((decorator) => { 1028 this.incrementCounters(decorator, FaultID.SendableFunctionDecorator); 1029 }); 1030 if (!Utils.hasSendableDecorator(tsFunctionDeclaration)) { 1031 this.incrementCounters(tsFunctionDeclaration, FaultID.SendableFunctionOverloadDecorator); 1032 } 1033 this.scanCapturedVarsInSendableScope(tsFunctionDeclaration, tsFunctionDeclaration, FaultID.SendableFunctionImportedVariables); 1034 } 1035 } 1036 1037 private handleMissingReturnType(funcLikeDecl: ts.FunctionLikeDeclaration | ts.MethodSignature): [boolean, ts.TypeNode | undefined] { 1038 // if (funcLikeDecl.type) return [false, funcLikeDecl.type]; 1039 1040 // Note: Return type can't be inferred for function without body. 1041 if (ts.isMethodSignature(funcLikeDecl) || !funcLikeDecl.body) { 1042 // Ambient flag is not exposed, and ts.NodeFlags is made const enum, so hardcode this value 1043 const isAmbientDeclaration = !!(funcLikeDecl.flags & (1 << 24)); 1044 const isSignature = ts.isMethodSignature(funcLikeDecl); 1045 if ((isSignature || isAmbientDeclaration) && !funcLikeDecl.type) { 1046 this.incrementCounters(funcLikeDecl, FaultID.LimitedReturnTypeInference); 1047 } 1048 return [false, undefined]; 1049 } 1050 1051 let autofixable = false; 1052 let autofix: Autofix[] | undefined; 1053 let newRetTypeNode: TypeNode | undefined; 1054 const isFuncExpr = isFunctionExpression(funcLikeDecl); 1055 1056 // Currently, ArkTS can't infer return type of function, when expression 1057 // in the return statement is a call to a function or method whose return 1058 // value type is omitted. In that case, we attempt to prepare an autofix. 1059 let hasLimitedRetTypeInference = this.hasLimitedTypeInferenceFromReturnExpr(funcLikeDecl.body); 1060 1061 const tsSignature = TypeScriptLinter.tsTypeChecker.getSignatureFromDeclaration(funcLikeDecl); 1062 if (tsSignature) { 1063 const tsRetType = TypeScriptLinter.tsTypeChecker.getReturnTypeOfSignature(tsSignature); 1064 1065 if (!tsRetType || Utils.isUnsupportedType(tsRetType)) { 1066 hasLimitedRetTypeInference = true; 1067 } 1068 else if (hasLimitedRetTypeInference) { 1069 newRetTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(tsRetType, funcLikeDecl, NodeBuilderFlags.None); 1070 autofixable = !!newRetTypeNode; 1071 1072 if (!isFuncExpr && newRetTypeNode && Autofixer.shouldAutofix(funcLikeDecl, FaultID.LimitedReturnTypeInference)) { 1073 autofix = [Autofixer.fixReturnType(funcLikeDecl, newRetTypeNode)]; 1074 } 1075 } 1076 } 1077 1078 // Don't report here if in function expression context. 1079 // See handleFunctionExpression for details. 1080 if (hasLimitedRetTypeInference && !isFuncExpr) { 1081 this.incrementCounters(funcLikeDecl, FaultID.LimitedReturnTypeInference, autofixable, autofix); 1082 } 1083 return [hasLimitedRetTypeInference && !newRetTypeNode, newRetTypeNode]; 1084 } 1085 1086 private hasLimitedTypeInferenceFromReturnExpr(funBody: ConciseBody): boolean { 1087 let hasLimitedTypeInference = false; 1088 const callback = (node: ts.Node): void => { 1089 if (hasLimitedTypeInference) { 1090 return; 1091 } 1092 1093 if ( 1094 ts.isReturnStatement(node) && node.expression && 1095 Utils.isCallToFunctionWithOmittedReturnType(Utils.unwrapParenthesized(node.expression)) 1096 ) { 1097 hasLimitedTypeInference = true; 1098 } 1099 1100 }; 1101 // Don't traverse other nested function-like declarations. 1102 const stopCondition = (node: ts.Node): boolean => { 1103 return ts.isFunctionDeclaration(node) || 1104 ts.isFunctionExpression(node) || 1105 ts.isMethodDeclaration(node) || 1106 ts.isAccessor(node) || 1107 ts.isArrowFunction(node); 1108 }; 1109 if (isBlock(funBody)) { 1110 this.forEachNodeInSubtree(funBody, callback, stopCondition); 1111 } 1112 else { 1113 const tsExpr = Utils.unwrapParenthesized(funBody); 1114 hasLimitedTypeInference = Utils.isCallToFunctionWithOmittedReturnType(tsExpr); 1115 } 1116 1117 return hasLimitedTypeInference; 1118 } 1119 1120 private isValidTypeForUnaryArithmeticOperator(type: ts.Type): boolean { 1121 const typeFlags = type.getFlags(); 1122 const numberLiteralFlags = ts.TypeFlags.BigIntLiteral | ts.TypeFlags.NumberLiteral; 1123 const numberLikeFlags = ts.TypeFlags.BigIntLike | ts.TypeFlags.NumberLike; 1124 const isNumberLike = !!(typeFlags & (numberLiteralFlags | numberLikeFlags)); 1125 1126 const isAllowedNumericType = Utils.isStdBigIntType(type) || Utils.isStdNumberType(type); 1127 1128 return isNumberLike || isAllowedNumericType; 1129 } 1130 1131 private handlePrefixUnaryExpression(node: Node) { 1132 const tsUnaryArithm = node as PrefixUnaryExpression; 1133 const tsUnaryOp = tsUnaryArithm.operator; 1134 const tsUnaryOperand = tsUnaryArithm.operand; 1135 if ( 1136 tsUnaryOp === SyntaxKind.PlusToken || 1137 tsUnaryOp === SyntaxKind.MinusToken || 1138 tsUnaryOp === SyntaxKind.TildeToken 1139 ) { 1140 const tsOperatndType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsUnaryOperand); 1141 const isTilde = tsUnaryOp === ts.SyntaxKind.TildeToken; 1142 const isInvalidTilde = 1143 isTilde && ts.isNumericLiteral(tsUnaryOperand) && !Utils.isIntegerConstantValue(tsUnaryOperand); 1144 if (!this.isValidTypeForUnaryArithmeticOperator(tsOperatndType) || isInvalidTilde) { 1145 this.incrementCounters(node, FaultID.UnaryArithmNotNumber); 1146 } 1147 } 1148 } 1149 1150 private handleBinaryExpression(node: Node): void { 1151 const tsBinaryExpr = node as BinaryExpression; 1152 const tsLhsExpr = tsBinaryExpr.left; 1153 const tsRhsExpr = tsBinaryExpr.right; 1154 1155 if (Utils.isAssignmentOperator(tsBinaryExpr.operatorToken)) { 1156 if (isObjectLiteralExpression(tsLhsExpr) || isArrayLiteralExpression(tsLhsExpr)) { 1157 this.incrementCounters(node, FaultID.DestructuringAssignment); 1158 } 1159 if (isPropertyAccessExpression(tsLhsExpr)) { 1160 const tsLhsSymbol = Utils.trueSymbolAtLocation(tsLhsExpr); 1161 const tsLhsBaseSymbol = Utils.trueSymbolAtLocation(tsLhsExpr.expression); 1162 if (tsLhsSymbol && (tsLhsSymbol.flags & SymbolFlags.Method)) { 1163 this.incrementCounters(tsLhsExpr, FaultID.MethodReassignment); 1164 } 1165 if ( 1166 Utils.isMethodAssignment(tsLhsSymbol) && tsLhsBaseSymbol && 1167 (tsLhsBaseSymbol.flags & SymbolFlags.Function) !== 0 1168 ) { 1169 this.incrementCounters(tsLhsExpr, FaultID.PropertyDeclOnFunction); 1170 } 1171 } 1172 } 1173 1174 const leftOperandType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsLhsExpr); 1175 const rightOperandType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsRhsExpr); 1176 1177 if (tsBinaryExpr.operatorToken.kind === SyntaxKind.PlusToken) { 1178 if (Utils.isEnumMemberType(leftOperandType) && Utils.isEnumMemberType(rightOperandType)) { 1179 if ( 1180 ((leftOperandType.flags & (TypeFlags.NumberLike)) && (rightOperandType.getFlags() & (TypeFlags.NumberLike))) || 1181 ((leftOperandType.flags & (TypeFlags.StringLike)) && (rightOperandType.getFlags() & (TypeFlags.StringLike))) 1182 ) { 1183 return; 1184 } 1185 } 1186 else if (Utils.isNumberLikeType(leftOperandType) && Utils.isNumberLikeType(rightOperandType)) { 1187 return; 1188 } 1189 else if (Utils.isStringLikeType(leftOperandType) || Utils.isStringLikeType(rightOperandType)) { 1190 return; 1191 } 1192 } 1193 else if ( 1194 tsBinaryExpr.operatorToken.kind === SyntaxKind.AmpersandToken || 1195 tsBinaryExpr.operatorToken.kind === SyntaxKind.BarToken || 1196 tsBinaryExpr.operatorToken.kind === SyntaxKind.CaretToken || 1197 tsBinaryExpr.operatorToken.kind === SyntaxKind.LessThanLessThanToken || 1198 tsBinaryExpr.operatorToken.kind === SyntaxKind.GreaterThanGreaterThanToken || 1199 tsBinaryExpr.operatorToken.kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken 1200 ) { 1201 if (!(Utils.isNumberLikeType(leftOperandType) && Utils.isNumberLikeType(rightOperandType))|| 1202 (tsLhsExpr.kind === SyntaxKind.NumericLiteral && !Utils.isIntegerConstantValue(tsLhsExpr as NumericLiteral)) || 1203 (tsRhsExpr.kind === SyntaxKind.NumericLiteral && !Utils.isIntegerConstantValue(tsRhsExpr as NumericLiteral)) 1204 ) { 1205 return; //this.incrementCounters(node, FaultID.BitOpWithWrongType); 1206 } 1207 } 1208 else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.CommaToken) { 1209 // CommaOpertor is allowed in 'for' statement initalizer and incrementor 1210 let tsExprNode: Node = tsBinaryExpr; 1211 let tsParentNode = tsExprNode.parent; 1212 while (tsParentNode && tsParentNode.kind === SyntaxKind.BinaryExpression) { 1213 tsExprNode = tsParentNode; 1214 tsParentNode = tsExprNode.parent; 1215 } 1216 1217 if (tsParentNode && tsParentNode.kind === SyntaxKind.ForStatement) { 1218 const tsForNode = tsParentNode as ForStatement; 1219 if (tsExprNode === tsForNode.initializer || tsExprNode === tsForNode.incrementor) return; 1220 } 1221 this.incrementCounters(node, FaultID.CommaOperator); 1222 } 1223 else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.InstanceOfKeyword) { 1224 const leftExpr = Utils.unwrapParenthesized(tsBinaryExpr.left); 1225 const leftSymbol = Utils.trueSymbolAtLocation(leftExpr); 1226 // In STS, the left-hand side expression may be of any reference type, otherwise 1227 // a compile-time error occurs. In addition, the left operand in STS cannot be a type. 1228 if (tsLhsExpr.kind === SyntaxKind.ThisKeyword) { 1229 return; 1230 } 1231 if (Utils.isPrimitiveType(leftOperandType) || isTypeNode(leftExpr) || Utils.isTypeSymbol(leftSymbol)) { 1232 this.incrementCounters(node, FaultID.InstanceofUnsupported); 1233 } 1234 } else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.InKeyword) { 1235 this.incrementCounters(tsBinaryExpr.operatorToken, FaultID.InOperator); 1236 } else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.EqualsToken) { 1237 this.checkAssignmentMatching(tsBinaryExpr, leftOperandType, tsRhsExpr); 1238 const typeNode = Utils.getVariableDeclarationTypeNode(tsLhsExpr); 1239 this.handleEsObjectAssignment(tsBinaryExpr, typeNode, tsRhsExpr); 1240 } 1241 } 1242 1243 private handleVariableDeclarationList(node: Node): void { 1244 const varDeclFlags = getCombinedNodeFlags(node); 1245 if (!(varDeclFlags & (NodeFlags.Let | NodeFlags.Const))) { 1246 this.incrementCounters(node, FaultID.VarDeclaration); 1247 } 1248 } 1249 1250 private handleVariableDeclaration(node: Node): void { 1251 const tsVarDecl = node as VariableDeclaration; 1252 if (isArrayBindingPattern(tsVarDecl.name) || isObjectBindingPattern(tsVarDecl.name)) { 1253 this.incrementCounters(node, FaultID.DestructuringDeclaration); 1254 } 1255 { 1256 // Check variable declaration for duplicate name. 1257 const visitBindingPatternNames = (tsBindingName: BindingName): void => { 1258 if (isIdentifier(tsBindingName)) { 1259 // The syntax kind of the declaration is defined here by the parent of 'BindingName' node. 1260 this.countDeclarationsWithDuplicateName(tsBindingName, tsBindingName, tsBindingName.parent.kind); 1261 } 1262 else { 1263 for (const tsBindingElem of tsBindingName.elements) { 1264 if (isOmittedExpression(tsBindingElem)) continue; 1265 1266 visitBindingPatternNames(tsBindingElem.name); 1267 } 1268 } 1269 }; 1270 1271 visitBindingPatternNames(tsVarDecl.name); 1272 } 1273 1274 if (tsVarDecl.type && tsVarDecl.initializer) { 1275 this.checkAssignmentMatching( 1276 tsVarDecl, 1277 TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsVarDecl.type), 1278 tsVarDecl.initializer 1279 ); 1280 } 1281 1282 this.handleEsObjectDelaration(tsVarDecl); 1283 this.handleDeclarationInferredType(tsVarDecl); 1284 this.handleDefiniteAssignmentAssertion(tsVarDecl); 1285 } 1286 1287 private handleEsObjectDelaration(node: ts.VariableDeclaration) { 1288 const isDeclaredESObject = !!node.type && Utils.isEsObjectType(node.type); 1289 const initalizerTypeNode = node.initializer && Utils.getVariableDeclarationTypeNode(node.initializer); 1290 const isInitializedWithESObject = !!initalizerTypeNode && Utils.isEsObjectType(initalizerTypeNode); 1291 const isLocal = Utils.isInsideBlock(node) 1292 if ((isDeclaredESObject || isInitializedWithESObject) && !isLocal) { 1293 this.incrementCounters(node, FaultID.EsObjectType); 1294 return; 1295 } 1296 1297 if (node.initializer) { 1298 this.handleEsObjectAssignment(node, node.type, node.initializer); 1299 return; 1300 } 1301 } 1302 1303 private handleEsObjectAssignment(node: ts.Node, nodeDeclType: ts.TypeNode | undefined, initializer: ts.Node) { 1304 const isTypeAnnotated = !!nodeDeclType; 1305 const isDeclaredESObject = !!nodeDeclType && Utils.isEsObjectType(nodeDeclType); 1306 const initalizerTypeNode = Utils.getVariableDeclarationTypeNode(initializer); 1307 const isInitializedWithESObject = !!initalizerTypeNode && Utils.isEsObjectType(initalizerTypeNode); 1308 if (isTypeAnnotated && !isDeclaredESObject && isInitializedWithESObject) { 1309 this.incrementCounters(node, FaultID.EsObjectType); 1310 return; 1311 } 1312 1313 if (isDeclaredESObject && !Utils.isValueAssignableToESObject(initializer)) { 1314 this.incrementCounters(node, FaultID.EsObjectType); 1315 } 1316 } 1317 1318 1319 private handleCatchClause(node: Node): void { 1320 const tsCatch = node as CatchClause; 1321 // In TS catch clause doesn't permit specification of the exception varible type except 'any' or 'unknown'. 1322 // It is not compatible with STS 'catch' where the exception varilab has to be of type 1323 // 'Exception' or derived from it. 1324 // So each 'catch' which has explicite type for the exception object goes to problems in strict mode. 1325 if (tsCatch.variableDeclaration && tsCatch.variableDeclaration.type) { 1326 this.incrementCounters(node, FaultID.CatchWithUnsupportedType); 1327 } 1328 } 1329 1330 private handleClassDeclaration(node: Node): void { 1331 const tsClassDecl = node as ClassDeclaration; 1332 this.staticBlocks.clear(); 1333 1334 if (tsClassDecl.name) { 1335 this.countDeclarationsWithDuplicateName(tsClassDecl.name, tsClassDecl); 1336 } 1337 this.countClassMembersWithDuplicateName(tsClassDecl); 1338 1339 const isSendableClass = Utils.hasSendableDecorator(tsClassDecl); 1340 if (isSendableClass) { 1341 Utils.getNonSendableDecorators(tsClassDecl)?.forEach((decorator) => { 1342 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 1343 }); 1344 tsClassDecl.typeParameters?.forEach((typeParamDecl) => { 1345 this.checkSendableTypeParameter(typeParamDecl); 1346 }); 1347 } 1348 1349 if (tsClassDecl.heritageClauses) { 1350 for (const hClause of tsClassDecl.heritageClauses) { 1351 if (!hClause) { 1352 continue; 1353 } 1354 this.checkClassDeclarationHeritageClause(hClause, isSendableClass); 1355 } 1356 } 1357 1358 if (isSendableClass) { 1359 tsClassDecl.members.forEach((classMember) => { 1360 this.scanCapturedVarsInSendableScope(classMember, tsClassDecl, FaultID.SendableCapturedVars); 1361 }); 1362 } 1363 1364 if (!this.skipArkTSStaticBlocksCheck) { 1365 this.processClassStaticBlocks(tsClassDecl); 1366 } 1367 } 1368 1369 private scanCapturedVarsInSendableScope(startNode: ts.Node, scope: ts.Node, faultId: FaultID): void { 1370 const callback = (node: ts.Node): void => { 1371 // Namespace import will introduce closure in the es2abc compiler stage 1372 if (!ts.isIdentifier(node) || this.checkNamespaceImportVar(node)) { 1373 return; 1374 } 1375 1376 // The "b" of "A.b" should not be checked since it's load from object "A" 1377 const parent: ts.Node = node.parent; 1378 if (ts.isPropertyAccessExpression(parent) && parent.name === node) { 1379 return; 1380 } 1381 // When overloading function, will misreport 1382 if (ts.isFunctionDeclaration(startNode) && startNode.name === node) { 1383 return; 1384 } 1385 1386 this.checkLocalDecl(node, scope, faultId); 1387 }; 1388 // Type nodes should not checked because no closure will be introduced 1389 const stopCondition = (node: ts.Node): boolean => { 1390 // already existed 'arkts-sendable-class-decoratos' error 1391 if (ts.isDecorator(node) && node.parent === startNode) { 1392 return true; 1393 } 1394 return ts.isTypeReferenceNode(node); 1395 }; 1396 this.forEachNodeInSubtree(startNode, callback, stopCondition); 1397 } 1398 1399 private checkLocalDecl(node: ts.Identifier, scope: ts.Node, faultId: FaultID): void { 1400 const trueSym = Utils.trueSymbolAtLocation(node); 1401 // Sendable decorator should be used in method of Sendable classes 1402 if (trueSym === undefined) { 1403 return; 1404 } 1405 1406 // Const enum member will be replaced by the exact value of it, no closure will be introduced 1407 if (Utils.isConstEnum(trueSym)) { 1408 return; 1409 } 1410 1411 const declarations = trueSym.getDeclarations(); 1412 if (declarations?.length) { 1413 this.checkLocalDeclWithSendableClosure(node, scope, declarations[0], faultId); 1414 } 1415 } 1416 1417 private checkLocalDeclWithSendableClosure( 1418 node: ts.Identifier, 1419 scope: ts.Node, 1420 decl: ts.Declaration, 1421 faultId: FaultID 1422 ): void { 1423 const declPosition = decl.getStart(); 1424 if (decl.getSourceFile().fileName !== node.getSourceFile().fileName || 1425 declPosition !== undefined && declPosition >= scope.getStart() && declPosition < scope.getEnd()) { 1426 return; 1427 } 1428 if (this.isFileExportSendableDecl(decl)) { 1429 // This part of the check is removed when the relevant functionality is implemented at runtime 1430 this.incrementCounters(node, FaultID.SendableClosureExport); 1431 } 1432 if (this.checkIsTopClosure(decl)) { 1433 return; 1434 } 1435 1436 /** 1437 * The cases in condition will introduce closure if defined in the same file as the Sendable class. The following 1438 * cases are excluded because they are not allowed in ArkTS: 1439 * 1. ImportEqualDecalration 1440 * 2. BindingElement 1441 */ 1442 if (ts.isVariableDeclaration(decl) || ts.isFunctionDeclaration(decl) || ts.isClassDeclaration(decl) || 1443 ts.isInterfaceDeclaration(decl) || ts.isEnumDeclaration(decl) || ts.isModuleDeclaration(decl) || 1444 ts.isParameter(decl)) { 1445 this.incrementCounters(node, faultId); 1446 } 1447 } 1448 1449 private checkIsTopClosure(decl: ts.Declaration): boolean { 1450 if (!ts.isSourceFile(decl.parent)) { 1451 return false; 1452 } 1453 if (ts.isClassDeclaration(decl) && Utils.isSendableClassOrInterface(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(decl))) { 1454 return true; 1455 } 1456 if (ts.isFunctionDeclaration(decl) && Utils.hasSendableDecoratorFunctionOverload(decl)) { 1457 return true; 1458 } 1459 return false; 1460 } 1461 1462 private checkNamespaceImportVar(node: ts.Node): boolean { 1463 // Namespace import cannot be determined by the true symbol 1464 const sym = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(node); 1465 const decls = sym?.getDeclarations(); 1466 if (decls?.length) { 1467 if (ts.isNamespaceImport(decls[0])) { 1468 this.incrementCounters(node, FaultID.SendableCapturedVars); 1469 return true; 1470 } 1471 } 1472 return false; 1473 } 1474 1475 isFileExportSendableDecl(decl: ts.Declaration): boolean { 1476 if ( 1477 !ts.isSourceFile(decl.parent) || 1478 (!ts.isClassDeclaration(decl) && !ts.isFunctionDeclaration(decl)) 1479 ) { 1480 return false; 1481 } 1482 if (!this.fileExportSendableDeclCaches) { 1483 this.fileExportSendableDeclCaches = Utils.searchFileExportDecl(decl.parent, Utils.SENDABLE_CLOSURE_DECLS); 1484 } 1485 return this.fileExportSendableDeclCaches.has(decl); 1486 } 1487 1488 private checkClassDeclarationHeritageClause(hClause: ts.HeritageClause, isSendableClass: boolean): void { 1489 for (const tsTypeExpr of hClause.types) { 1490 1491 /* 1492 * Always resolve type from 'tsTypeExpr' node, not from 'tsTypeExpr.expression' node, 1493 * as for the latter, type checker will return incorrect type result for classes in 1494 * 'extends' clause. Additionally, reduce reference, as mostly type checker returns 1495 * the TypeReference type objects for classes and interfaces. 1496 */ 1497 const tsExprType = Utils.reduceReference(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsTypeExpr)); 1498 const isSendableBaseType = Utils.isSendableClassOrInterface(tsExprType); 1499 if (tsExprType.isClass() && hClause.token === ts.SyntaxKind.ImplementsKeyword) { 1500 this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); 1501 } 1502 1503 if (!isSendableClass) { 1504 // Non-Sendable class can not implements sendable interface / extends sendable class 1505 if (isSendableBaseType) { 1506 this.incrementCounters(tsTypeExpr, FaultID.SendableClassInheritance); 1507 } 1508 continue; 1509 } 1510 1511 /* 1512 * Sendable class can implements any interface / extends only sendable class 1513 * Sendable class can not extends sendable class variable(local / import) 1514 */ 1515 if (hClause.token === ts.SyntaxKind.ExtendsKeyword) { 1516 if (!isSendableBaseType) { 1517 this.incrementCounters(tsTypeExpr, FaultID.SendableClassInheritance); 1518 continue; 1519 } 1520 if (!this.isValidSendableClassExtends(tsTypeExpr)) { 1521 this.incrementCounters(tsTypeExpr, FaultID.SendableClassInheritance); 1522 } 1523 } 1524 } 1525 } 1526 1527 private isValidSendableClassExtends(tsTypeExpr: ts.ExpressionWithTypeArguments): boolean { 1528 const expr = tsTypeExpr.expression; 1529 const sym = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(expr); 1530 if (sym && (sym.flags & ts.SymbolFlags.Class) === 0) { 1531 // handle non-class situation(local / import) 1532 if ((sym.flags & ts.SymbolFlags.Alias) !== 0) { 1533 1534 /* 1535 * Sendable class can not extends imported sendable class variable 1536 * Sendable class can extends imported sendable class 1537 */ 1538 const realSym = TypeScriptLinter.tsTypeChecker.getAliasedSymbol(sym); 1539 if (realSym && (realSym.flags & ts.SymbolFlags.Class) === 0) { 1540 return false; 1541 } 1542 return true; 1543 } 1544 return false; 1545 } 1546 return true; 1547 } 1548 1549 private checkSendableTypeParameter(typeParamDecl: ts.TypeParameterDeclaration): void { 1550 const defaultTypeNode = typeParamDecl.default; 1551 if (defaultTypeNode) { 1552 if (!Utils.isSendableTypeNode(defaultTypeNode)) { 1553 this.incrementCounters(defaultTypeNode, FaultID.SendableGenericTypes); 1554 } 1555 } 1556 } 1557 1558 private processClassStaticBlocks(classDecl: ts.ClassDeclaration): void { 1559 let hasStaticBlock = false; 1560 for (const element of classDecl.members) { 1561 if (ts.isClassStaticBlockDeclaration(element)) { 1562 if (hasStaticBlock) { 1563 this.incrementCounters(element, FaultID.MultipleStaticBlocks); 1564 } else { 1565 hasStaticBlock = true; 1566 } 1567 } 1568 } 1569 } 1570 1571 private handleModuleDeclaration(node: Node): void { 1572 const tsModuleDecl = node as ModuleDeclaration; 1573 this.countDeclarationsWithDuplicateName(tsModuleDecl.name, tsModuleDecl); 1574 1575 const tsModuleBody = tsModuleDecl.body; 1576 const tsModifiers = tsModuleDecl.modifiers; // TSC 4.2 doesn't have 'getModifiers()' method 1577 if (tsModuleBody) { 1578 if (isModuleBlock(tsModuleBody)) { 1579 for (const tsModuleStmt of tsModuleBody.statements) { 1580 switch (tsModuleStmt.kind) { 1581 case SyntaxKind.VariableStatement: 1582 case SyntaxKind.FunctionDeclaration: 1583 case SyntaxKind.ClassDeclaration: 1584 case SyntaxKind.InterfaceDeclaration: 1585 case SyntaxKind.TypeAliasDeclaration: 1586 case SyntaxKind.EnumDeclaration: 1587 case SyntaxKind.ExportDeclaration: 1588 break; 1589 // Nested namespace declarations are prohibited 1590 // but there is no cookbook recipe for it! 1591 case SyntaxKind.ModuleDeclaration: 1592 break; 1593 default: 1594 this.incrementCounters(tsModuleStmt, FaultID.NonDeclarationInNamespace); 1595 break; 1596 } 1597 } 1598 } 1599 } 1600 1601 if (!(tsModuleDecl.flags & NodeFlags.Namespace) && 1602 Utils.hasModifier(tsModifiers, SyntaxKind.DeclareKeyword)) { 1603 this.incrementCounters(tsModuleDecl, FaultID.ShorthandAmbientModuleDecl); 1604 } 1605 1606 if (isStringLiteral(tsModuleDecl.name) && tsModuleDecl.name.text.includes("*")) { 1607 this.incrementCounters(tsModuleDecl, FaultID.WildcardsInModuleName); 1608 } 1609 } 1610 1611 private handleTypeAliasDeclaration(node: Node): void { 1612 const tsTypeAlias = node as TypeAliasDeclaration; 1613 this.countDeclarationsWithDuplicateName(tsTypeAlias.name, tsTypeAlias); 1614 if (Utils.hasSendableDecorator(tsTypeAlias)) { 1615 if (!this.isSendableDecoratorValid(tsTypeAlias)) { 1616 return; 1617 } 1618 Utils.getNonSendableDecorators(tsTypeAlias)?.forEach((decorator) => { 1619 this.incrementCounters(decorator, FaultID.SendableTypeAliasDecorator); 1620 }); 1621 if (!ts.isFunctionTypeNode(tsTypeAlias.type)) { 1622 this.incrementCounters(tsTypeAlias.type, FaultID.SendableTypeAliasDeclaration); 1623 } 1624 } 1625 } 1626 1627 private handleImportClause(node: Node): void { 1628 const tsImportClause = node as ImportClause; 1629 if (tsImportClause.name) { 1630 this.countDeclarationsWithDuplicateName(tsImportClause.name, tsImportClause); 1631 } 1632 } 1633 1634 private handleImportSpecifier(node: Node): void { 1635 const tsImportSpecifier = node as ImportSpecifier; 1636 this.countDeclarationsWithDuplicateName(tsImportSpecifier.name, tsImportSpecifier); 1637 } 1638 1639 private handleNamespaceImport(node: Node): void { 1640 const tsNamespaceImport = node as NamespaceImport; 1641 this.countDeclarationsWithDuplicateName(tsNamespaceImport.name, tsNamespaceImport); 1642 } 1643 1644 private handleTypeAssertionExpression(node: Node): void { 1645 const tsTypeAssertion = node as TypeAssertion; 1646 if (tsTypeAssertion.type.getText() === "const") { 1647 this.incrementCounters(tsTypeAssertion, FaultID.ConstAssertion); 1648 } 1649 else { 1650 this.incrementCounters(node, FaultID.TypeAssertion); 1651 } 1652 } 1653 1654 private handleMethodDeclaration(node: Node): void { 1655 const tsMethodDecl = node as MethodDeclaration; 1656 Utils.getDecoratorsIfInSendableClass(tsMethodDecl)?.forEach((decorator) => { 1657 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 1658 }); 1659 let isStatic = false; 1660 if (tsMethodDecl.modifiers) { 1661 for (const mod of tsMethodDecl.modifiers) { 1662 if (mod.kind === SyntaxKind.StaticKeyword) { 1663 isStatic = true; 1664 break; 1665 } 1666 } 1667 } 1668 if (tsMethodDecl.body && isStatic) { 1669 this.reportThisKeywordsInScope(tsMethodDecl.body); 1670 } 1671 if (!tsMethodDecl.type) { 1672 this.handleMissingReturnType(tsMethodDecl); 1673 } 1674 if (tsMethodDecl.asteriskToken) { 1675 this.incrementCounters(node, FaultID.GeneratorFunction); 1676 } 1677 this.filterOutDecoratorsDiagnostics(ts.getDecorators(tsMethodDecl), Utils.NON_RETURN_FUNCTION_DECORATORS, 1678 { begin: tsMethodDecl.parameters.end, end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end }, 1679 Utils.FUNCTION_HAS_NO_RETURN_ERROR_CODE); 1680 } 1681 1682 private handleMethodSignature(node: ts.MethodSignature): void { 1683 const tsMethodSign = node as ts.MethodSignature; 1684 if (!tsMethodSign.type) { 1685 this.handleMissingReturnType(tsMethodSign); 1686 } 1687 } 1688 1689 private handleIdentifier(node: Node): void { 1690 const tsIdentifier = node as Identifier; 1691 const tsIdentSym = Utils.trueSymbolAtLocation(tsIdentifier); 1692 1693 if (!tsIdentSym) { 1694 return; 1695 } 1696 1697 if ( 1698 (tsIdentSym.flags & ts.SymbolFlags.Module) !== 0 && 1699 (tsIdentSym.flags & ts.SymbolFlags.Transient) !== 0 && 1700 tsIdentifier.text === "globalThis" 1701 ) { 1702 this.incrementCounters(node, FaultID.GlobalThis); 1703 } else { 1704 this.handleRestrictedValues(tsIdentifier, tsIdentSym); 1705 } 1706 } 1707 1708 private isAllowedClassValueContext(tsIdentifier: Identifier /* Param not used ! ??, tsIdentSym: Symbol */): boolean { 1709 let ctx: Node = tsIdentifier; 1710 while (isPropertyAccessExpression(ctx.parent) || isQualifiedName(ctx.parent)) { 1711 ctx = ctx.parent; 1712 } 1713 if (isPropertyAssignment(ctx.parent) && isObjectLiteralExpression(ctx.parent.parent)) { 1714 ctx = ctx.parent.parent; 1715 } 1716 if (isArrowFunction(ctx.parent) && ctx.parent.body === ctx) { 1717 ctx = ctx.parent; 1718 } 1719 1720 if (isCallExpression(ctx.parent) || isNewExpression(ctx.parent)) { 1721 const callee = ctx.parent.expression; 1722 const isAny = Utils.isAnyType(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(callee)); 1723 const isDynamic = isAny || Utils.hasLibraryType(callee); 1724 if (callee !== ctx && isDynamic) { 1725 return true; 1726 } 1727 } 1728 return false; 1729 } 1730 1731 private handleRestrictedValues(tsIdentifier: Identifier, tsIdentSym: Symbol) { 1732 const illegalValues = SymbolFlags.ConstEnum | SymbolFlags.RegularEnum | SymbolFlags.ValueModule | SymbolFlags.Class; 1733 // If module name is duplicated by another declaration, this increases the possibility 1734 // of finding a lot of false positives. Thus, do not check further in that case. 1735 if ((tsIdentSym.flags & SymbolFlags.ValueModule) !== 0) { 1736 if (!!tsIdentSym && Utils.symbolHasDuplicateName(tsIdentSym, SyntaxKind.ModuleDeclaration)) { 1737 return; 1738 } 1739 } 1740 if ((tsIdentSym.flags & illegalValues) === 0 || Utils.isStruct(tsIdentSym) || 1741 !this.identiferUseInValueContext(tsIdentifier, tsIdentSym)) { 1742 return; 1743 } 1744 if ((tsIdentSym.flags & SymbolFlags.Class) !== 0) { 1745 if (this.isAllowedClassValueContext(tsIdentifier /* param not used!?? , tsIdentSym */)) { 1746 return; 1747 } 1748 } 1749 1750 if (tsIdentSym.flags & SymbolFlags.ValueModule) { 1751 this.incrementCounters(tsIdentifier, FaultID.NamespaceAsObject); 1752 } 1753 else { 1754 // missing EnumAsObject 1755 this.incrementCounters(tsIdentifier, FaultID.ClassAsObject); 1756 } 1757 } 1758 1759 private identiferUseInValueContext( 1760 ident: Identifier, 1761 tsSym: Symbol 1762 ) { 1763 // If identifier is the right-most name of Property Access chain or Qualified name, 1764 // or it's a separate identifier expression, then identifier is being referenced as an value. 1765 let qualifiedStart: Node = ident; 1766 while (isPropertyAccessExpression(qualifiedStart.parent) || isQualifiedName(qualifiedStart.parent)) { 1767 qualifiedStart = qualifiedStart.parent; 1768 } 1769 const parent = qualifiedStart.parent; 1770 return !( 1771 // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery) 1772 (isTypeNode(parent) && !isTypeOfExpression(parent)) || 1773 // ElementAccess is allowed for enum types 1774 this.isEnumPropAccess(ident, tsSym, parent) || 1775 isExpressionWithTypeArguments(parent) || 1776 isExportAssignment(parent) || 1777 isExportSpecifier(parent) || 1778 isMetaProperty(parent) || 1779 isImportClause(parent) || 1780 isClassLike(parent) || 1781 isInterfaceDeclaration(parent) || 1782 isModuleDeclaration(parent) || 1783 isEnumDeclaration(parent) || 1784 isNamespaceImport(parent) || 1785 isImportSpecifier(parent) || 1786 isImportEqualsDeclaration(parent) || 1787 (isQualifiedName(qualifiedStart) && ident !== qualifiedStart.right) || 1788 (isPropertyAccessExpression(qualifiedStart) && 1789 ident !== qualifiedStart.name) || 1790 (isNewExpression(qualifiedStart.parent) && 1791 qualifiedStart === qualifiedStart.parent.expression) || 1792 (isBinaryExpression(qualifiedStart.parent) && 1793 qualifiedStart.parent.operatorToken.kind === 1794 SyntaxKind.InstanceOfKeyword) 1795 ); 1796 } 1797 1798 private isEnumPropAccess(ident: ts.Identifier, tsSym: ts.Symbol, context: ts.Node): boolean { 1799 return ts.isElementAccessExpression(context) && !!(tsSym.flags & ts.SymbolFlags.Enum) && 1800 (context.expression == ident || 1801 (ts.isPropertyAccessExpression(context.expression) && context.expression.name == ident)); 1802 } 1803 1804 private isElementAcessAllowed(type: ts.Type, argType: ts.Type): boolean { 1805 if (type.isUnion()) { 1806 for (const t of type.types) { 1807 if (!this.isElementAcessAllowed(t, argType)) { 1808 return false; 1809 } 1810 } 1811 return true; 1812 } 1813 1814 const typeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.None); 1815 1816 if (Utils.isArkTSCollectionsArrayLikeType(type)) { 1817 return Utils.isNumberLikeType(argType); 1818 } 1819 1820 return ( 1821 Utils.isLibraryType(type) || 1822 Utils.isAnyType(type) || 1823 Utils.isOrDerivedFrom(type, Utils.isArray) || 1824 Utils.isOrDerivedFrom(type, Utils.isTuple) || 1825 Utils.isOrDerivedFrom(type, Utils.isStdRecordType) || 1826 Utils.isOrDerivedFrom(type, Utils.isStringType) || 1827 Utils.isOrDerivedFrom(type, Utils.isStdMapType) || 1828 Utils.isIntrinsicObjectType(type) || 1829 Utils.isEnumType(type) || 1830 // we allow EsObject here beacuse it is reported later using FaultId.EsObjectType 1831 Utils.isEsObjectType(typeNode) 1832 ); 1833 } 1834 1835 private handleElementAccessExpression(node: ts.Node): void { 1836 const tsElementAccessExpr = node as ElementAccessExpression; 1837 const tsElementAccessExprSymbol = Utils.trueSymbolAtLocation(tsElementAccessExpr.expression); 1838 const tsElemAccessBaseExprType = Utils.getNonNullableType(Utils.getTypeOrTypeConstraintAtLocation(tsElementAccessExpr.expression)); 1839 const tsElemAccessArgType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.argumentExpression); 1840 1841 if ( 1842 // unnamed types do not have symbol, so need to check that explicitly 1843 !Utils.isLibrarySymbol(tsElementAccessExprSymbol) && 1844 !ts.isArrayLiteralExpression(tsElementAccessExpr.expression) && 1845 !this.isElementAcessAllowed(tsElemAccessBaseExprType, tsElemAccessArgType) 1846 ) { 1847 let autofix = Autofixer.fixPropertyAccessByIndex(node); 1848 const autofixable = autofix !== undefined; 1849 if (!Autofixer.shouldAutofix(node, FaultID.PropertyAccessByIndex)) { 1850 autofix = undefined; 1851 } 1852 this.incrementCounters(node, FaultID.PropertyAccessByIndex, autofixable, autofix); 1853 } 1854 if (Utils.hasEsObjectType(tsElementAccessExpr.expression)) { 1855 this.incrementCounters(node, FaultID.EsObjectType); 1856 } 1857 } 1858 1859 private handleEnumMember(node: Node): void { 1860 const tsEnumMember = node as EnumMember; 1861 const tsEnumMemberType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsEnumMember); 1862 const constVal = TypeScriptLinter.tsTypeChecker.getConstantValue(tsEnumMember); 1863 1864 if (tsEnumMember.initializer && !Utils.isValidEnumMemberInit(tsEnumMember.initializer)) { 1865 this.incrementCounters(node, FaultID.EnumMemberNonConstInit); 1866 } 1867 // check for type - all members should be of same type 1868 const enumDecl = tsEnumMember.parent; 1869 const firstEnumMember = enumDecl.members[0]; 1870 const firstEnumMemberType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(firstEnumMember); 1871 const firstElewmVal = TypeScriptLinter.tsTypeChecker.getConstantValue(firstEnumMember); 1872 // each string enum member has its own type 1873 // so check that value type is string 1874 if(constVal !==undefined && typeof constVal === "string" && 1875 firstElewmVal !==undefined && typeof firstElewmVal === "string") { 1876 return; 1877 } 1878 if (constVal !==undefined && typeof constVal === "number" && 1879 firstElewmVal !==undefined && typeof firstElewmVal === "number") { 1880 return; 1881 } 1882 if(firstEnumMemberType !== tsEnumMemberType) { 1883 this.incrementCounters(node, FaultID.EnumMemberNonConstInit); 1884 } 1885 } 1886 1887 private handleExportAssignment(node: Node): void { 1888 // (nsizov): check exportEquals and determine if it's an actual `export assignment` 1889 // or a `default export namespace` when this two cases will be determined in cookbook 1890 const exportAssignment = node as ExportAssignment; 1891 if (exportAssignment.isExportEquals) { 1892 this.incrementCounters(node, FaultID.ExportAssignment); 1893 } 1894 1895 if (!TypeScriptLinter.inSharedModule(node)) { 1896 return; 1897 } 1898 1899 if (!Utils.isShareableEntity(exportAssignment.expression)) { 1900 this.incrementCounters(exportAssignment.expression, FaultID.SharedModuleExports); 1901 } 1902 } 1903 1904 private handleCallExpression(node: Node): void { 1905 const tsCallExpr = node as CallExpression; 1906 1907 const calleeSym = Utils.trueSymbolAtLocation(tsCallExpr.expression); 1908 const callSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(tsCallExpr); 1909 1910 this.handleImportCall(tsCallExpr); 1911 this.handleRequireCall(tsCallExpr); 1912 // NOTE: Keep handleFunctionApplyBindPropCall above handleGenericCallWithNoTypeArgs here!!! 1913 if (calleeSym !== undefined) { 1914 this.handleStdlibAPICall(tsCallExpr, calleeSym); 1915 this.handleFunctionApplyBindPropCall(tsCallExpr, calleeSym); 1916 if (Utils.symbolHasEsObjectType(calleeSym)) { 1917 this.incrementCounters(tsCallExpr, FaultID.EsObjectType); 1918 } 1919 } 1920 if (callSignature !== undefined && !Utils.isLibrarySymbol(calleeSym)) { 1921 this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature); 1922 this.handleStructIdentAndUndefinedInArgs(tsCallExpr, callSignature); 1923 } 1924 this.handleLibraryTypeCall(tsCallExpr); 1925 1926 if (ts.isPropertyAccessExpression(tsCallExpr.expression) && Utils.hasEsObjectType(tsCallExpr.expression.expression)) { 1927 this.incrementCounters(node, FaultID.EsObjectType); 1928 } 1929 } 1930 1931 private handleEtsComponentExpression(node: ts.Node): void { 1932 // for all the checks we make EtsComponentExpression is compatible with the CallExpression 1933 const etsComponentExpression = node as ts.CallExpression; 1934 this.handleLibraryTypeCall(etsComponentExpression); 1935 } 1936 1937 private handleImportCall(tsCallExpr: CallExpression): void { 1938 if (tsCallExpr.expression.kind === SyntaxKind.ImportKeyword) { 1939 // relax rule#133 "arkts-no-runtime-import" 1940 // this.incrementCounters(tsCallExpr, FaultID.DynamicImport); 1941 const tsArgs = tsCallExpr.arguments; 1942 if (tsArgs.length > 1 && isObjectLiteralExpression(tsArgs[1])) { 1943 const objLitExpr = tsArgs[1] as ObjectLiteralExpression; 1944 for (const tsProp of objLitExpr.properties) { 1945 if (isPropertyAssignment(tsProp) || isShorthandPropertyAssignment(tsProp)) { 1946 if (tsProp.name.getText() === "assert") { 1947 this.incrementCounters(tsProp, FaultID.ImportAssertion); 1948 break; 1949 } 1950 } 1951 } 1952 } 1953 } 1954 } 1955 1956 private handleRequireCall(tsCallExpr: CallExpression): void { 1957 if ( 1958 isIdentifier(tsCallExpr.expression) && 1959 tsCallExpr.expression.text === "require" && 1960 isVariableDeclaration(tsCallExpr.parent) 1961 ) { 1962 const tsType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression); 1963 if (Utils.isInterfaceType(tsType) && tsType.symbol.name === "NodeRequire") { 1964 this.incrementCounters(tsCallExpr.parent, FaultID.ImportAssignment); 1965 } 1966 } 1967 } 1968 1969 private handleGenericCallWithNoTypeArgs(callLikeExpr: ts.CallExpression | ts.NewExpression, callSignature: ts.Signature) { 1970 1971 const tsSyntaxKind = isNewExpression(callLikeExpr) ? SyntaxKind.Constructor : SyntaxKind.FunctionDeclaration; 1972 const signDecl = TypeScriptLinter.tsTypeChecker.signatureToSignatureDeclaration(callSignature, tsSyntaxKind, 1973 undefined, NodeBuilderFlags.WriteTypeArgumentsOfSignature | NodeBuilderFlags.IgnoreErrors); 1974 1975 if (signDecl?.typeArguments) { 1976 const resolvedTypeArgs = signDecl.typeArguments; 1977 1978 const startTypeArg = callLikeExpr.typeArguments?.length ?? 0; 1979 for (let i = startTypeArg; i < resolvedTypeArgs.length; ++i) { 1980 const typeNode = resolvedTypeArgs[i]; 1981 // if compiler infers 'unknown' type there are 2 possible cases: 1982 // 1. Compiler unable to infer type from arguments and use 'unknown' 1983 // 2. Compiler infer 'unknown' from arguments 1984 // We report error in both cases. It is ok because we cannot use 'unknown' 1985 // in ArkTS and already have separate check for it. 1986 if (typeNode.kind == ts.SyntaxKind.UnknownKeyword) { 1987 this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs); 1988 break; 1989 } 1990 } 1991 } 1992 } 1993 1994 private static readonly listFunctionApplyCallApis = [ 1995 'Function.apply', 1996 'Function.call', 1997 'CallableFunction.apply', 1998 'CallableFunction.call' 1999 ]; 2000 2001 private static readonly listFunctionBindApis = [ 2002 'Function.bind', 2003 'CallableFunction.bind' 2004 ]; 2005 2006 private handleFunctionApplyBindPropCall(tsCallExpr: ts.CallExpression, calleeSym: ts.Symbol): void { 2007 const exprName = TypeScriptLinter.tsTypeChecker.getFullyQualifiedName(calleeSym); 2008 if (TypeScriptLinter.listFunctionApplyCallApis.includes(exprName)) { 2009 this.incrementCounters(tsCallExpr, FaultID.FunctionApplyCall); 2010 } 2011 if (TypeScriptLinter.listFunctionBindApis.includes(exprName)) { 2012 this.incrementCounters(tsCallExpr, FaultID.FunctionBind); 2013 } 2014 } 2015 2016 private handleStructIdentAndUndefinedInArgs(tsCallOrNewExpr: ts.CallExpression | ts.NewExpression, callSignature: ts.Signature) { 2017 if (!tsCallOrNewExpr.arguments) { 2018 return; 2019 } 2020 2021 for (let argIndex = 0; argIndex < tsCallOrNewExpr.arguments.length; ++argIndex) { 2022 const tsArg = tsCallOrNewExpr.arguments[argIndex]; 2023 const tsArgType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsArg); 2024 if (!tsArgType) continue; 2025 2026 let paramIndex = argIndex < callSignature.parameters.length ? argIndex : callSignature.parameters.length-1; 2027 let tsParamSym = callSignature.parameters[paramIndex]; 2028 if (!tsParamSym) continue; 2029 2030 const tsParamDecl = tsParamSym.valueDeclaration; 2031 if (tsParamDecl && isParameter(tsParamDecl)) { 2032 let tsParamType = TypeScriptLinter.tsTypeChecker.getTypeOfSymbolAtLocation(tsParamSym, tsParamDecl); 2033 if (tsParamDecl.dotDotDotToken && Utils.isGenericArrayType(tsParamType) && tsParamType.typeArguments) { 2034 tsParamType = tsParamType.typeArguments[0]; 2035 } 2036 if (!tsParamType) continue; 2037 2038 this.checkAssignmentMatching(tsArg, tsParamType, tsArg); 2039 } 2040 } 2041 } 2042 2043 // let re = new RegExp("^(" + arr.reduce((acc, v) => ((acc ? (acc + "|") : "") + v)) +")$") 2044 private static LimitedApis = new Map<string, {arr: Array<string> | null, fault: FaultID}> ([ 2045 ["global", {arr: Utils.LIMITED_STD_GLOBAL_FUNC, fault: FaultID.LimitedStdLibApi}], 2046 ["Object", {arr: Utils.LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}], 2047 ["ObjectConstructor", {arr: Utils.LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}], 2048 ["Reflect", {arr: Utils.LIMITED_STD_REFLECT_API, fault: FaultID.LimitedStdLibApi}], 2049 ["ProxyHandler", {arr: Utils.LIMITED_STD_PROXYHANDLER_API, fault: FaultID.LimitedStdLibApi}], 2050 ["Symbol", {arr: null, fault: FaultID.SymbolType}], 2051 ["SymbolConstructor", {arr: null, fault: FaultID.SymbolType}], 2052 ]) 2053 2054 private handleStdlibAPICall(callExpr: ts.CallExpression, calleeSym: ts.Symbol) { 2055 const name = calleeSym.getName(); 2056 const parName = Utils.getParentSymbolName(calleeSym); 2057 if (parName === undefined) { 2058 if (Utils.LIMITED_STD_GLOBAL_FUNC.includes(name)) { 2059 this.incrementCounters(callExpr, FaultID.LimitedStdLibApi); 2060 return; 2061 } 2062 let escapedName = calleeSym.escapedName; 2063 if (escapedName === 'Symbol' || escapedName === 'SymbolConstructor') { 2064 this.incrementCounters(callExpr, FaultID.SymbolType); 2065 } 2066 return; 2067 } 2068 let lookup = TypeScriptLinter.LimitedApis.get(parName); 2069 if (lookup !== undefined && (lookup.arr === null || lookup.arr.includes(name))) { 2070 this.incrementCounters(callExpr, lookup.fault); 2071 }; 2072 } 2073 2074 private findNonFilteringRangesFunctionCalls(callExpr: ts.CallExpression): { begin: number, end: number }[] { 2075 let args = callExpr.arguments; 2076 let result: { begin: number, end: number }[] = []; 2077 for (const arg of args) { 2078 if (ts.isArrowFunction(arg)) { 2079 let arrowFuncExpr = arg as ts.ArrowFunction; 2080 result.push({begin: arrowFuncExpr.body.pos, end: arrowFuncExpr.body.end}); 2081 } else if (ts.isCallExpression(arg)) { 2082 result.push({begin: arg.arguments.pos, end: arg.arguments.end}); 2083 } 2084 // there may be other cases 2085 } 2086 return result; 2087 } 2088 2089 private handleLibraryTypeCall(callExpr: ts.CallExpression) { 2090 const calleeType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(callExpr.expression); 2091 const inLibCall = Utils.isLibraryType(calleeType); 2092 const diagnosticMessages: Array<ts.DiagnosticMessageChain> = [] 2093 this.libraryTypeCallDiagnosticChecker.configure(inLibCall, diagnosticMessages); 2094 2095 let nonFilteringRanges = this.findNonFilteringRangesFunctionCalls(callExpr); 2096 let rangesToFilter: { begin: number, end: number }[] = []; 2097 if (nonFilteringRanges.length !== 0) { 2098 let rangesSize = nonFilteringRanges.length; 2099 rangesToFilter.push({ begin: callExpr.arguments.pos, end: nonFilteringRanges[0].begin }); 2100 rangesToFilter.push({ begin: nonFilteringRanges[rangesSize - 1].end, end: callExpr.arguments.end }); 2101 for (let i = 0; i < rangesSize - 1; i++) { 2102 rangesToFilter.push({ begin: nonFilteringRanges[i].end, end: nonFilteringRanges[i + 1].begin }); 2103 } 2104 } else { 2105 rangesToFilter.push({ begin: callExpr.arguments.pos, end: callExpr.arguments.end }) 2106 } 2107 2108 this.filterStrictDiagnostics({ 2109 [ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE]: (pos: number) => { 2110 return this.checkInRange(rangesToFilter, pos); 2111 }, 2112 [NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE]: (pos: number, length?: number) => { 2113 // The 'No-overload...' error is in some cases mounted on the callExpression node rather than a argument node 2114 return this.checkInRange(rangesToFilter, pos) && !(length && callExpr.end === pos + length); 2115 }, 2116 [TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE]: (pos: number) => { 2117 return this.checkInRange(rangesToFilter, pos); 2118 } 2119 }, 2120 this.libraryTypeCallDiagnosticChecker, 2121 inLibCall 2122 ); 2123 2124 diagnosticMessages.forEach(msgChain => { 2125 TypeScriptLinter.filteredDiagnosticMessages.push(msgChain); 2126 }); 2127 } 2128 2129 private handleNewExpression(node: Node): void { 2130 const tsNewExpr = node as NewExpression; 2131 let callSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(tsNewExpr); 2132 if (callSignature !== undefined) { 2133 this.handleStructIdentAndUndefinedInArgs(tsNewExpr, callSignature); 2134 this.handleGenericCallWithNoTypeArgs(tsNewExpr, callSignature); 2135 } 2136 this.handleSendableGenericTypes(tsNewExpr); 2137 } 2138 2139 private handleSendableGenericTypes(node: ts.NewExpression): void { 2140 const type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(node); 2141 if (!Utils.isSendableClassOrInterface(type)) { 2142 return; 2143 } 2144 2145 const typeArgs = node.typeArguments; 2146 if (!typeArgs || typeArgs.length === 0) { 2147 return; 2148 } 2149 2150 for (const arg of typeArgs) { 2151 if (!Utils.isSendableTypeNode(arg)) { 2152 this.incrementCounters(arg, FaultID.SendableGenericTypes); 2153 } 2154 } 2155 } 2156 2157 private handleAsExpression(node: Node): void { 2158 const tsAsExpr = node as AsExpression; 2159 if (tsAsExpr.type.getText() === "const") this.incrementCounters(node, FaultID.ConstAssertion); 2160 2161 const targetType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsAsExpr.type).getNonNullableType(); 2162 const exprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression).getNonNullableType(); 2163 // check for rule#65: "number as Number" and "boolean as Boolean" are disabled 2164 if( 2165 (Utils.isNumberLikeType(exprType) && Utils.isStdNumberType(targetType)) || 2166 (Utils.isBooleanLikeType(exprType) && Utils.isStdBooleanType(targetType)) 2167 ) { 2168 this.incrementCounters(node, FaultID.TypeAssertion); 2169 } 2170 if ( 2171 !Utils.isSendableClassOrInterface(exprType) && 2172 !Utils.isObject(exprType) && 2173 !Utils.isAnyType(exprType) && 2174 Utils.isSendableClassOrInterface(targetType) 2175 ) { 2176 this.incrementCounters(tsAsExpr, FaultID.SendableAsExpr); 2177 } 2178 if ( 2179 Utils.isWrongSendableFunctionAssignment(targetType, exprType) 2180 ) { 2181 this.incrementCounters(tsAsExpr, FaultID.SendableFunctionAsExpr); 2182 } 2183 } 2184 2185 private handleTypeReference(node: Node): void { 2186 const typeRef = node as TypeReferenceNode; 2187 2188 const isESObject = Utils.isEsObjectType(typeRef); 2189 const isPossiblyValidContext = Utils.isEsObjectPossiblyAllowed(typeRef); 2190 if (isESObject && !isPossiblyValidContext) { 2191 this.incrementCounters(node, FaultID.EsObjectType); 2192 return; 2193 } 2194 const typeName = Utils.entityNameToString(typeRef.typeName); 2195 const isStdUtilityType = Utils.LIMITED_STANDARD_UTILITY_TYPES.includes(typeName); 2196 if (isStdUtilityType) { 2197 this.incrementCounters(node, FaultID.UtilityType); 2198 return; 2199 } 2200 2201 // Using Partial<T> type is allowed only when its argument type is either Class or Interface. 2202 const isStdPartial = Utils.entityNameToString(typeRef.typeName) === 'Partial'; 2203 const hasSingleTypeArgument = !!typeRef.typeArguments && typeRef.typeArguments.length === 1; 2204 const firstTypeArg = !!typeRef.typeArguments && hasSingleTypeArgument && typeRef.typeArguments[0]; 2205 const argType = firstTypeArg && TypeScriptLinter.tsTypeChecker.getTypeFromTypeNode(firstTypeArg); 2206 if (isStdPartial && argType && !argType.isClassOrInterface()) { 2207 this.incrementCounters(node, FaultID.UtilityType); 2208 return; 2209 } 2210 2211 const typeNameType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(typeRef.typeName); 2212 if (Utils.isSendableClassOrInterface(typeNameType)) { 2213 this.checkSendableTypeArguments(typeRef); 2214 } 2215 } 2216 2217 private checkSendableTypeArguments(typeRef: TypeReferenceNode): void { 2218 if (typeRef.typeArguments) { 2219 for (const typeArg of typeRef.typeArguments) { 2220 if (!Utils.isSendableTypeNode(typeArg)) { 2221 this.incrementCounters(typeArg, FaultID.SendableGenericTypes); 2222 } 2223 } 2224 } 2225 } 2226 2227 private handleMetaProperty(node: Node): void { 2228 const tsMetaProperty = node as MetaProperty; 2229 if (tsMetaProperty.name.text === "target") { 2230 this.incrementCounters(node, FaultID.NewTarget); 2231 } 2232 } 2233 2234 private handleSpreadOp(node: Node) { 2235 // spread assignment is disabled 2236 // spread element is allowed only for arrays as rest parameter 2237 if (isSpreadElement(node)) { 2238 const spreadExprType = Utils.getTypeOrTypeConstraintAtLocation(node.expression); 2239 if (spreadExprType) { 2240 if (ts.isCallLikeExpression(node.parent) || ts.isArrayLiteralExpression(node.parent)) { 2241 if (Utils.isOrDerivedFrom(spreadExprType, Utils.isArray)) { 2242 return; 2243 } 2244 } 2245 } 2246 } 2247 this.incrementCounters(node, FaultID.SpreadOperator); 2248 } 2249 2250 private handleConstructSignature(node: Node) { 2251 switch (node.parent.kind) { 2252 case SyntaxKind.TypeLiteral: 2253 this.incrementCounters(node, FaultID.ConstructorType); 2254 break; 2255 case SyntaxKind.InterfaceDeclaration: 2256 this.incrementCounters(node, FaultID.ConstructorIface); 2257 break; 2258 default: 2259 return; 2260 } 2261 } 2262 2263 private handleExpressionWithTypeArguments(node: Node) { 2264 const tsTypeExpr = node as ExpressionWithTypeArguments; 2265 const symbol = Utils.trueSymbolAtLocation(tsTypeExpr.expression); 2266 if (!!symbol && Utils.isEsObjectSymbol(symbol)) { 2267 this.incrementCounters(tsTypeExpr, FaultID.EsObjectType); 2268 } 2269 } 2270 2271 private handleComputedPropertyName(node: ts.Node) { 2272 const computedProperty = node as ts.ComputedPropertyName; 2273 if (this.isSendableCompPropName(computedProperty)) { 2274 // cancel the '[Symbol.iterface]' restriction of 'sendable class/interface' in the '@arkts.collections.d.ets' file 2275 if (Utils.isSymbolIteratorExpression(computedProperty.expression)) { 2276 const declNode = computedProperty.parent?.parent; 2277 if (declNode && Utils.isArkTSCollectionsClassOrInterfaceDeclaration(declNode)) { 2278 return; 2279 } 2280 } 2281 this.incrementCounters(node, FaultID.SendableComputedPropName); 2282 } else if (!Utils.isValidComputedPropertyName(computedProperty, false)) { 2283 this.incrementCounters(node, FaultID.ComputedPropertyName); 2284 } 2285 } 2286 2287 private isSendableCompPropName(compProp: ts.ComputedPropertyName): boolean { 2288 const declNode = compProp.parent?.parent; 2289 if (declNode && ts.isClassDeclaration(declNode) && Utils.hasSendableDecorator(declNode)) { 2290 return true; 2291 } else if (declNode && ts.isInterfaceDeclaration(declNode)) { 2292 const declNodeType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(declNode); 2293 if (Utils.isSendableClassOrInterface(declNodeType)) { 2294 return true; 2295 } 2296 } 2297 return false; 2298 } 2299 2300 private handleGetAccessor(node: ts.GetAccessorDeclaration): void { 2301 Utils.getDecoratorsIfInSendableClass(node)?.forEach((decorator) => { 2302 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 2303 }); 2304 } 2305 2306 private handleSetAccessor(node: ts.SetAccessorDeclaration): void { 2307 Utils.getDecoratorsIfInSendableClass(node)?.forEach((decorator) => { 2308 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 2309 }); 2310 } 2311 2312 private handleDeclarationInferredType( 2313 decl: VariableDeclaration | PropertyDeclaration | ParameterDeclaration 2314 ) { 2315 // The type is explicitly specified, no need to check inferred type. 2316 if (decl.type) return; 2317 2318 // issue 13161: 2319 // In TypeScript, the catch clause variable must be 'any' or 'unknown' type. Since 2320 // ArkTS doesn't support these types, the type for such variable is simply omitted, 2321 // and we don't report it as an error. 2322 if (isCatchClause(decl.parent)) return; 2323 2324 //Destructuring declarations are not supported, do not process them. 2325 if (isArrayBindingPattern(decl.name) || isObjectBindingPattern(decl.name)) return; 2326 2327 // issue 13987: 2328 // When variable have no type annotation and no initial value, and 'noImplicitAny' 2329 // option is enabled, compiler attempts to infer type from variable references: 2330 // see https://github.com/microsoft/TypeScript/pull/11263. 2331 // In this case, we still want to report the error, since ArkTS doesn't allow 2332 // to omit both type annotation and initializer. 2333 if (((ts.isVariableDeclaration(decl) && ts.isVariableStatement(decl.parent.parent)) || ts.isPropertyDeclaration(decl)) && 2334 !decl.initializer) { 2335 if (ts.isPropertyDeclaration(decl) && 2336 this.sourceFile.scriptKind === ScriptKind.ETS && this.sourceFile.isDeclarationFile && 2337 decl.modifiers?.some(m => m.kind === SyntaxKind.PrivateKeyword)) { 2338 return; 2339 } 2340 this.incrementCounters(decl, FaultID.AnyType); 2341 return; 2342 } 2343 2344 const type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(decl); 2345 if (type) this.validateDeclInferredType(type, decl); 2346 } 2347 2348 private handleDefiniteAssignmentAssertion(decl: VariableDeclaration | PropertyDeclaration) { 2349 if (decl.exclamationToken === undefined) { 2350 return; 2351 } 2352 2353 if (decl.kind === ts.SyntaxKind.PropertyDeclaration) { 2354 const parentDecl = decl.parent; 2355 if (parentDecl.kind === ts.SyntaxKind.ClassDeclaration && Utils.hasSendableDecorator(parentDecl)) { 2356 this.incrementCounters(decl, FaultID.SendableDefiniteAssignment); 2357 return; 2358 } 2359 } 2360 this.incrementCounters(decl, FaultID.DefiniteAssignment); 2361 } 2362 2363 private validatedTypesSet = new Set<Type>(); 2364 2365 private checkAnyOrUnknownChildNode(node: ts.Node): boolean { 2366 if (node.kind === ts.SyntaxKind.AnyKeyword || 2367 node.kind === ts.SyntaxKind.UnknownKeyword) { 2368 return true; 2369 } 2370 for (let child of node.getChildren()) { 2371 if (this.checkAnyOrUnknownChildNode(child)) { 2372 return true; 2373 } 2374 } 2375 return false; 2376 } 2377 2378 private handleInferredObjectreference( 2379 type: ts.Type, 2380 decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 2381 ) { 2382 const typeArgs = TypeScriptLinter.tsTypeChecker.getTypeArguments(type as ts.TypeReference); 2383 if (typeArgs) { 2384 const haveAnyOrUnknownNodes = this.checkAnyOrUnknownChildNode(decl); 2385 if (!haveAnyOrUnknownNodes) { 2386 for (const typeArg of typeArgs) { 2387 this.validateDeclInferredType(typeArg, decl); 2388 } 2389 } 2390 } 2391 } 2392 2393 private validateDeclInferredType( 2394 type: Type, 2395 decl: VariableDeclaration | PropertyDeclaration | ParameterDeclaration 2396 ): void { 2397 if (type.aliasSymbol !== undefined) { 2398 return; 2399 } 2400 const isObject = type.flags & ts.TypeFlags.Object; 2401 const isReference = (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference; 2402 if (isObject && isReference) { 2403 this.handleInferredObjectreference(type, decl); 2404 return; 2405 } 2406 if (this.validatedTypesSet.has(type)) { 2407 return; 2408 } 2409 if (type.isUnion()) { 2410 this.validatedTypesSet.add(type); 2411 for (let unionElem of type.types) { 2412 this.validateDeclInferredType(unionElem, decl); 2413 } 2414 } 2415 2416 if (Utils.isAnyType(type)) { 2417 this.incrementCounters(decl, FaultID.AnyType); 2418 } 2419 else if (Utils.isUnknownType(type)) { 2420 this.incrementCounters(decl, FaultID.UnknownType); 2421 } 2422 } 2423 2424 private processNoCheckEntry(entry: PragmaPseudoMap[keyof PragmaPseudoMap]): void { 2425 if (entry.range?.kind === undefined || entry.range?.pos === undefined || entry.range?.end === undefined) { 2426 return; 2427 } 2428 this.incrementCounters(entry.range as ts.CommentRange, FaultID.ErrorSuppression); 2429 } 2430 2431 private reportThisKeywordsInScope(scope: ts.Block | ts.Expression): void { 2432 const callback = (node: ts.Node): void => { 2433 if (node.kind === ts.SyntaxKind.ThisKeyword) { 2434 this.incrementCounters(node, FaultID.FunctionContainsThis); 2435 } 2436 }; 2437 const stopCondition = (node: ts.Node): boolean => { 2438 const isClassLike = ts.isClassDeclaration(node) || ts.isClassExpression(node); 2439 const isFunctionLike = ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node); 2440 const isModuleDecl = ts.isModuleDeclaration(node); 2441 return isClassLike || isFunctionLike || isModuleDecl; 2442 }; 2443 this.forEachNodeInSubtree(scope, callback, stopCondition); 2444 } 2445 2446 private handleCommentDirectives(sourceFile: ts.SourceFile): void { 2447 2448 /* 2449 * We use a dirty hack to retrieve list of parsed comment directives by accessing 2450 * internal properties of SourceFile node. 2451 */ 2452 2453 // Handle comment directive '@ts-nocheck' 2454 const pragmas = sourceFile.pragmas; 2455 if (pragmas && pragmas instanceof Map) { 2456 const noCheckPragma: PragmaPseudoMap[keyof PragmaPseudoMap] | PragmaPseudoMap[keyof PragmaPseudoMap][] = pragmas.get('ts-nocheck'); 2457 if (noCheckPragma) { 2458 /* 2459 * The value is either a single entry or an array of entries. 2460 * Wrap up single entry with array to simplify processing. 2461 */ 2462 const noCheckEntries = Array.isArray(noCheckPragma) ? noCheckPragma : [noCheckPragma]; 2463 for (const entry of noCheckEntries) { 2464 this.processNoCheckEntry(entry); 2465 } 2466 } 2467 } 2468 2469 // Handle comment directives '@ts-ignore' and '@ts-expect-error' 2470 const commentDirectives = sourceFile.commentDirectives; 2471 if (commentDirectives && Array.isArray(commentDirectives)) { 2472 for (const directive of commentDirectives) { 2473 if (directive.range?.pos === undefined || directive.range?.end === undefined) { 2474 continue; 2475 } 2476 2477 const range = directive.range as ts.TextRange; 2478 const kind: ts.SyntaxKind = 2479 sourceFile.text.slice(range.pos, range.pos + 2) === '/*' ? 2480 ts.SyntaxKind.MultiLineCommentTrivia : 2481 ts.SyntaxKind.SingleLineCommentTrivia; 2482 const commentRange: ts.CommentRange = { 2483 pos: range.pos, 2484 end: range.end, 2485 kind 2486 }; 2487 2488 this.incrementCounters(commentRange, FaultID.ErrorSuppression); 2489 } 2490 } 2491 } 2492 2493 private handleClassStaticBlockDeclaration(node: ts.Node): void { 2494 if (this.skipArkTSStaticBlocksCheck) { 2495 return; 2496 } 2497 const classStaticBlockDecl = node as ts.ClassStaticBlockDeclaration; 2498 if (!ts.isClassDeclaration(classStaticBlockDecl.parent)) { 2499 return; 2500 } 2501 this.reportThisKeywordsInScope(classStaticBlockDecl.body); 2502 } 2503 2504 private handleIndexSignature(node: ts.Node): void { 2505 if (!Utils.isAllowedIndexSignature(node as ts.IndexSignatureDeclaration)) { 2506 this.incrementCounters(node, FaultID.IndexMember); 2507 } 2508 } 2509 2510 public lint(): void { 2511 this.visitSourceFile(this.sourceFile); 2512 this.handleCommentDirectives(this.sourceFile); 2513 } 2514 2515 private handleExportKeyword(node: ts.Node): void { 2516 const parentNode = node.parent; 2517 if (!TypeScriptLinter.inSharedModule(node) || ts.isModuleBlock(parentNode.parent)) { 2518 return; 2519 } 2520 2521 switch (parentNode.kind) { 2522 case ts.SyntaxKind.EnumDeclaration: 2523 if (Utils.isConstEnum(parentNode.symbol)) { 2524 break; 2525 } else { 2526 this.incrementCounters((parentNode as ts.NamedDeclaration).name ?? parentNode, FaultID.SharedModuleExports); 2527 } 2528 return; 2529 case ts.SyntaxKind.InterfaceDeclaration: 2530 case ts.SyntaxKind.FunctionDeclaration: 2531 case ts.SyntaxKind.ClassDeclaration: 2532 if (!Utils.isShareableType(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(parentNode))) { 2533 this.incrementCounters((parentNode as ts.NamedDeclaration).name ?? parentNode, FaultID.SharedModuleExports); 2534 } 2535 return; 2536 case ts.SyntaxKind.VariableStatement: 2537 for (const variableDeclaration of (parentNode as ts.VariableStatement).declarationList.declarations) { 2538 if (!Utils.isShareableEntity(variableDeclaration.name)) { 2539 this.incrementCounters(variableDeclaration.name, FaultID.SharedModuleExports); 2540 } 2541 } 2542 return; 2543 case ts.SyntaxKind.TypeAliasDeclaration: 2544 if (!Utils.isShareableEntity(parentNode)) { 2545 this.incrementCounters(parentNode, FaultID.SharedModuleExportsWarning); 2546 } 2547 return; 2548 default: 2549 this.incrementCounters(parentNode, FaultID.SharedModuleExports); 2550 } 2551 } 2552 2553 private handleExportDeclaration(node: ts.Node): void { 2554 if (!TypeScriptLinter.inSharedModule(node) || ts.isModuleBlock(node.parent)) { 2555 return; 2556 } 2557 2558 const exportDecl = node as ts.ExportDeclaration; 2559 if (exportDecl.exportClause === undefined) { 2560 this.incrementCounters(exportDecl, FaultID.SharedModuleNoWildcardExport); 2561 return; 2562 } 2563 2564 if (ts.isNamespaceExport(exportDecl.exportClause)) { 2565 if (!Utils.isShareableType(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(exportDecl.exportClause.name))) { 2566 this.incrementCounters(exportDecl.exportClause.name, FaultID.SharedModuleExports); 2567 } 2568 return; 2569 } 2570 2571 for (const exportSpecifier of exportDecl.exportClause.elements) { 2572 if (!Utils.isShareableEntity(exportSpecifier.name)) { 2573 this.incrementCounters(exportSpecifier.name, FaultID.SharedModuleExports); 2574 } 2575 } 2576 } 2577 2578 private handleReturnStatement(node: ts.Node): void { 2579 // The return value must match the return type of the 'function' 2580 const returnStat = node as ts.ReturnStatement; 2581 const expr = returnStat.expression; 2582 if (!expr) { 2583 return; 2584 } 2585 const lhsType = TypeScriptLinter.tsTypeChecker.getContextualType(expr); 2586 if (!lhsType) { 2587 return; 2588 } 2589 this.checkAssignmentMatching(node, lhsType, expr, true); 2590 } 2591 2592 /** 2593 * 'arkts-no-structural-typing' check was missing in some scenarios, 2594 * in order not to cause incompatibility, 2595 * only need to strictly match the type of filling the check again 2596 */ 2597 private checkAssignmentMatching( 2598 field: ts.Node, 2599 lhsType: ts.Type, 2600 rhsExpr: ts.Expression, 2601 isMissStructural: boolean = false 2602 ): void { 2603 const rhsType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(rhsExpr); 2604 // check that 'sendable typeAlias' is assigned correctly 2605 if (Utils.isWrongSendableFunctionAssignment(lhsType, rhsType)) { 2606 this.incrementCounters(field, FaultID.SendableFunctionAssignment); 2607 } 2608 const isStrict = Utils.needStrictMatchType(lhsType, rhsType); 2609 // 'isMissStructural' means that this assignment scenario was previously omitted, so only strict matches are checked now 2610 if (isMissStructural && !isStrict) { 2611 return; 2612 } 2613 if (Utils.needToDeduceStructuralIdentity(lhsType, rhsType, rhsExpr, isStrict)) { 2614 this.incrementCounters(field, FaultID.StructuralIdentity); 2615 } 2616 } 2617 2618 private handleDecorator(node: ts.Node): void { 2619 const decorator: ts.Decorator = node as ts.Decorator; 2620 if (Utils.getDecoratorName(decorator) === Utils.SENDABLE_DECORATOR) { 2621 const parent: ts.Node = decorator.parent; 2622 if (!parent || !Utils.SENDABLE_DECORATOR_NODES.includes(parent.kind)) { 2623 this.incrementCounters(decorator, FaultID.SendableDecoratorLimited); 2624 } 2625 } 2626 } 2627 2628 private isSendableDecoratorValid(decl: ts.FunctionDeclaration | ts.TypeAliasDeclaration): boolean { 2629 if ( 2630 this.compatibleSdkVersion > 12 || 2631 this.compatibleSdkVersion === 12 && (this.compatibleSdkVersionStage !== 'beta1' && this.compatibleSdkVersionStage !== 'beta2') 2632 ) { 2633 return true; 2634 } 2635 const curDecorator = Utils.getSendableDecorator(decl); 2636 if (curDecorator) { 2637 this.incrementCounters(curDecorator, FaultID.SendableBetaCompatible); 2638 } 2639 return false; 2640 } 2641} 2642} 2643} 2644