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