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