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