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