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