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