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_0 { 18 19//import Utils = Utils; 20 21import FaultID = Problems.FaultID; 22import faultsAttrs = Problems.faultsAttrs; 23 24//import cookBookMsg = cookBookMsg; 25//import cookBookTag = ts.cookBookTag; 26 27//import LinterConfig = ts.LinterConfig; 28 29import Autofix = Autofixer.Autofix; 30//import Autofixer = ts.Autofixer; 31 32import Logger = ts.perfLogger; 33 34import DiagnosticChecker = DiagnosticCheckerNamespace.DiagnosticChecker; 35import ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 36 LibraryTypeCallDiagnosticCheckerNamespace.ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE; 37import TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 38 LibraryTypeCallDiagnosticCheckerNamespace.TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE; 39import NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE = 40 LibraryTypeCallDiagnosticCheckerNamespace.NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE; 41import LibraryTypeCallDiagnosticChecker = 42 LibraryTypeCallDiagnosticCheckerNamespace.LibraryTypeCallDiagnosticChecker; 43 44//const logger = Logger.getLogger(); 45 46export interface ProblemInfo { 47 line: number; 48 column: number; 49 start: number; 50 end: number; 51 type: string; 52 severity: number; 53 problem: string; 54 suggest: string; 55 rule: string; 56 ruleTag: number; 57 autofixable: boolean; 58 autofix?: Autofix[]; 59} 60 61export class TypeScriptLinter { 62 static ideMode: boolean; 63 static strictMode: boolean; 64 static logTscErrors: boolean; 65 static warningsAsErrors: boolean; 66 static lintEtsOnly: boolean; 67 static totalVisitedNodes: number; 68 static nodeCounters: number[]; 69 static lineCounters: number[]; 70 71 static totalErrorLines: number; 72 static errorLineNumbersString: string; 73 static totalWarningLines: number; 74 static warningLineNumbersString: string; 75 static reportDiagnostics = true; 76 77 // The SyntaxKind enum defines additional elements at the end of the enum 78 // that serve as markers (FirstX/LastX). Those elements are initialized 79 // with indices of the previously defined elements. As result, the enum 80 // may return incorrect name for a certain kind index (e.g. 'FirstStatement' 81 // instead of 'VariableStatement'). 82 // The following code creates a map with correct syntax kind names. 83 // It can be used when need to print name of syntax kind of certain 84 // AST node in diagnostic messages. 85 //private static tsSyntaxKindNames: string[]; 86 87 static problemsInfos: ProblemInfo[] = []; 88 89 static filteredDiagnosticMessages: DiagnosticMessageChain[] = []; 90 91 public static initGlobals(): void { 92 TypeScriptLinter.filteredDiagnosticMessages = [] 93 } 94 95 public static initStatic(): void { 96 TypeScriptLinter.strictMode = true; 97 TypeScriptLinter.logTscErrors = false; 98 TypeScriptLinter.warningsAsErrors = false; 99 TypeScriptLinter.lintEtsOnly = true; 100 TypeScriptLinter.totalVisitedNodes = 0; 101 TypeScriptLinter.nodeCounters = []; 102 TypeScriptLinter.lineCounters = []; 103 104 TypeScriptLinter.totalErrorLines = 0; 105 TypeScriptLinter.totalWarningLines = 0; 106 TypeScriptLinter.errorLineNumbersString = ""; 107 TypeScriptLinter.warningLineNumbersString = ""; 108 109 Autofixer.autofixInfo.length = 0; 110 111 //TypeScriptLinter.tsSyntaxKindNames = []; 112 //const keys = Object.keys(ts.SyntaxKind); 113 //const keys: string[] = []; 114 //const values = Object.values(ts.SyntaxKind); 115 //const values: string[] = []; 116 117 /* 118 for (let i = 0; i < values.length; i++) { 119 const val = values[i]; 120 const kindNum = typeof val === "string" ? parseInt(val) : val; 121 if (kindNum && !TypeScriptLinter.tsSyntaxKindNames[kindNum]) 122 TypeScriptLinter.tsSyntaxKindNames[kindNum] = keys[i]; 123 } 124 */ 125 126 for (let i = 0; i < FaultID.LAST_ID; i++) { 127 TypeScriptLinter.nodeCounters[i] = 0; 128 TypeScriptLinter.lineCounters[i] = 0; 129 } 130 131 TypeScriptLinter.problemsInfos = []; 132 } 133 134 public static tsTypeChecker: TypeChecker; 135 136 currentErrorLine: number; 137 currentWarningLine: number; 138 staticBlocks: Set<string>; 139 libraryTypeCallDiagnosticChecker: LibraryTypeCallDiagnosticChecker; 140 skipArkTSStaticBlocksCheck: boolean; 141 142 constructor(private sourceFile: SourceFile, 143 /* private */ tsProgram: Program, 144 private tscStrictDiagnostics?: Map<Diagnostic[]>) { 145 TypeScriptLinter.tsTypeChecker = tsProgram.getLinterTypeChecker(); 146 this.currentErrorLine = 0; 147 this.currentWarningLine = 0; 148 this.staticBlocks = new Set<string>(); 149 this.libraryTypeCallDiagnosticChecker = new LibraryTypeCallDiagnosticChecker(TypeScriptLinter.filteredDiagnosticMessages); 150 151 const options = tsProgram.getCompilerOptions(); 152 this.skipArkTSStaticBlocksCheck = false; 153 if (options.skipArkTSStaticBlocksCheck) { 154 this.skipArkTSStaticBlocksCheck = options.skipArkTSStaticBlocksCheck as boolean; 155 } 156 } 157 158 public static clearTsTypeChecker(): void { 159 TypeScriptLinter.tsTypeChecker = {} as TypeChecker; 160 } 161 162 readonly handlersMap = new Map([ 163 [SyntaxKind.ObjectLiteralExpression, this.handleObjectLiteralExpression], 164 [SyntaxKind.ArrayLiteralExpression, this.handleArrayLiteralExpression], 165 [SyntaxKind.Parameter, this.handleParameter], 166 [SyntaxKind.EnumDeclaration, this.handleEnumDeclaration], 167 [SyntaxKind.InterfaceDeclaration, this.handleInterfaceDeclaration], 168 [SyntaxKind.ThrowStatement, this.handleThrowStatement], [SyntaxKind.ImportClause, this.handleImportClause], 169 [SyntaxKind.ForStatement, this.handleForStatement], 170 [SyntaxKind.ForInStatement, this.handleForInStatement], 171 [SyntaxKind.ForOfStatement, this.handleForOfStatement], 172 [SyntaxKind.ImportDeclaration, this.handleImportDeclaration], 173 [SyntaxKind.PropertyAccessExpression, this.handlePropertyAccessExpression], 174 [SyntaxKind.PropertyDeclaration, this.handlePropertyAssignmentOrDeclaration], 175 [SyntaxKind.PropertyAssignment, this.handlePropertyAssignmentOrDeclaration], 176 [SyntaxKind.FunctionExpression, this.handleFunctionExpression], 177 [SyntaxKind.ArrowFunction, this.handleArrowFunction], 178 [SyntaxKind.ClassExpression, this.handleClassExpression], [SyntaxKind.CatchClause, this.handleCatchClause], 179 [SyntaxKind.FunctionDeclaration, this.handleFunctionDeclaration], 180 [SyntaxKind.PrefixUnaryExpression, this.handlePrefixUnaryExpression], 181 [SyntaxKind.BinaryExpression, this.handleBinaryExpression], 182 [SyntaxKind.VariableDeclarationList, this.handleVariableDeclarationList], 183 [SyntaxKind.VariableDeclaration, this.handleVariableDeclaration], 184 [SyntaxKind.ClassDeclaration, this.handleClassDeclaration], 185 [SyntaxKind.ModuleDeclaration, this.handleModuleDeclaration], 186 [SyntaxKind.TypeAliasDeclaration, this.handleTypeAliasDeclaration], 187 [SyntaxKind.ImportSpecifier, this.handleImportSpecifier], 188 [SyntaxKind.NamespaceImport, this.handleNamespaceImport], 189 [SyntaxKind.TypeAssertionExpression, this.handleTypeAssertionExpression], 190 [SyntaxKind.MethodDeclaration, this.handleMethodDeclaration], 191 [SyntaxKind.Identifier, this.handleIdentifier], 192 [SyntaxKind.ElementAccessExpression, this.handleElementAccessExpression], 193 [SyntaxKind.EnumMember, this.handleEnumMember], [SyntaxKind.TypeReference, this.handleTypeReference], 194 [SyntaxKind.ExportAssignment, this.handleExportAssignment], 195 [SyntaxKind.CallExpression, this.handleCallExpression], [SyntaxKind.MetaProperty, this.handleMetaProperty], 196 [SyntaxKind.NewExpression, this.handleNewExpression], [SyntaxKind.AsExpression, this.handleAsExpression], 197 [SyntaxKind.SpreadElement, this.handleSpreadOp], [SyntaxKind.SpreadAssignment, this.handleSpreadOp], 198 [SyntaxKind.GetAccessor, this.handleGetAccessor], [SyntaxKind.SetAccessor, this.handleSetAccessor], 199 [SyntaxKind.ConstructSignature, this.handleConstructSignature], 200 [SyntaxKind.ExpressionWithTypeArguments, this.handleExpressionWithTypeArguments], 201 [ts.SyntaxKind.ComputedPropertyName, this.handleComputedPropertyName], 202 [SyntaxKind.ClassStaticBlockDeclaration, this.handleClassStaticBlockDeclaration], 203 ]); 204 205 public incrementCounters(node: Node | CommentRange, faultId: number, autofixable = false, autofix?: Autofix[]): void { 206 if (!TypeScriptLinter.strictMode && faultsAttrs[faultId].migratable) { return; } // In relax mode skip migratable 207 208 const startPos = Utils.getStartPos(node); 209 const endPos = Utils.getEndPos(node); 210 211 TypeScriptLinter.nodeCounters[faultId]++; 212 // TSC counts lines and columns from zero 213 let { line, character } = this.sourceFile.getLineAndCharacterOfPosition(startPos); 214 ++line; 215 ++character; 216 217 const faultDescr = LinterConfig.nodeDesc[faultId]; 218 const faultType = "unknown"; //TypeScriptLinter.tsSyntaxKindNames[node.kind]; 219 220 const cookBookMsgNum = faultsAttrs[faultId] ? Number(faultsAttrs[faultId].cookBookRef) : 0; 221 const cookBookTg = cookBookTag[cookBookMsgNum]; 222 let severity = Utils.ProblemSeverity.ERROR; 223 if (faultsAttrs[faultId] && faultsAttrs[faultId].warning) { 224 severity = Utils.ProblemSeverity.WARNING; 225 } 226 const badNodeInfo: ProblemInfo = { 227 line: line, 228 column: character, 229 start: startPos, 230 end: endPos, 231 type: faultType, 232 severity: severity, 233 problem: FaultID[faultId], 234 suggest: cookBookMsgNum > 0 ? cookBookMsg[cookBookMsgNum] : "", 235 rule: cookBookMsgNum > 0 && cookBookTg !== "" ? cookBookTg : faultDescr ? faultDescr : faultType, 236 ruleTag: cookBookMsgNum, 237 autofixable: autofixable, 238 autofix: autofix 239 }; 240 241 TypeScriptLinter.problemsInfos.push(badNodeInfo); 242 243 if (!TypeScriptLinter.reportDiagnostics) { 244 //logger.info( 245 Logger.logEvent( 246 `Warning: ${this.sourceFile.fileName} (${line}, ${character}): ${faultDescr ? faultDescr : faultType}` 247 ); 248 } 249 250 TypeScriptLinter.lineCounters[faultId]++; 251 252 if (faultsAttrs[faultId].warning) { 253 if (line !== this.currentWarningLine) { 254 this.currentWarningLine = line; 255 ++TypeScriptLinter.totalWarningLines; 256 TypeScriptLinter.warningLineNumbersString += line + ", " ; 257 } 258 } 259 else if (line !== this.currentErrorLine) { 260 this.currentErrorLine = line; 261 ++TypeScriptLinter.totalErrorLines; 262 TypeScriptLinter.errorLineNumbersString += line + ", "; 263 } 264 } 265 266 public visitTSNode(node: Node): void { 267 const self = this; 268 visitTSNodeImpl(node); 269 function visitTSNodeImpl(node: Node): void { 270 if (node === null || node.kind === null) { 271 return; 272 } 273 TypeScriptLinter.totalVisitedNodes++; 274 275 //if (TypeScriptLinter.tsSyntaxKindNames[node.kind] === "StructDeclaration") { 276 // if (node.kind === SyntaxKind.StructDeclaration) { 277 //if ( SyntaxKind[node.kind] === 'StructDeclaration') { 278 if(isStructDeclaration(node)) { 279 self.handleStructDeclaration(node); 280 return; 281 } 282 283 self.handleComments(node); 284 285 if (LinterConfig.terminalTokens.has(node.kind)) return; 286 287 const incrementedType = LinterConfig.incrementOnlyTokens.get(node.kind); 288 if (incrementedType !== undefined) { 289 self.incrementCounters(node, incrementedType); 290 } 291 else { 292 const handler = self.handlersMap.get(node.kind); 293 if (handler !== undefined) { 294 handler.call(self, node); 295 } 296 } 297 298 forEachChild(node, visitTSNodeImpl); 299 } 300 } 301 302 private countInterfaceExtendsDifferentPropertyTypes( 303 node: Node, 304 prop2type: Map<string /*, string*/>, 305 propName: string, 306 type: TypeNode | undefined 307 ): void { 308 if (type) { 309 const methodType = type.getText(); 310 const propType = prop2type.get(propName); 311 if (!propType) { 312 prop2type.set(propName, methodType); 313 } 314 else if (propType !== methodType) { 315 this.incrementCounters(node, FaultID.IntefaceExtendDifProps); 316 } 317 } 318 } 319 320 private countDeclarationsWithDuplicateName( 321 tsNode: Node, tsDeclNode: Node, tsDeclKind?: SyntaxKind 322 ): void { 323 const symbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsNode); 324 325 // If specific declaration kind is provided, check against it. 326 // Otherwise, use syntax kind of corresponding declaration node. 327 if (!!symbol && Utils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)) { 328 this.incrementCounters(tsDeclNode, FaultID.DeclWithDuplicateName); 329 } 330 } 331 332 private countClassMembersWithDuplicateName(tsClassDecl: ClassDeclaration): void { 333 for (const tsCurrentMember of tsClassDecl.members) { 334 if ( 335 !tsCurrentMember.name || 336 !(isIdentifier(tsCurrentMember.name) || isPrivateIdentifier(tsCurrentMember.name)) 337 ) { 338 continue; 339 } 340 for (const tsClassMember of tsClassDecl.members) { 341 if (tsCurrentMember === tsClassMember) continue; 342 343 if ( 344 !tsClassMember.name || 345 !(isIdentifier(tsClassMember.name) || isPrivateIdentifier(tsClassMember.name)) 346 ) { 347 continue; 348 } 349 if ( 350 isIdentifier(tsCurrentMember.name) && 351 isPrivateIdentifier(tsClassMember.name) && 352 tsCurrentMember.name.text === tsClassMember.name.text.substring(1) 353 ) { 354 this.incrementCounters(tsCurrentMember, FaultID.DeclWithDuplicateName); 355 break; 356 } 357 358 if ( 359 isPrivateIdentifier(tsCurrentMember.name) && 360 isIdentifier(tsClassMember.name) && 361 tsCurrentMember.name.text.substring(1) === tsClassMember.name.text 362 ) { 363 this.incrementCounters(tsCurrentMember, FaultID.DeclWithDuplicateName); 364 break; 365 } 366 } 367 } 368 } 369 370 private functionContainsThis(tsNode: Node): boolean { 371 let found = false; 372 373 function visitNode(tsNode: Node): void { 374 // Stop visiting child nodes if finished searching. 375 if (found) return; 376 377 if (tsNode.kind === SyntaxKind.ThisKeyword) { 378 found = true; 379 return; 380 } 381 382 // Visit children nodes. Skip any local declaration that defines 383 // its own scope as it needs to be checked separately. 384 if ( 385 !isClassDeclaration(tsNode) && 386 !isClassExpression(tsNode) && 387 !isModuleDeclaration(tsNode) && 388 !isFunctionDeclaration(tsNode) && 389 !isFunctionExpression(tsNode) 390 ) { 391 tsNode.forEachChild(visitNode); 392 } 393 } 394 395 visitNode(tsNode); 396 397 return found; 398 } 399 400 private isPrototypePropertyAccess(tsPropertyAccess: ts.PropertyAccessExpression, propAccessSym: ts.Symbol | undefined, baseExprSym: ts.Symbol | undefined, baseExprType: ts.Type): boolean { 401 if (!(isIdentifier(tsPropertyAccess.name) && tsPropertyAccess.name.text === "prototype")) { 402 return false; 403 } 404 // #13600: Relax prototype check when expression comes from interop. 405 let curPropAccess: Node = tsPropertyAccess; 406 while (curPropAccess && isPropertyAccessExpression(curPropAccess)) { 407 const baseExprSym = Utils.trueSymbolAtLocation(curPropAccess.expression); 408 if (Utils.isLibrarySymbol(baseExprSym)) { 409 return false; 410 } 411 curPropAccess = curPropAccess.expression; 412 } 413 if (ts.isIdentifier(curPropAccess) && curPropAccess.text !== 'prototype') { 414 const type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(curPropAccess); 415 if (Utils.isAnyType(type)) { 416 return false; 417 } 418 } 419 // Check if property symbol is "Prototype" 420 if (Utils.isPrototypeSymbol(propAccessSym)) return true; 421 422 // Check if symbol of LHS-expression is Class or Function. 423 if (Utils.isTypeSymbol(baseExprSym) || Utils.isFunctionSymbol(baseExprSym)) { 424 return true; 425 } 426 // Check if type of LHS expression Function type or Any type. 427 // The latter check is to cover cases with multiple prototype 428 // chain (as the 'Prototype' property should be 'Any' type): 429 // X.prototype.prototype.prototype = ... 430 const baseExprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode( 431 baseExprType, undefined, NodeBuilderFlags.None 432 ); 433 434 return ((baseExprTypeNode && isFunctionTypeNode(baseExprTypeNode)) || Utils.isAnyType(baseExprType)); 435 } 436 437 private interfaceInheritanceLint(node: Node, heritageClauses: NodeArray<HeritageClause>): void { 438 for (const hClause of heritageClauses) { 439 if (hClause.token !== SyntaxKind.ExtendsKeyword) continue; 440 441 const prop2type = new Map<string, string>(); 442 for (const tsTypeExpr of hClause.types) { 443 const tsExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); 444 if (tsExprType.isClass()) { 445 this.incrementCounters(node, FaultID.InterfaceExtendsClass); 446 } 447 else if (tsExprType.isClassOrInterface()) { 448 this.lintForInterfaceExtendsDifferentPorpertyTypes(node, tsExprType, prop2type); 449 } 450 } 451 } 452 } 453 454 private lintForInterfaceExtendsDifferentPorpertyTypes( 455 node: Node, tsExprType: Type, prop2type: Map</*string,*/ string> 456 ): void { 457 const props = tsExprType.getProperties(); 458 for (const p of props) { 459 if (!p.declarations) continue; 460 461 const decl: Declaration = p.declarations[0]; 462 if (decl.kind === SyntaxKind.MethodSignature) { 463 this.countInterfaceExtendsDifferentPropertyTypes( 464 node, prop2type, p.name, (decl as MethodSignature).type 465 ); 466 } 467 else if (decl.kind === SyntaxKind.MethodDeclaration) { 468 this.countInterfaceExtendsDifferentPropertyTypes( 469 node, prop2type, p.name, (decl as MethodDeclaration).type 470 ); 471 } 472 else if (decl.kind === SyntaxKind.PropertyDeclaration) { 473 this.countInterfaceExtendsDifferentPropertyTypes( 474 node, prop2type, p.name, (decl as PropertyDeclaration).type 475 ); 476 } 477 else if (decl.kind === SyntaxKind.PropertySignature) { 478 this.countInterfaceExtendsDifferentPropertyTypes( 479 node, prop2type, p.name, (decl as PropertySignature).type 480 ); 481 } 482 } 483 } 484 485 private handleObjectLiteralExpression(node: Node): void { 486 const objectLiteralExpr = node as ObjectLiteralExpression; 487 488 // If object literal is a part of destructuring assignment, then don't process it further. 489 if (Utils.isDestructuringAssignmentLHS(objectLiteralExpr)) { 490 return; 491 } 492 const objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(objectLiteralExpr); 493 if (!Utils.isStructObjectInitializer(objectLiteralExpr) && 494 !Utils.isDynamicLiteralInitializer(objectLiteralExpr) && 495 !Utils.isExpressionAssignableToType(objectLiteralType, objectLiteralExpr) 496 // !Utils.validateObjectLiteralType(objectLiteralType) || Utils.hasMemberFunction(objectLiteralExpr) || 497 // !Utils.validateFields(objectLiteralType, objectLiteralExpr) 498 ) { 499 this.incrementCounters(node, FaultID.ObjectLiteralNoContextType); 500 } 501 } 502 503 private handleArrayLiteralExpression(node: Node): void { 504 // If array literal is a part of destructuring assignment, then 505 // don't process it further. 506 if (Utils.isDestructuringAssignmentLHS(node as ArrayLiteralExpression)) return; 507 508 const arrayLitNode = node as ArrayLiteralExpression; 509 let noContextTypeForArrayLiteral = false; 510 511 // check that array literal consists of inferrable types 512 // e.g. there is no element which is untyped object literals 513 const arrayLitElements = arrayLitNode.elements; 514 for(const element of arrayLitElements) { 515 if(element.kind === SyntaxKind.ObjectLiteralExpression) { 516 const objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(element); 517 if (!Utils.isDynamicLiteralInitializer(arrayLitNode) && 518 !Utils.isExpressionAssignableToType(objectLiteralType, element)) { 519 noContextTypeForArrayLiteral = true; 520 break; 521 } 522 } 523 } 524 525 if (noContextTypeForArrayLiteral) { 526 this.incrementCounters(node, FaultID.ArrayLiteralNoContextType); 527 } 528 } 529 530 private handleParameter(node: Node): void { 531 const tsParam = node as ParameterDeclaration; 532 if (isArrayBindingPattern(tsParam.name) || isObjectBindingPattern(tsParam.name)) { 533 this.incrementCounters(node, FaultID.DestructuringParameter); 534 } 535 const tsParamMods = ts.getModifiers(tsParam); //tsParam.modifiers; 536 if ( 537 tsParamMods && 538 (Utils.hasModifier(tsParamMods, SyntaxKind.PublicKeyword) || 539 Utils.hasModifier(tsParamMods, SyntaxKind.ProtectedKeyword) || 540 Utils.hasModifier(tsParamMods, SyntaxKind.ReadonlyKeyword) || 541 Utils.hasModifier(tsParamMods, SyntaxKind.PrivateKeyword)) 542 ) { 543 this.incrementCounters(node, FaultID.ParameterProperties); 544 } 545 this.handleDecorators(ts.getDecorators(tsParam)); 546 547 this.handleDeclarationInferredType(tsParam); 548 } 549 550 private handleEnumDeclaration(node: Node): void { 551 const enumNode = node as EnumDeclaration; 552 this.countDeclarationsWithDuplicateName(enumNode.name, enumNode); 553 554 const enumSymbol = Utils.trueSymbolAtLocation(enumNode.name); 555 if (!enumSymbol) return; 556 557 const enumDecls = enumSymbol.getDeclarations(); 558 if (!enumDecls) return; 559 560 // Since type checker merges all declarations with the same name 561 // into one symbol, we need to check that there's more than one 562 // enum declaration related to that specific symbol. 563 // See 'countDeclarationsWithDuplicateName' method for details. 564 let enumDeclCount = 0; 565 for (const decl of enumDecls) { 566 if (decl.kind === SyntaxKind.EnumDeclaration) enumDeclCount++; 567 } 568 569 if (enumDeclCount > 1) this.incrementCounters(node, FaultID.EnumMerging); 570 } 571 572 private handleInterfaceDeclaration(node: Node): void { 573 const interfaceNode = node as InterfaceDeclaration; 574 const iSymbol = Utils.trueSymbolAtLocation(interfaceNode.name); 575 const iDecls = iSymbol ? iSymbol.getDeclarations() : null; 576 if (iDecls) { 577 // Since type checker merges all declarations with the same name 578 // into one symbol, we need to check that there's more than one 579 // interface declaration related to that specific symbol. 580 // See 'countDeclarationsWithDuplicateName' method for details. 581 let iDeclCount = 0; 582 for (const decl of iDecls) { 583 if (decl.kind === SyntaxKind.InterfaceDeclaration) iDeclCount++; 584 } 585 586 if (iDeclCount > 1) this.incrementCounters(node, FaultID.InterfaceMerging); 587 } 588 589 if (interfaceNode.heritageClauses) this.interfaceInheritanceLint(node, interfaceNode.heritageClauses); 590 591 this.countDeclarationsWithDuplicateName(interfaceNode.name, interfaceNode); 592 } 593 594 private handleThrowStatement(node: Node): void { 595 const throwStmt = node as ThrowStatement; 596 const throwExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(throwStmt.expression); 597 if (!throwExprType.isClassOrInterface() || !Utils.isDerivedFrom(throwExprType, Utils.CheckType.Error)) { 598 this.incrementCounters(node, FaultID.ThrowStatement); 599 } 600 } 601 602 private handleForStatement(node: Node): void { 603 const tsForStmt = node as ForStatement; 604 const tsForInit = tsForStmt.initializer; 605 if (tsForInit && (isArrayLiteralExpression(tsForInit) || isObjectLiteralExpression(tsForInit))) { 606 this.incrementCounters(tsForInit, FaultID.DestructuringAssignment); 607 } 608 } 609 610 private handleForInStatement(node: Node): void { 611 const tsForInStmt = node as ForInStatement; 612 const tsForInInit = tsForInStmt.initializer; 613 if (isArrayLiteralExpression(tsForInInit) || isObjectLiteralExpression(tsForInInit)) { 614 this.incrementCounters(tsForInInit, FaultID.DestructuringAssignment); 615 } 616 this.incrementCounters(node, FaultID.ForInStatement); 617 } 618 619 private handleForOfStatement(node: Node): void { 620 const tsForOfStmt = node as ForOfStatement; 621 const tsForOfInit = tsForOfStmt.initializer; 622 if (isArrayLiteralExpression(tsForOfInit) || isObjectLiteralExpression(tsForOfInit)) { 623 this.incrementCounters(tsForOfInit, FaultID.DestructuringAssignment); 624 } 625 } 626 627 private handleImportDeclaration(node: Node): void { 628 const importDeclNode = node as ImportDeclaration; 629 for (const stmt of importDeclNode.parent.statements) { 630 if (stmt === importDeclNode) { 631 break; 632 } 633 if (!isImportDeclaration(stmt)) { 634 this.incrementCounters(node, FaultID.ImportAfterStatement); 635 break; 636 } 637 } 638 const expr1 = importDeclNode.moduleSpecifier; 639 if (expr1.kind === SyntaxKind.StringLiteral) { 640 if (!importDeclNode.importClause) this.incrementCounters(node, FaultID.ImportFromPath); 641 } 642 } 643 644 private handlePropertyAccessExpression(node: Node): void { 645 if (ts.isCallExpression(node.parent) && node == node.parent.expression) { 646 return; 647 } 648 649 const propertyAccessNode = node as PropertyAccessExpression; 650 const exprSym = Utils.trueSymbolAtLocation(propertyAccessNode); 651 const baseExprSym = Utils.trueSymbolAtLocation(propertyAccessNode.expression); 652 const baseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(propertyAccessNode.expression); 653 if (this.isPrototypePropertyAccess(propertyAccessNode, exprSym, baseExprSym, baseExprType)) { 654 this.incrementCounters(propertyAccessNode.name, FaultID.Prototype); 655 } 656 if (!!exprSym && Utils.isSymbolAPI(exprSym) && !Utils.ALLOWED_STD_SYMBOL_API.includes(exprSym.getName())) { 657 this.incrementCounters(propertyAccessNode, FaultID.SymbolType); 658 } 659 if (!!baseExprSym && Utils.symbolHasEsObjectType(baseExprSym)) { 660 this.incrementCounters(propertyAccessNode, FaultID.EsObjectType); 661 } 662 } 663 664 private handlePropertyAssignmentOrDeclaration(node: Node) { 665 const propName = (node as PropertyAssignment | PropertyDeclaration).name; 666 667 if (propName && (propName.kind === SyntaxKind.NumericLiteral || propName.kind === SyntaxKind.StringLiteral)) { 668 // We can use literals as property names only when creating Record or any interop instances. 669 let isRecordObjectInitializer = false; 670 let isLibraryType = false; 671 let isDynamic = false; 672 if (isPropertyAssignment(node)) { 673 let objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(node.parent); 674 if (objectLiteralType) { 675 isRecordObjectInitializer = Utils.isStdRecordType(objectLiteralType); 676 isLibraryType = Utils.isLibraryType(objectLiteralType); 677 } 678 isDynamic = isLibraryType || Utils.isDynamicLiteralInitializer(node.parent); 679 } 680 681 if (!isRecordObjectInitializer && !isDynamic) { 682 let autofix: Autofix[] | undefined = Autofixer.fixLiteralAsPropertyName(node); 683 const autofixable = autofix !== undefined; 684 if (!Autofixer.shouldAutofix(node, FaultID.LiteralAsPropertyName)) { 685 autofix = undefined; 686 } 687 this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofixable, autofix); 688 } 689 } 690 691 if (isPropertyDeclaration(node)) { 692 const decorators = ts.getDecorators(node); 693 this.handleDecorators(decorators); 694 this.filterOutDecoratorsDiagnostics( 695 decorators, 696 Utils.NON_INITIALIZABLE_PROPERTY_DECORATORS, 697 { begin: propName.getStart(), end: propName.getStart() }, 698 Utils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE 699 ); 700 const classDecorators = ts.getDecorators(node.parent); 701 const propType = (node as ts.PropertyDeclaration).type?.getText(); 702 this.filterOutDecoratorsDiagnostics(classDecorators, Utils.NON_INITIALIZABLE_PROPERTY_ClASS_DECORATORS, 703 {begin: propName.getStart(), end: propName.getStart()}, Utils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE, propType); 704 this.handleDeclarationInferredType(node); 705 this.handleDefiniteAssignmentAssertion(node); 706 } 707 } 708 709 private filterOutDecoratorsDiagnostics(decorators: readonly Decorator[] | undefined, 710 expectedDecorators: readonly string[], 711 range: { begin: number, end: number}, 712 code: number, 713 prop_type?: string) { 714 // Filter out non-initializable property decorators from strict diagnostics. 715 if (this.tscStrictDiagnostics && this.sourceFile) { 716 if (decorators?.some(x => { 717 let decoratorName = ""; 718 if (isIdentifier(x.expression)) { 719 decoratorName = x.expression.text; 720 } 721 else if (isCallExpression(x.expression) && isIdentifier(x.expression.expression)) { 722 decoratorName = x.expression.expression.text; 723 } 724 // special case for property of type CustomDialogController of the @CustomDialog-decorated class 725 if (expectedDecorators.includes(Utils.NON_INITIALIZABLE_PROPERTY_ClASS_DECORATORS[0])) { 726 return expectedDecorators.includes(decoratorName) && prop_type === "CustomDialogController" 727 } 728 //return Utils.NON_INITIALIZABLE_PROPERTY_DECORATORS.includes(decoratorName); 729 return expectedDecorators.includes(decoratorName); 730 })) { 731 const file = normalizePath(this.sourceFile.fileName); 732 const tscDiagnostics = this.tscStrictDiagnostics.get(file); 733 if (tscDiagnostics) { 734 const filteredDiagnostics = tscDiagnostics.filter( 735 (val /*, idx, array */) => { 736 if (val.code !== code) return true; 737 if (val.start === undefined) return true; 738 if (val.start < range.begin) return true; 739 if (val.start > range.end) return true; 740 return false; 741 } 742 ); 743 this.tscStrictDiagnostics.set(file, filteredDiagnostics); 744 } 745 } 746 } 747 } 748 749 private checkInRange(rangesToFilter: { begin: number, end: number }[], pos: number): boolean { 750 for (let i = 0; i < rangesToFilter.length; i++) { 751 if (pos >= rangesToFilter[i].begin && pos < rangesToFilter[i].end) { 752 return false; 753 } 754 } 755 return true; 756 } 757 758 private filterStrictDiagnostics(filters: { [code: number]: (pos: number) => boolean }, 759 diagnosticChecker: DiagnosticChecker): boolean { 760 if (!this.tscStrictDiagnostics || !this.sourceFile) { 761 return false; 762 } 763 const file = normalizePath(this.sourceFile.fileName); 764 const tscDiagnostics = this.tscStrictDiagnostics.get(file); 765 if (!tscDiagnostics) { 766 return false; 767 } 768 769 const checkDiagnostic = (val: Diagnostic) => { 770 const checkInRange = filters[val.code]; 771 if (!checkInRange) { 772 return true; 773 } 774 if (val.start === undefined || checkInRange(val.start)) { 775 return true; 776 } 777 return diagnosticChecker.checkDiagnosticMessage(val.messageText); 778 }; 779 780 if (tscDiagnostics.every(checkDiagnostic)) { 781 return false; 782 } 783 this.tscStrictDiagnostics.set(file, tscDiagnostics.filter(checkDiagnostic)); 784 return true; 785 } 786 787 private handleFunctionExpression(node: Node): void { 788 const funcExpr = node as FunctionExpression; 789 const isGenerator = funcExpr.asteriskToken !== undefined; 790 const containsThis = this.functionContainsThis(funcExpr.body); 791 const hasValidContext = Utils.hasPredecessor(funcExpr, isClassLike) || 792 Utils.hasPredecessor(funcExpr, isInterfaceDeclaration); 793 const isGeneric = funcExpr.typeParameters !== undefined && funcExpr.typeParameters.length > 0; 794 const [hasUnfixableReturnType, newRetTypeNode] = this.handleMissingReturnType(funcExpr); 795 const autofixable = !isGeneric && !isGenerator && !containsThis && !hasUnfixableReturnType; 796 797 let autofix: Autofix[] | undefined; 798 if (autofixable && Autofixer.shouldAutofix(node, FaultID.FunctionExpression)) { 799 autofix = [ Autofixer.fixFunctionExpression(funcExpr, funcExpr.parameters, newRetTypeNode) ]; 800 } 801 802 this.incrementCounters(node, FaultID.FunctionExpression, autofixable, autofix); 803 if (isGeneric) { 804 this.incrementCounters(funcExpr, FaultID.LambdaWithTypeParameters); 805 } 806 if (isGenerator) { 807 this.incrementCounters(funcExpr, FaultID.GeneratorFunction); 808 } 809 if (containsThis && !hasValidContext) { 810 this.incrementCounters(funcExpr, FaultID.FunctionContainsThis); 811 } 812 if (hasUnfixableReturnType) { 813 this.incrementCounters(funcExpr, FaultID.LimitedReturnTypeInference); 814 } 815 } 816 817 private handleArrowFunction(node: Node): void { 818 const arrowFunc = node as ArrowFunction; 819 const containsThis = this.functionContainsThis(arrowFunc.body); 820 const hasValidContext = Utils.hasPredecessor(arrowFunc, isClassLike) || 821 Utils.hasPredecessor(arrowFunc, isInterfaceDeclaration); 822 if (containsThis && !hasValidContext) { 823 this.incrementCounters(arrowFunc, FaultID.FunctionContainsThis); 824 } 825 826 const contextType = TypeScriptLinter.tsTypeChecker.getContextualType(arrowFunc); 827 if (!(contextType && Utils.isLibraryType(contextType))) { 828 if (!arrowFunc.type) { 829 this.handleMissingReturnType(arrowFunc); 830 } 831 832 if (arrowFunc.typeParameters && arrowFunc.typeParameters.length > 0) { 833 this.incrementCounters(node, FaultID.LambdaWithTypeParameters); 834 } 835 } 836 } 837 838 private handleClassExpression(node: Node): void { 839 //let tsClassExpr = node as ClassExpression; 840 this.incrementCounters(node, FaultID.ClassExpression); 841 //this.handleDecorators(tsClassExpr.decorators); 842 } 843 844 private handleFunctionDeclaration(node: Node) { 845 const tsFunctionDeclaration = node as FunctionDeclaration; 846 if (!tsFunctionDeclaration.type) this.handleMissingReturnType(tsFunctionDeclaration); 847 if (tsFunctionDeclaration.name) { 848 this.countDeclarationsWithDuplicateName(tsFunctionDeclaration.name, tsFunctionDeclaration); 849 } 850 if (tsFunctionDeclaration.body && this.functionContainsThis(tsFunctionDeclaration.body)) { 851 this.incrementCounters(node, FaultID.FunctionContainsThis); 852 } 853 if (!isSourceFile(tsFunctionDeclaration.parent) && !isModuleBlock(tsFunctionDeclaration.parent)) { 854 this.incrementCounters(tsFunctionDeclaration, FaultID.LocalFunction); 855 } 856 if (tsFunctionDeclaration.asteriskToken) this.incrementCounters(node, FaultID.GeneratorFunction); 857 } 858 859 private handleMissingReturnType(funcLikeDecl: FunctionLikeDeclaration): [boolean, TypeNode | undefined] { 860 // if (funcLikeDecl.type) return [false, funcLikeDecl.type]; 861 862 // Note: Return type can't be inferred for function without body. 863 if (!funcLikeDecl.body) return [false, undefined]; 864 865 let autofixable = false; 866 let autofix: Autofix[] | undefined; 867 let newRetTypeNode: TypeNode | undefined; 868 const isFuncExpr = isFunctionExpression(funcLikeDecl); 869 870 // Currently, ArkTS can't infer return type of function, when expression 871 // in the return statement is a call to a function or method whose return 872 // value type is omitted. In that case, we attempt to prepare an autofix. 873 let hasLimitedRetTypeInference = this.hasLimitedTypeInferenceFromReturnExpr(funcLikeDecl.body); 874 875 const tsSignature = TypeScriptLinter.tsTypeChecker.getSignatureFromDeclaration(funcLikeDecl); 876 if (tsSignature) { 877 const tsRetType = TypeScriptLinter.tsTypeChecker.getReturnTypeOfSignature(tsSignature); 878 879 if (!tsRetType || Utils.isUnsupportedType(tsRetType)) { 880 hasLimitedRetTypeInference = true; 881 } 882 else if (hasLimitedRetTypeInference) { 883 newRetTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(tsRetType, funcLikeDecl, NodeBuilderFlags.None); 884 autofixable = !!newRetTypeNode; 885 886 if (!isFuncExpr && newRetTypeNode && Autofixer.shouldAutofix(funcLikeDecl, FaultID.LimitedReturnTypeInference)) { 887 autofix = [Autofixer.fixReturnType(funcLikeDecl, newRetTypeNode)]; 888 } 889 } 890 } 891 892 // Don't report here if in function expression context. 893 // See handleFunctionExpression for details. 894 if (hasLimitedRetTypeInference && !isFuncExpr) { 895 this.incrementCounters(funcLikeDecl, FaultID.LimitedReturnTypeInference, autofixable, autofix); 896 } 897 return [hasLimitedRetTypeInference && !newRetTypeNode, newRetTypeNode]; 898 } 899 900 private hasLimitedTypeInferenceFromReturnExpr(funBody: ConciseBody): boolean { 901 let hasLimitedTypeInference = false; 902 function visitNode(tsNode: Node): void { 903 if (hasLimitedTypeInference) { 904 return; 905 } 906 907 if ( 908 isReturnStatement(tsNode) && tsNode.expression && 909 Utils.isCallToFunctionWithOmittedReturnType(Utils.unwrapParenthesized(tsNode.expression)) 910 ) { 911 hasLimitedTypeInference = true; 912 return; 913 } 914 915 // Visit children nodes. Don't traverse other nested function-like declarations. 916 if ( 917 !isFunctionDeclaration(tsNode) && 918 !isFunctionExpression(tsNode) && 919 !isMethodDeclaration(tsNode) && 920 !isAccessor(tsNode) && 921 !isArrowFunction(tsNode) 922 ) { 923 tsNode.forEachChild(visitNode); 924 } 925 } 926 927 if (isBlock(funBody)) { 928 visitNode(funBody); 929 } 930 else { 931 const tsExpr = Utils.unwrapParenthesized(funBody); 932 hasLimitedTypeInference = Utils.isCallToFunctionWithOmittedReturnType(tsExpr); 933 } 934 935 return hasLimitedTypeInference; 936 } 937 938 private handlePrefixUnaryExpression(node: Node) { 939 const tsUnaryArithm = node as PrefixUnaryExpression; 940 const tsUnaryOp = tsUnaryArithm.operator; 941 if ( 942 tsUnaryOp === SyntaxKind.PlusToken || 943 tsUnaryOp === SyntaxKind.MinusToken || 944 tsUnaryOp === SyntaxKind.TildeToken 945 ) { 946 const tsOperatndType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsUnaryArithm.operand); 947 if (!(tsOperatndType.getFlags() & (TypeFlags.NumberLike | TypeFlags.BigIntLiteral)) || 948 (tsUnaryOp === SyntaxKind.TildeToken && tsUnaryArithm.operand.kind === SyntaxKind.NumericLiteral && 949 !Utils.isIntegerConstantValue(tsUnaryArithm.operand as NumericLiteral)) 950 ) { 951 this.incrementCounters(node, FaultID.UnaryArithmNotNumber); 952 } 953 } 954 } 955 956 private handleBinaryExpression(node: Node): void { 957 const tsBinaryExpr = node as BinaryExpression; 958 const tsLhsExpr = tsBinaryExpr.left; 959 const tsRhsExpr = tsBinaryExpr.right; 960 961 if (Utils.isAssignmentOperator(tsBinaryExpr.operatorToken)) { 962 if (isObjectLiteralExpression(tsLhsExpr) || isArrayLiteralExpression(tsLhsExpr)) { 963 this.incrementCounters(node, FaultID.DestructuringAssignment); 964 } 965 if (isPropertyAccessExpression(tsLhsExpr)) { 966 const tsLhsSymbol = Utils.trueSymbolAtLocation(tsLhsExpr); 967 const tsLhsBaseSymbol = Utils.trueSymbolAtLocation(tsLhsExpr.expression); 968 if (tsLhsSymbol && (tsLhsSymbol.flags & SymbolFlags.Method)) { 969 this.incrementCounters(tsLhsExpr, FaultID.NoUndefinedPropAccess); 970 } 971 if ( 972 Utils.isMethodAssignment(tsLhsSymbol) && tsLhsBaseSymbol && 973 (tsLhsBaseSymbol.flags & SymbolFlags.Function) !== 0 974 ) { 975 this.incrementCounters(tsLhsExpr, FaultID.PropertyDeclOnFunction); 976 } 977 } 978 } 979 980 const leftOperandType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsLhsExpr); 981 const rightOperandType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsRhsExpr); 982 983 if (tsBinaryExpr.operatorToken.kind === SyntaxKind.PlusToken) { 984 if (Utils.isEnumMemberType(leftOperandType) && Utils.isEnumMemberType(rightOperandType)) { 985 if ( 986 ((leftOperandType.flags & (TypeFlags.NumberLike)) && (rightOperandType.getFlags() & (TypeFlags.NumberLike))) || 987 ((leftOperandType.flags & (TypeFlags.StringLike)) && (rightOperandType.getFlags() & (TypeFlags.StringLike))) 988 ) { 989 return; 990 } 991 } 992 else if (Utils.isNumberType(leftOperandType) && Utils.isNumberType(rightOperandType)) { 993 return; 994 } 995 else if (Utils.isStringLikeType(leftOperandType) || Utils.isStringLikeType(rightOperandType)) { 996 return; 997 } 998 } 999 else if ( 1000 tsBinaryExpr.operatorToken.kind === SyntaxKind.AmpersandToken || 1001 tsBinaryExpr.operatorToken.kind === SyntaxKind.BarToken || 1002 tsBinaryExpr.operatorToken.kind === SyntaxKind.CaretToken || 1003 tsBinaryExpr.operatorToken.kind === SyntaxKind.LessThanLessThanToken || 1004 tsBinaryExpr.operatorToken.kind === SyntaxKind.GreaterThanGreaterThanToken || 1005 tsBinaryExpr.operatorToken.kind === SyntaxKind.GreaterThanGreaterThanGreaterThanToken 1006 ) { 1007 if (!(Utils.isNumberType(leftOperandType) && Utils.isNumberType(rightOperandType))|| 1008 (tsLhsExpr.kind === SyntaxKind.NumericLiteral && !Utils.isIntegerConstantValue(tsLhsExpr as NumericLiteral)) || 1009 (tsRhsExpr.kind === SyntaxKind.NumericLiteral && !Utils.isIntegerConstantValue(tsRhsExpr as NumericLiteral)) 1010 ) { 1011 return; //this.incrementCounters(node, FaultID.BitOpWithWrongType); 1012 } 1013 } 1014 else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.CommaToken) { 1015 // CommaOpertor is allowed in 'for' statement initalizer and incrementor 1016 let tsExprNode: Node = tsBinaryExpr; 1017 let tsParentNode = tsExprNode.parent; 1018 while (tsParentNode && tsParentNode.kind === SyntaxKind.BinaryExpression) { 1019 tsExprNode = tsParentNode; 1020 tsParentNode = tsExprNode.parent; 1021 } 1022 1023 if (tsParentNode && tsParentNode.kind === SyntaxKind.ForStatement) { 1024 const tsForNode = tsParentNode as ForStatement; 1025 if (tsExprNode === tsForNode.initializer || tsExprNode === tsForNode.incrementor) return; 1026 } 1027 this.incrementCounters(node, FaultID.CommaOperator); 1028 } 1029 else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.InstanceOfKeyword) { 1030 const leftExpr = Utils.unwrapParenthesized(tsBinaryExpr.left); 1031 const leftSymbol = Utils.trueSymbolAtLocation(leftExpr); 1032 // In STS, the left-hand side expression may be of any reference type, otherwise 1033 // a compile-time error occurs. In addition, the left operand in STS cannot be a type. 1034 if (tsLhsExpr.kind === SyntaxKind.ThisKeyword) { 1035 return; 1036 } 1037 if (Utils.isPrimitiveType(leftOperandType) || isTypeNode(leftExpr) || Utils.isTypeSymbol(leftSymbol)) { 1038 this.incrementCounters(node, FaultID.InstanceofUnsupported); 1039 } 1040 } 1041 else if (tsBinaryExpr.operatorToken.kind === SyntaxKind.EqualsToken) { 1042 if (Utils.needToDeduceStructuralIdentity(rightOperandType, leftOperandType)) { 1043 this.incrementCounters(tsBinaryExpr, FaultID.StructuralIdentity); 1044 } 1045 const typeNode = Utils.getVariableDeclarationTypeNode(tsLhsExpr); 1046 this.handleEsObjectAssignment(tsBinaryExpr, typeNode, tsRhsExpr); 1047 } 1048 } 1049 1050 private handleVariableDeclarationList(node: Node): void { 1051 const varDeclFlags = getCombinedNodeFlags(node); 1052 if (!(varDeclFlags & (NodeFlags.Let | NodeFlags.Const))) { 1053 this.incrementCounters(node, FaultID.VarDeclaration); 1054 } 1055 } 1056 1057 private handleVariableDeclaration(node: Node): void { 1058 const tsVarDecl = node as VariableDeclaration; 1059 if (isArrayBindingPattern(tsVarDecl.name) || isObjectBindingPattern(tsVarDecl.name)) { 1060 this.incrementCounters(node, FaultID.DestructuringDeclaration); 1061 } 1062 { 1063 // Check variable declaration for duplicate name. 1064 const visitBindingPatternNames = (tsBindingName: BindingName): void => { 1065 if (isIdentifier(tsBindingName)) { 1066 // The syntax kind of the declaration is defined here by the parent of 'BindingName' node. 1067 this.countDeclarationsWithDuplicateName(tsBindingName, tsBindingName, tsBindingName.parent.kind); 1068 } 1069 else { 1070 for (const tsBindingElem of tsBindingName.elements) { 1071 if (isOmittedExpression(tsBindingElem)) continue; 1072 1073 visitBindingPatternNames(tsBindingElem.name); 1074 } 1075 } 1076 }; 1077 1078 visitBindingPatternNames(tsVarDecl.name); 1079 } 1080 1081 if (tsVarDecl.type && tsVarDecl.initializer) { 1082 const tsVarInit = tsVarDecl.initializer; 1083 const tsVarType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsVarDecl.type); 1084 const tsInitType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsVarInit); 1085 if (Utils.needToDeduceStructuralIdentity(tsInitType, tsVarType)) { 1086 this.incrementCounters(tsVarDecl, FaultID.StructuralIdentity); 1087 } 1088 } 1089 1090 this.handleEsObjectDelaration(tsVarDecl); 1091 this.handleDeclarationInferredType(tsVarDecl); 1092 this.handleDefiniteAssignmentAssertion(tsVarDecl); 1093 } 1094 1095 private handleEsObjectDelaration(node: ts.VariableDeclaration) { 1096 const isDeclaredESObject = !!node.type && Utils.isEsObjectType(node.type); 1097 const initalizerTypeNode = node.initializer && Utils.getVariableDeclarationTypeNode(node.initializer); 1098 const isInitializedWithESObject = !!initalizerTypeNode && Utils.isEsObjectType(initalizerTypeNode); 1099 const isLocal = Utils.isInsideBlock(node) 1100 if ((isDeclaredESObject || isInitializedWithESObject) && !isLocal) { 1101 this.incrementCounters(node, FaultID.EsObjectType); 1102 return; 1103 } 1104 1105 if (node.initializer) { 1106 this.handleEsObjectAssignment(node, node.type, node.initializer); 1107 return; 1108 } 1109 } 1110 1111 private handleEsObjectAssignment(node: ts.Node, nodeDeclType: ts.TypeNode | undefined, initializer: ts.Node) { 1112 const isTypeAnnotated = !!nodeDeclType; 1113 const isDeclaredESObject = !!nodeDeclType && Utils.isEsObjectType(nodeDeclType); 1114 const initalizerTypeNode = Utils.getVariableDeclarationTypeNode(initializer); 1115 const isInitializedWithESObject = !!initalizerTypeNode && Utils.isEsObjectType(initalizerTypeNode); 1116 if (isTypeAnnotated && !isDeclaredESObject && isInitializedWithESObject) { 1117 this.incrementCounters(node, FaultID.EsObjectType); 1118 return; 1119 } 1120 1121 if (isDeclaredESObject && !Utils.isValueAssignableToESObject(initializer)) { 1122 this.incrementCounters(node, FaultID.EsObjectType); 1123 } 1124 } 1125 1126 1127 private handleCatchClause(node: Node): void { 1128 const tsCatch = node as CatchClause; 1129 // In TS catch clause doesn't permit specification of the exception varible type except 'any' or 'unknown'. 1130 // It is not compatible with STS 'catch' where the exception varilab has to be of type 1131 // 'Exception' or derived from it. 1132 // So each 'catch' which has explicite type for the exception object goes to problems in strict mode. 1133 if (tsCatch.variableDeclaration && tsCatch.variableDeclaration.type) { 1134 this.incrementCounters(node, FaultID.CatchWithUnsupportedType); 1135 } 1136 } 1137 1138 private handleClassDeclaration(node: Node): void { 1139 const tsClassDecl = node as ClassDeclaration; 1140 this.staticBlocks.clear(); 1141 1142 if (tsClassDecl.name) { 1143 this.countDeclarationsWithDuplicateName(tsClassDecl.name, tsClassDecl); 1144 } 1145 this.countClassMembersWithDuplicateName(tsClassDecl); 1146 1147 const visitHClause = (hClause: HeritageClause) => { 1148 for (const tsTypeExpr of hClause.types) { 1149 const tsExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); 1150 if (tsExprType.isClass() && hClause.token === SyntaxKind.ImplementsKeyword) { 1151 this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); 1152 } 1153 } 1154 }; 1155 1156 if (tsClassDecl.heritageClauses) { 1157 for (const hClause of tsClassDecl.heritageClauses) { 1158 if (!hClause) { 1159 continue; 1160 } 1161 visitHClause(hClause); 1162 } 1163 } 1164 1165 this.handleDecorators(ts.getDecorators(tsClassDecl)); 1166 1167 if (!this.skipArkTSStaticBlocksCheck) { 1168 let hasStaticBlock = false; 1169 for (const element of tsClassDecl.members) { 1170 if (ts.isClassStaticBlockDeclaration(element)) { 1171 if (hasStaticBlock) { 1172 this.incrementCounters(element, FaultID.MultipleStaticBlocks); 1173 } else { 1174 hasStaticBlock = true; 1175 } 1176 } 1177 } 1178 } 1179 } 1180 1181 private handleModuleDeclaration(node: Node): void { 1182 const tsModuleDecl = node as ModuleDeclaration; 1183 this.countDeclarationsWithDuplicateName(tsModuleDecl.name, tsModuleDecl); 1184 1185 const tsModuleBody = tsModuleDecl.body; 1186 const tsModifiers = tsModuleDecl.modifiers; // TSC 4.2 doesn't have 'getModifiers()' method 1187 if (tsModuleBody) { 1188 if (isModuleBlock(tsModuleBody)) { 1189 for (const tsModuleStmt of tsModuleBody.statements) { 1190 switch (tsModuleStmt.kind) { 1191 case SyntaxKind.VariableStatement: 1192 case SyntaxKind.FunctionDeclaration: 1193 case SyntaxKind.ClassDeclaration: 1194 case SyntaxKind.InterfaceDeclaration: 1195 case SyntaxKind.TypeAliasDeclaration: 1196 case SyntaxKind.EnumDeclaration: 1197 case SyntaxKind.ExportDeclaration: 1198 break; 1199 // Nested namespace declarations are prohibited 1200 // but there is no cookbook recipe for it! 1201 case SyntaxKind.ModuleDeclaration: 1202 break; 1203 default: 1204 this.incrementCounters(tsModuleStmt, FaultID.NonDeclarationInNamespace); 1205 break; 1206 } 1207 } 1208 } 1209 } 1210 1211 if (!(tsModuleDecl.flags & NodeFlags.Namespace) && 1212 Utils.hasModifier(tsModifiers, SyntaxKind.DeclareKeyword)) { 1213 this.incrementCounters(tsModuleDecl, FaultID.ShorthandAmbientModuleDecl); 1214 } 1215 1216 if (isStringLiteral(tsModuleDecl.name) && tsModuleDecl.name.text.includes("*")) { 1217 this.incrementCounters(tsModuleDecl, FaultID.WildcardsInModuleName); 1218 } 1219 } 1220 1221 private handleTypeAliasDeclaration(node: Node): void { 1222 const tsTypeAlias = node as TypeAliasDeclaration; 1223 this.countDeclarationsWithDuplicateName(tsTypeAlias.name, tsTypeAlias); 1224 } 1225 1226 private handleImportClause(node: Node): void { 1227 const tsImportClause = node as ImportClause; 1228 if (tsImportClause.name) { 1229 this.countDeclarationsWithDuplicateName(tsImportClause.name, tsImportClause); 1230 } 1231 1232 if (tsImportClause.namedBindings && isNamedImports(tsImportClause.namedBindings)) { 1233 const nonDefaultSpecs: ImportSpecifier[] = []; 1234 let defaultSpec: ImportSpecifier | undefined; 1235 for (const importSpec of tsImportClause.namedBindings.elements) { 1236 if (Utils.isDefaultImport(importSpec)) defaultSpec = importSpec; 1237 else nonDefaultSpecs.push(importSpec); 1238 } 1239 if (defaultSpec) { 1240 let autofix: Autofix[] | undefined; 1241 // if (Autofixer.shouldAutofix(defaultSpec, FaultID.DefaultImport)) 1242 // autofix = [ Autofixer.fixDefaultImport(tsImportClause, defaultSpec, nonDefaultSpecs) ]; 1243 this.incrementCounters(defaultSpec, FaultID.DefaultImport, true, autofix); 1244 } 1245 } 1246 } 1247 1248 private handleImportSpecifier(node: Node): void { 1249 const tsImportSpecifier = node as ImportSpecifier; 1250 this.countDeclarationsWithDuplicateName(tsImportSpecifier.name, tsImportSpecifier); 1251 } 1252 1253 private handleNamespaceImport(node: Node): void { 1254 const tsNamespaceImport = node as NamespaceImport; 1255 this.countDeclarationsWithDuplicateName(tsNamespaceImport.name, tsNamespaceImport); 1256 } 1257 1258 private handleTypeAssertionExpression(node: Node): void { 1259 const tsTypeAssertion = node as TypeAssertion; 1260 if (tsTypeAssertion.type.getText() === "const") { 1261 this.incrementCounters(tsTypeAssertion, FaultID.ConstAssertion); 1262 } 1263 else { 1264 this.incrementCounters(node, FaultID.TypeAssertion); 1265 } 1266 } 1267 1268 private handleMethodDeclaration(node: Node): void { 1269 const tsMethodDecl = node as MethodDeclaration; 1270 const hasThis = this.functionContainsThis(tsMethodDecl); 1271 let isStatic = false; 1272 if (tsMethodDecl.modifiers) { 1273 for (const mod of tsMethodDecl.modifiers) { 1274 if (mod.kind === SyntaxKind.StaticKeyword) { 1275 isStatic = true; 1276 break; 1277 } 1278 } 1279 } 1280 1281 if (isStatic && hasThis) { 1282 this.incrementCounters(node, FaultID.FunctionContainsThis); 1283 } 1284 if (!tsMethodDecl.type) { 1285 this.handleMissingReturnType(tsMethodDecl); 1286 } 1287 if (tsMethodDecl.asteriskToken) { 1288 this.incrementCounters(node, FaultID.GeneratorFunction); 1289 } 1290 this.handleDecorators(ts.getDecorators(tsMethodDecl)); 1291 this.filterOutDecoratorsDiagnostics(ts.getDecorators(tsMethodDecl), Utils.NON_RETURN_FUNCTION_DECORATORS, 1292 { begin: tsMethodDecl.parameters.end, end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end }, 1293 Utils.FUNCTION_HAS_NO_RETURN_ERROR_CODE); 1294 } 1295 1296 private handleIdentifier(node: Node): void { 1297 const tsIdentifier = node as Identifier; 1298 const tsIdentSym = Utils.trueSymbolAtLocation(tsIdentifier); 1299 1300 if (!tsIdentSym) { 1301 return; 1302 } 1303 1304 if ( 1305 (tsIdentSym.flags & ts.SymbolFlags.Module) !== 0 && 1306 (tsIdentSym.flags & ts.SymbolFlags.Transient) !== 0 && 1307 tsIdentifier.text === "globalThis" 1308 ) { 1309 this.incrementCounters(node, FaultID.GlobalThis); 1310 } else { 1311 this.handleRestrictedValues(tsIdentifier, tsIdentSym); 1312 } 1313 } 1314 1315 private isAllowedClassValueContext(tsIdentifier: Identifier /* Param not used ! ??, tsIdentSym: Symbol */): boolean { 1316 let ctx: Node = tsIdentifier; 1317 while (isPropertyAccessExpression(ctx.parent) || isQualifiedName(ctx.parent)) { 1318 ctx = ctx.parent; 1319 } 1320 if (isPropertyAssignment(ctx.parent) && isObjectLiteralExpression(ctx.parent.parent)) { 1321 ctx = ctx.parent.parent; 1322 } 1323 if (isArrowFunction(ctx.parent) && ctx.parent.body === ctx) { 1324 ctx = ctx.parent; 1325 } 1326 1327 if (isCallExpression(ctx.parent) || isNewExpression(ctx.parent)) { 1328 const callee = ctx.parent.expression; 1329 const isAny = Utils.isAnyType(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(callee)); 1330 const isDynamic = isAny || Utils.hasLibraryType(callee); 1331 if (callee !== ctx && isDynamic) { 1332 return true; 1333 } 1334 } 1335 return false; 1336 } 1337 1338 private handleRestrictedValues(tsIdentifier: Identifier, tsIdentSym: Symbol) { 1339 const illegalValues = SymbolFlags.ConstEnum | SymbolFlags.RegularEnum | SymbolFlags.ValueModule | SymbolFlags.Class; 1340 // If module name is duplicated by another declaration, this increases the possibility 1341 // of finding a lot of false positives. Thus, do not check further in that case. 1342 if ((tsIdentSym.flags & SymbolFlags.ValueModule) !== 0) { 1343 if (!!tsIdentSym && Utils.symbolHasDuplicateName(tsIdentSym, SyntaxKind.ModuleDeclaration)) { 1344 return; 1345 } 1346 } 1347 if ((tsIdentSym.flags & illegalValues) === 0 || Utils.isStruct(tsIdentSym) || 1348 !this.identiferUseInValueContext(tsIdentifier, tsIdentSym)) { 1349 return; 1350 } 1351 if ((tsIdentSym.flags & SymbolFlags.Class) !== 0) { 1352 if (this.isAllowedClassValueContext(tsIdentifier /* param not used!?? , tsIdentSym */)) { 1353 return; 1354 } 1355 } 1356 1357 if (tsIdentSym.flags & SymbolFlags.ValueModule) { 1358 this.incrementCounters(tsIdentifier, FaultID.NamespaceAsObject); 1359 } 1360 else { 1361 // missing EnumAsObject 1362 this.incrementCounters(tsIdentifier, FaultID.ClassAsObject); 1363 } 1364 } 1365 1366 private identiferUseInValueContext( 1367 ident: Identifier, 1368 tsSym: Symbol 1369 ) { 1370 // If identifier is the right-most name of Property Access chain or Qualified name, 1371 // or it's a separate identifier expression, then identifier is being referenced as an value. 1372 let qualifiedStart: Node = ident; 1373 while (isPropertyAccessExpression(qualifiedStart.parent) || isQualifiedName(qualifiedStart.parent)) { 1374 qualifiedStart = qualifiedStart.parent; 1375 } 1376 const parent = qualifiedStart.parent; 1377 return !( 1378 // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery) 1379 (isTypeNode(parent) && !isTypeOfExpression(parent)) || 1380 // ElementAccess is allowed for enum types 1381 this.isEnumPropAccess(ident, tsSym, parent) || 1382 isExpressionWithTypeArguments(parent) || 1383 isExportAssignment(parent) || 1384 isExportSpecifier(parent) || 1385 isMetaProperty(parent) || 1386 isImportClause(parent) || 1387 isClassLike(parent) || 1388 isInterfaceDeclaration(parent) || 1389 isModuleDeclaration(parent) || 1390 isEnumDeclaration(parent) || 1391 isNamespaceImport(parent) || 1392 isImportSpecifier(parent) || 1393 isImportEqualsDeclaration(parent) || 1394 (isQualifiedName(qualifiedStart) && ident !== qualifiedStart.right) || 1395 (isPropertyAccessExpression(qualifiedStart) && 1396 ident !== qualifiedStart.name) || 1397 (isNewExpression(qualifiedStart.parent) && 1398 qualifiedStart === qualifiedStart.parent.expression) || 1399 (isBinaryExpression(qualifiedStart.parent) && 1400 qualifiedStart.parent.operatorToken.kind === 1401 SyntaxKind.InstanceOfKeyword) 1402 ); 1403 } 1404 1405 private isEnumPropAccess(ident: ts.Identifier, tsSym: ts.Symbol, context: ts.Node): boolean { 1406 return ts.isElementAccessExpression(context) && !!(tsSym.flags & ts.SymbolFlags.Enum) && 1407 (context.expression == ident || 1408 (ts.isPropertyAccessExpression(context.expression) && context.expression.name == ident)); 1409 } 1410 1411 private handleElementAccessExpression(node: Node): void { 1412 const tsElementAccessExpr = node as ElementAccessExpression; 1413 const tsElemAccessBaseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.expression); 1414 const tsElemAccessBaseExprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(tsElemAccessBaseExprType, undefined, NodeBuilderFlags.None); 1415 const isDerivedFromArray = Utils.isDerivedFrom(tsElemAccessBaseExprType, Utils.CheckType.Array); 1416 const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() && 1417 !Utils.isGenericArrayType(tsElemAccessBaseExprType) && 1418 !isDerivedFromArray; 1419 const checkThisOrSuper = Utils.isThisOrSuperExpr(tsElementAccessExpr.expression) && !isDerivedFromArray; 1420 1421 // if (this.tsUtils.isEnumType(tsElemAccessBaseExprType)) { 1422 // implement argument expression type check 1423 // let argType = this.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.argumentExpression); 1424 // if (argType.aliasSymbol == this.tsUtils.trueSymbolAtLocation(tsElementAccessExpr.expression)) { 1425 // return; 1426 // } 1427 // check if constant EnumMember inferred ... 1428 // this.incrementCounters(node, FaultID.PropertyAccessByIndex, autofixable, autofix); 1429 // } 1430 if ( 1431 !Utils.isLibraryType(tsElemAccessBaseExprType) && !Utils.isTypedArray(tsElemAccessBaseExprTypeNode) && 1432 (checkClassOrInterface || 1433 Utils.isObjectLiteralType(tsElemAccessBaseExprType) || checkThisOrSuper) 1434 ) { 1435 let autofix = Autofixer.fixPropertyAccessByIndex(node); 1436 const autofixable = autofix !== undefined; 1437 if (!Autofixer.shouldAutofix(node, FaultID.PropertyAccessByIndex)) { 1438 autofix = undefined; 1439 } 1440 this.incrementCounters(node, FaultID.PropertyAccessByIndex, autofixable, autofix); 1441 } 1442 if (Utils.hasEsObjectType(tsElementAccessExpr.expression)) { 1443 this.incrementCounters(node, FaultID.EsObjectType); 1444 } 1445 } 1446 1447 private handleEnumMember(node: Node): void { 1448 const tsEnumMember = node as EnumMember; 1449 const tsEnumMemberType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsEnumMember); 1450 const constVal = TypeScriptLinter.tsTypeChecker.getConstantValue(tsEnumMember); 1451 1452 if (tsEnumMember.initializer && !Utils.isValidEnumMemberInit(tsEnumMember.initializer)) { 1453 this.incrementCounters(node, FaultID.EnumMemberNonConstInit); 1454 } 1455 // check for type - all members should be of same type 1456 const enumDecl = tsEnumMember.parent; 1457 const firstEnumMember = enumDecl.members[0]; 1458 const firstEnumMemberType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(firstEnumMember); 1459 const firstElewmVal = TypeScriptLinter.tsTypeChecker.getConstantValue(firstEnumMember); 1460 // each string enum member has its own type 1461 // so check that value type is string 1462 if(constVal !==undefined && typeof constVal === "string" && 1463 firstElewmVal !==undefined && typeof firstElewmVal === "string") { 1464 return; 1465 } 1466 if (constVal !==undefined && typeof constVal === "number" && 1467 firstElewmVal !==undefined && typeof firstElewmVal === "number") { 1468 return; 1469 } 1470 if(firstEnumMemberType !== tsEnumMemberType) { 1471 this.incrementCounters(node, FaultID.EnumMemberNonConstInit); 1472 } 1473 } 1474 1475 private handleExportAssignment(node: Node): void { 1476 // (nsizov): check exportEquals and determine if it's an actual `export assignment` 1477 // or a `default export namespace` when this two cases will be determined in cookbook 1478 const exportAssignment = node as ExportAssignment; 1479 if (exportAssignment.isExportEquals) { 1480 this.incrementCounters(node, FaultID.ExportAssignment); 1481 } 1482 } 1483 1484 private handleCallExpression(node: Node): void { 1485 const tsCallExpr = node as CallExpression; 1486 1487 const calleeSym = Utils.trueSymbolAtLocation(tsCallExpr.expression); 1488 const calleeType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression); 1489 const callSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(tsCallExpr); 1490 1491 this.handleImportCall(tsCallExpr); 1492 this.handleRequireCall(tsCallExpr); 1493 // NOTE: Keep handleFunctionApplyBindPropCall above handleGenericCallWithNoTypeArgs here!!! 1494 if (calleeSym !== undefined) { 1495 this.handleStdlibAPICall(tsCallExpr, calleeSym); 1496 this.handleFunctionApplyBindPropCall(tsCallExpr, calleeSym); 1497 if (Utils.symbolHasEsObjectType(calleeSym)) { 1498 this.incrementCounters(tsCallExpr, FaultID.EsObjectType); 1499 } 1500 } 1501 if (callSignature !== undefined) { 1502 if (!Utils.isLibrarySymbol(calleeSym)) { 1503 this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature); 1504 } 1505 this.handleStructIdentAndUndefinedInArgs(tsCallExpr, callSignature); 1506 } 1507 this.handleLibraryTypeCall(tsCallExpr, calleeType); 1508 1509 if (ts.isPropertyAccessExpression(tsCallExpr.expression) && Utils.hasEsObjectType(tsCallExpr.expression.expression)) { 1510 this.incrementCounters(node, FaultID.EsObjectType); 1511 } 1512 } 1513 1514 private handleImportCall(tsCallExpr: CallExpression): void { 1515 if (tsCallExpr.expression.kind === SyntaxKind.ImportKeyword) { 1516 // relax rule#133 "arkts-no-runtime-import" 1517 // this.incrementCounters(tsCallExpr, FaultID.DynamicImport); 1518 const tsArgs = tsCallExpr.arguments; 1519 if (tsArgs.length > 1 && isObjectLiteralExpression(tsArgs[1])) { 1520 const objLitExpr = tsArgs[1] as ObjectLiteralExpression; 1521 for (const tsProp of objLitExpr.properties) { 1522 if (isPropertyAssignment(tsProp) || isShorthandPropertyAssignment(tsProp)) { 1523 if (tsProp.name.getText() === "assert") { 1524 this.incrementCounters(tsProp, FaultID.ImportAssertion); 1525 break; 1526 } 1527 } 1528 } 1529 } 1530 } 1531 } 1532 1533 private handleRequireCall(tsCallExpr: CallExpression): void { 1534 if ( 1535 isIdentifier(tsCallExpr.expression) && 1536 tsCallExpr.expression.text === "require" && 1537 isVariableDeclaration(tsCallExpr.parent) 1538 ) { 1539 const tsType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression); 1540 if (Utils.isInterfaceType(tsType) && tsType.symbol.name === "NodeRequire") { 1541 this.incrementCounters(tsCallExpr.parent, FaultID.ImportAssignment); 1542 } 1543 } 1544 } 1545 1546 private handleGenericCallWithNoTypeArgs(callLikeExpr: ts.CallExpression | ts.NewExpression, callSignature: ts.Signature) { 1547 1548 const tsSyntaxKind = isNewExpression(callLikeExpr) ? SyntaxKind.Constructor : SyntaxKind.FunctionDeclaration; 1549 const signDecl = TypeScriptLinter.tsTypeChecker.signatureToSignatureDeclaration(callSignature, tsSyntaxKind, 1550 undefined, NodeBuilderFlags.WriteTypeArgumentsOfSignature | NodeBuilderFlags.IgnoreErrors); 1551 1552 if (signDecl?.typeArguments) { 1553 const resolvedTypeArgs = signDecl.typeArguments; 1554 1555 const startTypeArg = callLikeExpr.typeArguments?.length ?? 0; 1556 for (let i = startTypeArg; i < resolvedTypeArgs.length; ++i) { 1557 const typeNode = resolvedTypeArgs[i]; 1558 // if compiler infers 'unknown' type there are 2 possible cases: 1559 // 1. Compiler unable to infer type from arguments and use 'unknown' 1560 // 2. Compiler infer 'unknown' from arguments 1561 // We report error in both cases. It is ok because we cannot use 'unknown' 1562 // in ArkTS and already have separate check for it. 1563 if (typeNode.kind == ts.SyntaxKind.UnknownKeyword) { 1564 this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs); 1565 break; 1566 } 1567 } 1568 } 1569 } 1570 1571 private static listApplyBindCallApis = [ 1572 "Function.apply", 1573 "Function.call", 1574 "Function.bind", 1575 "CallableFunction.apply", 1576 "CallableFunction.call", 1577 "CallableFunction.bind" 1578 ]; 1579 private handleFunctionApplyBindPropCall(tsCallExpr: ts.CallExpression, calleeSym: ts.Symbol) { 1580 const exprName = TypeScriptLinter.tsTypeChecker.getFullyQualifiedName(calleeSym); 1581 if (TypeScriptLinter.listApplyBindCallApis.includes(exprName)) { 1582 this.incrementCounters(tsCallExpr, FaultID.FunctionApplyBindCall); 1583 } 1584 } 1585 1586 private handleStructIdentAndUndefinedInArgs(tsCallOrNewExpr: ts.CallExpression | ts.NewExpression, callSignature: ts.Signature) { 1587 if (!tsCallOrNewExpr.arguments) { 1588 return; 1589 } 1590 1591 for (let argIndex = 0; argIndex < tsCallOrNewExpr.arguments.length; ++argIndex) { 1592 const tsArg = tsCallOrNewExpr.arguments[argIndex]; 1593 const tsArgType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsArg); 1594 if (!tsArgType) continue; 1595 1596 let paramIndex = argIndex < callSignature.parameters.length ? argIndex : callSignature.parameters.length-1; 1597 let tsParamSym = callSignature.parameters[paramIndex]; 1598 if (!tsParamSym) continue; 1599 1600 const tsParamDecl = tsParamSym.valueDeclaration; 1601 if (tsParamDecl && isParameter(tsParamDecl)) { 1602 let tsParamType = TypeScriptLinter.tsTypeChecker.getTypeOfSymbolAtLocation(tsParamSym, tsParamDecl); 1603 if (tsParamDecl.dotDotDotToken && Utils.isGenericArrayType(tsParamType) && tsParamType.typeArguments) { 1604 tsParamType = tsParamType.typeArguments[0]; 1605 } 1606 if (!tsParamType) continue; 1607 1608 if (Utils.needToDeduceStructuralIdentity(tsArgType, tsParamType)){ 1609 this.incrementCounters(tsArg, FaultID.StructuralIdentity); 1610 } 1611 } 1612 } 1613 } 1614 1615 // let re = new RegExp("^(" + arr.reduce((acc, v) => ((acc ? (acc + "|") : "") + v)) +")$") 1616 private static LimitedApis = new Map<string, {arr: Array<string> | null, fault: FaultID}> ([ 1617 ["global", {arr: Utils.LIMITED_STD_GLOBAL_FUNC, fault: FaultID.LimitedStdLibApi}], 1618 ["Object", {arr: Utils.LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}], 1619 ["ObjectConstructor", {arr: Utils.LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}], 1620 ["Reflect", {arr: Utils.LIMITED_STD_REFLECT_API, fault: FaultID.LimitedStdLibApi}], 1621 ["ProxyHandler", {arr: Utils.LIMITED_STD_PROXYHANDLER_API, fault: FaultID.LimitedStdLibApi}], 1622 ["Symbol", {arr: null, fault: FaultID.SymbolType}], 1623 ["SymbolConstructor", {arr: null, fault: FaultID.SymbolType}], 1624 ]) 1625 1626 private handleStdlibAPICall(callExpr: ts.CallExpression, calleeSym: ts.Symbol) { 1627 const name = calleeSym.getName(); 1628 const parName = Utils.getParentSymbolName(calleeSym); 1629 if (parName === undefined) { 1630 if (Utils.LIMITED_STD_GLOBAL_FUNC.includes(name)) { 1631 this.incrementCounters(callExpr, FaultID.LimitedStdLibApi); 1632 return; 1633 } 1634 let escapedName = calleeSym.escapedName; 1635 if (escapedName === 'Symbol' || escapedName === 'SymbolConstructor') { 1636 this.incrementCounters(callExpr, FaultID.SymbolType); 1637 } 1638 return; 1639 } 1640 let lookup = TypeScriptLinter.LimitedApis.get(parName); 1641 if (lookup !== undefined && (lookup.arr === null || lookup.arr.includes(name))) { 1642 this.incrementCounters(callExpr, lookup.fault); 1643 }; 1644 } 1645 1646 private findNonFilteringRangesFunctionCalls(callExpr: ts.CallExpression): { begin: number, end: number }[] { 1647 let args = callExpr.arguments; 1648 let result: { begin: number, end: number }[] = []; 1649 for (const arg of args) { 1650 if (ts.isArrowFunction(arg)) { 1651 let arrowFuncExpr = arg as ts.ArrowFunction; 1652 result.push({begin: arrowFuncExpr.body.pos, end: arrowFuncExpr.body.end}); 1653 } else if (ts.isCallExpression(arg)) { 1654 result.push({begin: arg.arguments.pos, end: arg.arguments.end}); 1655 } 1656 // there may be other cases 1657 } 1658 return result; 1659 } 1660 1661 private handleLibraryTypeCall(callExpr: ts.CallExpression, calleeType: ts.Type) { 1662 let inLibCall = Utils.isLibraryType(calleeType); 1663 const diagnosticMessages: Array<ts.DiagnosticMessageChain> = [] 1664 this.libraryTypeCallDiagnosticChecker.configure(inLibCall, diagnosticMessages); 1665 1666 let nonFilteringRanges = this.findNonFilteringRangesFunctionCalls(callExpr); 1667 let rangesToFilter: { begin: number, end: number }[] = []; 1668 if (nonFilteringRanges.length !== 0) { 1669 let rangesSize = nonFilteringRanges.length; 1670 rangesToFilter.push({ begin: callExpr.arguments.pos, end: nonFilteringRanges[0].begin }) 1671 rangesToFilter.push({ begin: nonFilteringRanges[rangesSize - 1].end, end: callExpr.arguments.end }) 1672 for (let i = 0; i < rangesSize - 1; i++) { 1673 rangesToFilter.push({ begin: nonFilteringRanges[i].end, end: nonFilteringRanges[i + 1].begin }) 1674 } 1675 } else { 1676 rangesToFilter.push({ begin: callExpr.arguments.pos, end: callExpr.arguments.end }) 1677 } 1678 1679 this.filterStrictDiagnostics({ 1680 [ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE]: (pos: number) => { 1681 return this.checkInRange([{begin: callExpr.pos, end: callExpr.end}], pos); 1682 }, 1683 [NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE]: (pos: number) => { 1684 return this.checkInRange([{begin: callExpr.pos, end: callExpr.end}], pos); 1685 }, 1686 [TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE]: (pos: number) => { 1687 return this.checkInRange(rangesToFilter, pos); 1688 } 1689 }, 1690 this.libraryTypeCallDiagnosticChecker 1691 ); 1692 1693 for (const msgChain of diagnosticMessages) { 1694 TypeScriptLinter.filteredDiagnosticMessages.push(msgChain); 1695 } 1696 } 1697 1698 private handleNewExpression(node: Node): void { 1699 const tsNewExpr = node as NewExpression; 1700 let callSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(tsNewExpr); 1701 if (callSignature !== undefined) { 1702 this.handleStructIdentAndUndefinedInArgs(tsNewExpr, callSignature); 1703 this.handleGenericCallWithNoTypeArgs(tsNewExpr, callSignature); 1704 } 1705 } 1706 1707 private handleAsExpression(node: Node): void { 1708 const tsAsExpr = node as AsExpression; 1709 if (tsAsExpr.type.getText() === "const") this.incrementCounters(node, FaultID.ConstAssertion); 1710 1711 const targetType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsAsExpr.type).getNonNullableType(); 1712 const exprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression).getNonNullableType(); 1713 if (Utils.needToDeduceStructuralIdentity(exprType, targetType, true)) { 1714 this.incrementCounters(tsAsExpr, FaultID.StructuralIdentity); 1715 } 1716 // check for rule#65: "number as Number" and "boolean as Boolean" are disabled 1717 if( 1718 (Utils.isNumberType(exprType) && targetType.getSymbol()?.getName() === "Number") || 1719 (Utils.isBooleanType(exprType) && targetType.getSymbol()?.getName() === "Boolean") 1720 ) { 1721 this.incrementCounters(node, FaultID.TypeAssertion); 1722 } 1723 } 1724 1725 private handleTypeReference(node: Node): void { 1726 const typeRef = node as TypeReferenceNode; 1727 1728 const isESObject = Utils.isEsObjectType(typeRef); 1729 const isPossiblyValidContext = Utils.isEsObjectPossiblyAllowed(typeRef); 1730 if (isESObject && !isPossiblyValidContext) { 1731 this.incrementCounters(node, FaultID.EsObjectType); 1732 return; 1733 } 1734 const typeName = Utils.entityNameToString(typeRef.typeName); 1735 const isStdUtilityType = Utils.LIMITED_STANDARD_UTILITY_TYPES.includes(typeName); 1736 if (isStdUtilityType) { 1737 this.incrementCounters(node, FaultID.UtilityType); 1738 return; 1739 } 1740 1741 // Using Partial<T> type is allowed only when its argument type is either Class or Interface. 1742 const isStdPartial = Utils.entityNameToString(typeRef.typeName) === 'Partial'; 1743 const hasSingleTypeArgument = !!typeRef.typeArguments && typeRef.typeArguments.length === 1; 1744 const firstTypeArg = !!typeRef.typeArguments && hasSingleTypeArgument && typeRef.typeArguments[0]; 1745 const argType = firstTypeArg && TypeScriptLinter.tsTypeChecker.getTypeFromTypeNode(firstTypeArg); 1746 if (isStdPartial && argType && !argType.isClassOrInterface()) { 1747 this.incrementCounters(node, FaultID.UtilityType); 1748 return; 1749 } 1750 } 1751 1752 private handleMetaProperty(node: Node): void { 1753 const tsMetaProperty = node as MetaProperty; 1754 if (tsMetaProperty.name.text === "target") { 1755 this.incrementCounters(node, FaultID.NewTarget); 1756 } 1757 } 1758 1759 private handleStructDeclaration(node: Node): void { 1760 node.forEachChild(child => { 1761 // Skip synthetic constructor in Struct declaration. 1762 if (!isConstructorDeclaration(child)) this.visitTSNode(child); 1763 }); 1764 } 1765 1766 private handleSpreadOp(node: Node) { 1767 // spread assignment is disabled 1768 // spread element is allowed only for arrays as rest parameter 1769 if (isSpreadElement(node)) { 1770 const spreadElemNode = node; 1771 const spreadExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(spreadElemNode.expression); 1772 if (spreadExprType) { 1773 const spreadExprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(spreadExprType, undefined, NodeBuilderFlags.None); 1774 if (spreadExprTypeNode !== undefined && 1775 (isCallLikeExpression(node.parent) || isArrayLiteralExpression(node.parent))) { 1776 if (isArrayTypeNode(spreadExprTypeNode) || 1777 Utils.isTypedArray(spreadExprTypeNode) || 1778 Utils.isDerivedFrom(spreadExprType, Utils.CheckType.Array)) { 1779 return; 1780 } 1781 } 1782 } 1783 } 1784 this.incrementCounters(node, FaultID.SpreadOperator); 1785 } 1786 1787 private handleConstructSignature(node: Node) { 1788 switch (node.parent.kind) { 1789 case SyntaxKind.TypeLiteral: 1790 this.incrementCounters(node, FaultID.ConstructorType); 1791 break; 1792 case SyntaxKind.InterfaceDeclaration: 1793 this.incrementCounters(node, FaultID.ConstructorIface); 1794 break; 1795 default: 1796 return; 1797 } 1798 } 1799 1800 private handleComments(node: Node) { 1801 // Note: Same comment may be owned by several nodes if their 1802 // start/end position matches. Thus, look for the most parental 1803 // owner of the specific comment (by the node's position). 1804 const srcText = node.getSourceFile().getFullText(); 1805 1806 const parent = node.parent; 1807 if (!parent || parent.getFullStart() !== node.getFullStart()) { 1808 const leadingComments = getLeadingCommentRanges(srcText, node.getFullStart()); 1809 if (leadingComments) { 1810 for (const comment of leadingComments) { 1811 this.checkErrorSuppressingAnnotation(comment, srcText); 1812 } 1813 } 1814 } 1815 1816 if (!parent || parent.getEnd() !== node.getEnd()) { 1817 const trailingComments = getTrailingCommentRanges(srcText, node.getEnd()); 1818 if (trailingComments) { 1819 for (const comment of trailingComments) { 1820 this.checkErrorSuppressingAnnotation(comment, srcText); 1821 } 1822 } 1823 } 1824 } 1825 1826 private handleExpressionWithTypeArguments(node: Node) { 1827 const tsTypeExpr = node as ExpressionWithTypeArguments; 1828 const symbol = Utils.trueSymbolAtLocation(tsTypeExpr.expression); 1829 if (!!symbol && Utils.isEsObjectSymbol(symbol)) { 1830 this.incrementCounters(tsTypeExpr, FaultID.EsObjectType); 1831 } 1832 } 1833 1834 private handleComputedPropertyName(node: ts.Node) { 1835 const computedProperty = node as ts.ComputedPropertyName; 1836 const symbol = Utils.trueSymbolAtLocation(computedProperty.expression); 1837 if (!!symbol && Utils.isSymbolIterator(symbol)) { 1838 return 1839 } 1840 this.incrementCounters(node, FaultID.ComputedPropertyName); 1841 } 1842 1843 private checkErrorSuppressingAnnotation(comment: CommentRange, srcText: string) { 1844 const commentContent = comment.kind === SyntaxKind.MultiLineCommentTrivia 1845 ? srcText.slice(comment.pos + 2, comment.end - 2) 1846 : srcText.slice(comment.pos + 2, comment.end); 1847 // if comment is multiline end closing '*/' is not at the same line as '@ts-xxx' - do nothing (#13851) 1848 if (commentContent.endsWith('\n')) { 1849 return 1850 } 1851 const trimmedContent = commentContent.trim(); 1852 if (trimmedContent.startsWith("@ts-ignore") || 1853 trimmedContent.startsWith("@ts-nocheck") || 1854 trimmedContent.startsWith("@ts-expect-error")) { 1855 this.incrementCounters(comment, FaultID.ErrorSuppression); 1856 } 1857 } 1858 1859 private handleDecorators(decorators: readonly Decorator[] | undefined): void { 1860 if (!decorators) return; 1861 1862 for (const decorator of decorators) { 1863 let decoratorName = ""; 1864 if (isIdentifier(decorator.expression)) { 1865 decoratorName = decorator.expression.text; 1866 } 1867 else if (isCallExpression(decorator.expression) && isIdentifier(decorator.expression.expression)) { 1868 decoratorName = decorator.expression.expression.text; 1869 } 1870 if (!Utils.ARKUI_DECORATORS.includes(decoratorName)) { 1871 this.incrementCounters(decorator, FaultID.UnsupportedDecorators); 1872 } 1873 } 1874 } 1875 1876 private handleGetAccessor(node: Node) { 1877 this.handleDecorators(ts.getDecorators(node as GetAccessorDeclaration)); 1878 } 1879 1880 private handleSetAccessor(node: Node) { 1881 this.handleDecorators(ts.getDecorators(node as SetAccessorDeclaration)); 1882 } 1883 1884 private handleDeclarationInferredType( 1885 decl: VariableDeclaration | PropertyDeclaration | ParameterDeclaration 1886 ) { 1887 // The type is explicitly specified, no need to check inferred type. 1888 if (decl.type) return; 1889 1890 // issue 13161: 1891 // In TypeScript, the catch clause variable must be 'any' or 'unknown' type. Since 1892 // ArkTS doesn't support these types, the type for such variable is simply omitted, 1893 // and we don't report it as an error. 1894 if (isCatchClause(decl.parent)) return; 1895 1896 //Destructuring declarations are not supported, do not process them. 1897 if (isArrayBindingPattern(decl.name) || isObjectBindingPattern(decl.name)) return; 1898 1899 const type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(decl); 1900 if (type) this.validateDeclInferredType(type, decl); 1901 } 1902 1903 private handleDefiniteAssignmentAssertion(decl: VariableDeclaration | PropertyDeclaration) { 1904 if (decl.exclamationToken !== undefined) { 1905 this.incrementCounters(decl, FaultID.DefiniteAssignment); 1906 } 1907 } 1908 1909 private validatedTypesSet = new Set<Type>(); 1910 1911 private checkAnyOrUnknownChildNode(node: ts.Node): boolean { 1912 if (node.kind === ts.SyntaxKind.AnyKeyword || 1913 node.kind === ts.SyntaxKind.UnknownKeyword) { 1914 return true; 1915 } 1916 const isAnyOrUnknown = forEachChild(node, (child) => { 1917 if (this.checkAnyOrUnknownChildNode(child)) { 1918 return true; 1919 } 1920 return undefined; 1921 }); 1922 return !!isAnyOrUnknown; 1923 } 1924 1925 private handleInferredObjectreference( 1926 type: ts.Type, 1927 decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 1928 ) { 1929 const typeArgs = TypeScriptLinter.tsTypeChecker.getTypeArguments(type as ts.TypeReference); 1930 if (typeArgs) { 1931 const haveAnyOrUnknownNodes = this.checkAnyOrUnknownChildNode(decl); 1932 if (!haveAnyOrUnknownNodes) { 1933 for (const typeArg of typeArgs) { 1934 this.validateDeclInferredType(typeArg, decl); 1935 } 1936 } 1937 } 1938 } 1939 1940 private validateDeclInferredType( 1941 type: Type, 1942 decl: VariableDeclaration | PropertyDeclaration | ParameterDeclaration 1943 ): void { 1944 if (type.aliasSymbol !== undefined) { 1945 return; 1946 } 1947 const isObject = type.flags & ts.TypeFlags.Object; 1948 const isReference = (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference; 1949 if (isObject && isReference) { 1950 this.handleInferredObjectreference(type, decl); 1951 return; 1952 } 1953 if (this.validatedTypesSet.has(type)) { 1954 return; 1955 } 1956 if (type.isUnion()) { 1957 this.validatedTypesSet.add(type); 1958 for (let unionElem of type.types) { 1959 this.validateDeclInferredType(unionElem, decl); 1960 } 1961 } 1962 1963 if (Utils.isAnyType(type)) { 1964 this.incrementCounters(decl, FaultID.AnyType); 1965 } 1966 else if (Utils.isUnknownType(type)) { 1967 this.incrementCounters(decl, FaultID.UnknownType); 1968 } 1969 } 1970 1971 private handleClassStaticBlockDeclaration(node: ts.Node): void { 1972 if (this.skipArkTSStaticBlocksCheck) { 1973 return; 1974 } 1975 const classStaticBlockDecl = node as ts.ClassStaticBlockDeclaration; 1976 if (!ts.isClassDeclaration(classStaticBlockDecl.parent)) { 1977 return; 1978 } 1979 if (this.functionContainsThis(node)) { 1980 this.incrementCounters(node, FaultID.FunctionContainsThis); 1981 } 1982 } 1983 1984 public lint(): void { 1985 this.visitTSNode(this.sourceFile); 1986 } 1987 1988} 1989} 1990} 1991