1/* 2 * Copyright (c) 2022-2025 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 { FaultID } from './Problems'; 19import { TypeScriptLinterConfig } from './TypeScriptLinterConfig'; 20import type { Autofix } from './autofixes/Autofixer'; 21import { Autofixer } from './autofixes/Autofixer'; 22import { PROMISE_METHODS, SYMBOL, SYMBOL_CONSTRUCTOR, TsUtils } from './utils/TsUtils'; 23import { FUNCTION_HAS_NO_RETURN_ERROR_CODE } from './utils/consts/FunctionHasNoReturnErrorCode'; 24import { LIMITED_STANDARD_UTILITY_TYPES } from './utils/consts/LimitedStandardUtilityTypes'; 25import { LIKE_FUNCTION } from './utils/consts/LikeFunction'; 26import { METHOD_DECLARATION } from './utils/consts/MethodDeclaration'; 27import { METHOD_SIGNATURE } from './utils/consts/MethodSignature'; 28import { OPTIONAL_METHOD } from './utils/consts/OptionalMethod'; 29import { 30 STRINGLITERAL_NUMBER, 31 STRINGLITERAL_STRING, 32 STRINGLITERAL_INT, 33 STRINGLITERAL_BYTE, 34 STRINGLITERAL_SHORT, 35 STRINGLITERAL_CHAR, 36 STRINGLITERAL_LONG, 37 STRINGLITERAL_FROM, 38 STRINGLITERAL_ARRAY 39} from './utils/consts/StringLiteral'; 40import { 41 NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS, 42 NON_INITIALIZABLE_PROPERTY_DECORATORS, 43 NON_INITIALIZABLE_PROPERTY_DECORATORS_TSC 44} from './utils/consts/NonInitializablePropertyDecorators'; 45import { NON_RETURN_FUNCTION_DECORATORS } from './utils/consts/NonReturnFunctionDecorators'; 46import { PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE } from './utils/consts/PropertyHasNoInitializerErrorCode'; 47import { 48 CONCURRENT_DECORATOR, 49 ISCONCURRENT, 50 SENDABLE_DECORATOR, 51 SENDABLE_DECORATOR_NODES, 52 SENDABLE_FUNCTION_UNSUPPORTED_STAGES_IN_API12, 53 SENDBALE_FUNCTION_START_VERSION, 54 TASKPOOL 55} from './utils/consts/SendableAPI'; 56import { DEFAULT_COMPATIBLE_SDK_VERSION, DEFAULT_COMPATIBLE_SDK_VERSION_STAGE } from './utils/consts/VersionInfo'; 57import { forEachNodeInSubtree } from './utils/functions/ForEachNodeInSubtree'; 58import { hasPredecessor } from './utils/functions/HasPredecessor'; 59import { isStdLibrarySymbol, isStdLibraryType } from './utils/functions/IsStdLibrary'; 60import { isStruct, isStructDeclaration } from './utils/functions/IsStruct'; 61import { 62 LibraryTypeCallDiagnosticChecker, 63 ErrorType as DiagnosticCheckerErrorType 64} from './utils/functions/LibraryTypeCallDiagnosticChecker'; 65import { 66 ALLOWED_STD_SYMBOL_API, 67 LIMITED_STD_API, 68 LIMITED_STD_GLOBAL_API, 69 LIMITED_STD_OBJECT_API, 70 LIMITED_STD_PROXYHANDLER_API, 71 LIMITED_STD_REFLECT_API, 72 MODULE_IMPORTS, 73 ARKTSUTILS_MODULES, 74 ARKTSUTILS_LOCKS_MEMBER 75} from './utils/consts/LimitedStdAPI'; 76import { SupportedStdCallApiChecker } from './utils/functions/SupportedStdCallAPI'; 77import { identiferUseInValueContext } from './utils/functions/identiferUseInValueContext'; 78import { isAssignmentOperator } from './utils/functions/isAssignmentOperator'; 79import { StdClassVarDecls } from './utils/consts/StdClassVariableDeclarations'; 80import type { LinterOptions } from './LinterOptions'; 81import { BUILTIN_GENERIC_CONSTRUCTORS } from './utils/consts/BuiltinGenericConstructor'; 82import { DEFAULT_DECORATOR_WHITE_LIST } from './utils/consts/DefaultDecoratorWhitelist'; 83import { INVALID_IDENTIFIER_KEYWORDS } from './utils/consts/InValidIndentifierKeywords'; 84import { WORKER_MODULES, WORKER_TEXT } from './utils/consts/WorkerAPI'; 85import { COLLECTIONS_TEXT, COLLECTIONS_MODULES } from './utils/consts/CollectionsAPI'; 86import { ASON_TEXT, ASON_MODULES, ARKTS_UTILS_TEXT, JSON_TEXT, ASON_WHITE_SET } from './utils/consts/ArkTSUtilsAPI'; 87import { interanlFunction } from './utils/consts/InternalFunction'; 88import { ETS_PART, PATH_SEPARATOR } from './utils/consts/OhmUrl'; 89import { 90 DOUBLE_DOLLAR_IDENTIFIER, 91 THIS_IDENTIFIER, 92 STATE_STYLES, 93 CustomDecoratorName, 94 observedDecoratorName, 95 skipImportDecoratorName, 96 ENTRY_DECORATOR_NAME, 97 PROVIDE_DECORATOR_NAME, 98 PROVIDE_ALLOW_OVERRIDE_PROPERTY_NAME, 99 ARKUI_PACKAGE_NAME, 100 MAKE_OBSERVED, 101 ARKUI_STATE_MANAGEMENT, 102 PropDecoratorName, 103 PropFunctionName, 104 StorageTypeName, 105 customLayoutFunctionName 106} from './utils/consts/ArkuiConstants'; 107import { arkuiImportList } from './utils/consts/ArkuiImportList'; 108import type { IdentifierAndArguments, ForbidenAPICheckResult } from './utils/consts/InteropAPI'; 109import { 110 NONE, 111 OBJECT_LITERAL, 112 OBJECT_PROPERTIES, 113 REFLECT_LITERAL, 114 REFLECT_PROPERTIES 115} from './utils/consts/InteropAPI'; 116import { EXTNAME_TS, EXTNAME_D_TS, EXTNAME_JS } from './utils/consts/ExtensionName'; 117import { ARKTS_IGNORE_DIRS_OH_MODULES } from './utils/consts/ArktsIgnorePaths'; 118import type { ApiInfo, ApiListItem } from './utils/consts/SdkWhitelist'; 119import { ApiList, SdkProblem, SdkNameInfo } from './utils/consts/SdkWhitelist'; 120import * as apiWhiteList from './data/SdkWhitelist.json'; 121import * as builtinWhiteList from './data/BuiltinList.json'; 122import { 123 BuiltinProblem, 124 SYMBOL_ITERATOR, 125 BUILTIN_DISABLE_CALLSIGNATURE, 126 GET_OWN_PROPERTY_NAMES_TEXT 127} from './utils/consts/BuiltinWhiteList'; 128import { 129 USE_SHARED, 130 USE_CONCURRENT, 131 ESLIB_SHAREDMEMORY_FILENAME, 132 ESLIB_SHAREDARRAYBUFFER, 133 TASKPOOL_MODULES 134} from './utils/consts/ConcurrentAPI'; 135import { 136 DEPRECATED_TASKPOOL_METHOD_SETCLONELIST, 137 DEPRECATED_TASKPOOL_METHOD_SETTRANSFERLIST, 138 STDLIB_TASK_CLASS_NAME, 139 STDLIB_TASKPOOL_OBJECT_NAME 140} from './utils/consts/TaskpoolAPI'; 141import { BaseTypeScriptLinter } from './BaseTypeScriptLinter'; 142import type { ArrayAccess, UncheckedIdentifier } from './utils/consts/RuntimeCheckAPI'; 143import { NUMBER_LITERAL } from './utils/consts/RuntimeCheckAPI'; 144import { globalApiAssociatedInfo } from './utils/consts/AssociatedInfo'; 145import { ARRAY_API_LIST } from './utils/consts/ArraysAPI'; 146import { 147 ABILITY_KIT, 148 ASYNC_LIFECYCLE_SDK_LIST, 149 ON_DESTROY, 150 ON_DISCONNECT, 151 PROMISE, 152 SERVICE_EXTENSION_ABILITY, 153 VOID 154} from './utils/consts/AsyncLifecycleSDK'; 155import { ERROR_PROP_LIST } from './utils/consts/ErrorProp'; 156import { D_ETS, D_TS } from './utils/consts/TsSuffix'; 157import { arkTsBuiltInTypeName } from './utils/consts/ArkuiImportList'; 158import { ERROR_TASKPOOL_PROP_LIST } from './utils/consts/ErrorProp'; 159 160interface InterfaceSymbolTypeResult { 161 propNames: string[]; 162 typeNames: string[]; 163 allProps: Map<string, string>; 164} 165interface InterfaceSymbolTypePropertyNames { 166 propertyNames: string[]; 167 typeNames: string[]; 168} 169 170export class TypeScriptLinter extends BaseTypeScriptLinter { 171 supportedStdCallApiChecker: SupportedStdCallApiChecker; 172 173 autofixer: Autofixer | undefined; 174 private fileExportDeclCaches: Set<ts.Node> | undefined; 175 176 private useStatic?: boolean; 177 178 private readonly compatibleSdkVersion: number; 179 private readonly compatibleSdkVersionStage: string; 180 private static sharedModulesCache: Map<string, boolean>; 181 static nameSpaceFunctionCache: Map<string, Set<string>>; 182 private readonly constVariableInitCache: Map<ts.Symbol, number | null> = new Map(); 183 static funcMap: Map<string, Map<string, Set<ApiInfo>>> = new Map<string, Map<string, Set<ApiInfo>>>(); 184 private interfaceMap: Map<string, Set<ApiInfo>> = new Map<string, Set<ApiInfo>>(); 185 static pathMap: Map<string, Set<ApiInfo>>; 186 static indexedTypeSet: Set<ApiListItem>; 187 static globalApiInfo: Map<string, Set<ApiListItem>>; 188 static symbotIterSet: Set<string>; 189 static missingAttributeSet: Set<string>; 190 static literalAsPropertyNameTypeSet: Set<ApiListItem>; 191 private localApiListItem: ApiListItem | undefined = undefined; 192 static constructorFuncsSet: Set<ApiListItem>; 193 static ConstructorIfaceSet: Set<ApiListItem>; 194 195 static initGlobals(): void { 196 TypeScriptLinter.sharedModulesCache = new Map<string, boolean>(); 197 TypeScriptLinter.nameSpaceFunctionCache = new Map<string, Set<string>>(); 198 TypeScriptLinter.pathMap = new Map<string, Set<ApiInfo>>(); 199 TypeScriptLinter.globalApiInfo = new Map<string, Set<ApiListItem>>(); 200 TypeScriptLinter.funcMap = new Map<string, Map<string, Set<ApiInfo>>>(); 201 TypeScriptLinter.symbotIterSet = new Set<string>(); 202 TypeScriptLinter.missingAttributeSet = new Set<string>(); 203 TypeScriptLinter.initSdkWhitelist(); 204 TypeScriptLinter.initSdkBuiltinInfo(); 205 TypeScriptLinter.initBuiltinlist(); 206 } 207 208 initSdkInfo(): void { 209 this.interfaceMap = new Map<string, Set<ApiInfo>>(); 210 } 211 212 static initSdkBuiltinInfo(): void { 213 const list: ApiList = new ApiList(builtinWhiteList); 214 if (list?.api_list?.length > 0) { 215 for (const item of list.api_list) { 216 switch (item.api_info.problem) { 217 case BuiltinProblem.MissingAttributes: 218 TypeScriptLinter.missingAttributeSet.add(item.file_path); 219 break; 220 case BuiltinProblem.SymbolIterator: 221 TypeScriptLinter.symbotIterSet.add(item.file_path); 222 break; 223 case BuiltinProblem.LimitedThisArg: 224 TypeScriptLinter.initSdkBuiltinThisArgsWhitelist(item); 225 break; 226 default: 227 } 228 } 229 } 230 } 231 232 static initSdkBuiltinThisArgsWhitelist(item: ApiListItem): void { 233 if (item.file_path === '' || !item.api_info.api_name) { 234 return; 235 } 236 237 let funcApiInfos: Map<string, Set<ApiInfo>> | undefined = TypeScriptLinter.funcMap.get(item.api_info.api_name); 238 if (!funcApiInfos) { 239 funcApiInfos = new Map<string, Set<ApiInfo>>(); 240 TypeScriptLinter.funcMap.set(item.api_info.api_name, funcApiInfos); 241 } 242 TypeScriptLinter.addOrUpdateData(funcApiInfos, item.file_path, item.api_info); 243 } 244 245 private initEtsHandlers(): void { 246 247 /* 248 * some syntax elements are ArkTs-specific and are only implemented inside patched 249 * compiler, so we initialize those handlers if corresponding properties do exist 250 */ 251 const etsComponentExpression: ts.SyntaxKind | undefined = ts.SyntaxKind.EtsComponentExpression; 252 if (etsComponentExpression) { 253 this.handlersMap.set(etsComponentExpression, this.handleEtsComponentExpression); 254 } 255 } 256 257 private static addSdkIndexedTypeSetData(item: ApiListItem): void { 258 if (item.api_info.problem === SdkProblem.IndexedAccessType) { 259 TypeScriptLinter.indexedTypeSet.add(item); 260 } 261 } 262 263 private static addSdkliteralAsPropertyNameTypeSetData(item: ApiListItem): void { 264 if (item.api_info.problem === SdkProblem.LiteralAsPropertyName) { 265 TypeScriptLinter.literalAsPropertyNameTypeSet.add(item); 266 } 267 } 268 269 private static addSdkConstructorFuncsSetData(item: ApiListItem): void { 270 if (item.api_info.problem === SdkProblem.ConstructorFuncs) { 271 TypeScriptLinter.constructorFuncsSet.add(item); 272 } 273 } 274 275 private static addGlobalApiInfosCollocetionData(item: ApiListItem): void { 276 const problemType = item.api_info.problem; 277 const isGlobal = item.is_global; 278 if (isGlobal) { 279 if (!TypeScriptLinter.globalApiInfo.has(problemType)) { 280 TypeScriptLinter.globalApiInfo.set(problemType, new Set<ApiListItem>()); 281 } 282 const setApiListItem = TypeScriptLinter.globalApiInfo.get(problemType); 283 setApiListItem?.add(item); 284 } 285 } 286 287 private static addSdkConstructorIfaceSetData(item: ApiListItem): void { 288 if (item.api_info.problem === SdkProblem.ConstructorIface) { 289 TypeScriptLinter.ConstructorIfaceSet.add(item); 290 } 291 } 292 293 private static initSdkWhitelist(): void { 294 TypeScriptLinter.indexedTypeSet = new Set<ApiListItem>(); 295 TypeScriptLinter.literalAsPropertyNameTypeSet = new Set<ApiListItem>(); 296 TypeScriptLinter.constructorFuncsSet = new Set<ApiListItem>(); 297 const list: ApiList = new ApiList(apiWhiteList); 298 TypeScriptLinter.ConstructorIfaceSet = new Set<ApiListItem>(); 299 if (list?.api_list?.length > 0) { 300 for (const item of list.api_list) { 301 if (item.file_path !== '') { 302 TypeScriptLinter.addOrUpdateData(TypeScriptLinter.pathMap, `'${item.file_path}'`, item.api_info); 303 } 304 item.import_path.forEach((path) => { 305 TypeScriptLinter.addOrUpdateData(TypeScriptLinter.pathMap, `'${path}'`, item.api_info); 306 }); 307 TypeScriptLinter.addSdkIndexedTypeSetData(item); 308 TypeScriptLinter.addSdkliteralAsPropertyNameTypeSetData(item); 309 TypeScriptLinter.addSdkConstructorFuncsSetData(item); 310 TypeScriptLinter.addGlobalApiInfosCollocetionData(item); 311 TypeScriptLinter.addSdkConstructorIfaceSetData(item); 312 } 313 } 314 } 315 316 private static initBuiltinlist(): void { 317 const list: ApiList = new ApiList(builtinWhiteList); 318 if (list?.api_list?.length > 0) { 319 for (const item of list.api_list) { 320 TypeScriptLinter.addGlobalApiInfosCollocetionData(item); 321 } 322 } 323 } 324 325 private static addOrUpdateData(map: Map<string, Set<ApiInfo>>, path: string, data: ApiInfo): void { 326 let apiInfos = map.get(path); 327 if (!apiInfos) { 328 apiInfos = new Set<ApiInfo>(); 329 map.set(path, apiInfos); 330 } 331 apiInfos.add(data); 332 } 333 334 constructor( 335 tsTypeChecker: ts.TypeChecker, 336 options: LinterOptions, 337 sourceFile: ts.SourceFile, 338 readonly tscStrictDiagnostics?: Map<string, ts.Diagnostic[]> 339 ) { 340 super(tsTypeChecker, options, sourceFile); 341 this.supportedStdCallApiChecker = new SupportedStdCallApiChecker(this.tsUtils, this.tsTypeChecker); 342 this.compatibleSdkVersion = options.compatibleSdkVersion || DEFAULT_COMPATIBLE_SDK_VERSION; 343 this.compatibleSdkVersionStage = options.compatibleSdkVersionStage || DEFAULT_COMPATIBLE_SDK_VERSION_STAGE; 344 this.initEtsHandlers(); 345 this.initSdkInfo(); 346 } 347 348 readonly handlersMap = new Map([ 349 [ts.SyntaxKind.ObjectLiteralExpression, this.handleObjectLiteralExpression], 350 [ts.SyntaxKind.ArrayLiteralExpression, this.handleArrayLiteralExpression], 351 [ts.SyntaxKind.Parameter, this.handleParameter], 352 [ts.SyntaxKind.EnumDeclaration, this.handleEnumDeclaration], 353 [ts.SyntaxKind.InterfaceDeclaration, this.handleInterfaceDeclaration], 354 [ts.SyntaxKind.TryStatement, this.handleTryStatement], 355 [ts.SyntaxKind.ThrowStatement, this.handleThrowStatement], 356 [ts.SyntaxKind.ImportClause, this.handleImportClause], 357 [ts.SyntaxKind.ForStatement, this.handleForStatement], 358 [ts.SyntaxKind.ForInStatement, this.handleForInStatement], 359 [ts.SyntaxKind.ForOfStatement, this.handleForOfStatement], 360 [ts.SyntaxKind.ImportDeclaration, this.handleImportDeclaration], 361 [ts.SyntaxKind.PropertyAccessExpression, this.handlePropertyAccessExpression], 362 [ts.SyntaxKind.PropertyDeclaration, this.handlePropertyDeclaration], 363 [ts.SyntaxKind.PropertyAssignment, this.handlePropertyAssignment], 364 [ts.SyntaxKind.PropertySignature, this.handlePropertySignature], 365 [ts.SyntaxKind.FunctionExpression, this.handleFunctionExpression], 366 [ts.SyntaxKind.ArrowFunction, this.handleArrowFunction], 367 [ts.SyntaxKind.CatchClause, this.handleCatchClause], 368 [ts.SyntaxKind.FunctionDeclaration, this.handleFunctionDeclaration], 369 [ts.SyntaxKind.PrefixUnaryExpression, this.handlePrefixUnaryExpression], 370 [ts.SyntaxKind.BinaryExpression, this.handleBinaryExpression], 371 [ts.SyntaxKind.VariableDeclarationList, this.handleVariableDeclarationList], 372 [ts.SyntaxKind.VariableDeclaration, this.handleVariableDeclaration], 373 [ts.SyntaxKind.ClassDeclaration, this.handleClassDeclaration], 374 [ts.SyntaxKind.ModuleDeclaration, this.handleModuleDeclaration], 375 [ts.SyntaxKind.TypeAliasDeclaration, this.handleTypeAliasDeclaration], 376 [ts.SyntaxKind.ImportSpecifier, this.handleImportSpecifier], 377 [ts.SyntaxKind.NamespaceImport, this.handleNamespaceImport], 378 [ts.SyntaxKind.TypeAssertionExpression, this.handleTypeAssertionExpression], 379 [ts.SyntaxKind.MethodDeclaration, this.handleMethodDeclaration], 380 [ts.SyntaxKind.TupleType, this.handleTupleType], 381 [ts.SyntaxKind.MethodSignature, this.handleMethodSignature], 382 [ts.SyntaxKind.ClassStaticBlockDeclaration, this.handleClassStaticBlockDeclaration], 383 [ts.SyntaxKind.Identifier, this.handleIdentifier], 384 [ts.SyntaxKind.ElementAccessExpression, this.handleElementAccessExpression], 385 [ts.SyntaxKind.EnumMember, this.handleEnumMember], 386 [ts.SyntaxKind.TypeReference, this.handleTypeReference], 387 [ts.SyntaxKind.ExportAssignment, this.handleExportAssignment], 388 [ts.SyntaxKind.CallExpression, this.handleCallExpression], 389 [ts.SyntaxKind.MetaProperty, this.handleMetaProperty], 390 [ts.SyntaxKind.NewExpression, this.handleNewExpression], 391 [ts.SyntaxKind.AsExpression, this.handleAsExpression], 392 [ts.SyntaxKind.SpreadElement, this.handleSpreadOp], 393 [ts.SyntaxKind.SpreadAssignment, this.handleSpreadOp], 394 [ts.SyntaxKind.GetAccessor, this.handleGetAccessor], 395 [ts.SyntaxKind.SetAccessor, this.handleSetAccessor], 396 [ts.SyntaxKind.StringLiteral, this.handleStringLiteral], 397 [ts.SyntaxKind.ConstructSignature, this.handleConstructSignature], 398 [ts.SyntaxKind.ExpressionWithTypeArguments, this.handleExpressionWithTypeArguments], 399 [ts.SyntaxKind.ComputedPropertyName, this.handleComputedPropertyName], 400 [ts.SyntaxKind.Constructor, this.handleConstructorDeclaration], 401 [ts.SyntaxKind.PrivateIdentifier, this.handlePrivateIdentifier], 402 [ts.SyntaxKind.IndexSignature, this.handleIndexSignature], 403 [ts.SyntaxKind.TypeLiteral, this.handleTypeLiteral], 404 [ts.SyntaxKind.ExportKeyword, this.handleExportKeyword], 405 [ts.SyntaxKind.ExportDeclaration, this.handleExportDeclaration], 406 [ts.SyntaxKind.ReturnStatement, this.handleReturnStatement], 407 [ts.SyntaxKind.Decorator, this.handleDecorator], 408 [ts.SyntaxKind.ImportType, this.handleImportType], 409 [ts.SyntaxKind.AsteriskAsteriskToken, this.handleExponentOperation], 410 [ts.SyntaxKind.VoidExpression, this.handleVoidExpression], 411 [ts.SyntaxKind.AsteriskAsteriskEqualsToken, this.handleExponentOperation], 412 [ts.SyntaxKind.RegularExpressionLiteral, this.handleRegularExpressionLiteral], 413 [ts.SyntaxKind.DebuggerStatement, this.handleDebuggerStatement], 414 [ts.SyntaxKind.SwitchStatement, this.handleSwitchStatement], 415 [ts.SyntaxKind.UnionType, this.handleUnionType], 416 [ts.SyntaxKind.ArrayType, this.handleArrayType], 417 [ts.SyntaxKind.LiteralType, this.handleLimitedLiteralType], 418 [ts.SyntaxKind.NonNullExpression, this.handleNonNullExpression], 419 [ts.SyntaxKind.HeritageClause, this.handleHeritageClause], 420 [ts.SyntaxKind.TaggedTemplateExpression, this.handleTaggedTemplatesExpression], 421 [ts.SyntaxKind.StructDeclaration, this.handleStructDeclaration], 422 [ts.SyntaxKind.TypeOfExpression, this.handleInterOpImportJsOnTypeOfNode], 423 [ts.SyntaxKind.AwaitExpression, this.handleAwaitExpression], 424 [ts.SyntaxKind.PostfixUnaryExpression, this.handlePostfixUnaryExpression], 425 [ts.SyntaxKind.BigIntLiteral, this.handleBigIntLiteral], 426 [ts.SyntaxKind.NumericLiteral, this.handleNumericLiteral] 427 ]); 428 429 lint(): void { 430 if (this.options.enableAutofix || this.options.migratorMode) { 431 this.autofixer = new Autofixer(this.tsTypeChecker, this.tsUtils, this.sourceFile, this.options.cancellationToken); 432 } 433 434 this.useStatic = this.tsUtils.isArkts12File(this.sourceFile); 435 this.fileExportDeclCaches = undefined; 436 this.extractImportedNames(this.sourceFile); 437 this.visitSourceFile(this.sourceFile); 438 this.handleCommentDirectives(this.sourceFile); 439 this.processInterfacesToImport(this.sourceFile); 440 } 441 442 private visitSourceFile(sf: ts.SourceFile): void { 443 const callback = (node: ts.Node): void => { 444 this.fileStats.visitedNodes++; 445 if (isStructDeclaration(node)) { 446 // early exit via exception if cancellation was requested 447 this.options.cancellationToken?.throwIfCancellationRequested(); 448 } 449 const incrementedType = TypeScriptLinterConfig.incrementOnlyTokens.get(node.kind); 450 if (incrementedType !== undefined) { 451 this.incrementCounters(node, incrementedType); 452 } else { 453 const handler = this.handlersMap.get(node.kind); 454 if (handler !== undefined) { 455 456 /* 457 * possibly requested cancellation will be checked in a limited number of handlers 458 * checked nodes are selected as construct nodes, similar to how TSC does 459 */ 460 handler.call(this, node); 461 } 462 } 463 }; 464 const stopCondition = (node: ts.Node): boolean => { 465 if (!node) { 466 return true; 467 } 468 if (this.options.incrementalLintInfo?.shouldSkipCheck(node)) { 469 return true; 470 } 471 // Skip synthetic constructor in Struct declaration. 472 if (node.parent && isStructDeclaration(node.parent) && ts.isConstructorDeclaration(node)) { 473 return true; 474 } 475 if (TypeScriptLinterConfig.terminalTokens.has(node.kind)) { 476 return true; 477 } 478 return false; 479 }; 480 forEachNodeInSubtree(sf, callback, stopCondition); 481 } 482 483 private countInterfaceExtendsDifferentPropertyTypes( 484 node: ts.Node, 485 prop2type: Map<string, string>, 486 propName: string, 487 type: ts.TypeNode | undefined 488 ): void { 489 if (type) { 490 const methodType = type.getText(); 491 const propType = prop2type.get(propName); 492 if (!propType) { 493 prop2type.set(propName, methodType); 494 } else if (propType !== methodType) { 495 this.incrementCounters(node, FaultID.IntefaceExtendDifProps); 496 } 497 } 498 } 499 500 private countDeclarationsWithDuplicateName(tsNode: ts.Node, tsDeclNode: ts.Node, tsDeclKind?: ts.SyntaxKind): void { 501 const symbol = this.tsTypeChecker.getSymbolAtLocation(tsNode); 502 503 /* 504 * If specific declaration kind is provided, check against it. 505 * Otherwise, use syntax kind of corresponding declaration node. 506 */ 507 if (!!symbol && TsUtils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)) { 508 this.incrementCounters(tsDeclNode, FaultID.DeclWithDuplicateName); 509 } 510 } 511 512 private countClassMembersWithDuplicateName(tsClassDecl: ts.ClassDeclaration): void { 513 for (const currentMember of tsClassDecl.members) { 514 if (this.tsUtils.classMemberHasDuplicateName(currentMember, tsClassDecl, false)) { 515 this.incrementCounters(currentMember, FaultID.DeclWithDuplicateName); 516 } 517 } 518 } 519 520 private isPrototypePropertyAccess( 521 tsPropertyAccess: ts.PropertyAccessExpression, 522 propAccessSym: ts.Symbol | undefined, 523 baseExprSym: ts.Symbol | undefined, 524 baseExprType: ts.Type 525 ): boolean { 526 if (!(ts.isIdentifier(tsPropertyAccess.name) && tsPropertyAccess.name.text === 'prototype')) { 527 return false; 528 } 529 530 // #13600: Relax prototype check when expression comes from interop. 531 let curPropAccess: ts.Node = tsPropertyAccess; 532 while (curPropAccess && ts.isPropertyAccessExpression(curPropAccess)) { 533 const baseExprSym = this.tsUtils.trueSymbolAtLocation(curPropAccess.expression); 534 if (this.tsUtils.isLibrarySymbol(baseExprSym)) { 535 return false; 536 } 537 curPropAccess = curPropAccess.expression; 538 } 539 540 if (ts.isIdentifier(curPropAccess) && curPropAccess.text !== 'prototype') { 541 const type = this.tsTypeChecker.getTypeAtLocation(curPropAccess); 542 if (TsUtils.isAnyType(type)) { 543 return false; 544 } 545 } 546 547 // Check if property symbol is 'Prototype' 548 if (TsUtils.isPrototypeSymbol(propAccessSym)) { 549 return true; 550 } 551 // Check if symbol of LHS-expression is Class or Function. 552 if (TsUtils.isTypeSymbol(baseExprSym) || TsUtils.isFunctionSymbol(baseExprSym)) { 553 return true; 554 } 555 556 /* 557 * Check if type of LHS expression Function type or Any type. 558 * The latter check is to cover cases with multiple prototype 559 * chain (as the 'Prototype' property should be 'Any' type): 560 * X.prototype.prototype.prototype = ... 561 */ 562 const baseExprTypeNode = this.tsTypeChecker.typeToTypeNode(baseExprType, undefined, ts.NodeBuilderFlags.None); 563 return baseExprTypeNode && ts.isFunctionTypeNode(baseExprTypeNode) || TsUtils.isAnyType(baseExprType); 564 } 565 566 private interfaceInheritanceLint(node: ts.Node, heritageClauses: ts.NodeArray<ts.HeritageClause>): void { 567 for (const hClause of heritageClauses) { 568 if (hClause.token !== ts.SyntaxKind.ExtendsKeyword) { 569 continue; 570 } 571 const prop2type = new Map<string, string>(); 572 for (const tsTypeExpr of hClause.types) { 573 const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); 574 if (tsExprType.isClass()) { 575 this.incrementCounters(tsTypeExpr, FaultID.InterfaceExtendsClass); 576 } else if (tsExprType.isClassOrInterface()) { 577 this.lintForInterfaceExtendsDifferentPorpertyTypes(node, tsExprType, prop2type); 578 } 579 } 580 } 581 } 582 583 private lintForInterfaceExtendsDifferentPorpertyTypes( 584 node: ts.Node, 585 tsExprType: ts.Type, 586 prop2type: Map<string, string> 587 ): void { 588 const props = tsExprType.getProperties(); 589 for (const p of props) { 590 if (!p.declarations) { 591 continue; 592 } 593 const decl: ts.Declaration = p.declarations[0]; 594 const isPropertyDecl = ts.isPropertySignature(decl) || ts.isPropertyDeclaration(decl); 595 const isMethodDecl = ts.isMethodSignature(decl) || ts.isMethodDeclaration(decl); 596 if (isMethodDecl || isPropertyDecl) { 597 this.countInterfaceExtendsDifferentPropertyTypes(node, prop2type, p.name, decl.type); 598 } 599 } 600 } 601 602 private handleObjectLiteralExpression(node: ts.Node): void { 603 const objectLiteralExpr = node as ts.ObjectLiteralExpression; 604 // If object literal is a part of destructuring assignment, then don't process it further. 605 if (TsUtils.isDestructuringAssignmentLHS(objectLiteralExpr)) { 606 return; 607 } 608 609 const objectLiteralType = this.tsTypeChecker.getContextualType(objectLiteralExpr); 610 if (objectLiteralType && this.tsUtils.typeContainsSendableClassOrInterface(objectLiteralType)) { 611 this.incrementCounters(node, FaultID.SendableObjectInitialization); 612 } else if ( 613 // issue 13082: Allow initializing struct instances with object literal. 614 !this.tsUtils.isStructObjectInitializer(objectLiteralExpr) && 615 !this.tsUtils.isDynamicLiteralInitializer(objectLiteralExpr) && 616 !this.tsUtils.isObjectLiteralAssignable(objectLiteralType, objectLiteralExpr) 617 ) { 618 const autofix = this.autofixer?.fixUntypedObjectLiteral(objectLiteralExpr, objectLiteralType); 619 this.incrementCounters(node, FaultID.ObjectLiteralNoContextType, autofix); 620 } 621 622 if (this.options.arkts2) { 623 this.handleObjectLiteralProperties(objectLiteralType, objectLiteralExpr); 624 } 625 } 626 627 static ifValidObjectLiteralProperty( 628 prop: ts.ObjectLiteralElementLike, 629 objLitExpr: ts.ObjectLiteralExpression 630 ): boolean { 631 return ( 632 ts.isPropertyAssignment(prop) || 633 ts.isShorthandPropertyAssignment(prop) && 634 (ts.isCallExpression(objLitExpr.parent) || ts.isNewExpression(objLitExpr.parent)) 635 ); 636 } 637 638 private handleObjectLiteralProperties( 639 objectLiteralType: ts.Type | undefined, 640 objectLiteralExpr: ts.ObjectLiteralExpression 641 ): void { 642 let objLiteralAutofix: Autofix[] | undefined; 643 const invalidProps = objectLiteralExpr.properties.filter((prop) => { 644 return !TypeScriptLinter.ifValidObjectLiteralProperty(prop, objectLiteralExpr); 645 }); 646 647 if ( 648 invalidProps.some((prop) => { 649 return ts.isMethodDeclaration(prop) || ts.isAccessor(prop); 650 }) 651 ) { 652 objLiteralAutofix = this.autofixer?.fixTypedObjectLiteral(objectLiteralExpr, objectLiteralType); 653 } 654 655 for (const prop of invalidProps) { 656 const autofix = ts.isShorthandPropertyAssignment(prop) ? 657 this.autofixer?.fixShorthandPropertyAssignment(prop) : 658 objLiteralAutofix; 659 this.incrementCounters(prop, FaultID.ObjectLiteralProperty, autofix); 660 } 661 } 662 663 private handleArrayLiteralExpression(node: ts.Node): void { 664 665 /* 666 * If array literal is a part of destructuring assignment, then 667 * don't process it further. 668 */ 669 if (TsUtils.isDestructuringAssignmentLHS(node as ts.ArrayLiteralExpression)) { 670 return; 671 } 672 const arrayLitNode = node as ts.ArrayLiteralExpression; 673 const arrayLitType = this.tsTypeChecker.getContextualType(arrayLitNode); 674 if (arrayLitType && this.tsUtils.typeContainsSendableClassOrInterface(arrayLitType)) { 675 this.incrementCounters(node, FaultID.SendableObjectInitialization); 676 return; 677 } 678 679 this.checkArrayElementsAndReportErrors(node, arrayLitNode, arrayLitType); 680 681 this.handleObjectLiteralAssignmentToClass(arrayLitNode); 682 } 683 684 private checkArrayElementsAndReportErrors( 685 node: ts.Node, 686 arrayLitNode: ts.ArrayLiteralExpression, 687 arrayLitType: undefined | ts.Type 688 ): void { 689 const parent = arrayLitNode.parent; 690 const arrayLitElements = arrayLitNode.elements; 691 const arrayElementIsEmpty = arrayLitElements.length === 0; 692 let emptyContextTypeForArrayLiteral = false; 693 694 /* 695 * check that array literal consists of inferrable types 696 * e.g. there is no element which is untyped object literals 697 */ 698 const isPromiseEmptyArray = this.checkPromiseEmptyArray(parent, arrayElementIsEmpty); 699 const isEmptyArray = this.options.arkts2 && !arrayLitType && arrayElementIsEmpty; 700 if (isPromiseEmptyArray) { 701 this.incrementCounters(arrayLitNode, FaultID.NosparseArray); 702 } else if (isEmptyArray) { 703 this.incrementCounters(node, FaultID.NosparseArray); 704 } 705 706 for (const element of arrayLitElements) { 707 const elementContextType = this.tsTypeChecker.getContextualType(element); 708 if (ts.isObjectLiteralExpression(element)) { 709 if ( 710 !this.tsUtils.isDynamicLiteralInitializer(arrayLitNode) && 711 !this.tsUtils.isObjectLiteralAssignable(elementContextType, element) 712 ) { 713 emptyContextTypeForArrayLiteral = true; 714 break; 715 } 716 } 717 if (elementContextType) { 718 this.checkAssignmentMatching(element, elementContextType, element, true); 719 } 720 if (this.options.arkts2 && ts.isOmittedExpression(element)) { 721 this.incrementCounters(element, FaultID.NosparseArray); 722 } 723 } 724 if (emptyContextTypeForArrayLiteral) { 725 this.incrementCounters(node, FaultID.ArrayLiteralNoContextType); 726 } 727 } 728 729 private checkPromiseEmptyArray(parent: ts.Node, arrayElementIsEmpty: boolean): boolean { 730 if (this.options.arkts2 && ts.isCallExpression(parent) && arrayElementIsEmpty) { 731 const callExpr = parent; 732 const methodName = TypeScriptLinter.getPromiseMethodName(callExpr.expression); 733 if (methodName && PROMISE_METHODS.has(methodName)) { 734 return true; 735 } 736 return false; 737 } 738 return false; 739 } 740 741 private static getPromiseMethodName(node: ts.Expression): string | undefined { 742 if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'Promise') { 743 return node.name.text; 744 } 745 return undefined; 746 } 747 748 private handleStructDeclaration(node: ts.StructDeclaration): void { 749 if (!this.options.arkts2) { 750 return; 751 } 752 this.handleStructDeclarationForLayout(node); 753 this.handleInvalidIdentifier(node); 754 } 755 756 private handleParameter(node: ts.Node): void { 757 const tsParam = node as ts.ParameterDeclaration; 758 TsUtils.getDecoratorsIfInSendableClass(tsParam)?.forEach((decorator) => { 759 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 760 }); 761 this.handleDeclarationDestructuring(tsParam); 762 this.handleDeclarationInferredType(tsParam); 763 this.handleInvalidIdentifier(tsParam); 764 this.handleSdkGlobalApi(tsParam); 765 const typeNode = tsParam.type; 766 if (this.options.arkts2 && typeNode && TsUtils.typeContainsVoid(typeNode)) { 767 const autofix = this.autofixer?.fixLimitedVoidType(tsParam); 768 this.incrementCounters(typeNode, FaultID.LimitedVoidType, autofix); 769 } 770 this.handlePropertyDescriptorInScenarios(tsParam); 771 } 772 773 private handleEnumDeclaration(node: ts.Node): void { 774 const enumNode = node as ts.EnumDeclaration; 775 this.countDeclarationsWithDuplicateName(enumNode.name, enumNode); 776 const enumSymbol = this.tsUtils.trueSymbolAtLocation(enumNode.name); 777 if (!enumSymbol) { 778 return; 779 } 780 const enumDecls = enumSymbol.getDeclarations(); 781 if (!enumDecls) { 782 return; 783 } 784 if (this.options.arkts2) { 785 this.handleInvalidIdentifier(enumNode); 786 } 787 788 /* 789 * Since type checker merges all declarations with the same name 790 * into one symbol, we need to check that there's more than one 791 * enum declaration related to that specific symbol. 792 * See 'countDeclarationsWithDuplicateName' method for details. 793 */ 794 let enumDeclCount = 0; 795 const enumDeclsInFile: ts.Declaration[] = []; 796 const nodeSrcFile = enumNode.getSourceFile(); 797 for (const decl of enumDecls) { 798 if (decl.kind === ts.SyntaxKind.EnumDeclaration) { 799 if (nodeSrcFile === decl.getSourceFile()) { 800 enumDeclsInFile.push(decl); 801 } 802 enumDeclCount++; 803 } 804 } 805 806 if (enumDeclCount > 1) { 807 const autofix = this.autofixer?.fixEnumMerging(enumSymbol, enumDeclsInFile); 808 this.incrementCounters(node, FaultID.EnumMerging, autofix); 809 } 810 } 811 812 private handleInterfaceDeclaration(node: ts.Node): void { 813 // early exit via exception if cancellation was requested 814 this.options.cancellationToken?.throwIfCancellationRequested(); 815 816 const interfaceNode = node as ts.InterfaceDeclaration; 817 818 if (this.options.arkts2) { 819 this.handleInvalidIdentifier(interfaceNode); 820 } 821 822 const iSymbol = this.tsUtils.trueSymbolAtLocation(interfaceNode.name); 823 const iDecls = iSymbol ? iSymbol.getDeclarations() : null; 824 if (iDecls) { 825 826 /* 827 * Since type checker merges all declarations with the same name 828 * into one symbol, we need to check that there's more than one 829 * interface declaration related to that specific symbol. 830 * See 'countDeclarationsWithDuplicateName' method for details. 831 */ 832 let iDeclCount = 0; 833 for (const decl of iDecls) { 834 if (decl.kind === ts.SyntaxKind.InterfaceDeclaration) { 835 iDeclCount++; 836 } 837 } 838 if (iDeclCount > 1) { 839 this.incrementCounters(node, FaultID.InterfaceMerging); 840 } 841 } 842 if (interfaceNode.heritageClauses) { 843 this.interfaceInheritanceLint(node, interfaceNode.heritageClauses); 844 } 845 this.countDeclarationsWithDuplicateName(interfaceNode.name, interfaceNode); 846 } 847 848 private handleTryStatement(node: ts.TryStatement): void { 849 if (!this.options.arkts2) { 850 return; 851 } 852 853 for (const stmt of node.tryBlock.statements) { 854 if (!ts.isExpressionStatement(stmt)) { 855 continue; 856 } 857 const callExpr = stmt.expression; 858 if (!ts.isCallExpression(callExpr)) { 859 continue; 860 } 861 const ident = callExpr.expression; 862 if (!ts.isIdentifier(ident)) { 863 continue; 864 } 865 866 this.handleTsInterop(ident, () => { 867 this.tsFunctionInteropHandler(callExpr); 868 }); 869 870 this.handleJsInterop(ident, () => { 871 this.jsFunctionInteropHandler(callExpr); 872 }); 873 } 874 } 875 876 private tsFunctionInteropHandler(callExpr: ts.CallExpression): void { 877 this.checkInteropFunctionThrows(callExpr, FaultID.InteropTSFunctionInvoke); 878 } 879 880 private jsFunctionInteropHandler(callExpr: ts.CallExpression): void { 881 this.checkInteropFunctionThrows(callExpr, FaultID.InteropJSFunctionInvoke); 882 } 883 884 private checkInteropFunctionThrows(callExpr: ts.CallExpression, faultId: FaultID): void { 885 const signature = this.tsTypeChecker.getResolvedSignature(callExpr); 886 if (!signature) { 887 return; 888 } 889 890 if (!signature.declaration) { 891 return; 892 } 893 894 const functionSymbol = this.getFunctionSymbol(signature.declaration); 895 const functionDeclaration = functionSymbol?.valueDeclaration; 896 if (!functionDeclaration) { 897 return; 898 } 899 900 if (!TypeScriptLinter.isFunctionLike(functionDeclaration)) { 901 return; 902 } 903 if (this.containsThrowNonError(functionDeclaration)) { 904 this.incrementCounters(callExpr, faultId); 905 } 906 } 907 908 private containsThrowNonError(node: ts.FunctionDeclaration | ts.MethodDeclaration | ts.FunctionExpression): boolean { 909 if (!node.body) { 910 return false; 911 } 912 913 const statements = node.body.statements; 914 for (const stmt of statements) { 915 if (!ts.isThrowStatement(stmt)) { 916 continue; 917 } 918 return this.tsUtils.checkStatementForErrorClass(stmt); 919 } 920 return false; 921 } 922 923 private handleThrowStatement(node: ts.Node): void { 924 const throwStmt = node as ts.ThrowStatement; 925 const throwExprType = this.tsTypeChecker.getTypeAtLocation(throwStmt.expression); 926 if ( 927 !throwExprType.isClassOrInterface() || 928 !this.tsUtils.isOrDerivedFrom(throwExprType, this.tsUtils.isStdErrorType) 929 ) { 930 this.incrementCounters(node, FaultID.ThrowStatement); 931 } 932 } 933 934 private checkForLoopDestructuring(forInit: ts.ForInitializer): void { 935 if (ts.isVariableDeclarationList(forInit) && forInit.declarations.length === 1) { 936 const varDecl = forInit.declarations[0]; 937 if ( 938 this.options.useRtLogic && 939 (ts.isArrayBindingPattern(varDecl.name) || ts.isObjectBindingPattern(varDecl.name)) 940 ) { 941 this.incrementCounters(varDecl, FaultID.DestructuringDeclaration); 942 } 943 } 944 if (ts.isArrayLiteralExpression(forInit) || ts.isObjectLiteralExpression(forInit)) { 945 this.incrementCounters(forInit, FaultID.DestructuringAssignment); 946 } 947 } 948 949 /* 950 * this should report the point of access to the array 951 * and also should report the identifier type 952 */ 953 private checkElementAccessOfArray(statement: ts.Node): ArrayAccess | false { 954 if (ts.isElementAccessExpression(statement)) { 955 return this.isElementAccessOfArray(statement); 956 } 957 958 for (const children of statement.getChildren()) { 959 return this.checkElementAccessOfArray(children); 960 } 961 return false; 962 } 963 964 private isElementAccessOfArray(expr: ts.ElementAccessExpression): false | ArrayAccess { 965 if (!ts.isIdentifier(expr.expression)) { 966 return false; 967 } 968 const type = this.tsTypeChecker.getTypeAtLocation(expr.expression); 969 if (!this.tsUtils.isArray(type)) { 970 return false; 971 } 972 const accessArgument = expr.argumentExpression; 973 if (ts.isNumericLiteral(accessArgument)) { 974 return { 975 pos: expr.getEnd(), 976 accessingIdentifier: NUMBER_LITERAL, 977 arrayIdent: expr.expression 978 }; 979 } 980 981 if (ts.isIdentifier(accessArgument)) { 982 return { 983 pos: expr.getEnd(), 984 accessingIdentifier: accessArgument, 985 arrayIdent: expr.expression 986 }; 987 } 988 return false; 989 } 990 991 private handleForStatement(node: ts.Node): void { 992 const tsForStmt = node as ts.ForStatement; 993 const tsForInit = tsForStmt.initializer; 994 if (tsForInit) { 995 this.checkForLoopDestructuring(tsForInit); 996 } 997 } 998 999 private checkConditionForArrayAccess(condition: ts.Expression, arraySymbol: ts.Symbol): UncheckedIdentifier { 1000 if (!ts.isBinaryExpression(condition)) { 1001 return undefined; 1002 } 1003 const { left, right } = condition; 1004 1005 if (ts.isBinaryExpression(left)) { 1006 return this.checkConditionForArrayAccess(left, arraySymbol); 1007 } 1008 if (ts.isBinaryExpression(right)) { 1009 return this.checkConditionForArrayAccess(right, arraySymbol); 1010 } 1011 1012 if (this.isArrayLengthAccess(left, arraySymbol)) { 1013 if (ts.isNumericLiteral(right)) { 1014 return NUMBER_LITERAL; 1015 } 1016 if (!ts.isIdentifier(right)) { 1017 return undefined; 1018 } 1019 return right; 1020 } 1021 1022 if (this.isArrayLengthAccess(right, arraySymbol)) { 1023 if (ts.isNumericLiteral(left)) { 1024 return NUMBER_LITERAL; 1025 } 1026 if (!ts.isIdentifier(left)) { 1027 return undefined; 1028 } 1029 return left; 1030 } 1031 1032 return undefined; 1033 } 1034 1035 private isArrayLengthAccess(expr: ts.Expression, arraySymbol: ts.Symbol): boolean { 1036 if (!ts.isPropertyAccessExpression(expr)) { 1037 return false; 1038 } 1039 if (this.tsUtils.trueSymbolAtLocation(expr.expression) !== arraySymbol) { 1040 return false; 1041 } 1042 if (expr.name.text !== 'length') { 1043 return false; 1044 } 1045 1046 return true; 1047 } 1048 1049 private checkBodyHasArrayAccess(loopBody: ts.Block): ArrayAccess | undefined { 1050 let arrayAccessResult: undefined | ArrayAccess; 1051 // check if this element access expression is of an array. 1052 for (const child of loopBody.statements) { 1053 const result = this.checkElementAccessOfArray(child); 1054 if (!result) { 1055 continue; 1056 } 1057 arrayAccessResult = result; 1058 } 1059 return arrayAccessResult; 1060 } 1061 1062 private handleForInStatement(node: ts.Node): void { 1063 const tsForInStmt = node as ts.ForInStatement; 1064 const tsForInInit = tsForInStmt.initializer; 1065 this.checkForLoopDestructuring(tsForInInit); 1066 this.incrementCounters(node, FaultID.ForInStatement); 1067 } 1068 1069 private handleForOfStatement(node: ts.Node): void { 1070 const tsForOfStmt = node as ts.ForOfStatement; 1071 const tsForOfInit = tsForOfStmt.initializer; 1072 this.checkForLoopDestructuring(tsForOfInit); 1073 this.handleForOfJsArray(tsForOfStmt); 1074 } 1075 1076 private updateDataSdkJsonInfo(importDeclNode: ts.ImportDeclaration, importClause: ts.ImportClause): void { 1077 const sdkInfo = TypeScriptLinter.pathMap.get(importDeclNode.moduleSpecifier.getText()); 1078 if (!sdkInfo) { 1079 return; 1080 } 1081 if (importClause.name) { 1082 const importClauseName = importClause.name.text; 1083 sdkInfo.forEach((info) => { 1084 TypeScriptLinter.addOrUpdateData(this.interfaceMap, importClauseName, info); 1085 }); 1086 } 1087 if (importClause.namedBindings) { 1088 const namedImports = importClause.namedBindings as ts.NamedImports; 1089 if (!namedImports.elements) { 1090 return; 1091 } 1092 namedImports.elements.forEach((element) => { 1093 const elementName = element.name.getText(); 1094 sdkInfo.forEach((info) => { 1095 TypeScriptLinter.addOrUpdateData(this.interfaceMap, elementName, info); 1096 }); 1097 }); 1098 } 1099 } 1100 1101 private handleImportDeclaration(node: ts.Node): void { 1102 // early exit via exception if cancellation was requested 1103 this.options.cancellationToken?.throwIfCancellationRequested(); 1104 const importDeclNode = node as ts.ImportDeclaration; 1105 this.handleImportModule(importDeclNode); 1106 if (this.options.arkts2) { 1107 const importClause = importDeclNode.importClause; 1108 if (!importClause || !importClause.name && !importClause.namedBindings) { 1109 this.incrementCounters(node, FaultID.NoSideEffectImport); 1110 } else { 1111 this.updateDataSdkJsonInfo(importDeclNode, importClause); 1112 } 1113 } 1114 if (importDeclNode.parent.statements) { 1115 for (const stmt of importDeclNode.parent.statements) { 1116 if (stmt === importDeclNode) { 1117 break; 1118 } 1119 if (!ts.isImportDeclaration(stmt)) { 1120 this.incrementCounters(node, FaultID.ImportAfterStatement); 1121 break; 1122 } 1123 } 1124 } 1125 1126 const expr = importDeclNode.moduleSpecifier; 1127 if (expr.kind === ts.SyntaxKind.StringLiteral) { 1128 if (importDeclNode.assertClause) { 1129 this.incrementCounters(importDeclNode.assertClause, FaultID.ImportAssertion); 1130 } 1131 const stringLiteral = expr as ts.StringLiteral; 1132 this.handleSdkSendable(stringLiteral); 1133 } 1134 1135 // handle no side effect import in sendable module 1136 this.handleSharedModuleNoSideEffectImport(importDeclNode); 1137 this.handleInvalidIdentifier(importDeclNode); 1138 this.checkStdLibConcurrencyImport(importDeclNode); 1139 this.handleInterOpImportJs(importDeclNode); 1140 this.checkForDeprecatedModules(node); 1141 } 1142 1143 private checkForDeprecatedModules(node: ts.Node): void { 1144 if (!ts.isImportDeclaration(node)) { 1145 return; 1146 } 1147 1148 const deprecatedModules = ['@ohos.file.sendablePhotoAccessHelper']; 1149 1150 const importDecl = node; 1151 const moduleSpecifier = importDecl.moduleSpecifier; 1152 1153 if (ts.isStringLiteral(moduleSpecifier)) { 1154 const moduleName = moduleSpecifier.text; 1155 if (deprecatedModules.includes(moduleName)) { 1156 this.incrementCounters(moduleSpecifier, FaultID.SdkTypeQuery); 1157 } 1158 } 1159 } 1160 1161 private handleSdkSendable(tsStringLiteral: ts.StringLiteral): void { 1162 if (!this.options.arkts2) { 1163 return; 1164 } 1165 1166 const moduleSpecifierValue = tsStringLiteral.getText(); 1167 const sdkInfos = TypeScriptLinter.pathMap.get(moduleSpecifierValue); 1168 1169 if (!sdkInfos || sdkInfos.size === 0) { 1170 return; 1171 } 1172 if (moduleSpecifierValue.includes('sendable')) { 1173 this.incrementCounters(tsStringLiteral, FaultID.SendablePropTypeFromSdk); 1174 } 1175 } 1176 1177 private handleImportModule(importDeclNode: ts.ImportDeclaration): void { 1178 if (!this.options.arkts2) { 1179 return; 1180 } 1181 1182 const modulePath = importDeclNode.moduleSpecifier.getText().slice(1, -1); 1183 if (modulePath.startsWith('./') || modulePath.startsWith('../')) { 1184 1185 /* 1186 * Reason for this method to check the oh module imports, 1187 * We do not use relative paths when importing from OhModules, 1188 * So we do not check the relative paths 1189 */ 1190 return; 1191 } 1192 if (!importDeclNode.importClause) { 1193 return; 1194 } 1195 1196 const pathParts = modulePath.split(PATH_SEPARATOR); 1197 const etsIdx = pathParts.indexOf(ETS_PART); 1198 1199 if (this.options.wholeProjectPath) { 1200 if (TsUtils.checkFileExists(etsIdx !== 0, importDeclNode, modulePath, this.options.wholeProjectPath)) { 1201 return; 1202 } 1203 } 1204 1205 if (TsUtils.isValidOhModulePath(modulePath) || !TsUtils.isOhModule(modulePath)) { 1206 // Valid or paths that we do not check because they are not ohModules 1207 return; 1208 } 1209 1210 if (etsIdx === 0) { 1211 const autofix = this.autofixer?.addDefaultModuleToPath(pathParts, importDeclNode); 1212 this.incrementCounters(importDeclNode, FaultID.OhmUrlFullPath, autofix); 1213 return; 1214 } 1215 1216 const autofix = this.autofixer?.fixImportPath(pathParts, etsIdx, importDeclNode); 1217 this.incrementCounters(importDeclNode, FaultID.OhmUrlFullPath, autofix); 1218 } 1219 1220 private handleSharedModuleNoSideEffectImport(node: ts.ImportDeclaration): void { 1221 // check 'use shared' 1222 if (TypeScriptLinter.inSharedModule(node) && !node.importClause) { 1223 this.incrementCounters(node, FaultID.SharedNoSideEffectImport); 1224 } 1225 } 1226 1227 private static inSharedModule(node: ts.Node): boolean { 1228 const sourceFile: ts.SourceFile = node.getSourceFile(); 1229 const modulePath = path.normalize(sourceFile.fileName); 1230 if (TypeScriptLinter.sharedModulesCache.has(modulePath)) { 1231 return TypeScriptLinter.sharedModulesCache.get(modulePath)!; 1232 } 1233 const isSharedModule: boolean = TsUtils.isSharedModule(sourceFile); 1234 TypeScriptLinter.sharedModulesCache.set(modulePath, isSharedModule); 1235 return isSharedModule; 1236 } 1237 1238 private handlePropertyAccessExpression(node: ts.Node): void { 1239 this.handleMakeObserved(node as ts.PropertyAccessExpression); 1240 this.handleStateStyles(node as ts.PropertyAccessExpression); 1241 this.handleDoubleDollar(node); 1242 this.handleQuotedHyphenPropsDeprecated(node as ts.PropertyAccessExpression); 1243 this.handleSdkTypeQuery(node as ts.PropertyAccessExpression); 1244 this.checkUnionTypes(node as ts.PropertyAccessExpression); 1245 this.handleLimitedVoidTypeFromSdkOnPropertyAccessExpression(node as ts.PropertyAccessExpression); 1246 this.checkDepricatedIsConcurrent(node as ts.PropertyAccessExpression); 1247 this.propertyAccessExpressionForBuiltin(node as ts.PropertyAccessExpression); 1248 this.checkConstrutorAccess(node as ts.PropertyAccessExpression); 1249 this.handleTaskPoolDeprecatedUsages(node as ts.PropertyAccessExpression); 1250 this.handleNoTuplesArraysForPropertyAccessExpression(node as ts.PropertyAccessExpression); 1251 if (ts.isCallExpression(node.parent) && node === node.parent.expression) { 1252 return; 1253 } 1254 const propertyAccessNode = node as ts.PropertyAccessExpression; 1255 const exprSym = this.tsUtils.trueSymbolAtLocation(propertyAccessNode); 1256 const baseExprSym = this.tsUtils.trueSymbolAtLocation(propertyAccessNode.expression); 1257 const baseExprType = this.tsTypeChecker.getTypeAtLocation(propertyAccessNode.expression); 1258 this.handleTsInterop(propertyAccessNode, () => { 1259 const type = this.tsTypeChecker.getTypeAtLocation(propertyAccessNode.expression); 1260 this.checkUsageOfTsTypes(type, propertyAccessNode.expression); 1261 }); 1262 this.propertyAccessExpressionForInterop(propertyAccessNode); 1263 if (this.isPrototypePropertyAccess(propertyAccessNode, exprSym, baseExprSym, baseExprType)) { 1264 this.incrementCounters(propertyAccessNode.name, FaultID.Prototype); 1265 } 1266 if ( 1267 !this.options.arkts2 && 1268 !!exprSym && 1269 this.tsUtils.isStdSymbolAPI(exprSym) && 1270 !ALLOWED_STD_SYMBOL_API.includes(exprSym.getName()) 1271 ) { 1272 this.incrementCounters(propertyAccessNode, FaultID.SymbolType); 1273 } 1274 if (this.options.advancedClassChecks && this.tsUtils.isClassObjectExpression(propertyAccessNode.expression)) { 1275 this.incrementCounters(propertyAccessNode.expression, FaultID.ClassAsObject); 1276 } 1277 if (!!baseExprSym && TsUtils.symbolHasEsObjectType(baseExprSym)) { 1278 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 1279 this.incrementCounters(propertyAccessNode, faultId); 1280 } 1281 if (TsUtils.isSendableFunction(baseExprType) || this.tsUtils.hasSendableTypeAlias(baseExprType)) { 1282 this.incrementCounters(propertyAccessNode, FaultID.SendableFunctionProperty); 1283 } 1284 this.checkFunctionProperty(propertyAccessNode, baseExprSym, baseExprType); 1285 this.handleSdkForConstructorFuncs(propertyAccessNode); 1286 this.fixJsImportPropertyAccessExpression(node); 1287 } 1288 1289 propertyAccessExpressionForBuiltin(decl: ts.PropertyAccessExpression): void { 1290 if (this.options.arkts2) { 1291 this.handleSymbolIterator(decl); 1292 this.handleGetOwnPropertyNames(decl); 1293 this.handlePropertyDescriptorInScenarios(decl); 1294 } 1295 } 1296 1297 private isJsRelated(node: ts.Expression): boolean { 1298 if (this.tsUtils.isJsImport(node)) { 1299 return true; 1300 } 1301 1302 if (ts.isNewExpression(node)) { 1303 return this.tsUtils.isJsImport(node.expression); 1304 } 1305 1306 if (ts.isIdentifier(node)) { 1307 const symbol = this.tsUtils.trueSymbolAtLocation(node); 1308 if (!symbol) { 1309 return false; 1310 } 1311 1312 const declarations = symbol.getDeclarations(); 1313 if (!declarations || declarations.length === 0) { 1314 return false; 1315 } 1316 1317 for (const declaration of declarations) { 1318 if (ts.isVariableDeclaration(declaration) && declaration.initializer) { 1319 return this.isJsRelated(declaration.initializer); 1320 } 1321 } 1322 } 1323 1324 return false; 1325 } 1326 1327 propertyAccessExpressionForInterop(propertyAccessNode: ts.PropertyAccessExpression): void { 1328 if (!this.useStatic || !this.options.arkts2) { 1329 return; 1330 } 1331 1332 const getFirstObjectNode = (propertyAccessNode: ts.PropertyAccessExpression): ts.Expression => { 1333 let current: ts.Expression = propertyAccessNode.expression; 1334 while (ts.isPropertyAccessExpression(current)) { 1335 current = current.expression; 1336 } 1337 1338 return current; 1339 }; 1340 1341 const firstObjNode = getFirstObjectNode(propertyAccessNode); 1342 const isJsObject = this.isJsRelated(firstObjNode); 1343 if (!isJsObject) { 1344 return; 1345 } 1346 1347 if (ts.isBinaryExpression(propertyAccessNode.parent)) { 1348 const isAssignment = propertyAccessNode.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken; 1349 const autofix = isAssignment ? 1350 this.autofixer?.fixInteropBinaryExpression(propertyAccessNode.parent) : 1351 this.autofixer?.fixInteropPropertyAccessExpression(propertyAccessNode); 1352 1353 this.incrementCounters( 1354 isAssignment ? propertyAccessNode.parent : propertyAccessNode, 1355 FaultID.InteropObjectProperty, 1356 autofix 1357 ); 1358 } else { 1359 const autofix = this.autofixer?.fixInteropPropertyAccessExpression(propertyAccessNode); 1360 this.incrementCounters(propertyAccessNode, FaultID.InteropObjectProperty, autofix); 1361 } 1362 } 1363 1364 private checkDepricatedIsConcurrent(node: ts.PropertyAccessExpression): void { 1365 if (!this.options.arkts2) { 1366 return; 1367 } 1368 if (!ts.isCallExpression(node.parent)) { 1369 return; 1370 } 1371 const methodName = node.name.getText(); 1372 1373 if (methodName !== ISCONCURRENT) { 1374 return; 1375 } 1376 const symbol = this.tsUtils.trueSymbolAtLocation(node.expression); 1377 if (!symbol) { 1378 return; 1379 } 1380 if (symbol.name === TASKPOOL) { 1381 const decl = TsUtils.getDeclaration(symbol); 1382 1383 if (!decl) { 1384 return; 1385 } 1386 1387 const sourceFile = decl.getSourceFile(); 1388 const fileName = path.basename(sourceFile.fileName); 1389 1390 if ( 1391 TASKPOOL_MODULES.some((moduleName) => { 1392 return fileName.startsWith(moduleName) && (fileName.endsWith(D_TS) || fileName.endsWith(D_ETS)); 1393 }) 1394 ) { 1395 this.incrementCounters(node.name, FaultID.IsConcurrentDeprecated); 1396 } 1397 } 1398 } 1399 1400 checkFunctionProperty( 1401 node: ts.PropertyAccessExpression, 1402 baseExprSym: ts.Symbol | undefined, 1403 baseExprType: ts.Type 1404 ): void { 1405 if (!this.options.arkts2) { 1406 return; 1407 } 1408 1409 if ( 1410 baseExprSym && TsUtils.isFunctionSymbol(baseExprSym) || 1411 this.tsUtils.isStdFunctionType(baseExprType) || 1412 TsUtils.isFunctionalType(baseExprType) && TsUtils.isAnonymousType(baseExprType) 1413 ) { 1414 this.incrementCounters(node.expression, FaultID.PropertyDeclOnFunction); 1415 } 1416 } 1417 1418 private checkUsageOfTsTypes(baseType: ts.Type, node: ts.Node): void { 1419 const typeString = this.tsTypeChecker.typeToString(baseType); 1420 if ( 1421 TsUtils.isAnyType(baseType) || 1422 TsUtils.isUnknownType(baseType) || 1423 this.tsUtils.isStdFunctionType(baseType) || 1424 typeString === 'symbol' 1425 ) { 1426 this.incrementCounters(node, FaultID.InteropDirectAccessToTSTypes); 1427 } 1428 } 1429 1430 checkUnionTypes(propertyAccessNode: ts.PropertyAccessExpression): void { 1431 if (!this.options.arkts2) { 1432 return; 1433 } 1434 const baseExprType = this.tsTypeChecker.getTypeAtLocation(propertyAccessNode.expression); 1435 if (!baseExprType.isUnion() || this.tsTypeChecker.typeToString(baseExprType) === 'ArrayBufferLike') { 1436 return; 1437 } 1438 const allType = baseExprType.types; 1439 const commonPropertyType = allType.filter((type) => { 1440 return this.tsUtils.findProperty(type, propertyAccessNode.name.getText()) !== undefined; 1441 }); 1442 const typeMap = new Map(); 1443 if (commonPropertyType.length === allType.length) { 1444 allType.forEach((type) => { 1445 this.handleTypeMember(type, propertyAccessNode.name.getText(), typeMap); 1446 }); 1447 if (typeMap.size > 1) { 1448 this.incrementCounters(propertyAccessNode, FaultID.AvoidUnionTypes); 1449 } 1450 } 1451 } 1452 1453 private handleTypeMember( 1454 type: ts.Type, 1455 memberName: string, 1456 typeMap: Map<string | ts.Type | undefined, string> 1457 ): void { 1458 const propertySymbol = this.tsUtils.findProperty(type, memberName); 1459 if (!propertySymbol?.declarations) { 1460 return; 1461 } 1462 const propertyType = this.tsTypeChecker.getTypeOfSymbolAtLocation(propertySymbol, propertySymbol.declarations[0]); 1463 const symbol = propertySymbol.valueDeclaration; 1464 if (!symbol) { 1465 return; 1466 } 1467 if (ts.isMethodDeclaration(symbol)) { 1468 const returnType = this.getMethodReturnType(propertySymbol); 1469 typeMap.set(returnType, memberName); 1470 } else { 1471 typeMap.set(propertyType, memberName); 1472 } 1473 } 1474 1475 private getMethodReturnType(symbol: ts.Symbol): string | undefined { 1476 const declaration = symbol.valueDeclaration ?? (symbol.declarations?.[0] as ts.Node | undefined); 1477 if (!declaration) { 1478 return undefined; 1479 } 1480 const methodType = this.tsTypeChecker.getTypeOfSymbolAtLocation(symbol, declaration); 1481 const signatures = methodType.getCallSignatures(); 1482 if (signatures.length === 0) { 1483 return 'void'; 1484 } 1485 const returnType = signatures[0].getReturnType(); 1486 return this.tsTypeChecker.typeToString(returnType); 1487 } 1488 1489 private handleLiteralAsPropertyName(node: ts.PropertyDeclaration | ts.PropertySignature): void { 1490 const propName = node.name; 1491 if (!!propName && (ts.isNumericLiteral(propName) || this.options.arkts2 && ts.isStringLiteral(propName))) { 1492 const autofix = this.autofixer?.fixLiteralAsPropertyNamePropertyName(propName); 1493 this.incrementCounters(node.name, FaultID.LiteralAsPropertyName, autofix); 1494 } 1495 } 1496 1497 private handlePropertyDeclaration(node: ts.PropertyDeclaration): void { 1498 const propName = node.name; 1499 this.handleLiteralAsPropertyName(node); 1500 const decorators = ts.getDecorators(node); 1501 this.filterOutDecoratorsDiagnostics( 1502 decorators, 1503 this.options.useRtLogic ? NON_INITIALIZABLE_PROPERTY_DECORATORS : NON_INITIALIZABLE_PROPERTY_DECORATORS_TSC, 1504 { begin: propName.getStart(), end: propName.getStart() }, 1505 PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE 1506 ); 1507 const classDecorators = ts.getDecorators(node.parent); 1508 const propType = node.type?.getText(); 1509 if (this.options.arkts2 && node.type && TsUtils.typeContainsVoid(node.type)) { 1510 const autofix = this.autofixer?.fixLimitedVoidType(node); 1511 this.incrementCounters(node.type, FaultID.LimitedVoidType, autofix); 1512 } 1513 this.filterOutDecoratorsDiagnostics( 1514 classDecorators, 1515 NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS, 1516 { begin: propName.getStart(), end: propName.getStart() }, 1517 PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE, 1518 propType 1519 ); 1520 if (node.type && node.initializer) { 1521 this.checkAssignmentMatching(node, this.tsTypeChecker.getTypeAtLocation(node.type), node.initializer, true); 1522 this.checkFunctionTypeCompatible(node.type, node.initializer); 1523 } 1524 this.handleDeclarationInferredType(node); 1525 this.handleDefiniteAssignmentAssertion(node); 1526 this.handleSendableClassProperty(node); 1527 this.checkAssignmentNumericSemanticslyPro(node); 1528 this.handleInvalidIdentifier(node); 1529 this.handleStructPropertyDecl(node); 1530 this.handlePropertyDeclarationForProp(node); 1531 this.handleSdkGlobalApi(node); 1532 this.handleObjectLiteralAssignmentToClass(node); 1533 } 1534 1535 private handleSendableClassProperty(node: ts.PropertyDeclaration): void { 1536 const classNode = node.parent; 1537 if (!ts.isClassDeclaration(classNode) || !TsUtils.hasSendableDecorator(classNode)) { 1538 return; 1539 } 1540 const typeNode = node.type; 1541 if (!typeNode) { 1542 const autofix = this.autofixer?.fixSendableExplicitFieldType(node); 1543 this.incrementCounters(node, FaultID.SendableExplicitFieldType, autofix); 1544 return; 1545 } 1546 TsUtils.getDecoratorsIfInSendableClass(node)?.forEach((decorator) => { 1547 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 1548 }); 1549 if (!this.tsUtils.isSendableTypeNode(typeNode)) { 1550 this.incrementCounters(node, FaultID.SendablePropType); 1551 } 1552 } 1553 1554 private handlePropertyAssignment(node: ts.PropertyAssignment): void { 1555 this.handleDollarBind(node); 1556 this.handlePropertyAssignmentForProp(node); 1557 1558 this.handleQuotedHyphenPropsDeprecated(node); 1559 const propName = node.name; 1560 if (!propName || !(ts.isNumericLiteral(propName) || this.options.arkts2 && ts.isStringLiteral(propName))) { 1561 return; 1562 } 1563 1564 /* 1565 * We can use literals as property names only when creating Record or any interop instances. 1566 * We can also initialize with constant string literals. 1567 * Assignment with string enum values is handled in handleComputedPropertyName 1568 */ 1569 let isRecordObjectInitializer = false; 1570 let isLibraryType = false; 1571 let isDynamic = false; 1572 const objectLiteralType = this.tsTypeChecker.getContextualType(node.parent); 1573 if (objectLiteralType) { 1574 isRecordObjectInitializer = this.tsUtils.checkTypeSet(objectLiteralType, this.tsUtils.isStdRecordType); 1575 isLibraryType = this.tsUtils.isLibraryType(objectLiteralType); 1576 } 1577 1578 isDynamic = isLibraryType || this.tsUtils.isDynamicLiteralInitializer(node.parent); 1579 if (!isRecordObjectInitializer && !isDynamic) { 1580 const autofix = this.autofixer?.fixLiteralAsPropertyNamePropertyAssignment(node); 1581 this.incrementCounters(node.name, FaultID.LiteralAsPropertyName, autofix); 1582 } 1583 } 1584 1585 private static getAllClassesFromSourceFile(sourceFile: ts.SourceFile): ts.ClassDeclaration[] { 1586 const allClasses: ts.ClassDeclaration[] = []; 1587 function visit(node: ts.Node): void { 1588 if (ts.isClassDeclaration(node)) { 1589 allClasses.push(node); 1590 } 1591 ts.forEachChild(node, visit); 1592 } 1593 visit(sourceFile); 1594 return allClasses; 1595 } 1596 1597 private static getAllInterfaceFromSourceFile(sourceFile: ts.SourceFile): ts.InterfaceDeclaration[] { 1598 const allInterfaces: ts.InterfaceDeclaration[] = []; 1599 function visit(node: ts.Node): void { 1600 if (ts.isInterfaceDeclaration(node)) { 1601 allInterfaces.push(node); 1602 } 1603 ts.forEachChild(node, visit); 1604 } 1605 visit(sourceFile); 1606 return allInterfaces; 1607 } 1608 1609 private handlePropertySignature(node: ts.PropertySignature): void { 1610 this.handleInterfaceProperty(node); 1611 this.handleLiteralAsPropertyName(node); 1612 this.handleSendableInterfaceProperty(node); 1613 this.handleInvalidIdentifier(node); 1614 const typeNode = node.type; 1615 if (this.options.arkts2 && typeNode && typeNode.kind === ts.SyntaxKind.VoidKeyword) { 1616 this.incrementCounters(typeNode, FaultID.LimitedVoidType); 1617 } 1618 } 1619 1620 private handleInterfaceProperty(node: ts.PropertySignature): void { 1621 if (this.options.arkts2 && ts.isInterfaceDeclaration(node.parent)) { 1622 if (node.type && ts.isFunctionTypeNode(node.type)) { 1623 const interfaceName = node.parent.name.getText(); 1624 const propertyName = node.name.getText(); 1625 const allClasses = TypeScriptLinter.getAllClassesFromSourceFile(this.sourceFile); 1626 const allInterfaces = TypeScriptLinter.getAllInterfaceFromSourceFile(this.sourceFile); 1627 this.visitClassMembers(allClasses, interfaceName, propertyName); 1628 this.visitInterfaceMembers(allInterfaces, interfaceName, propertyName); 1629 } 1630 } 1631 } 1632 1633 private visitInterfaceMembers( 1634 interfaces: ts.InterfaceDeclaration[], 1635 interfaceName: string, 1636 propertyName: string 1637 ): void { 1638 void this; 1639 interfaces.some((interfaceDecl) => { 1640 const implementsClause = this.getExtendsClause(interfaceDecl); 1641 if ( 1642 implementsClause?.types.some((type) => { 1643 return type.expression.getText() === interfaceName; 1644 }) 1645 ) { 1646 this.checkInterfaceForProperty(interfaceDecl, propertyName); 1647 } 1648 }); 1649 } 1650 1651 private getExtendsClause(interfaceDecl: ts.InterfaceDeclaration): ts.HeritageClause | undefined { 1652 void this; 1653 return interfaceDecl.heritageClauses?.find((clause) => { 1654 return clause.token === ts.SyntaxKind.ExtendsKeyword; 1655 }); 1656 } 1657 1658 private checkInterfaceForProperty(interfaceDecl: ts.InterfaceDeclaration, propertyName: string): void { 1659 for (const member of interfaceDecl.members) { 1660 if (ts.isMethodSignature(member) && member.name.getText() === propertyName) { 1661 this.incrementCounters(member, FaultID.MethodOverridingField); 1662 } 1663 } 1664 } 1665 1666 private getImplementsClause(classDecl: ts.ClassDeclaration): ts.HeritageClause | undefined { 1667 void this; 1668 return classDecl.heritageClauses?.find((clause) => { 1669 return clause.token === ts.SyntaxKind.ImplementsKeyword; 1670 }); 1671 } 1672 1673 private checkClassForProperty(classDecl: ts.ClassDeclaration, propertyName: string): void { 1674 for (const member of classDecl.members) { 1675 if (ts.isMethodDeclaration(member) && member.name.getText() === propertyName) { 1676 this.incrementCounters(member, FaultID.MethodOverridingField); 1677 } 1678 } 1679 } 1680 1681 private visitClassMembers(classes: ts.ClassDeclaration[], interfaceName: string, propertyName: string): void { 1682 void this; 1683 classes.some((classDecl) => { 1684 const implementsClause = this.getImplementsClause(classDecl); 1685 if ( 1686 implementsClause?.types.some((type) => { 1687 return type.expression.getText() === interfaceName; 1688 }) 1689 ) { 1690 this.checkClassForProperty(classDecl, propertyName); 1691 } 1692 }); 1693 } 1694 1695 private handleSendableInterfaceProperty(node: ts.PropertySignature): void { 1696 const typeNode = node.type; 1697 if (!typeNode) { 1698 return; 1699 } 1700 const interfaceNode = node.parent; 1701 const interfaceNodeType = this.tsTypeChecker.getTypeAtLocation(interfaceNode); 1702 if (!ts.isInterfaceDeclaration(interfaceNode) || !this.tsUtils.isSendableClassOrInterface(interfaceNodeType)) { 1703 return; 1704 } 1705 if (!this.tsUtils.isSendableTypeNode(typeNode)) { 1706 this.incrementCounters(node, FaultID.SendablePropType); 1707 } 1708 } 1709 1710 private filterOutDecoratorsDiagnostics( 1711 decorators: readonly ts.Decorator[] | undefined, 1712 expectedDecorators: readonly string[], 1713 range: { begin: number; end: number }, 1714 code: number, 1715 propType?: string 1716 ): void { 1717 // Filter out non-initializable property decorators from strict diagnostics. 1718 if (this.tscStrictDiagnostics && this.sourceFile) { 1719 if ( 1720 decorators?.some((decorator) => { 1721 const decoratorName = TsUtils.getDecoratorName(decorator); 1722 // special case for property of type CustomDialogController of the @CustomDialog-decorated class 1723 if (expectedDecorators.includes(NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS[0])) { 1724 return expectedDecorators.includes(decoratorName) && propType === 'CustomDialogController'; 1725 } 1726 return expectedDecorators.includes(decoratorName); 1727 }) 1728 ) { 1729 this.filterOutDiagnostics(range, code); 1730 } 1731 } 1732 } 1733 1734 private filterOutDiagnostics(range: { begin: number; end: number }, code: number): void { 1735 // Filter out strict diagnostics within the given range with the given code. 1736 if (!this.tscStrictDiagnostics || !this.sourceFile) { 1737 return; 1738 } 1739 const file = path.normalize(this.sourceFile.fileName); 1740 const tscDiagnostics = this.tscStrictDiagnostics.get(file); 1741 if (tscDiagnostics) { 1742 const filteredDiagnostics = tscDiagnostics.filter((val) => { 1743 if (val.code !== code) { 1744 return true; 1745 } 1746 if (val.start === undefined) { 1747 return true; 1748 } 1749 if (val.start < range.begin) { 1750 return true; 1751 } 1752 if (val.start > range.end) { 1753 return true; 1754 } 1755 return false; 1756 }); 1757 this.tscStrictDiagnostics.set(file, filteredDiagnostics); 1758 } 1759 } 1760 1761 private static isClassLikeOrIface(node: ts.Node): boolean { 1762 return ts.isClassLike(node) || ts.isInterfaceDeclaration(node); 1763 } 1764 1765 private handleFunctionExpression(node: ts.Node): void { 1766 const funcExpr = node as ts.FunctionExpression; 1767 const isGenerator = funcExpr.asteriskToken !== undefined; 1768 const [hasUnfixableReturnType, newRetTypeNode] = this.handleMissingReturnType(funcExpr); 1769 const autofix = this.autofixer?.fixFunctionExpression( 1770 funcExpr, 1771 newRetTypeNode, 1772 ts.getModifiers(funcExpr), 1773 isGenerator, 1774 hasUnfixableReturnType 1775 ); 1776 this.incrementCounters(funcExpr, FaultID.FunctionExpression, autofix); 1777 if (isGenerator) { 1778 this.incrementCounters(funcExpr, FaultID.GeneratorFunction); 1779 } 1780 if (!hasPredecessor(funcExpr, TypeScriptLinter.isClassLikeOrIface)) { 1781 this.reportThisKeywordsInScope(funcExpr.body); 1782 } 1783 if (hasUnfixableReturnType) { 1784 this.incrementCounters(funcExpr, FaultID.LimitedReturnTypeInference); 1785 } 1786 this.handleLimitedVoidFunction(funcExpr); 1787 } 1788 1789 private handleArrowFunction(node: ts.Node): void { 1790 const arrowFunc = node as ts.ArrowFunction; 1791 if (!hasPredecessor(arrowFunc, TypeScriptLinter.isClassLikeOrIface)) { 1792 this.reportThisKeywordsInScope(arrowFunc.body); 1793 } 1794 const contextType = this.tsTypeChecker.getContextualType(arrowFunc); 1795 if (!(contextType && this.tsUtils.isLibraryType(contextType))) { 1796 if (!arrowFunc.type) { 1797 this.handleMissingReturnType(arrowFunc); 1798 } 1799 } 1800 this.checkDefaultParamBeforeRequired(arrowFunc); 1801 this.handleLimitedVoidFunction(arrowFunc); 1802 } 1803 1804 private handleFunctionDeclaration(node: ts.Node): void { 1805 // early exit via exception if cancellation was requested 1806 this.options.cancellationToken?.throwIfCancellationRequested(); 1807 1808 const tsFunctionDeclaration = node as ts.FunctionDeclaration; 1809 if (!tsFunctionDeclaration.type) { 1810 this.handleMissingReturnType(tsFunctionDeclaration); 1811 } 1812 if (tsFunctionDeclaration.name) { 1813 this.countDeclarationsWithDuplicateName(tsFunctionDeclaration.name, tsFunctionDeclaration); 1814 } 1815 if (tsFunctionDeclaration.body) { 1816 this.reportThisKeywordsInScope(tsFunctionDeclaration.body); 1817 } 1818 const funcDeclParent = tsFunctionDeclaration.parent; 1819 if (!ts.isSourceFile(funcDeclParent) && !ts.isModuleBlock(funcDeclParent)) { 1820 const autofix = this.autofixer?.fixNestedFunction(tsFunctionDeclaration); 1821 this.incrementCounters(tsFunctionDeclaration, FaultID.LocalFunction, autofix); 1822 } 1823 if (tsFunctionDeclaration.asteriskToken) { 1824 this.incrementCounters(node, FaultID.GeneratorFunction); 1825 } 1826 if (TsUtils.hasSendableDecoratorFunctionOverload(tsFunctionDeclaration)) { 1827 if (!this.isSendableDecoratorValid(tsFunctionDeclaration)) { 1828 return; 1829 } 1830 TsUtils.getNonSendableDecorators(tsFunctionDeclaration)?.forEach((decorator) => { 1831 this.incrementCounters(decorator, FaultID.SendableFunctionDecorator); 1832 }); 1833 if (!TsUtils.hasSendableDecorator(tsFunctionDeclaration)) { 1834 const autofix = this.autofixer?.addSendableDecorator(tsFunctionDeclaration); 1835 this.incrementCounters(tsFunctionDeclaration, FaultID.SendableFunctionOverloadDecorator, autofix); 1836 } 1837 this.scanCapturedVarsInSendableScope( 1838 tsFunctionDeclaration, 1839 tsFunctionDeclaration, 1840 FaultID.SendableFunctionImportedVariables 1841 ); 1842 } 1843 this.handleTSOverload(tsFunctionDeclaration); 1844 this.checkAssignmentNumericSemanticsFuntion(tsFunctionDeclaration); 1845 this.handleInvalidIdentifier(tsFunctionDeclaration); 1846 this.checkDefaultParamBeforeRequired(tsFunctionDeclaration); 1847 this.handleLimitedVoidFunction(tsFunctionDeclaration); 1848 } 1849 1850 private handleMissingReturnType( 1851 funcLikeDecl: ts.FunctionLikeDeclaration | ts.MethodSignature 1852 ): [boolean, ts.TypeNode | undefined] { 1853 if (this.options.useRtLogic && funcLikeDecl.type) { 1854 return [false, funcLikeDecl.type]; 1855 } 1856 1857 // Note: Return type can't be inferred for function without body. 1858 const isSignature = ts.isMethodSignature(funcLikeDecl); 1859 if (isSignature || !funcLikeDecl.body) { 1860 // Ambient flag is not exposed, so we apply dirty hack to make it visible 1861 const isDeclareDeclaration = TsUtils.isAmbientNode(funcLikeDecl); 1862 if ((isSignature || isDeclareDeclaration) && !funcLikeDecl.type) { 1863 this.incrementCounters(funcLikeDecl, FaultID.LimitedReturnTypeInference); 1864 } 1865 return [false, undefined]; 1866 } 1867 1868 return this.tryAutofixMissingReturnType(funcLikeDecl); 1869 } 1870 1871 private tryAutofixMissingReturnType(funcLikeDecl: ts.FunctionLikeDeclaration): [boolean, ts.TypeNode | undefined] { 1872 if (!funcLikeDecl.body) { 1873 return [false, undefined]; 1874 } 1875 1876 let autofix: Autofix[] | undefined; 1877 let newRetTypeNode: ts.TypeNode | undefined; 1878 const isFuncExpr = ts.isFunctionExpression(funcLikeDecl); 1879 1880 /* 1881 * Currently, ArkTS can't infer return type of function, when expression 1882 * in the return statement is a call to a function or method whose return 1883 * value type is omitted. In that case, we attempt to prepare an autofix. 1884 */ 1885 let hasLimitedRetTypeInference = this.hasLimitedTypeInferenceFromReturnExpr(funcLikeDecl.body); 1886 const tsSignature = this.tsTypeChecker.getSignatureFromDeclaration(funcLikeDecl); 1887 if (tsSignature) { 1888 const tsRetType = this.tsTypeChecker.getReturnTypeOfSignature(tsSignature); 1889 if ( 1890 !tsRetType || 1891 !this.options.arkts2 && TsUtils.isUnsupportedType(tsRetType) || 1892 this.options.arkts2 && this.tsUtils.isUnsupportedTypeArkts2(tsRetType) 1893 ) { 1894 hasLimitedRetTypeInference = true; 1895 } else if (hasLimitedRetTypeInference) { 1896 newRetTypeNode = this.tsTypeChecker.typeToTypeNode(tsRetType, funcLikeDecl, ts.NodeBuilderFlags.None); 1897 if (this.autofixer !== undefined && newRetTypeNode && !isFuncExpr) { 1898 autofix = this.autofixer.fixMissingReturnType(funcLikeDecl, newRetTypeNode); 1899 } 1900 } 1901 } 1902 1903 /* 1904 * Don't report here if in function expression context. 1905 * See handleFunctionExpression for details. 1906 */ 1907 if (hasLimitedRetTypeInference && !isFuncExpr) { 1908 this.incrementCounters(funcLikeDecl, FaultID.LimitedReturnTypeInference, autofix); 1909 } 1910 1911 return [hasLimitedRetTypeInference && !newRetTypeNode, newRetTypeNode]; 1912 } 1913 1914 private hasLimitedTypeInferenceFromReturnExpr(funBody: ts.ConciseBody): boolean { 1915 let hasLimitedTypeInference = false; 1916 const callback = (node: ts.Node): void => { 1917 if (hasLimitedTypeInference) { 1918 return; 1919 } 1920 if ( 1921 ts.isReturnStatement(node) && 1922 node.expression && 1923 this.tsUtils.isCallToFunctionWithOmittedReturnType(TsUtils.unwrapParenthesized(node.expression)) 1924 ) { 1925 hasLimitedTypeInference = true; 1926 } 1927 }; 1928 // Don't traverse other nested function-like declarations. 1929 const stopCondition = (node: ts.Node): boolean => { 1930 return ( 1931 ts.isFunctionDeclaration(node) || 1932 ts.isFunctionExpression(node) || 1933 ts.isMethodDeclaration(node) || 1934 ts.isAccessor(node) || 1935 ts.isArrowFunction(node) 1936 ); 1937 }; 1938 if (ts.isBlock(funBody)) { 1939 forEachNodeInSubtree(funBody, callback, stopCondition); 1940 } else { 1941 const tsExpr = TsUtils.unwrapParenthesized(funBody); 1942 hasLimitedTypeInference = this.tsUtils.isCallToFunctionWithOmittedReturnType(tsExpr); 1943 } 1944 return hasLimitedTypeInference; 1945 } 1946 1947 private isValidTypeForUnaryArithmeticOperator(type: ts.Type): boolean { 1948 const typeFlags = type.getFlags(); 1949 const numberLiteralFlags = ts.TypeFlags.BigIntLiteral | ts.TypeFlags.NumberLiteral; 1950 const numberLikeFlags = ts.TypeFlags.BigIntLike | ts.TypeFlags.NumberLike; 1951 const isNumberLike = !!(typeFlags & (numberLiteralFlags | numberLikeFlags)); 1952 1953 const isAllowedNumericType = this.tsUtils.isStdBigIntType(type) || this.tsUtils.isStdNumberType(type); 1954 1955 return isNumberLike || isAllowedNumericType; 1956 } 1957 1958 private handleInteropOperand(tsUnaryArithm: ts.PrefixUnaryExpression): void { 1959 const processPropertyAccess = (expr: ts.PropertyAccessExpression | ts.ParenthesizedExpression): void => { 1960 const propertyAccess = ts.isParenthesizedExpression(expr) ? expr.expression : expr; 1961 1962 if (ts.isPropertyAccessExpression(propertyAccess)) { 1963 const exprSym = this.tsUtils.trueSymbolAtLocation(propertyAccess.expression); 1964 const declaration = exprSym?.declarations?.[0]; 1965 this.checkAndProcessDeclaration(declaration, tsUnaryArithm); 1966 } 1967 }; 1968 1969 if (ts.isPropertyAccessExpression(tsUnaryArithm.operand) || ts.isParenthesizedExpression(tsUnaryArithm.operand)) { 1970 processPropertyAccess(tsUnaryArithm.operand); 1971 } 1972 } 1973 1974 private checkAndProcessDeclaration( 1975 declaration: ts.Declaration | undefined, 1976 tsUnaryArithm: ts.PrefixUnaryExpression 1977 ): void { 1978 if (declaration?.getSourceFile().fileName.endsWith(EXTNAME_JS)) { 1979 if ( 1980 [ 1981 ts.SyntaxKind.PlusToken, 1982 ts.SyntaxKind.ExclamationToken, 1983 ts.SyntaxKind.TildeToken, 1984 ts.SyntaxKind.MinusToken 1985 ].includes(tsUnaryArithm.operator) 1986 ) { 1987 const autofix = this.autofixer?.fixInteropInterfaceConvertNum(tsUnaryArithm); 1988 this.incrementCounters(tsUnaryArithm, FaultID.InteropNoHaveNum, autofix); 1989 } 1990 } 1991 } 1992 1993 private handlePostfixUnaryExpression(node: ts.Node): void { 1994 const unaryExpr = node as ts.PostfixUnaryExpression; 1995 if (unaryExpr.operator === ts.SyntaxKind.PlusPlusToken || unaryExpr.operator === ts.SyntaxKind.MinusMinusToken) { 1996 this.checkAutoIncrementDecrement(unaryExpr); 1997 } 1998 } 1999 2000 private handlePrefixUnaryExpression(node: ts.Node): void { 2001 const tsUnaryArithm = node as ts.PrefixUnaryExpression; 2002 if (this.useStatic && this.options.arkts2) { 2003 const tsUnaryArithm = node as ts.PrefixUnaryExpression; 2004 this.handleInteropOperand(tsUnaryArithm); 2005 } 2006 const tsUnaryOp = tsUnaryArithm.operator; 2007 const tsUnaryOperand = tsUnaryArithm.operand; 2008 if ( 2009 tsUnaryOp === ts.SyntaxKind.PlusToken || 2010 tsUnaryOp === ts.SyntaxKind.MinusToken || 2011 tsUnaryOp === ts.SyntaxKind.TildeToken 2012 ) { 2013 const tsOperatndType = this.tsTypeChecker.getTypeAtLocation(tsUnaryOperand); 2014 const isTilde = tsUnaryOp === ts.SyntaxKind.TildeToken; 2015 const isInvalidTilde = 2016 isTilde && ts.isNumericLiteral(tsUnaryOperand) && !this.tsUtils.isIntegerConstantValue(tsUnaryOperand); 2017 if (!this.isValidTypeForUnaryArithmeticOperator(tsOperatndType) || isInvalidTilde) { 2018 this.incrementCounters(node, FaultID.UnaryArithmNotNumber); 2019 } 2020 } 2021 if ( 2022 tsUnaryArithm.operator === ts.SyntaxKind.PlusPlusToken || 2023 tsUnaryArithm.operator === ts.SyntaxKind.MinusMinusToken 2024 ) { 2025 this.checkAutoIncrementDecrement(tsUnaryArithm); 2026 } 2027 } 2028 2029 private handleBinaryExpression(node: ts.Node): void { 2030 const tsBinaryExpr = node as ts.BinaryExpression; 2031 const tsLhsExpr = tsBinaryExpr.left; 2032 const tsRhsExpr = tsBinaryExpr.right; 2033 if (isAssignmentOperator(tsBinaryExpr.operatorToken)) { 2034 this.processBinaryAssignment(tsBinaryExpr, tsLhsExpr, tsRhsExpr); 2035 } 2036 const leftOperandType = this.tsTypeChecker.getTypeAtLocation(tsLhsExpr); 2037 const typeNode = this.tsUtils.getVariableDeclarationTypeNode(tsLhsExpr); 2038 switch (tsBinaryExpr.operatorToken.kind) { 2039 // FaultID.BitOpWithWrongType - removed as rule #61 2040 case ts.SyntaxKind.CommaToken: 2041 this.processBinaryComma(tsBinaryExpr); 2042 break; 2043 case ts.SyntaxKind.InstanceOfKeyword: 2044 this.processBinaryInstanceOf(node, tsLhsExpr, leftOperandType); 2045 this.handleInstanceOfExpression(tsBinaryExpr); 2046 break; 2047 case ts.SyntaxKind.InKeyword: 2048 this.incrementCounters(tsBinaryExpr.operatorToken, FaultID.InOperator); 2049 break; 2050 case ts.SyntaxKind.EqualsToken: 2051 this.handleTsInterop(tsLhsExpr, () => { 2052 this.checkUsageOfTsTypes(leftOperandType, tsBinaryExpr); 2053 }); 2054 this.checkAssignmentMatching(tsBinaryExpr, leftOperandType, tsRhsExpr); 2055 this.checkFunctionTypeCompatible(typeNode, tsRhsExpr); 2056 this.handleEsObjectAssignment(tsBinaryExpr, typeNode, tsRhsExpr); 2057 this.handleSdkGlobalApi(tsBinaryExpr); 2058 break; 2059 case ts.SyntaxKind.AmpersandAmpersandEqualsToken: 2060 case ts.SyntaxKind.QuestionQuestionEqualsToken: 2061 case ts.SyntaxKind.BarBarEqualsToken: 2062 if (this.options.arkts2) { 2063 this.incrementCounters(tsBinaryExpr.operatorToken, FaultID.UnsupportOperator); 2064 } 2065 break; 2066 default: 2067 } 2068 this.checkInterOpImportJsDataCompare(tsBinaryExpr); 2069 this.checkInteropEqualityJudgment(tsBinaryExpr); 2070 this.handleNumericBigintCompare(tsBinaryExpr); 2071 this.handleArkTSPropertyAccess(tsBinaryExpr); 2072 this.handleObjectLiteralAssignmentToClass(tsBinaryExpr); 2073 this.handleAssignmentNotsLikeSmartType(tsBinaryExpr); 2074 } 2075 2076 private checkInterOpImportJsDataCompare(expr: ts.BinaryExpression): void { 2077 if (!this.useStatic || !this.options.arkts2 || !TypeScriptLinter.isComparisonOperator(expr.operatorToken.kind)) { 2078 return; 2079 } 2080 2081 const processExpression = (expr: ts.Expression): void => { 2082 const symbol = this.tsUtils.trueSymbolAtLocation(expr); 2083 if (this.isJsFileSymbol(symbol) || this.isJsFileExpression(expr)) { 2084 const autofix = this.autofixer?.fixInteropOperators(expr); 2085 this.incrementCounters(expr, FaultID.InterOpImportJsDataCompare, autofix); 2086 } 2087 }; 2088 2089 processExpression(expr.left); 2090 processExpression(expr.right); 2091 } 2092 2093 private static isComparisonOperator(kind: ts.SyntaxKind): boolean { 2094 return [ 2095 ts.SyntaxKind.GreaterThanToken, 2096 ts.SyntaxKind.LessThanToken, 2097 ts.SyntaxKind.GreaterThanEqualsToken, 2098 ts.SyntaxKind.LessThanEqualsToken 2099 ].includes(kind); 2100 } 2101 2102 private isJsFileSymbol(symbol: ts.Symbol | undefined): boolean { 2103 if (!symbol) { 2104 return false; 2105 } 2106 2107 const declaration = symbol.declarations?.[0]; 2108 if (!declaration || !ts.isVariableDeclaration(declaration)) { 2109 return false; 2110 } 2111 2112 const initializer = declaration.initializer; 2113 return initializer ? this.isJsFileExpression(initializer) : false; 2114 } 2115 2116 private isJsFileExpression(expr: ts.Expression): boolean { 2117 if (ts.isPropertyAccessExpression(expr)) { 2118 const initializerSym = this.tsUtils.trueSymbolAtLocation(expr.expression); 2119 return initializerSym?.declarations?.[0]?.getSourceFile()?.fileName.endsWith(EXTNAME_JS) ?? false; 2120 } 2121 return expr.getSourceFile()?.fileName.endsWith(EXTNAME_JS) ?? false; 2122 } 2123 2124 private checkInteropEqualityJudgment(tsBinaryExpr: ts.BinaryExpression): void { 2125 if (this.useStatic && this.options.arkts2) { 2126 switch (tsBinaryExpr.operatorToken.kind) { 2127 case ts.SyntaxKind.EqualsEqualsToken: 2128 case ts.SyntaxKind.ExclamationEqualsToken: 2129 case ts.SyntaxKind.EqualsEqualsEqualsToken: 2130 case ts.SyntaxKind.ExclamationEqualsEqualsToken: 2131 if (this.tsUtils.isJsImport(tsBinaryExpr.left) || this.tsUtils.isJsImport(tsBinaryExpr.right)) { 2132 const autofix = this.autofixer?.fixInteropEqualityOperator(tsBinaryExpr, tsBinaryExpr.operatorToken.kind); 2133 this.incrementCounters(tsBinaryExpr, FaultID.InteropEqualityJudgment, autofix); 2134 } 2135 break; 2136 default: 2137 } 2138 } 2139 } 2140 2141 private handleTsInterop(nodeToCheck: ts.Node, handler: { (): void }): void { 2142 if (!this.options.arkts2 || !this.useStatic) { 2143 return; 2144 } 2145 2146 const declarationNode = this.tsUtils.getDeclarationNode(nodeToCheck); 2147 if (!declarationNode) { 2148 return; 2149 } 2150 2151 const fileName = declarationNode.getSourceFile().fileName; 2152 if (fileName.includes(ARKTS_IGNORE_DIRS_OH_MODULES)) { 2153 return; 2154 } 2155 if (!fileName.endsWith(EXTNAME_TS)) { 2156 return; 2157 } 2158 2159 if (fileName.endsWith(EXTNAME_D_TS)) { 2160 return; 2161 } 2162 2163 handler(); 2164 } 2165 2166 private handleJsInterop(nodeToCheck: ts.Node, handler: { (): void }): void { 2167 if (!this.options.arkts2 || !this.useStatic) { 2168 return; 2169 } 2170 2171 const declarationNode = this.tsUtils.getDeclarationNode(nodeToCheck); 2172 if (!declarationNode) { 2173 return; 2174 } 2175 2176 const fileName = declarationNode.getSourceFile().fileName; 2177 if (fileName.includes(ARKTS_IGNORE_DIRS_OH_MODULES)) { 2178 return; 2179 } 2180 if (!fileName.endsWith(EXTNAME_JS)) { 2181 return; 2182 } 2183 2184 if (fileName.endsWith(EXTNAME_D_TS)) { 2185 return; 2186 } 2187 2188 handler(); 2189 } 2190 2191 private processBinaryAssignment( 2192 binaryExpr: ts.BinaryExpression, 2193 tsLhsExpr: ts.Expression, 2194 tsRhsExpr: ts.Expression 2195 ): void { 2196 this.handleDestructuringAssignment(binaryExpr, tsLhsExpr, tsRhsExpr); 2197 2198 if (ts.isPropertyAccessExpression(tsLhsExpr)) { 2199 const tsLhsSymbol = this.tsUtils.trueSymbolAtLocation(tsLhsExpr); 2200 const tsLhsBaseSymbol = this.tsUtils.trueSymbolAtLocation(tsLhsExpr.expression); 2201 if (tsLhsSymbol && tsLhsSymbol.flags & ts.SymbolFlags.Method) { 2202 this.incrementCounters(tsLhsExpr, FaultID.MethodReassignment); 2203 } 2204 if ( 2205 !this.options.arkts2 && 2206 TsUtils.isMethodAssignment(tsLhsSymbol) && 2207 tsLhsBaseSymbol && 2208 (tsLhsBaseSymbol.flags & ts.SymbolFlags.Function) !== 0 2209 ) { 2210 this.incrementCounters(tsLhsExpr, FaultID.PropertyDeclOnFunction); 2211 } 2212 } 2213 } 2214 2215 private checkAssignmentNumericSemanticsly(node: ts.VariableDeclaration): void { 2216 if (!this.options.arkts2) { 2217 return; 2218 } 2219 const initializer = node.initializer; 2220 const name = node.name; 2221 if (node.type || !initializer || !ts.isIdentifier(name)) { 2222 return; 2223 } 2224 2225 // Early return if the variable is imported from JS 2226 if (this.tsUtils.isPossiblyImportedFromJS(name) || this.tsUtils.isPossiblyImportedFromJS(initializer)) { 2227 return; 2228 } 2229 2230 if ( 2231 ts.isBinaryExpression(initializer) && 2232 ts.isCallExpression(initializer.left) && 2233 TsUtils.isAppStorageAccess(initializer.left) 2234 ) { 2235 return; 2236 } 2237 2238 const sym = this.tsTypeChecker.getSymbolAtLocation(name); 2239 if (!sym) { 2240 return; 2241 } 2242 2243 const type = this.tsTypeChecker.getTypeOfSymbolAtLocation(sym, name); 2244 const typeText = this.tsTypeChecker.typeToString(type); 2245 const isEnum = this.isNumericEnumType(type); 2246 if (TsUtils.isNumberLike(type, typeText, isEnum)) { 2247 const autofix = this.autofixer?.fixVariableDeclaration(node, isEnum); 2248 this.incrementCounters(node, FaultID.NumericSemantics, autofix); 2249 } 2250 } 2251 2252 private isEnumType(type: ts.Type): boolean { 2253 if (type.flags & ts.TypeFlags.Enum) { 2254 return true; 2255 } 2256 2257 if (type.symbol?.flags & ts.SymbolFlags.Enum) { 2258 return true; 2259 } 2260 2261 if (type.flags & ts.TypeFlags.EnumLiteral) { 2262 return true; 2263 } 2264 2265 if (type.isUnion()) { 2266 return type.types.some((t) => { 2267 return this.isEnumType(t); 2268 }); 2269 } 2270 return false; 2271 } 2272 2273 private isNumericEnumType(type: ts.Type): boolean { 2274 if (!this.isEnumType(type)) { 2275 return false; 2276 } 2277 const declarations = type.symbol?.getDeclarations() || []; 2278 const enumMemberDecl = declarations.find(ts.isEnumMember); 2279 if (enumMemberDecl) { 2280 const value = this.tsTypeChecker.getConstantValue(enumMemberDecl); 2281 return typeof value === STRINGLITERAL_NUMBER; 2282 } 2283 2284 const enumDecl = declarations.find(ts.isEnumDeclaration); 2285 if (enumDecl) { 2286 return enumDecl.members.every((member) => { 2287 const memberType = this.tsTypeChecker.getTypeAtLocation(member.name); 2288 return (memberType.flags & ts.TypeFlags.NumberLike) !== 0; 2289 }); 2290 } 2291 return false; 2292 } 2293 2294 private checkAssignmentNumericSemanticsFuntion(node: ts.FunctionDeclaration): void { 2295 if (!this.options.arkts2) { 2296 return; 2297 } 2298 for (const param of node.parameters) { 2299 if (param.type) { 2300 continue; 2301 } 2302 const sym = this.tsTypeChecker.getSymbolAtLocation(param.name); 2303 if (!sym) { 2304 continue; 2305 } 2306 2307 const type = this.tsTypeChecker.getTypeOfSymbolAtLocation(sym, param.name); 2308 const typeText = this.tsTypeChecker.typeToString(type); 2309 if (typeText === STRINGLITERAL_NUMBER) { 2310 const autofix = this.autofixer?.fixParameter(param); 2311 if (autofix) { 2312 this.incrementCounters(node, FaultID.NumericSemantics, autofix); 2313 } 2314 } 2315 } 2316 if (!node.type) { 2317 const signature = this.tsTypeChecker.getSignatureFromDeclaration(node); 2318 if (!signature) { 2319 return; 2320 } 2321 const retType = this.tsTypeChecker.getReturnTypeOfSignature(signature); 2322 if ((retType.getFlags() & ts.TypeFlags.Number) !== 0) { 2323 const returnTypeNode = this.tsTypeChecker.typeToTypeNode(retType, node, ts.NodeBuilderFlags.None); 2324 if (!returnTypeNode) { 2325 return; 2326 } 2327 const autofix = this.autofixer?.fixMissingReturnType(node, returnTypeNode); 2328 this.incrementCounters(node, FaultID.NumericSemantics, autofix); 2329 } 2330 } 2331 } 2332 2333 private checkAssignmentNumericSemanticslyPro(node: ts.PropertyDeclaration): void { 2334 if (!this.options.arkts2) { 2335 return; 2336 } 2337 2338 const initializer = node.initializer; 2339 const name = node.name; 2340 if (node.type || !initializer || !ts.isIdentifier(name)) { 2341 return; 2342 } 2343 2344 const isNumberArray = ts.isArrayLiteralExpression(initializer) && TypeScriptLinter.isNumberArray(initializer); 2345 const isNumber = !isNumberArray && TypeScriptLinter.isNumericInitializer(initializer); 2346 2347 const sym = this.tsTypeChecker.getSymbolAtLocation(name); 2348 if (!sym) { 2349 return; 2350 } 2351 2352 if (!isNumber && !isNumberArray) { 2353 return; 2354 } 2355 const type = this.tsTypeChecker.getTypeOfSymbolAtLocation(sym, name); 2356 const typeText = this.tsTypeChecker.typeToString(type); 2357 const typeFlags = type.flags; 2358 if (isNumber && (typeText === STRINGLITERAL_NUMBER || (typeFlags & ts.TypeFlags.NumberLiteral) !== 0)) { 2359 const autofix = this.autofixer?.fixPropertyDeclaration(node); 2360 this.incrementCounters(node, FaultID.NumericSemantics, autofix); 2361 } 2362 this.checkAssignmentNumericSemanticsArray(node, isNumberArray); 2363 } 2364 2365 checkAssignmentNumericSemanticsArray(node: ts.PropertyDeclaration, isNumberArray: boolean): void { 2366 if (isNumberArray) { 2367 const autofix = this.autofixer?.fixPropertyDeclarationNumericSemanticsArray(node); 2368 this.incrementCounters(node, FaultID.NumericSemantics, autofix); 2369 } 2370 } 2371 2372 private static isNumericInitializer(node: ts.Node): boolean { 2373 if (ts.isNumericLiteral(node)) { 2374 return true; 2375 } 2376 if ( 2377 ts.isPrefixUnaryExpression(node) && 2378 node.operator === ts.SyntaxKind.MinusToken && 2379 ts.isNumericLiteral(node.operand) 2380 ) { 2381 return true; 2382 } 2383 return false; 2384 } 2385 2386 private static isNumberArray(arrayLiteral: ts.ArrayLiteralExpression): boolean { 2387 return arrayLiteral.elements.every((element) => { 2388 if (ts.isSpreadElement(element)) { 2389 return false; 2390 } 2391 return TypeScriptLinter.isNumericInitializer(element); 2392 }); 2393 } 2394 2395 private handleDestructuringAssignment(node: ts.Node, tsLhsExpr: ts.Expression, tsRhsExpr: ts.Expression): void { 2396 if (ts.isObjectLiteralExpression(tsLhsExpr)) { 2397 const autofix = this.autofixer?.fixObjectLiteralExpressionDestructAssignment(node as ts.BinaryExpression); 2398 this.incrementCounters(node, FaultID.DestructuringAssignment, autofix); 2399 } else if (ts.isArrayLiteralExpression(tsLhsExpr)) { 2400 const rhsType = this.tsTypeChecker.getTypeAtLocation(tsRhsExpr); 2401 const isArrayOrTuple = 2402 this.tsUtils.isOrDerivedFrom(rhsType, this.tsUtils.isArray) || 2403 this.tsUtils.isOrDerivedFrom(rhsType, TsUtils.isTuple); 2404 const hasNestedObjectDestructuring = TsUtils.hasNestedObjectDestructuring(tsLhsExpr); 2405 2406 if ( 2407 !this.options.useRelaxedRules || 2408 !isArrayOrTuple || 2409 hasNestedObjectDestructuring || 2410 TsUtils.destructuringAssignmentHasSpreadOperator(tsLhsExpr) 2411 ) { 2412 const autofix = this.autofixer?.fixArrayBindingPatternAssignment(node as ts.BinaryExpression, isArrayOrTuple); 2413 this.incrementCounters(node, FaultID.DestructuringAssignment, autofix); 2414 } 2415 } 2416 } 2417 2418 private processBinaryComma(tsBinaryExpr: ts.BinaryExpression): void { 2419 // CommaOpertor is allowed in 'for' statement initalizer and incrementor 2420 let tsExprNode: ts.Node = tsBinaryExpr; 2421 let tsParentNode = tsExprNode.parent; 2422 while (tsParentNode && tsParentNode.kind === ts.SyntaxKind.BinaryExpression) { 2423 tsExprNode = tsParentNode; 2424 tsParentNode = tsExprNode.parent; 2425 if ((tsExprNode as ts.BinaryExpression).operatorToken.kind === ts.SyntaxKind.CommaToken) { 2426 // Need to return if one comma enclosed in expression with another comma to avoid multiple reports on one line 2427 return; 2428 } 2429 } 2430 if (tsParentNode && tsParentNode.kind === ts.SyntaxKind.ForStatement) { 2431 const tsForNode = tsParentNode as ts.ForStatement; 2432 if (tsExprNode === tsForNode.initializer || tsExprNode === tsForNode.incrementor) { 2433 return; 2434 } 2435 } 2436 if (tsParentNode && tsParentNode.kind === ts.SyntaxKind.ExpressionStatement) { 2437 const autofix = this.autofixer?.fixCommaOperator(tsExprNode); 2438 this.incrementCounters(tsExprNode, FaultID.CommaOperator, autofix); 2439 return; 2440 } 2441 2442 this.incrementCounters(tsBinaryExpr as ts.Node, FaultID.CommaOperator); 2443 } 2444 2445 private processBinaryInstanceOf(node: ts.Node, tsLhsExpr: ts.Expression, leftOperandType: ts.Type): void { 2446 const leftExpr = TsUtils.unwrapParenthesized(tsLhsExpr); 2447 const leftSymbol = this.tsUtils.trueSymbolAtLocation(leftExpr); 2448 2449 /* 2450 * In ETS, the left-hand side expression may be of any reference type, otherwise 2451 * a compile-time error occurs. In addition, the left operand in ETS cannot be a type. 2452 */ 2453 if (tsLhsExpr.kind === ts.SyntaxKind.ThisKeyword) { 2454 return; 2455 } 2456 2457 if (TsUtils.isPrimitiveType(leftOperandType) || ts.isTypeNode(leftExpr) || TsUtils.isTypeSymbol(leftSymbol)) { 2458 this.incrementCounters(node, FaultID.InstanceofUnsupported); 2459 } 2460 } 2461 2462 private handleVariableDeclarationList(node: ts.Node): void { 2463 const varDeclFlags = ts.getCombinedNodeFlags(node); 2464 if (!(varDeclFlags & (ts.NodeFlags.Let | ts.NodeFlags.Const))) { 2465 const autofix = this.autofixer?.fixVarDeclaration(node as ts.VariableDeclarationList); 2466 this.incrementCounters(node, FaultID.VarDeclaration, autofix); 2467 } 2468 } 2469 2470 private handleVariableDeclaration(node: ts.Node): void { 2471 const tsVarDecl = node as ts.VariableDeclaration; 2472 this.handleVariableDeclarationForProp(tsVarDecl); 2473 if ( 2474 !this.options.useRtLogic || 2475 ts.isVariableDeclarationList(tsVarDecl.parent) && ts.isVariableStatement(tsVarDecl.parent.parent) 2476 ) { 2477 this.handleDeclarationDestructuring(tsVarDecl); 2478 } 2479 2480 // Check variable declaration for duplicate name. 2481 this.checkVarDeclForDuplicateNames(tsVarDecl.name); 2482 2483 if (tsVarDecl.type && tsVarDecl.initializer) { 2484 this.checkAssignmentMatching( 2485 tsVarDecl, 2486 this.tsTypeChecker.getTypeAtLocation(tsVarDecl.type), 2487 tsVarDecl.initializer 2488 ); 2489 this.checkFunctionTypeCompatible(tsVarDecl.type, tsVarDecl.initializer); 2490 } 2491 this.handleEsValueDeclaration(tsVarDecl); 2492 this.handleDeclarationInferredType(tsVarDecl); 2493 this.handleDefiniteAssignmentAssertion(tsVarDecl); 2494 this.handleLimitedVoidType(tsVarDecl); 2495 this.handleInvalidIdentifier(tsVarDecl); 2496 this.checkAssignmentNumericSemanticsly(tsVarDecl); 2497 this.checkTypeFromSdk(tsVarDecl.type); 2498 this.handleNoStructuralTyping(tsVarDecl); 2499 this.handleObjectLiteralforUnionTypeInterop(tsVarDecl); 2500 this.handleObjectLiteralAssignmentToClass(tsVarDecl); 2501 this.handleObjectLiteralAssignment(tsVarDecl); 2502 this.handlePropertyDescriptorInScenarios(tsVarDecl); 2503 this.handleSdkGlobalApi(tsVarDecl); 2504 } 2505 2506 private checkTypeFromSdk(type: ts.TypeNode | undefined): void { 2507 if (!this.options.arkts2 || !type) { 2508 return; 2509 } 2510 2511 const fullTypeName = type.getText(); 2512 const nameArr = fullTypeName.split('.'); 2513 const sdkInfos = this.interfaceMap.get(nameArr[0]); 2514 if (!sdkInfos || sdkInfos.size === 0) { 2515 return; 2516 } 2517 2518 for (const sdkInfo of sdkInfos) { 2519 if (sdkInfo.api_name && nameArr.includes(sdkInfo.api_name)) { 2520 this.incrementCounters(type, FaultID.LimitedVoidTypeFromSdk); 2521 return; 2522 } 2523 } 2524 } 2525 2526 private static extractUsedObjectType(tsVarDecl: ts.VariableDeclaration): InterfaceSymbolTypePropertyNames | null { 2527 const result = { 2528 propertyNames: [] as string[], 2529 typeNames: [] as string[] 2530 }; 2531 2532 if (!this.isObjectLiteralWithProperties(tsVarDecl)) { 2533 return null; 2534 } 2535 2536 this.processObjectLiteralProperties(tsVarDecl.initializer as ts.ObjectLiteralExpression, result); 2537 return result.propertyNames.length > 0 ? result : null; 2538 } 2539 2540 private static isObjectLiteralWithProperties(tsVarDecl: ts.VariableDeclaration): boolean { 2541 return ( 2542 tsVarDecl.initializer !== undefined && 2543 ts.isObjectLiteralExpression(tsVarDecl.initializer) && 2544 tsVarDecl.initializer.properties.length > 0 2545 ); 2546 } 2547 2548 private static processObjectLiteralProperties( 2549 objectLiteral: ts.ObjectLiteralExpression, 2550 result: { propertyNames: string[]; typeNames: string[] } 2551 ): void { 2552 objectLiteral.properties.forEach((property) => { 2553 if (!ts.isPropertyAssignment(property)) { 2554 return; 2555 } 2556 2557 const propertyName = property.name.getText(); 2558 result.propertyNames.push(propertyName); 2559 2560 if (ts.isNewExpression(property.initializer)) { 2561 const typeName = property.initializer.expression.getText(); 2562 result.typeNames.push(typeName); 2563 } 2564 }); 2565 } 2566 2567 private interfaceSymbolType(tsVarDecl: ts.VariableDeclaration): InterfaceSymbolTypeResult | null { 2568 if (!tsVarDecl.type) { 2569 return null; 2570 } 2571 2572 const typeSymbol = this.getTypeSymbol(tsVarDecl); 2573 if (!typeSymbol) { 2574 return null; 2575 } 2576 2577 const interfaceType = this.getInterfaceType(tsVarDecl); 2578 if (!interfaceType) { 2579 return null; 2580 } 2581 2582 return this.collectInterfaceProperties(interfaceType, tsVarDecl); 2583 } 2584 2585 private getTypeSymbol(tsVarDecl: ts.VariableDeclaration): ts.Symbol | null { 2586 const typeNode = ts.isTypeReferenceNode(tsVarDecl.type!) ? tsVarDecl.type.typeName : tsVarDecl.type; 2587 return this.tsTypeChecker.getSymbolAtLocation(typeNode!) ?? null; 2588 } 2589 2590 private getInterfaceType(tsVarDecl: ts.VariableDeclaration): ts.InterfaceType | null { 2591 const type = this.tsTypeChecker.getTypeAtLocation(tsVarDecl.type!); 2592 return type && (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Interface ? (type as ts.InterfaceType) : null; 2593 } 2594 2595 private collectInterfaceProperties( 2596 interfaceType: ts.InterfaceType, 2597 tsVarDecl: ts.VariableDeclaration 2598 ): InterfaceSymbolTypeResult { 2599 const result = { 2600 propNames: [] as string[], 2601 typeNames: [] as string[], 2602 allProps: new Map<string, string>() 2603 }; 2604 2605 this.collectPropertiesRecursive(interfaceType, result, tsVarDecl); 2606 return result; 2607 } 2608 2609 private collectPropertiesRecursive( 2610 type: ts.Type, 2611 result: { 2612 propNames: string[]; 2613 typeNames: string[]; 2614 allProps: Map<string, string>; 2615 }, 2616 tsVarDecl: ts.VariableDeclaration 2617 ): void { 2618 type.getProperties().forEach((property) => { 2619 this.collectProperty(property, result, tsVarDecl); 2620 }); 2621 2622 if ('getBaseTypes' in type) { 2623 type.getBaseTypes()?.forEach((baseType) => { 2624 this.collectPropertiesRecursive(baseType, result, tsVarDecl); 2625 }); 2626 } 2627 } 2628 2629 private collectProperty( 2630 property: ts.Symbol, 2631 result: { 2632 propNames: string[]; 2633 typeNames: string[]; 2634 allProps: Map<string, string>; 2635 }, 2636 tsVarDecl: ts.VariableDeclaration 2637 ): void { 2638 const propName = property.getName(); 2639 const propType = this.tsTypeChecker.getTypeOfSymbolAtLocation( 2640 property, 2641 property.valueDeclaration || tsVarDecl.type! 2642 ); 2643 const typeString = this.tsTypeChecker.typeToString(propType); 2644 2645 if (!result.allProps.has(propName)) { 2646 result.propNames.push(propName); 2647 result.typeNames.push(typeString); 2648 result.allProps.set(propName, typeString); 2649 } 2650 } 2651 2652 handleNoStructuralTyping(tsVarDecl: ts.VariableDeclaration): void { 2653 const { interfaceInfo, actualUsage } = this.getTypeComparisonData(tsVarDecl); 2654 if (!interfaceInfo || !actualUsage) { 2655 return; 2656 } 2657 if (!this.options.arkts2) { 2658 return; 2659 } 2660 const actualMap = TypeScriptLinter.createActualTypeMap(actualUsage); 2661 const hasMismatch = TypeScriptLinter.checkTypeMismatches(interfaceInfo, actualMap); 2662 2663 if (hasMismatch) { 2664 this.incrementCounters(tsVarDecl, FaultID.StructuralIdentity); 2665 } 2666 } 2667 2668 private getTypeComparisonData(tsVarDecl: ts.VariableDeclaration): { 2669 interfaceInfo: { propNames: string[]; typeNames: string[]; allProps: Map<string, string> } | null; 2670 actualUsage: { 2671 propertyNames: string[]; 2672 typeNames: string[]; 2673 } | null; 2674 } { 2675 return { 2676 interfaceInfo: this.interfaceSymbolType(tsVarDecl), 2677 actualUsage: TypeScriptLinter.extractUsedObjectType(tsVarDecl) 2678 }; 2679 } 2680 2681 private static createActualTypeMap(actualUsage: { 2682 propertyNames: string[]; 2683 typeNames: string[]; 2684 }): Map<string, string> { 2685 const actualMap = new Map<string, string>(); 2686 actualUsage.propertyNames.forEach((prop, index) => { 2687 if (actualUsage.typeNames[index]) { 2688 actualMap.set(prop, actualUsage.typeNames[index]); 2689 } 2690 }); 2691 return actualMap; 2692 } 2693 2694 private static checkTypeMismatches( 2695 interfaceInfo: { allProps: Map<string, string> }, 2696 actualMap: Map<string, string> 2697 ): boolean { 2698 let hasMismatch = false; 2699 2700 interfaceInfo.allProps.forEach((expectedType, prop) => { 2701 if (!actualMap.has(prop)) { 2702 return; 2703 } 2704 2705 const actualType = actualMap.get(prop)!; 2706 if (expectedType !== actualType) { 2707 hasMismatch = true; 2708 } 2709 }); 2710 2711 return hasMismatch; 2712 } 2713 2714 private handleDeclarationDestructuring(decl: ts.VariableDeclaration | ts.ParameterDeclaration): void { 2715 const faultId = ts.isVariableDeclaration(decl) ? FaultID.DestructuringDeclaration : FaultID.DestructuringParameter; 2716 if (ts.isObjectBindingPattern(decl.name)) { 2717 const autofix = ts.isVariableDeclaration(decl) ? 2718 this.autofixer?.fixObjectBindingPatternDeclarations(decl) : 2719 undefined; 2720 this.incrementCounters(decl, faultId, autofix); 2721 } else if (ts.isArrayBindingPattern(decl.name)) { 2722 // Array destructuring is allowed only for Arrays/Tuples and without spread operator. 2723 const rhsType = this.tsTypeChecker.getTypeAtLocation(decl.initializer ?? decl.name); 2724 const isArrayOrTuple = 2725 rhsType && 2726 (this.tsUtils.isOrDerivedFrom(rhsType, this.tsUtils.isArray) || 2727 this.tsUtils.isOrDerivedFrom(rhsType, TsUtils.isTuple)); 2728 const hasNestedObjectDestructuring = TsUtils.hasNestedObjectDestructuring(decl.name); 2729 2730 if ( 2731 !this.options.useRelaxedRules || 2732 !isArrayOrTuple || 2733 hasNestedObjectDestructuring || 2734 TsUtils.destructuringDeclarationHasSpreadOperator(decl.name) 2735 ) { 2736 const autofix = ts.isVariableDeclaration(decl) ? 2737 this.autofixer?.fixArrayBindingPatternDeclarations(decl, isArrayOrTuple) : 2738 undefined; 2739 this.incrementCounters(decl, faultId, autofix); 2740 } 2741 } 2742 } 2743 2744 private checkVarDeclForDuplicateNames(tsBindingName: ts.BindingName): void { 2745 if (ts.isIdentifier(tsBindingName)) { 2746 // The syntax kind of the declaration is defined here by the parent of 'BindingName' node. 2747 this.countDeclarationsWithDuplicateName(tsBindingName, tsBindingName, tsBindingName.parent.kind); 2748 return; 2749 } 2750 for (const tsBindingElem of tsBindingName.elements) { 2751 if (ts.isOmittedExpression(tsBindingElem)) { 2752 continue; 2753 } 2754 2755 this.checkVarDeclForDuplicateNames(tsBindingElem.name); 2756 } 2757 } 2758 2759 private handleEsValueDeclaration(node: ts.VariableDeclaration): void { 2760 const isDeclaredESValue = !!node.type && TsUtils.isEsValueType(node.type); 2761 const initalizerTypeNode = node.initializer && this.tsUtils.getVariableDeclarationTypeNode(node.initializer); 2762 const isInitializedWithESValue = !!initalizerTypeNode && TsUtils.isEsValueType(initalizerTypeNode); 2763 const isLocal = TsUtils.isInsideBlock(node); 2764 if ((isDeclaredESValue || isInitializedWithESValue) && !isLocal) { 2765 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 2766 this.incrementCounters(node, faultId); 2767 return; 2768 } 2769 2770 if (node.initializer) { 2771 this.handleEsObjectAssignment(node, node.type, node.initializer); 2772 } 2773 } 2774 2775 private handleEsObjectAssignment(node: ts.Node, nodeDeclType: ts.TypeNode | undefined, initializer: ts.Node): void { 2776 const isTypeAnnotated = !!nodeDeclType; 2777 const isDeclaredESValue = isTypeAnnotated && TsUtils.isEsValueType(nodeDeclType); 2778 const initalizerTypeNode = this.tsUtils.getVariableDeclarationTypeNode(initializer); 2779 const isInitializedWithESValue = !!initalizerTypeNode && TsUtils.isEsValueType(initalizerTypeNode); 2780 if (isTypeAnnotated && !isDeclaredESValue && isInitializedWithESValue) { 2781 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 2782 this.incrementCounters(node, faultId); 2783 return; 2784 } 2785 2786 if (isDeclaredESValue && !this.tsUtils.isValueAssignableToESValue(initializer)) { 2787 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 2788 this.incrementCounters(node, faultId); 2789 } 2790 } 2791 2792 private handleCatchClause(node: ts.Node): void { 2793 const tsCatch = node as ts.CatchClause; 2794 2795 /* 2796 * In TS catch clause doesn't permit specification of the exception varible type except 'any' or 'unknown'. 2797 * It is not compatible with ETS 'catch' where the exception variable has to be of type 2798 * Error or derived from it. 2799 * So each 'catch' which has explicit type for the exception object goes to problems. 2800 */ 2801 if (tsCatch.variableDeclaration?.type) { 2802 const autofix = this.autofixer?.dropTypeOnVarDecl(tsCatch.variableDeclaration); 2803 this.incrementCounters(node, FaultID.CatchWithUnsupportedType, autofix); 2804 } 2805 2806 if (this.options.arkts2 && tsCatch.variableDeclaration?.name) { 2807 const varDeclName = tsCatch.variableDeclaration?.name.getText(); 2808 tsCatch.block.statements.forEach((statement) => { 2809 this.checkTsLikeCatchType(statement, varDeclName); 2810 }); 2811 } 2812 } 2813 2814 private checkTsLikeCatchType(node: ts.Node, variableDeclarationName: string): void { 2815 if (!node) { 2816 return; 2817 } 2818 for (const child of node.getChildren()) { 2819 if (ts.isPropertyAccessExpression(child)) { 2820 if (child.expression.getText() === variableDeclarationName && !ERROR_PROP_LIST.has(child.name.getText())) { 2821 this.incrementCounters(child, FaultID.TsLikeCatchType); 2822 } 2823 } 2824 this.checkTsLikeCatchType(child, variableDeclarationName); 2825 } 2826 } 2827 2828 private handleClassExtends(tsClassDecl: ts.ClassDeclaration): void { 2829 if (!this.options.arkts2) { 2830 return; 2831 } 2832 const allClasses = TypeScriptLinter.getAllClassesFromSourceFile(this.sourceFile); 2833 const classMap = new Map<string, ts.ClassDeclaration>(); 2834 allClasses.forEach((classDecl) => { 2835 if (classDecl.name && !classDecl.heritageClauses) { 2836 classMap.set(classDecl.name.getText(), classDecl); 2837 } 2838 }); 2839 if (!tsClassDecl.heritageClauses) { 2840 return; 2841 } 2842 tsClassDecl.heritageClauses.forEach((clause) => { 2843 clause.types.forEach((type) => { 2844 const baseClassName = type.expression.getText(); 2845 const baseClass = classMap.get(baseClassName); 2846 if (baseClass && ts.isClassDeclaration(baseClass)) { 2847 this.checkMembersConsistency(tsClassDecl, baseClass); 2848 } 2849 }); 2850 }); 2851 } 2852 2853 private checkMembersConsistency(derivedClass: ts.ClassDeclaration, baseClass: ts.ClassDeclaration): void { 2854 const baseMethods = new Set<string>(); 2855 baseClass.members.forEach((member) => { 2856 if (ts.isMethodDeclaration(member)) { 2857 baseMethods.add(member.name.getText()); 2858 } 2859 }); 2860 derivedClass.members.forEach((member) => { 2861 const memberName = member.name?.getText(); 2862 if (memberName && baseMethods.has(memberName)) { 2863 if (ts.isPropertyDeclaration(member)) { 2864 this.incrementCounters(member, FaultID.MethodOverridingField); 2865 } 2866 } 2867 }); 2868 } 2869 2870 private handleClassDeclaration(node: ts.Node): void { 2871 // early exit via exception if cancellation was requested 2872 this.options.cancellationToken?.throwIfCancellationRequested(); 2873 2874 const tsClassDecl = node as ts.ClassDeclaration; 2875 this.handleClassExtends(tsClassDecl); 2876 if (tsClassDecl.name) { 2877 this.countDeclarationsWithDuplicateName(tsClassDecl.name, tsClassDecl); 2878 } 2879 this.countClassMembersWithDuplicateName(tsClassDecl); 2880 2881 const isSendableClass = TsUtils.hasSendableDecorator(tsClassDecl); 2882 if (isSendableClass) { 2883 TsUtils.getNonSendableDecorators(tsClassDecl)?.forEach((decorator) => { 2884 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 2885 }); 2886 tsClassDecl.typeParameters?.forEach((typeParamDecl) => { 2887 this.checkSendableTypeParameter(typeParamDecl); 2888 }); 2889 } 2890 2891 if (tsClassDecl.heritageClauses) { 2892 for (const hClause of tsClassDecl.heritageClauses) { 2893 if (!hClause) { 2894 continue; 2895 } 2896 this.checkClassDeclarationHeritageClause(hClause, isSendableClass); 2897 } 2898 } 2899 2900 // Check captured variables for sendable class 2901 if (isSendableClass) { 2902 tsClassDecl.members.forEach((classMember) => { 2903 this.scanCapturedVarsInSendableScope(classMember, tsClassDecl, FaultID.SendableCapturedVars); 2904 }); 2905 } 2906 2907 this.processClassStaticBlocks(tsClassDecl); 2908 this.handleInvalidIdentifier(tsClassDecl); 2909 this.handleSdkMethod(tsClassDecl); 2910 this.handleNotsLikeSmartType(tsClassDecl); 2911 } 2912 2913 private static findFinalExpression(typeNode: ts.TypeNode): ts.Node { 2914 let currentNode = typeNode; 2915 2916 /* 2917 * CC-OFFNXT(no_explicit_any) std lib 2918 * Handle comment directive '@ts-nocheck' 2919 */ 2920 while ((currentNode as any).expression) { 2921 2922 /* 2923 * CC-OFFNXT(no_explicit_any) std lib 2924 * Handle comment directive '@ts-nocheck' 2925 */ 2926 currentNode = (currentNode as any).expression; 2927 } 2928 return currentNode; 2929 } 2930 2931 private processSdkMethodClauseTypes( 2932 tsClassDecl: ts.ClassDeclaration, 2933 heritageClause: ts.HeritageClause, 2934 methodName?: string 2935 ): boolean { 2936 return heritageClause.types.some((type) => { 2937 const parentName = ts.isPropertyAccessExpression(type.expression) ? 2938 type.expression.name.text : 2939 type.expression.getText(); 2940 const fullTypeName = TypeScriptLinter.findFinalExpression(type).getText(); 2941 const sdkInfos = this.interfaceMap.get(fullTypeName); 2942 if (!sdkInfos || sdkInfos.size === 0) { 2943 return false; 2944 } 2945 2946 return Array.from(sdkInfos).some((sdkInfo) => { 2947 if (sdkInfo.api_type !== METHOD_SIGNATURE && sdkInfo.api_type !== METHOD_DECLARATION) { 2948 return false; 2949 } 2950 2951 if (!methodName && sdkInfo.parent_api[0].api_name === parentName) { 2952 this.processSdkInfoWithMembers(sdkInfo, tsClassDecl.members, tsClassDecl); 2953 return false; 2954 } 2955 2956 const symbol = this.tsTypeChecker.getSymbolAtLocation(type.expression); 2957 return TypeScriptLinter.isHeritageClauseisThirdPartyBySymbol(symbol) && sdkInfo.api_name === methodName; 2958 }); 2959 }); 2960 } 2961 2962 private handleSdkMethod(tsClassDecl: ts.ClassDeclaration): void { 2963 if ( 2964 !this.options.arkts2 || 2965 !tsClassDecl.heritageClauses || 2966 tsClassDecl.heritageClauses.length === 0 || 2967 !tsClassDecl.members || 2968 tsClassDecl.members.length === 0 2969 ) { 2970 return; 2971 } 2972 2973 for (const heritageClause of tsClassDecl.heritageClauses) { 2974 if (!heritageClause.types || heritageClause.types.length === 0) { 2975 continue; 2976 } 2977 this.processSdkMethodClauseTypes(tsClassDecl, heritageClause); 2978 } 2979 } 2980 2981 private processSdkInfoWithMembers( 2982 sdkInfo: ApiInfo, 2983 members: ts.NodeArray<ts.ClassElement>, 2984 tsClassDecl: ts.ClassDeclaration 2985 ): void { 2986 for (const member of members) { 2987 if (!ts.isMethodDeclaration(member)) { 2988 continue; 2989 } 2990 2991 const memberName = member.name?.getText(); 2992 if (sdkInfo.api_name === memberName) { 2993 if ( 2994 !TypeScriptLinter.areParametersEqual(sdkInfo.api_func_args ?? [], member.parameters) && 2995 !TypeScriptLinter.areGenericsParametersEqual(sdkInfo.api_func_args ?? [], tsClassDecl) 2996 ) { 2997 return; 2998 } 2999 this.incrementCounters( 3000 member, 3001 sdkInfo.problem === OPTIONAL_METHOD ? FaultID.OptionalMethodFromSdk : FaultID.LimitedVoidTypeFromSdk 3002 ); 3003 } 3004 } 3005 } 3006 3007 private static areParametersEqual( 3008 sdkFuncArgs: { name: string; type: string }[], 3009 memberParams: ts.NodeArray<ts.ParameterDeclaration> 3010 ): boolean { 3011 const apiParamCout = sdkFuncArgs.length; 3012 const memberParamCout = memberParams.length; 3013 if (apiParamCout > memberParamCout && sdkFuncArgs[memberParamCout]) { 3014 return false; 3015 } 3016 3017 for (let i = 0; i < apiParamCout; i++) { 3018 const typeName = memberParams[i]?.type?.getText(); 3019 if (!typeName?.match(sdkFuncArgs[i].type)) { 3020 return false; 3021 } 3022 } 3023 return true; 3024 } 3025 3026 private processLimitedVoidTypeFromSdkOnClassDeclaration( 3027 tsClassDecl: ts.ClassDeclaration, 3028 methodName?: string 3029 ): boolean { 3030 if ( 3031 !this.options.arkts2 || 3032 !tsClassDecl.heritageClauses || 3033 tsClassDecl.heritageClauses.length === 0 || 3034 !tsClassDecl.members || 3035 tsClassDecl.members.length === 0 3036 ) { 3037 return false; 3038 } 3039 let res: boolean = false; 3040 for (const heritageClause of tsClassDecl.heritageClauses) { 3041 if (heritageClause.types?.length) { 3042 res = this.processSdkMethodClauseTypes(tsClassDecl, heritageClause, methodName); 3043 break; 3044 } 3045 } 3046 return res; 3047 } 3048 3049 private static isHeritageClauseisThirdPartyBySymbol(symbol: ts.Symbol | undefined): boolean { 3050 if (!symbol) { 3051 return false; 3052 } 3053 const declarations = symbol.getDeclarations(); 3054 if (declarations && declarations.length > 0) { 3055 const firstDeclaration = declarations[0]; 3056 if (ts.isImportSpecifier(firstDeclaration)) { 3057 return true; 3058 } 3059 } 3060 return false; 3061 } 3062 3063 private handleLimitedVoidTypeFromSdkOnPropertyAccessExpression(node: ts.PropertyAccessExpression): void { 3064 if (!this.options.arkts2) { 3065 return; 3066 } 3067 const sym = this.getOriginalSymbol(node.name); 3068 if (!sym) { 3069 return; 3070 } 3071 const methodName = node.name.getText(); 3072 const declaration = sym.declarations?.[0]; 3073 if (declaration && ts.isClassDeclaration(declaration.parent)) { 3074 if (this.processLimitedVoidTypeFromSdkOnClassDeclaration(declaration.parent, methodName)) { 3075 this.incrementCounters(node, FaultID.LimitedVoidTypeFromSdk); 3076 } 3077 } 3078 } 3079 3080 private static areGenericsParametersEqual( 3081 sdkFuncArgs: { name: string; type: string }[], 3082 node: ts.ClassDeclaration 3083 ): boolean { 3084 if (!ts.isClassDeclaration(node)) { 3085 return false; 3086 } 3087 const apiParamCout = sdkFuncArgs.length; 3088 const typeParameters = node.typeParameters; 3089 if (!typeParameters) { 3090 return false; 3091 } 3092 typeParameters.forEach((typeParam) => { 3093 if (!typeParam.constraint) { 3094 return false; 3095 } 3096 for (let i = 0; i < apiParamCout; i++) { 3097 if (!typeParam.constraint.getText().match(sdkFuncArgs[i].type)) { 3098 return false; 3099 } 3100 } 3101 return true; 3102 }); 3103 return true; 3104 } 3105 3106 private handleNotSupportCustomDecorators(decorator: ts.Decorator): void { 3107 if (!this.options.arkts2) { 3108 return; 3109 } 3110 3111 let decoratorName; 3112 if (ts.isCallExpression(decorator.expression)) { 3113 decoratorName = decorator.expression.expression.getText(this.sourceFile); 3114 } else { 3115 decoratorName = decorator.expression.getText(this.sourceFile); 3116 } 3117 if (!DEFAULT_DECORATOR_WHITE_LIST.includes(decoratorName)) { 3118 this.incrementCounters(decorator, FaultID.DecoratorsNotSupported); 3119 } 3120 } 3121 3122 private checkClassDeclarationHeritageClause(hClause: ts.HeritageClause, isSendableClass: boolean): void { 3123 for (const tsTypeExpr of hClause.types) { 3124 3125 /* 3126 * Always resolve type from 'tsTypeExpr' node, not from 'tsTypeExpr.expression' node, 3127 * as for the latter, type checker will return incorrect type result for classes in 3128 * 'extends' clause. Additionally, reduce reference, as mostly type checker returns 3129 * the TypeReference type objects for classes and interfaces. 3130 */ 3131 const tsExprType = TsUtils.reduceReference(this.tsTypeChecker.getTypeAtLocation(tsTypeExpr)); 3132 const isSendableBaseType = this.tsUtils.isSendableClassOrInterface(tsExprType); 3133 if (tsExprType.isClass() && hClause.token === ts.SyntaxKind.ImplementsKeyword) { 3134 this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); 3135 } 3136 if (!isSendableClass) { 3137 // Non-Sendable class can not implements sendable interface / extends sendable class 3138 if (isSendableBaseType) { 3139 const autofix = this.autofixer?.addClassSendableDecorator(hClause, tsTypeExpr); 3140 this.incrementCounters(tsTypeExpr, FaultID.SendableClassInheritance, autofix); 3141 } 3142 continue; 3143 } 3144 3145 /* 3146 * Sendable class can implements any interface / extends only sendable class 3147 * Sendable class can not extends sendable class variable(local / import) 3148 */ 3149 if (hClause.token === ts.SyntaxKind.ExtendsKeyword) { 3150 if (!isSendableBaseType) { 3151 this.incrementCounters(tsTypeExpr, FaultID.SendableClassInheritance); 3152 continue; 3153 } 3154 if (!this.tsUtils.isValidSendableClassExtends(tsTypeExpr)) { 3155 this.incrementCounters(tsTypeExpr, FaultID.SendableClassInheritance); 3156 } 3157 } 3158 } 3159 } 3160 3161 private checkSendableTypeParameter(typeParamDecl: ts.TypeParameterDeclaration): void { 3162 const defaultTypeNode = typeParamDecl.default; 3163 if (defaultTypeNode) { 3164 if (!this.tsUtils.isSendableTypeNode(defaultTypeNode)) { 3165 this.incrementCounters(defaultTypeNode, FaultID.SendableGenericTypes); 3166 } 3167 } 3168 } 3169 3170 private processClassStaticBlocks(classDecl: ts.ClassDeclaration): void { 3171 let staticBlocksCntr = 0; 3172 const staticBlockNodes: ts.Node[] = []; 3173 for (const element of classDecl.members) { 3174 if (ts.isClassStaticBlockDeclaration(element)) { 3175 if (this.options.arkts2 && this.useStatic) { 3176 this.incrementCounters(element, FaultID.NoStaticOnClass); 3177 } 3178 staticBlockNodes[staticBlocksCntr] = element; 3179 staticBlocksCntr++; 3180 } 3181 } 3182 if (staticBlocksCntr > 1) { 3183 const autofix = this.autofixer?.fixMultipleStaticBlocks(staticBlockNodes); 3184 // autofixes for all additional static blocks are the same 3185 for (let i = 1; i < staticBlocksCntr; i++) { 3186 this.incrementCounters(staticBlockNodes[i], FaultID.MultipleStaticBlocks, autofix); 3187 } 3188 } 3189 } 3190 3191 private handleModuleDeclaration(node: ts.Node): void { 3192 // early exit via exception if cancellation was requested 3193 this.options.cancellationToken?.throwIfCancellationRequested(); 3194 3195 const tsModuleDecl = node as ts.ModuleDeclaration; 3196 3197 this.countDeclarationsWithDuplicateName(tsModuleDecl.name, tsModuleDecl); 3198 3199 if (this.options.arkts2) { 3200 this.handleInvalidIdentifier(tsModuleDecl); 3201 } 3202 3203 const tsModuleBody = tsModuleDecl.body; 3204 const tsModifiers = ts.getModifiers(tsModuleDecl); 3205 if (tsModuleBody) { 3206 if (ts.isModuleBlock(tsModuleBody)) { 3207 this.handleModuleBlock(tsModuleBody); 3208 } 3209 } 3210 3211 if ( 3212 this.options.arkts2 && 3213 tsModuleBody && 3214 ts.isModuleBlock(tsModuleBody) && 3215 tsModuleDecl.flags & ts.NodeFlags.Namespace 3216 ) { 3217 this.handleNameSpaceModuleBlock(tsModuleBody, (tsModuleDecl.name as ts.Identifier).escapedText.toString()); 3218 } 3219 3220 if ( 3221 !(tsModuleDecl.flags & ts.NodeFlags.Namespace) && 3222 TsUtils.hasModifier(tsModifiers, ts.SyntaxKind.DeclareKeyword) 3223 ) { 3224 this.incrementCounters(tsModuleDecl, FaultID.ShorthandAmbientModuleDecl); 3225 } 3226 3227 if (ts.isStringLiteral(tsModuleDecl.name) && tsModuleDecl.name.text.includes('*')) { 3228 this.incrementCounters(tsModuleDecl, FaultID.WildcardsInModuleName); 3229 } 3230 } 3231 3232 private handleNameSpaceModuleBlock(moduleBlock: ts.ModuleBlock, nameSpace: string): void { 3233 if (!TypeScriptLinter.nameSpaceFunctionCache.has(nameSpace)) { 3234 TypeScriptLinter.nameSpaceFunctionCache.set(nameSpace, new Set<string>()); 3235 } 3236 3237 const nameSet = TypeScriptLinter.nameSpaceFunctionCache.get(nameSpace)!; 3238 3239 for (const statement of moduleBlock.statements) { 3240 const names = TypeScriptLinter.getDeclarationNames(statement); 3241 for (const name of names) { 3242 if (nameSet.has(name)) { 3243 this.incrementCounters(statement, FaultID.NoDuplicateFunctionName); 3244 } else { 3245 nameSet.add(name); 3246 } 3247 } 3248 } 3249 } 3250 3251 private static getDeclarationNames(statement: ts.Statement): Set<string> { 3252 const names = new Set<string>(); 3253 3254 if ( 3255 ts.isFunctionDeclaration(statement) && statement.name && statement.body || 3256 ts.isClassDeclaration(statement) && statement.name || 3257 ts.isInterfaceDeclaration(statement) && statement.name || 3258 ts.isEnumDeclaration(statement) && statement.name 3259 ) { 3260 names.add(statement.name.text); 3261 return names; 3262 } 3263 3264 if (ts.isVariableStatement(statement)) { 3265 for (const decl of statement.declarationList.declarations) { 3266 if (ts.isIdentifier(decl.name)) { 3267 names.add(decl.name.text); 3268 } 3269 } 3270 } 3271 3272 return names; 3273 } 3274 3275 private handleModuleBlock(moduleBlock: ts.ModuleBlock): void { 3276 for (const tsModuleStmt of moduleBlock.statements) { 3277 switch (tsModuleStmt.kind) { 3278 case ts.SyntaxKind.VariableStatement: 3279 case ts.SyntaxKind.FunctionDeclaration: 3280 case ts.SyntaxKind.ClassDeclaration: 3281 case ts.SyntaxKind.InterfaceDeclaration: 3282 case ts.SyntaxKind.TypeAliasDeclaration: 3283 case ts.SyntaxKind.EnumDeclaration: 3284 case ts.SyntaxKind.ExportDeclaration: 3285 break; 3286 3287 /* 3288 * Nested namespace declarations are prohibited 3289 * but there is no cookbook recipe for it! 3290 */ 3291 case ts.SyntaxKind.ModuleDeclaration: 3292 break; 3293 default: 3294 this.incrementCounters(tsModuleStmt, FaultID.NonDeclarationInNamespace); 3295 break; 3296 } 3297 } 3298 } 3299 3300 private handleTypeAliasDeclaration(node: ts.Node): void { 3301 const tsTypeAlias = node as ts.TypeAliasDeclaration; 3302 this.countDeclarationsWithDuplicateName(tsTypeAlias.name, tsTypeAlias); 3303 this.handleInvalidIdentifier(tsTypeAlias); 3304 if (TsUtils.hasSendableDecorator(tsTypeAlias)) { 3305 if (!this.isSendableDecoratorValid(tsTypeAlias)) { 3306 return; 3307 } 3308 TsUtils.getNonSendableDecorators(tsTypeAlias)?.forEach((decorator) => { 3309 this.incrementCounters(decorator, FaultID.SendableTypeAliasDecorator); 3310 }); 3311 if (!ts.isFunctionTypeNode(tsTypeAlias.type)) { 3312 this.incrementCounters(tsTypeAlias.type, FaultID.SendableTypeAliasDeclaration); 3313 } 3314 } 3315 if (this.options.arkts2 && tsTypeAlias.type.kind === ts.SyntaxKind.VoidKeyword) { 3316 this.incrementCounters(tsTypeAlias.type, FaultID.LimitedVoidType); 3317 } 3318 } 3319 3320 private handleTupleType(node: ts.TupleTypeNode): void { 3321 if (!this.options.arkts2) { 3322 return; 3323 } 3324 3325 node.elements.forEach((elementType) => { 3326 if (elementType.kind === ts.SyntaxKind.VoidKeyword) { 3327 this.incrementCounters(elementType, FaultID.LimitedVoidType); 3328 } 3329 }); 3330 } 3331 3332 private handleImportClause(node: ts.Node): void { 3333 const tsImportClause = node as ts.ImportClause; 3334 if (this.options.arkts2 && tsImportClause.isLazy) { 3335 const autofix = this.autofixer?.fixImportClause(tsImportClause); 3336 this.incrementCounters(node, FaultID.ImportLazyIdentifier, autofix); 3337 } 3338 if (tsImportClause.name) { 3339 this.countDeclarationsWithDuplicateName(tsImportClause.name, tsImportClause); 3340 } 3341 } 3342 3343 private handleImportSpecifier(node: ts.Node): void { 3344 const importSpec = node as ts.ImportSpecifier; 3345 this.countDeclarationsWithDuplicateName(importSpec.name, importSpec); 3346 } 3347 3348 private handleNamespaceImport(node: ts.Node): void { 3349 const tsNamespaceImport = node as ts.NamespaceImport; 3350 this.countDeclarationsWithDuplicateName(tsNamespaceImport.name, tsNamespaceImport); 3351 } 3352 3353 private handleTypeAssertionExpression(node: ts.Node): void { 3354 const tsTypeAssertion = node as ts.TypeAssertion; 3355 if (tsTypeAssertion.type.getText() === 'const') { 3356 this.incrementCounters(tsTypeAssertion, FaultID.ConstAssertion); 3357 } else { 3358 const autofix = this.autofixer?.fixTypeAssertion(tsTypeAssertion); 3359 this.incrementCounters(node, FaultID.TypeAssertion, autofix); 3360 } 3361 } 3362 3363 private handleMethodDeclaration(node: ts.Node): void { 3364 const tsMethodDecl = node as ts.MethodDeclaration; 3365 TsUtils.getDecoratorsIfInSendableClass(tsMethodDecl)?.forEach((decorator) => { 3366 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 3367 }); 3368 let isStatic = false; 3369 if (tsMethodDecl.modifiers) { 3370 for (const mod of tsMethodDecl.modifiers) { 3371 if (mod.kind === ts.SyntaxKind.StaticKeyword) { 3372 isStatic = true; 3373 break; 3374 } 3375 } 3376 } 3377 if (tsMethodDecl.body && isStatic) { 3378 this.reportThisKeywordsInScope(tsMethodDecl.body); 3379 } 3380 if (!tsMethodDecl.type) { 3381 this.handleMissingReturnType(tsMethodDecl); 3382 } 3383 if (tsMethodDecl.asteriskToken) { 3384 this.incrementCounters(node, FaultID.GeneratorFunction); 3385 } 3386 this.filterOutDecoratorsDiagnostics( 3387 ts.getDecorators(tsMethodDecl), 3388 NON_RETURN_FUNCTION_DECORATORS, 3389 { begin: tsMethodDecl.parameters.end, end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end }, 3390 FUNCTION_HAS_NO_RETURN_ERROR_CODE 3391 ); 3392 if (this.options.arkts2 && tsMethodDecl.questionToken) { 3393 this.incrementCounters(tsMethodDecl.questionToken, FaultID.OptionalMethod); 3394 } 3395 this.handleInvalidIdentifier(tsMethodDecl); 3396 if (!this.tsUtils.isAbstractMethodInAbstractClass(node)) { 3397 this.handleTSOverload(tsMethodDecl); 3398 } 3399 this.checkDefaultParamBeforeRequired(tsMethodDecl); 3400 this.handleMethodInherit(tsMethodDecl); 3401 this.handleSdkGlobalApi(tsMethodDecl); 3402 this.handleLimitedVoidFunction(tsMethodDecl); 3403 this.checkVoidLifecycleReturn(tsMethodDecl); 3404 } 3405 3406 private handleLimitedVoidFunction(node: ts.FunctionLikeDeclaration): void { 3407 const typeNode = node.type; 3408 if (!typeNode || !ts.isUnionTypeNode(typeNode)) { 3409 return; 3410 } 3411 const containsVoid = typeNode.types.some((t) => { 3412 return t.kind === ts.SyntaxKind.VoidKeyword; 3413 }); 3414 if (this.options.arkts2 && containsVoid) { 3415 const autofix = this.autofixer?.fixLimitedVoidTypeFunction(node); 3416 this.incrementCounters(typeNode, FaultID.LimitedVoidType, autofix); 3417 } 3418 } 3419 3420 private checkDefaultParamBeforeRequired(node: ts.FunctionLikeDeclarationBase): void { 3421 if (!this.options.arkts2) { 3422 return; 3423 } 3424 3425 const params = node.parameters; 3426 let seenRequired = false; 3427 3428 for (let i = params.length - 1; i >= 0; i--) { 3429 const param = params[i]; 3430 3431 const isOptional = !!param.initializer || !!param.questionToken; 3432 3433 if (!isOptional) { 3434 seenRequired = true; 3435 continue; 3436 } 3437 3438 if (seenRequired && param.initializer) { 3439 this.incrementCounters(param.name, FaultID.DefaultArgsBehindRequiredArgs); 3440 } 3441 } 3442 } 3443 3444 private handleMethodInherit(node: ts.MethodDeclaration): void { 3445 if (!this.options.arkts2 || !node.name || !ts.isIdentifier(node.name)) { 3446 return; 3447 } 3448 3449 const classDecl = node.parent; 3450 if (!ts.isClassDeclaration(classDecl)) { 3451 return; 3452 } 3453 3454 const classType = this.tsTypeChecker.getTypeAtLocation(classDecl); 3455 const allBaseTypes = this.getAllBaseTypes(classType, classDecl); 3456 if (!allBaseTypes || allBaseTypes.length === 0) { 3457 return; 3458 } 3459 3460 const methodName = node.name.text; 3461 3462 for (const baseType of allBaseTypes) { 3463 const baseMethod = baseType.getProperty(methodName); 3464 if (!baseMethod) { 3465 continue; 3466 } 3467 3468 const baseMethodDecl = baseMethod.declarations?.find( 3469 (d) => { 3470 return (ts.isMethodDeclaration(d) || ts.isMethodSignature(d)) && 3471 this.tsTypeChecker.getTypeAtLocation(d.parent) === baseType; 3472 } 3473 ) as ts.MethodDeclaration | ts.MethodSignature; 3474 3475 if (!baseMethodDecl) { 3476 continue; 3477 } 3478 3479 this.checkMethodParameters(node, baseMethodDecl); 3480 3481 this.checkMethodReturnType(node, baseMethodDecl); 3482 3483 break; 3484 } 3485 } 3486 3487 private getAllBaseTypes(type: ts.Type, classDecl: ts.ClassDeclaration): ts.Type[] | undefined { 3488 const baseClasses = type.getBaseTypes() || []; 3489 if (!classDecl.heritageClauses) { 3490 return baseClasses; 3491 } 3492 const interfaces: ts.Type[] = []; 3493 for (const clause of classDecl.heritageClauses) { 3494 if (clause.token !== ts.SyntaxKind.ImplementsKeyword) { 3495 continue; 3496 } 3497 for (const typeNode of clause.types) { 3498 const interfaceType = this.tsTypeChecker.getTypeAtLocation(typeNode); 3499 interfaces.push(interfaceType); 3500 const parentInterfaces = interfaceType.getBaseTypes(); 3501 if (parentInterfaces) { 3502 interfaces.push(...parentInterfaces); 3503 } 3504 } 3505 } 3506 return [...baseClasses, ...interfaces]; 3507 } 3508 3509 /** 3510 * Checks method parameter compatibility 3511 * Derived parameter types must be same or wider than base (contravariance principle) 3512 */ 3513 private checkMethodParameters( 3514 derivedMethod: ts.MethodDeclaration, 3515 baseMethod: ts.MethodDeclaration | ts.MethodSignature 3516 ): void { 3517 const derivedParams = derivedMethod.parameters; 3518 const baseParams = baseMethod.parameters; 3519 3520 if (derivedParams.length !== baseParams.length) { 3521 this.incrementCounters(derivedMethod.name, FaultID.MethodInheritRule); 3522 return; 3523 } 3524 3525 const paramCount = Math.min(derivedParams.length, baseParams.length); 3526 3527 for (let i = 0; i < paramCount; i++) { 3528 const baseParamType = this.tsTypeChecker.getTypeAtLocation(baseParams[i]); 3529 const derivedParamType = this.tsTypeChecker.getTypeAtLocation(derivedParams[i]); 3530 3531 if (!this.isTypeSameOrWider(baseParamType, derivedParamType)) { 3532 this.incrementCounters(derivedParams[i], FaultID.MethodInheritRule); 3533 } 3534 } 3535 } 3536 3537 /** 3538 * Checks return type compatibility 3539 * Derived return type must be same or narrower than base (covariance principle) 3540 */ 3541 private checkMethodReturnType( 3542 derivedMethod: ts.MethodDeclaration, 3543 baseMethod: ts.MethodDeclaration | ts.MethodSignature 3544 ): void { 3545 if ( 3546 this.IsVoidTypeOnActualReturnType(baseMethod) && 3547 derivedMethod.type && 3548 !this.IsVoidTypeOnActualReturnType(derivedMethod) 3549 ) { 3550 this.incrementCounters(derivedMethod.type, FaultID.MethodInheritRule); 3551 return; 3552 } 3553 3554 if (!baseMethod.type || !derivedMethod.type) { 3555 return; 3556 } 3557 3558 const baseReturnType = this.tsTypeChecker.getTypeAtLocation(baseMethod.type); 3559 const derivedReturnType = this.tsTypeChecker.getTypeAtLocation(derivedMethod.type); 3560 3561 if (this.isDerivedTypeAssignable(derivedReturnType, baseReturnType)) { 3562 return; 3563 } 3564 3565 if (!this.isTypeAssignable(derivedReturnType, baseReturnType)) { 3566 this.incrementCounters(derivedMethod.type, FaultID.MethodInheritRule); 3567 } 3568 } 3569 3570 private IsVoidTypeOnActualReturnType(method: ts.MethodDeclaration | ts.MethodSignature): boolean | undefined { 3571 let type: ts.Type | undefined; 3572 if (method.type) { 3573 type = this.tsTypeChecker.getTypeAtLocation(method.type); 3574 } else { 3575 const signature = this.tsTypeChecker.getSignatureFromDeclaration(method); 3576 if (signature) { 3577 type = this.tsTypeChecker.getReturnTypeOfSignature(signature); 3578 } 3579 } 3580 return type && TsUtils.isVoidType(type); 3581 } 3582 3583 /** 3584 * Child type should include all types of parent type (be same or wider). 3585 * Returns true if every type in baseType is also included in derivedType. 3586 */ 3587 private isTypeSameOrWider(baseType: ts.Type, derivedType: ts.Type): boolean { 3588 const baseTypeSet = new Set(this.flattenUnionTypes(baseType)); 3589 const derivedTypeSet = new Set(this.flattenUnionTypes(derivedType)); 3590 3591 // Check if every type in baseType is also present in derivedType 3592 for (const typeStr of baseTypeSet) { 3593 if (!derivedTypeSet.has(typeStr)) { 3594 return false; 3595 } 3596 } 3597 3598 return true; 3599 } 3600 3601 // Checks structural assignability between two types. 3602 private isTypeAssignable(fromType: ts.Type, toType: ts.Type): boolean { 3603 if (this.isDerivedTypeAssignable(fromType, toType)) { 3604 return true; 3605 } 3606 const fromTypes = this.flattenUnionTypes(fromType); 3607 const toTypes = new Set(this.flattenUnionTypes(toType)); 3608 3609 // All types in `fromTypes` should exist in `toTypes` for assignability. 3610 return fromTypes.every((typeStr) => { 3611 return toTypes.has(typeStr); 3612 }); 3613 } 3614 3615 private isDerivedTypeAssignable(derivedType: ts.Type, baseType: ts.Type): boolean { 3616 const baseSymbol = baseType.getSymbol(); 3617 const derivedSymbol = derivedType.getSymbol(); 3618 3619 if (!baseSymbol || !derivedSymbol) { 3620 return false; 3621 } 3622 const baseDeclarations = baseSymbol.getDeclarations(); 3623 const derivedDeclarations = derivedSymbol.getDeclarations(); 3624 3625 if (!baseDeclarations || !derivedDeclarations) { 3626 return false; 3627 } 3628 const baseTypeNode = baseDeclarations[0]; 3629 const derivedTypeNode = derivedDeclarations[0]; 3630 3631 if (ts.isClassDeclaration(baseTypeNode) && ts.isClassDeclaration(derivedTypeNode)) { 3632 const baseTypes = this.tsTypeChecker.getTypeAtLocation(derivedTypeNode).getBaseTypes(); 3633 const baseTypesExtends = baseTypes?.some((t) => { 3634 return t === baseType; 3635 }); 3636 if (baseTypesExtends) { 3637 return true; 3638 } 3639 } 3640 3641 return false; 3642 } 3643 3644 // Converts union types into an array of type strings for easy comparison. 3645 private flattenUnionTypes(type: ts.Type): string[] { 3646 if (type.isUnion()) { 3647 return type.types.map((t) => { 3648 return this.tsTypeChecker.typeToString(t); 3649 }); 3650 } 3651 return [this.tsTypeChecker.typeToString(type)]; 3652 } 3653 3654 private checkClassImplementsMethod(classDecl: ts.ClassDeclaration, methodName: string): boolean { 3655 for (const member of classDecl.members) { 3656 if (member.name?.getText() === methodName) { 3657 if (ts.isPropertyDeclaration(member)) { 3658 this.incrementCounters(member, FaultID.MethodOverridingField); 3659 } 3660 } 3661 } 3662 return false; 3663 } 3664 3665 private handleMethodSignature(node: ts.MethodSignature): void { 3666 const tsMethodSign = node; 3667 if (this.options.arkts2 && ts.isInterfaceDeclaration(node.parent)) { 3668 const methodName = node.name.getText(); 3669 const interfaceName = node.parent.name.getText(); 3670 const allClasses = TypeScriptLinter.getAllClassesFromSourceFile(this.sourceFile); 3671 const allInterfaces = TypeScriptLinter.getAllInterfaceFromSourceFile(this.sourceFile); 3672 allClasses.forEach((classDecl) => { 3673 if (this.classImplementsInterface(classDecl, interfaceName)) { 3674 this.checkClassImplementsMethod(classDecl, methodName); 3675 } 3676 }); 3677 allInterfaces.forEach((interDecl) => { 3678 if (this.interfaceExtendsInterface(interDecl, interfaceName)) { 3679 this.checkInterfaceExtendsMethod(interDecl, methodName); 3680 } 3681 }); 3682 } 3683 if (!tsMethodSign.type) { 3684 this.handleMissingReturnType(tsMethodSign); 3685 } 3686 if (this.options.arkts2 && tsMethodSign.questionToken) { 3687 this.incrementCounters(tsMethodSign.questionToken, FaultID.OptionalMethod); 3688 } 3689 this.handleInvalidIdentifier(tsMethodSign); 3690 } 3691 3692 private interfaceExtendsInterface(interDecl: ts.InterfaceDeclaration, interfaceName: string): boolean { 3693 void this; 3694 if (!interDecl.heritageClauses) { 3695 return false; 3696 } 3697 return interDecl.heritageClauses.some((clause) => { 3698 return clause.types.some((type) => { 3699 return ( 3700 ts.isExpressionWithTypeArguments(type) && 3701 ts.isIdentifier(type.expression) && 3702 type.expression.text === interfaceName 3703 ); 3704 }); 3705 }); 3706 } 3707 3708 private checkInterfaceExtendsMethod(interDecl: ts.InterfaceDeclaration, methodName: string): void { 3709 for (const member of interDecl.members) { 3710 if (member.name?.getText() === methodName) { 3711 if (ts.isPropertySignature(member)) { 3712 this.incrementCounters(member, FaultID.MethodOverridingField); 3713 } 3714 } 3715 } 3716 } 3717 3718 private classImplementsInterface(classDecl: ts.ClassDeclaration, interfaceName: string): boolean { 3719 void this; 3720 if (!classDecl.heritageClauses) { 3721 return false; 3722 } 3723 return classDecl.heritageClauses.some((clause) => { 3724 return clause.types.some((type) => { 3725 return ( 3726 ts.isExpressionWithTypeArguments(type) && 3727 ts.isIdentifier(type.expression) && 3728 type.expression.text === interfaceName 3729 ); 3730 }); 3731 }); 3732 } 3733 3734 private handleClassStaticBlockDeclaration(node: ts.Node): void { 3735 const classStaticBlockDecl = node as ts.ClassStaticBlockDeclaration; 3736 if (!ts.isClassDeclaration(classStaticBlockDecl.parent)) { 3737 return; 3738 } 3739 this.reportThisKeywordsInScope(classStaticBlockDecl.body); 3740 } 3741 3742 private handleIdentifier(node: ts.Node): void { 3743 if (!ts.isIdentifier(node)) { 3744 return; 3745 } 3746 this.handleInterfaceImport(node); 3747 this.checkAsonSymbol(node); 3748 const tsIdentifier = node; 3749 this.handleTsInterop(tsIdentifier, () => { 3750 const parent = tsIdentifier.parent; 3751 if (ts.isPropertyAccessExpression(parent) || ts.isImportSpecifier(parent)) { 3752 return; 3753 } 3754 3755 const type = this.tsTypeChecker.getTypeAtLocation(tsIdentifier); 3756 this.checkUsageOfTsTypes(type, tsIdentifier); 3757 }); 3758 3759 const tsIdentSym = this.tsUtils.trueSymbolAtLocation(tsIdentifier); 3760 if (!tsIdentSym) { 3761 return; 3762 } 3763 3764 const isArkTs2 = this.options.arkts2; 3765 const isGlobalThis = tsIdentifier.text === 'globalThis'; 3766 3767 if ( 3768 isGlobalThis && 3769 (tsIdentSym.flags & ts.SymbolFlags.Module) !== 0 && 3770 (tsIdentSym.flags & ts.SymbolFlags.Transient) !== 0 3771 ) { 3772 this.handleGlobalThisCase(tsIdentifier, isArkTs2); 3773 } else { 3774 if (isArkTs2) { 3775 this.checkLimitedStdlibApi(tsIdentifier, tsIdentSym); 3776 } 3777 this.handleRestrictedValues(tsIdentifier, tsIdentSym); 3778 } 3779 3780 if (isArkTs2 && this.tsTypeChecker.isArgumentsSymbol(tsIdentSym)) { 3781 this.incrementCounters(node, FaultID.ArgumentsObject); 3782 } 3783 3784 if (isArkTs2) { 3785 this.checkWorkerSymbol(tsIdentSym, node); 3786 this.checkCollectionsSymbol(tsIdentSym, node); 3787 this.checkConcurrencySymbol(tsIdentSym, node); 3788 } 3789 } 3790 3791 private handlePropertyDescriptorInScenarios(node: ts.Node): void { 3792 if (ts.isVariableDeclaration(node)) { 3793 const name = node.name; 3794 this.handlePropertyDescriptor(name); 3795 3796 const type = node.type; 3797 if (!type || !ts.isTypeReferenceNode(type)) { 3798 return; 3799 } 3800 const typeName = type.typeName; 3801 this.handlePropertyDescriptor(typeName); 3802 } 3803 3804 if (ts.isParameter(node)) { 3805 const name = node.name; 3806 this.handlePropertyDescriptor(name); 3807 3808 const type = node.type; 3809 if (!type || !ts.isTypeReferenceNode(type)) { 3810 return; 3811 } 3812 const typeName = type.typeName; 3813 this.handlePropertyDescriptor(typeName); 3814 } 3815 3816 if (ts.isPropertyAccessExpression(node)) { 3817 const name = node.name; 3818 this.handlePropertyDescriptor(name); 3819 3820 const expression = node.expression; 3821 this.handlePropertyDescriptor(expression); 3822 } 3823 } 3824 3825 private handlePropertyDescriptor(node: ts.Node): void { 3826 if (!this.options.arkts2) { 3827 return; 3828 } 3829 3830 const symbol = this.tsUtils.trueSymbolAtLocation(node); 3831 if (!symbol || !ts.isIdentifier(node)) { 3832 return; 3833 } 3834 const tsIdentifier = node; 3835 const type = this.tsTypeChecker.getTypeOfSymbolAtLocation(symbol, tsIdentifier); 3836 3837 const typeSymbol = type.getSymbol(); 3838 const typeName = typeSymbol ? typeSymbol.getName() : symbol.getName(); 3839 3840 const noPropertyDescriptorSet = TypeScriptLinter.globalApiInfo.get(BuiltinProblem.BuiltinNoPropertyDescriptor); 3841 if (!noPropertyDescriptorSet) { 3842 return; 3843 } 3844 3845 const matchedApi = [...noPropertyDescriptorSet].some((apiInfoItem) => { 3846 if (apiInfoItem.api_info.parent_api?.length <= 0) { 3847 return false; 3848 } 3849 const apiInfoParentName = apiInfoItem.api_info.parent_api[0].api_name; 3850 const apiTypeName = apiInfoItem.api_info.method_return_type; 3851 const isSameApi = apiInfoParentName === typeName || apiTypeName === typeName; 3852 const decl = TsUtils.getDeclaration(typeSymbol ? typeSymbol : symbol); 3853 const sourceFileName = path.normalize(decl?.getSourceFile().fileName || ''); 3854 const isSameFile = sourceFileName.endsWith(path.normalize(apiInfoItem.file_path)); 3855 return isSameFile && isSameApi; 3856 }); 3857 3858 if (matchedApi) { 3859 this.incrementCounters(tsIdentifier, FaultID.NoPropertyDescriptor); 3860 } 3861 } 3862 3863 private handleGlobalThisCase(node: ts.Identifier, isArkTs2: boolean | undefined): void { 3864 let faultId = FaultID.GlobalThis; 3865 let targetNode: ts.Node = node; 3866 3867 if (!isArkTs2) { 3868 this.incrementCounters(targetNode, faultId); 3869 return; 3870 } 3871 faultId = FaultID.GlobalThisError; 3872 3873 if (ts.isPropertyAccessExpression(node.parent)) { 3874 const parentExpression = node.parent.parent; 3875 if ( 3876 parentExpression && 3877 ts.isBinaryExpression(parentExpression) && 3878 parentExpression.operatorToken.kind === ts.SyntaxKind.EqualsToken 3879 ) { 3880 targetNode = parentExpression; 3881 } else { 3882 targetNode = node.parent; 3883 } 3884 } 3885 3886 this.incrementCounters(targetNode, faultId); 3887 } 3888 3889 // hard-coded alternative to TypeScriptLinter.advancedClassChecks 3890 private isAllowedClassValueContext(tsIdentifier: ts.Identifier): boolean { 3891 let ctx: ts.Node = tsIdentifier; 3892 while (ts.isPropertyAccessExpression(ctx.parent) || ts.isQualifiedName(ctx.parent)) { 3893 ctx = ctx.parent; 3894 } 3895 if (ts.isPropertyAssignment(ctx.parent) && ts.isObjectLiteralExpression(ctx.parent.parent)) { 3896 ctx = ctx.parent.parent; 3897 } 3898 if (ts.isArrowFunction(ctx.parent) && ctx.parent.body === ctx) { 3899 ctx = ctx.parent; 3900 } 3901 3902 if (ts.isCallExpression(ctx.parent) || ts.isNewExpression(ctx.parent)) { 3903 const callee = ctx.parent.expression; 3904 const isAny = TsUtils.isAnyType(this.tsTypeChecker.getTypeAtLocation(callee)); 3905 const isDynamic = isAny || this.tsUtils.hasLibraryType(callee); 3906 if (callee !== ctx && isDynamic) { 3907 return true; 3908 } 3909 } 3910 return false; 3911 } 3912 3913 private isStdlibClassVarDecl(ident: ts.Identifier, sym: ts.Symbol): boolean { 3914 3915 /* 3916 * Most standard JS classes are defined in TS stdlib as ambient global 3917 * variables with interface constructor type and require special check 3918 * when they are being referenced in code. 3919 */ 3920 3921 if ( 3922 !isStdLibrarySymbol(sym) || 3923 !sym.valueDeclaration || 3924 !ts.isVariableDeclaration(sym.valueDeclaration) || 3925 !TsUtils.isAmbientNode(sym.valueDeclaration) 3926 ) { 3927 return false; 3928 } 3929 3930 /* 3931 * issue 24075: TS supports calling the constructor of built-in types 3932 * as function (without 'new' keyword): `const a = Number('10')` 3933 * Such cases need to be filtered out. 3934 */ 3935 if (ts.isCallExpression(ident.parent) && ident.parent.expression === ident) { 3936 return false; 3937 } 3938 3939 const classVarDeclType = StdClassVarDecls.get(sym.name); 3940 if (!classVarDeclType) { 3941 return false; 3942 } 3943 const declType = this.tsTypeChecker.getTypeAtLocation(ident); 3944 return declType.symbol && declType.symbol.name === classVarDeclType; 3945 } 3946 3947 private handleRestrictedValues(tsIdentifier: ts.Identifier, tsIdentSym: ts.Symbol): void { 3948 const illegalValues = 3949 ts.SymbolFlags.ConstEnum | 3950 ts.SymbolFlags.RegularEnum | 3951 ts.SymbolFlags.ValueModule | 3952 (this.options.advancedClassChecks ? 0 : ts.SymbolFlags.Class); 3953 3954 /* 3955 * If module name is duplicated by another declaration, this increases the possibility 3956 * of finding a lot of false positives. Thus, do not check further in that case. 3957 */ 3958 if ((tsIdentSym.flags & ts.SymbolFlags.ValueModule) !== 0) { 3959 if (!!tsIdentSym && TsUtils.symbolHasDuplicateName(tsIdentSym, ts.SyntaxKind.ModuleDeclaration)) { 3960 return; 3961 } 3962 } 3963 3964 if ( 3965 (tsIdentSym.flags & illegalValues) === 0 && !this.isStdlibClassVarDecl(tsIdentifier, tsIdentSym) || 3966 isStruct(tsIdentSym) || 3967 !identiferUseInValueContext(tsIdentifier, tsIdentSym) 3968 ) { 3969 return; 3970 } 3971 3972 if ((tsIdentSym.flags & ts.SymbolFlags.Class) !== 0) { 3973 if (!this.options.advancedClassChecks && this.isAllowedClassValueContext(tsIdentifier)) { 3974 return; 3975 } 3976 } 3977 3978 this.handleIllegalSymbolUsage(tsIdentifier, tsIdentSym); 3979 } 3980 3981 private handleIllegalSymbolUsage(tsIdentifier: ts.Identifier, tsIdentSym: ts.Symbol): void { 3982 if (tsIdentSym.flags & ts.SymbolFlags.ValueModule) { 3983 this.incrementCounters(tsIdentifier, FaultID.NamespaceAsObject); 3984 return; 3985 } 3986 3987 const typeName = tsIdentifier.getText(); 3988 const isWrapperObject = typeName === 'Number' || typeName === 'String' || typeName === 'Boolean'; 3989 if (isWrapperObject) { 3990 return; 3991 } 3992 3993 // Special-case element-access cast for autofix: (X as object)["prop"] 3994 const asExpr = tsIdentifier.parent; 3995 let elemAccess: ts.ElementAccessExpression | undefined; 3996 3997 if ( 3998 ts.isAsExpression(asExpr) && 3999 ts.isParenthesizedExpression(asExpr.parent) && 4000 ts.isElementAccessExpression(asExpr.parent.parent) && 4001 ts.isStringLiteral(asExpr.parent.parent.argumentExpression) 4002 ) { 4003 // only care if it’s literally “as object” && static-class casts 4004 if (asExpr.type.getText() === 'object' && tsIdentSym.flags & ts.SymbolFlags.Class) { 4005 elemAccess = asExpr.parent.parent; 4006 } 4007 } 4008 4009 const autofix = elemAccess ? this.autofixer?.fixPropertyAccessByIndex(elemAccess) : undefined; 4010 const faultId = this.options.arkts2 ? FaultID.ClassAsObjectError : FaultID.ClassAsObject; 4011 this.incrementCounters(tsIdentifier, faultId, autofix); 4012 } 4013 4014 private isElementAcessAllowed(type: ts.Type, argType: ts.Type): boolean { 4015 if (type.isUnion()) { 4016 for (const t of type.types) { 4017 if (!this.isElementAcessAllowed(t, argType)) { 4018 return false; 4019 } 4020 } 4021 return true; 4022 } 4023 4024 const typeNode = this.tsTypeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.None); 4025 4026 if (this.tsUtils.isArkTSCollectionsArrayLikeType(type)) { 4027 return this.tsUtils.isNumberLikeType(argType); 4028 } 4029 4030 return ( 4031 this.tsUtils.isLibraryType(type) || 4032 TsUtils.isAnyType(type) || 4033 this.tsUtils.isOrDerivedFrom(type, this.tsUtils.isIndexableArray) || 4034 this.tsUtils.isOrDerivedFrom(type, TsUtils.isTuple) || 4035 this.tsUtils.isOrDerivedFrom(type, this.tsUtils.isStdRecordType) || 4036 this.tsUtils.isOrDerivedFrom(type, this.tsUtils.isStringType) || 4037 !this.options.arkts2 && 4038 (this.tsUtils.isOrDerivedFrom(type, this.tsUtils.isStdMapType) || TsUtils.isIntrinsicObjectType(type)) || 4039 TsUtils.isEnumType(type) || 4040 // we allow EsObject here beacuse it is reported later using FaultId.EsObjectType 4041 TsUtils.isEsValueType(typeNode) 4042 ); 4043 } 4044 4045 private handleElementAccessExpression(node: ts.Node): void { 4046 const tsElementAccessExpr = node as ts.ElementAccessExpression; 4047 const tsElemAccessBaseExprType = this.tsUtils.getNonNullableType( 4048 this.tsUtils.getTypeOrTypeConstraintAtLocation(tsElementAccessExpr.expression) 4049 ); 4050 const tsElemAccessArgType = this.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.argumentExpression); 4051 4052 if (this.tsUtils.hasEsObjectType(tsElementAccessExpr.expression)) { 4053 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 4054 this.incrementCounters(node, faultId); 4055 } 4056 if (this.tsUtils.isOrDerivedFrom(tsElemAccessBaseExprType, this.tsUtils.isIndexableArray)) { 4057 this.handleIndexNegative(node); 4058 } 4059 this.checkPropertyAccessByIndex(tsElementAccessExpr, tsElemAccessBaseExprType, tsElemAccessArgType); 4060 this.checkArrayUsageWithoutBound(tsElementAccessExpr); 4061 this.checkArrayIndexType(tsElemAccessBaseExprType, tsElemAccessArgType, tsElementAccessExpr); 4062 this.fixJsImportElementAccessExpression(tsElementAccessExpr); 4063 this.checkInterOpImportJsIndex(tsElementAccessExpr); 4064 this.checkEnumGetMemberValue(tsElementAccessExpr); 4065 } 4066 4067 private checkPropertyAccessByIndex( 4068 tsElementAccessExpr: ts.ElementAccessExpression, 4069 tsElemAccessBaseExprType: ts.Type, 4070 tsElemAccessArgType: ts.Type 4071 ): void { 4072 const tsElementAccessExprSymbol = this.tsUtils.trueSymbolAtLocation(tsElementAccessExpr.expression); 4073 4074 const isSet = TsUtils.isSetExpression(tsElementAccessExpr); 4075 const isSetIndexable = 4076 isSet && 4077 this.tsUtils.isSetIndexableType( 4078 tsElemAccessBaseExprType, 4079 tsElemAccessArgType, 4080 this.tsTypeChecker.getTypeAtLocation((tsElementAccessExpr.parent as ts.BinaryExpression).right) 4081 ); 4082 4083 const isGet = !isSet; 4084 const isGetIndexable = isGet && this.tsUtils.isGetIndexableType(tsElemAccessBaseExprType, tsElemAccessArgType); 4085 4086 if ( 4087 // unnamed types do not have symbol, so need to check that explicitly 4088 this.tsUtils.isLibrarySymbol(tsElementAccessExprSymbol) || 4089 ts.isArrayLiteralExpression(tsElementAccessExpr.expression) || 4090 this.isElementAcessAllowed(tsElemAccessBaseExprType, tsElemAccessArgType) || 4091 this.options.arkts2 && isGetIndexable || 4092 this.options.arkts2 && isSetIndexable 4093 ) { 4094 return; 4095 } 4096 4097 if (this.isStaticClassAccess(tsElementAccessExpr)) { 4098 return; 4099 } 4100 4101 const autofix = this.autofixer?.fixPropertyAccessByIndex(tsElementAccessExpr); 4102 this.incrementCounters(tsElementAccessExpr, FaultID.PropertyAccessByIndex, autofix); 4103 } 4104 4105 /** 4106 * Returns true if this element-access is a static-class cast (e.g., (A as object)["foo"]). 4107 */ 4108 private isStaticClassAccess(expr: ts.ElementAccessExpression): boolean { 4109 const inner = expr.expression; 4110 if ( 4111 ts.isParenthesizedExpression(inner) && 4112 ts.isAsExpression(inner.expression) && 4113 ts.isIdentifier(inner.expression.expression) 4114 ) { 4115 const sym = this.tsTypeChecker.getSymbolAtLocation(inner.expression.expression); 4116 return !!(sym && sym.flags & ts.SymbolFlags.Class); 4117 } 4118 return false; 4119 } 4120 4121 private checkInterOpImportJsIndex(expr: ts.ElementAccessExpression): void { 4122 if (!this.useStatic || !this.options.arkts2) { 4123 return; 4124 } 4125 4126 const exprSym = this.tsUtils.trueSymbolAtLocation(expr.expression); 4127 if (!exprSym) { 4128 return; 4129 } 4130 4131 const exprDecl = TsUtils.getDeclaration(exprSym); 4132 if (!exprDecl || !ts.isVariableDeclaration(exprDecl)) { 4133 return; 4134 } 4135 4136 const initializer = exprDecl.initializer; 4137 if (!initializer || !ts.isPropertyAccessExpression(initializer)) { 4138 return; 4139 } 4140 4141 const initSym = this.tsUtils.trueSymbolAtLocation(initializer.expression); 4142 if (!initSym) { 4143 return; 4144 } 4145 4146 const initDecl = TsUtils.getDeclaration(initSym); 4147 if (!initDecl?.getSourceFile().fileName.endsWith(EXTNAME_JS)) { 4148 return; 4149 } 4150 4151 if (ts.isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) { 4152 const autofix = this.autofixer?.fixInteropArrayBinaryExpression(expr.parent); 4153 this.incrementCounters(expr.parent, FaultID.InterOpImportJsIndex, autofix); 4154 } else { 4155 const autofix = this.autofixer?.fixInteropArrayElementAccessExpression(expr); 4156 this.incrementCounters(expr, FaultID.InterOpImportJsIndex, autofix); 4157 } 4158 } 4159 4160 private checkArrayIndexType(exprType: ts.Type, argType: ts.Type, expr: ts.ElementAccessExpression): void { 4161 if (!this.options.arkts2 || !this.tsUtils.isOrDerivedFrom(exprType, this.tsUtils.isIndexableArray)) { 4162 return; 4163 } 4164 4165 const validStringLiteralTypes = [ 4166 STRINGLITERAL_INT, 4167 STRINGLITERAL_BYTE, 4168 STRINGLITERAL_SHORT, 4169 STRINGLITERAL_LONG, 4170 STRINGLITERAL_CHAR 4171 ]; 4172 const argTypeString = this.tsTypeChecker.typeToString(argType); 4173 4174 if (this.tsUtils.isNumberLikeType(argType)) { 4175 this.handleNumericArgument(expr.argumentExpression, argType); 4176 } else if (!validStringLiteralTypes.includes(argTypeString)) { 4177 this.incrementCounters(expr.argumentExpression, FaultID.ArrayIndexExprType); 4178 } 4179 } 4180 4181 private handleNumericArgument(argExpr: ts.Expression, argType: ts.Type): void { 4182 const isNumericLiteral = ts.isNumericLiteral(argExpr); 4183 const argText = argExpr.getText(); 4184 const argValue = Number(argText); 4185 4186 if (isNumericLiteral) { 4187 const isInteger = Number.isInteger(argValue); 4188 const containsDot = argText.includes('.'); 4189 4190 if (!isInteger || containsDot) { 4191 const autofix = this.autofixer?.fixArrayIndexExprType(argExpr); 4192 this.incrementCounters(argExpr, FaultID.ArrayIndexExprType, autofix); 4193 } 4194 } else if (this.tsTypeChecker.typeToString(argType) === 'number') { 4195 if (this.isArrayIndexValidNumber(argExpr)) { 4196 return; 4197 } 4198 const autofix = this.autofixer?.fixArrayIndexExprType(argExpr); 4199 this.incrementCounters(argExpr, FaultID.ArrayIndexExprType, autofix); 4200 } else { 4201 this.checkNumericArgumentDeclaration(argExpr); 4202 } 4203 4204 if (ts.isConditionalExpression(argExpr)) { 4205 this.incrementCounters(argExpr, FaultID.ArrayIndexExprType); 4206 } 4207 } 4208 4209 private checkNumericArgumentDeclaration(argExpr: ts.Expression): void { 4210 const symbol = this.tsTypeChecker.getSymbolAtLocation(argExpr); 4211 4212 if (!symbol) { 4213 return; 4214 } 4215 4216 const declarations = symbol.getDeclarations(); 4217 if (!declarations || declarations.length === 0) { 4218 return; 4219 } 4220 4221 const firstDeclaration = declarations[0] as ts.VariableDeclaration; 4222 const initializer = firstDeclaration.initializer; 4223 const initializerText = initializer ? initializer.getText() : 'undefined'; 4224 const isNumericInitializer = initializer && ts.isNumericLiteral(initializer); 4225 const initializerNumber = isNumericInitializer ? Number(initializerText) : NaN; 4226 const isUnsafeNumber = isNumericInitializer && !Number.isInteger(initializerNumber); 4227 4228 if (isUnsafeNumber) { 4229 const autofix = this.autofixer?.fixArrayIndexExprType(argExpr); 4230 this.incrementCounters(argExpr, FaultID.ArrayIndexExprType, autofix); 4231 } 4232 4233 if (initializerText === 'undefined') { 4234 this.handleUndefinedInitializer(argExpr, firstDeclaration); 4235 } 4236 } 4237 4238 private evaluateValueFromDeclaration(argExpr: ts.Expression): number | null | 'skip' { 4239 const declaration = this.tsUtils.getDeclarationNode(argExpr); 4240 if (!declaration) { 4241 return null; 4242 } 4243 4244 if (!ts.isVariableDeclaration(declaration)) { 4245 return null; 4246 } 4247 4248 if (declaration.type !== undefined) { 4249 return 'skip'; 4250 } 4251 4252 const initializer = declaration.initializer; 4253 if (!initializer) { 4254 return null; 4255 } 4256 4257 if (!ts.isNumericLiteral(initializer)) { 4258 return null; 4259 } 4260 4261 const numericValue = Number(initializer.text); 4262 if (!Number.isInteger(numericValue)) { 4263 return null; 4264 } 4265 4266 return numericValue; 4267 } 4268 4269 private isArrayIndexValidNumber(argExpr: ts.Expression): boolean { 4270 let evaluatedValue: number | null = null; 4271 if (ts.isParenthesizedExpression(argExpr)) { 4272 return this.isArrayIndexValidNumber(argExpr.expression); 4273 } 4274 4275 if (ts.isBinaryExpression(argExpr)) { 4276 evaluatedValue = this.evaluateNumericValueFromBinaryExpression(argExpr); 4277 } else { 4278 const evalResult = this.evaluateValueFromDeclaration(argExpr); 4279 if (evalResult === 'skip') { 4280 return false; 4281 } 4282 evaluatedValue = evalResult; 4283 } 4284 4285 if (evaluatedValue === null) { 4286 return false; 4287 } 4288 4289 const valueString = String(evaluatedValue); 4290 4291 if (!Number.isInteger(evaluatedValue)) { 4292 return false; 4293 } 4294 if (valueString.includes('.')) { 4295 return false; 4296 } 4297 4298 return evaluatedValue >= 0; 4299 } 4300 4301 private handleUndefinedInitializer(argExpr: ts.Expression, declaration: ts.VariableDeclaration): void { 4302 if (ts.isParameter(declaration)) { 4303 const autofix = this.autofixer?.fixArrayIndexExprType(argExpr); 4304 this.incrementCounters(argExpr, FaultID.ArrayIndexExprType, autofix); 4305 } else { 4306 this.incrementCounters(argExpr, FaultID.ArrayIndexExprType); 4307 } 4308 } 4309 4310 private handleEnumMember(node: ts.Node): void { 4311 const tsEnumMember = node as ts.EnumMember; 4312 const tsEnumMemberType = this.tsTypeChecker.getTypeAtLocation(tsEnumMember); 4313 const constVal = this.tsTypeChecker.getConstantValue(tsEnumMember); 4314 const tsEnumMemberName = tsEnumMember.name; 4315 if (this.options.arkts2) { 4316 this.handleInvalidIdentifier(tsEnumMember); 4317 if (ts.isStringLiteral(tsEnumMemberName)) { 4318 this.handleStringLiteralEnumMember(tsEnumMember, tsEnumMemberName, node); 4319 } 4320 } 4321 4322 if (tsEnumMember.initializer && !this.tsUtils.isValidEnumMemberInit(tsEnumMember.initializer)) { 4323 this.incrementCounters(node, FaultID.EnumMemberNonConstInit); 4324 } 4325 // check for type - all members should be of same type 4326 const enumDecl = tsEnumMember.parent; 4327 const firstEnumMember = enumDecl.members[0]; 4328 const firstEnumMemberType = this.tsTypeChecker.getTypeAtLocation(firstEnumMember); 4329 const firstElewmVal = this.tsTypeChecker.getConstantValue(firstEnumMember); 4330 this.handleEnumNotSupportFloat(tsEnumMember); 4331 4332 /* 4333 * each string enum member has its own type 4334 * so check that value type is string 4335 */ 4336 if ( 4337 constVal !== undefined && 4338 typeof constVal === STRINGLITERAL_STRING && 4339 firstElewmVal !== undefined && 4340 typeof firstElewmVal === STRINGLITERAL_STRING 4341 ) { 4342 return; 4343 } 4344 if ( 4345 constVal !== undefined && 4346 typeof constVal === STRINGLITERAL_NUMBER && 4347 firstElewmVal !== undefined && 4348 typeof firstElewmVal === STRINGLITERAL_NUMBER 4349 ) { 4350 return; 4351 } 4352 if (firstEnumMemberType !== tsEnumMemberType) { 4353 this.incrementCounters(node, FaultID.EnumMemberNonConstInit); 4354 } 4355 } 4356 4357 private handleStringLiteralEnumMember( 4358 tsEnumMember: ts.EnumMember, 4359 tsEnumMemberName: ts.StringLiteral, 4360 node: ts.Node 4361 ): void { 4362 const autofix = this.autofixer?.fixLiteralAsPropertyNamePropertyName(tsEnumMemberName, tsEnumMember); 4363 this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofix); 4364 } 4365 4366 private handleEnumNotSupportFloat(enumMember: ts.EnumMember): void { 4367 if (!this.options.arkts2) { 4368 return; 4369 } 4370 const initializer = enumMember.initializer; 4371 if (!initializer) { 4372 return; 4373 } 4374 let value; 4375 if (ts.isNumericLiteral(initializer)) { 4376 value = parseFloat(initializer.text); 4377 } else if (ts.isPrefixUnaryExpression(initializer)) { 4378 const operand = initializer.operand; 4379 value = ts.isNumericLiteral(operand) ? parseFloat(operand.text) : value; 4380 } else { 4381 return; 4382 } 4383 4384 if (!Number.isInteger(value)) { 4385 this.incrementCounters(enumMember, FaultID.EnumMemberNonConstInit); 4386 } 4387 } 4388 4389 private handleExportAssignment(node: ts.Node): void { 4390 const exportAssignment = node as ts.ExportAssignment; 4391 if (exportAssignment.isExportEquals) { 4392 this.incrementCounters(node, FaultID.ExportAssignment); 4393 } 4394 4395 if (!TypeScriptLinter.inSharedModule(node)) { 4396 return; 4397 } 4398 4399 if (!this.tsUtils.isShareableEntity(exportAssignment.expression)) { 4400 this.incrementCounters(exportAssignment.expression, FaultID.SharedModuleExports); 4401 } 4402 } 4403 4404 private processCalleeSym(calleeSym: ts.Symbol, tsCallExpr: ts.CallExpression): void { 4405 const name = calleeSym.getName(); 4406 const parName = this.tsUtils.getParentSymbolName(calleeSym); 4407 if (!this.options.arkts2) { 4408 this.handleStdlibAPICall(tsCallExpr, calleeSym, name, parName); 4409 this.handleFunctionApplyBindPropCall(tsCallExpr, calleeSym); 4410 } else { 4411 this.handleBuiltinThisArgs(tsCallExpr, calleeSym, name, parName); 4412 } 4413 if (TsUtils.symbolHasEsObjectType(calleeSym)) { 4414 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 4415 this.incrementCounters(tsCallExpr, faultId); 4416 } 4417 // Need to process Symbol call separately in order to not report two times when using Symbol API 4418 if (this.options.arkts2 && this.tsUtils.isStdSymbol(calleeSym)) { 4419 this.incrementCounters(tsCallExpr, FaultID.SymbolType); 4420 } 4421 4422 if (this.options.arkts2 && calleeSym.getEscapedName() === 'pow' && isStdLibrarySymbol(calleeSym)) { 4423 this.incrementCounters(tsCallExpr, FaultID.MathPow); 4424 } 4425 4426 if (this.options.arkts2 && calleeSym.getEscapedName() === 'RegExp' && isStdLibrarySymbol(calleeSym)) { 4427 const autofix = this.autofixer?.fixRegularExpressionLiteral(tsCallExpr); 4428 this.incrementCounters(tsCallExpr, FaultID.RegularExpressionLiteral, autofix); 4429 } 4430 } 4431 4432 private handleSdkPropertyAccessByIndex(tsCallExpr: ts.CallExpression): void { 4433 const propertyAccessNode = tsCallExpr.expression as ts.PropertyAccessExpression; 4434 if (!ts.isPropertyAccessExpression(propertyAccessNode)) { 4435 return; 4436 } 4437 4438 const funcName = propertyAccessNode.name; 4439 const indexedTypeSdkInfos = Array.from(TypeScriptLinter.indexedTypeSet); 4440 const isCallInDeprecatedApi = indexedTypeSdkInfos.some((indexedTypeSdkInfo) => { 4441 const isApiNameMismatch = funcName?.getText() !== indexedTypeSdkInfo.api_info.api_name; 4442 if (isApiNameMismatch) { 4443 return false; 4444 } 4445 4446 const funcDecls = this.tsTypeChecker.getTypeAtLocation(funcName).symbol?.declarations; 4447 return funcDecls?.some((declaration) => { 4448 const interfaceDecl = declaration.parent as ts.InterfaceDeclaration; 4449 if (!(ts.isMethodSignature(declaration) && ts.isInterfaceDeclaration(interfaceDecl))) { 4450 return false; 4451 } 4452 const declFileFromJson = path.normalize(interfaceDecl.getSourceFile().fileName); 4453 const declFileFromSdk = path.normalize(indexedTypeSdkInfo.file_path); 4454 const isSameSdkFilePath = declFileFromJson.endsWith(declFileFromSdk); 4455 const interfaceNameData = indexedTypeSdkInfo.api_info.parent_api[0].api_name; 4456 const isSameInterfaceName = interfaceDecl.name.getText() === interfaceNameData; 4457 return isSameSdkFilePath && isSameInterfaceName; 4458 }); 4459 }); 4460 if (isCallInDeprecatedApi) { 4461 this.incrementCounters(tsCallExpr.expression, FaultID.PropertyAccessByIndexFromSdk); 4462 } 4463 } 4464 4465 private handleBuiltinCtorCallSignature(tsCallExpr: ts.CallExpression | ts.TypeReferenceNode): void { 4466 if (!this.options.arkts2) { 4467 return; 4468 } 4469 const node = ts.isCallExpression(tsCallExpr) ? tsCallExpr.expression : tsCallExpr.typeName; 4470 const constructorType = this.tsTypeChecker.getTypeAtLocation(node); 4471 const callSignatures = constructorType.getCallSignatures(); 4472 if (callSignatures.length === 0 || BUILTIN_DISABLE_CALLSIGNATURE.includes(node.getText())) { 4473 return; 4474 } 4475 const isSameApi = callSignatures.some((callSignature) => { 4476 const callSignatureDecl = callSignature.getDeclaration(); 4477 if (!ts.isCallSignatureDeclaration(callSignatureDecl)) { 4478 return false; 4479 } 4480 const parentDecl = callSignatureDecl.parent; 4481 const parentName = ts.isInterfaceDeclaration(parentDecl) ? parentDecl.name.getText() : ''; 4482 const BultinNoCtorFuncApiInfoSet = TypeScriptLinter.globalApiInfo.get(BuiltinProblem.BuiltinNoCtorFunc); 4483 if (!BultinNoCtorFuncApiInfoSet) { 4484 return false; 4485 } 4486 const isSameApi = [...BultinNoCtorFuncApiInfoSet].some((apiInfoItem) => { 4487 if (apiInfoItem.api_info.parent_api?.length <= 0) { 4488 return false; 4489 } 4490 const apiInfoParentName = apiInfoItem.api_info.parent_api[0].api_name; 4491 return apiInfoParentName === parentName; 4492 }); 4493 return isSameApi; 4494 }); 4495 if (isSameApi) { 4496 this.incrementCounters(node, FaultID.BuiltinNoCtorFunc); 4497 } 4498 } 4499 4500 private handleCallExpression(node: ts.Node): void { 4501 const tsCallExpr = node as ts.CallExpression; 4502 this.handleStateStyles(tsCallExpr); 4503 this.handleBuiltinCtorCallSignature(tsCallExpr); 4504 this.handleSdkConstructorIfaceForCallExpression(tsCallExpr); 4505 if (this.options.arkts2 && tsCallExpr.typeArguments !== undefined) { 4506 this.handleSdkPropertyAccessByIndex(tsCallExpr); 4507 } 4508 const calleeSym = this.tsUtils.trueSymbolAtLocation(tsCallExpr.expression); 4509 const callSignature = this.tsTypeChecker.getResolvedSignature(tsCallExpr); 4510 this.handleImportCall(tsCallExpr); 4511 this.handleRequireCall(tsCallExpr); 4512 if (calleeSym !== undefined) { 4513 this.processCalleeSym(calleeSym, tsCallExpr); 4514 } 4515 if (callSignature !== undefined) { 4516 if (!this.tsUtils.isLibrarySymbol(calleeSym)) { 4517 this.handleStructIdentAndUndefinedInArgs(tsCallExpr, callSignature); 4518 this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature); 4519 } else if (this.options.arkts2) { 4520 this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature); 4521 } 4522 this.handleNotsLikeSmartTypeOnCallExpression(tsCallExpr, callSignature); 4523 } 4524 this.handleInteropForCallExpression(tsCallExpr); 4525 this.handleLibraryTypeCall(tsCallExpr); 4526 if ( 4527 ts.isPropertyAccessExpression(tsCallExpr.expression) && 4528 this.tsUtils.hasEsObjectType(tsCallExpr.expression.expression) 4529 ) { 4530 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 4531 this.incrementCounters(node, faultId); 4532 } 4533 if ( 4534 !ts.isExpressionStatement(tsCallExpr.parent) && 4535 !ts.isVoidExpression(tsCallExpr.parent) && 4536 !ts.isArrowFunction(tsCallExpr.parent) && 4537 !(ts.isConditionalExpression(tsCallExpr.parent) && ts.isExpressionStatement(tsCallExpr.parent.parent)) 4538 ) { 4539 this.handleLimitedVoidWithCall(tsCallExpr); 4540 } 4541 this.handleAppStorageCallExpression(tsCallExpr); 4542 this.fixJsImportCallExpression(tsCallExpr); 4543 this.handleInteropForCallJSExpression(tsCallExpr, calleeSym, callSignature); 4544 this.handleNoTsLikeFunctionCall(tsCallExpr); 4545 this.handleObjectLiteralInFunctionArgs(tsCallExpr); 4546 this.handleSdkGlobalApi(tsCallExpr); 4547 this.handleObjectLiteralAssignmentToClass(tsCallExpr); 4548 this.checkRestrictedAPICall(tsCallExpr); 4549 } 4550 4551 handleNoTsLikeFunctionCall(callExpr: ts.CallExpression): void { 4552 if (!this.options.arkts2) { 4553 return; 4554 } 4555 4556 const expression = callExpr.expression; 4557 const type = this.tsTypeChecker.getTypeAtLocation(expression); 4558 const typeText = this.tsTypeChecker.typeToString(type); 4559 if (typeText === LIKE_FUNCTION) { 4560 const autofix = this.autofixer?.fixNoTsLikeFunctionCall(callExpr); 4561 this.incrementCounters(expression, FaultID.ExplicitFunctionType, autofix); 4562 } 4563 } 4564 4565 private handleAppStorageCallExpression(tsCallExpr: ts.CallExpression): void { 4566 if (!this.options.arkts2 || !tsCallExpr) { 4567 return; 4568 } 4569 4570 if (!TsUtils.isAppStorageAccess(tsCallExpr)) { 4571 return; 4572 } 4573 4574 let varDecl: ts.VariableDeclaration | undefined; 4575 let parent = tsCallExpr.parent; 4576 4577 while (parent) { 4578 if (ts.isVariableDeclaration(parent)) { 4579 varDecl = parent; 4580 break; 4581 } 4582 parent = parent.parent; 4583 } 4584 4585 if (!varDecl || varDecl.type) { 4586 return; 4587 } 4588 const callReturnType = this.tsTypeChecker.getTypeAtLocation(tsCallExpr); 4589 const isNumberReturnType = callReturnType.flags & ts.TypeFlags.Number; 4590 const isNumberGeneric = ((): boolean => { 4591 if (tsCallExpr.typeArguments?.length !== 1) { 4592 return false; 4593 } 4594 const callText = tsCallExpr.getText(); 4595 if (callText.startsWith('Array<') || callText.startsWith('Set<') || callText.startsWith('Map<')) { 4596 return false; 4597 } 4598 const typeArg = tsCallExpr.typeArguments[0]; 4599 if (typeArg.kind === ts.SyntaxKind.NumberKeyword) { 4600 return true; 4601 } 4602 4603 if (ts.isTypeReferenceNode(typeArg)) { 4604 return ts.isIdentifier(typeArg.typeName) && typeArg.typeName.text === STRINGLITERAL_NUMBER; 4605 } 4606 return typeArg.getText().trim() === STRINGLITERAL_NUMBER; 4607 })(); 4608 4609 if (isNumberGeneric && !isNumberReturnType) { 4610 const autofix = this.autofixer?.fixAppStorageCallExpression(tsCallExpr); 4611 this.incrementCounters(tsCallExpr, FaultID.NumericSemantics, autofix); 4612 } 4613 } 4614 4615 handleInteropForCallJSExpression( 4616 tsCallExpr: ts.CallExpression, 4617 sym: ts.Symbol | undefined, 4618 callSignature: ts.Signature | undefined 4619 ): void { 4620 if (!this.options.arkts2 || !this.useStatic) { 4621 return; 4622 } 4623 if (ts.isAwaitExpression(tsCallExpr.parent) || ts.isTypeOfExpression(tsCallExpr.parent)) { 4624 return; 4625 } 4626 4627 if (!callSignature || this.isDeclaredInArkTs2(callSignature)) { 4628 return; 4629 } 4630 4631 if (!sym?.declarations?.[0]?.getSourceFile().fileName.endsWith(EXTNAME_JS)) { 4632 return; 4633 } 4634 4635 const autofix = this.autofixer?.fixInteropInvokeExpression(tsCallExpr); 4636 4637 this.incrementCounters( 4638 tsCallExpr, 4639 ts.isPropertyAccessExpression(tsCallExpr.expression) ? FaultID.InteropCallObjectMethods : FaultID.CallJSFunction, 4640 autofix 4641 ); 4642 } 4643 4644 private handleInteropForCallExpression(tsCallExpr: ts.CallExpression): void { 4645 if (!this.options.arkts2 || !this.useStatic) { 4646 return; 4647 } 4648 4649 const callSignature = this.tsTypeChecker.getResolvedSignature(tsCallExpr); 4650 if (!callSignature) { 4651 return; 4652 } 4653 4654 if (!this.isDeclaredInArkTs2(callSignature)) { 4655 return; 4656 } 4657 4658 this.checkForForbiddenAPIs(callSignature, tsCallExpr); 4659 } 4660 4661 private isExportedEntityDeclaredInJs(exportDecl: ts.ExportDeclaration): boolean { 4662 if (!this.options.arkts2 || !this.useStatic) { 4663 return false; 4664 } 4665 4666 // For named exports with braces { ... } 4667 if (exportDecl.exportClause && ts.isNamedExports(exportDecl.exportClause)) { 4668 for (const exportSpecifier of exportDecl.exportClause.elements) { 4669 const identifier = exportSpecifier.name; 4670 if (this.tsUtils.isImportedFromJS(identifier)) { 4671 return true; 4672 } 4673 } 4674 } 4675 4676 // For namespace exports (export * as namespace from ...) 4677 if (exportDecl.exportClause && ts.isNamespaceExport(exportDecl.exportClause)) { 4678 const namespaceIdentifier = exportDecl.exportClause.name; 4679 if (this.tsUtils.isImportedFromJS(namespaceIdentifier)) { 4680 return true; 4681 } 4682 } 4683 4684 return false; 4685 } 4686 4687 private isDeclaredInArkTs2(callSignature: ts.Signature): boolean | undefined { 4688 const declarationSourceFile = callSignature?.declaration?.getSourceFile(); 4689 if (!declarationSourceFile) { 4690 return undefined; 4691 } 4692 if (!declarationSourceFile.statements) { 4693 return undefined; 4694 } 4695 4696 if (this.tsUtils.isArkts12File(declarationSourceFile)) { 4697 return true; 4698 } 4699 return false; 4700 } 4701 4702 private checkRestrictedAPICall(node: ts.CallExpression): void { 4703 if (TypeScriptLinter.isReflectAPICall(node)) { 4704 this.incrementCounters(node.parent, FaultID.InteropCallReflect); 4705 } 4706 } 4707 4708 static isReflectAPICall(callExpr: ts.CallExpression): boolean { 4709 if (ts.isPropertyAccessExpression(callExpr.expression)) { 4710 const expr = callExpr.expression.expression; 4711 if (ts.isIdentifier(expr) && expr.text === REFLECT_LITERAL) { 4712 return true; 4713 } 4714 } 4715 4716 if (ts.isElementAccessExpression(callExpr.expression)) { 4717 const expr = callExpr.expression.expression; 4718 if (ts.isIdentifier(expr) && expr.text === REFLECT_LITERAL) { 4719 return true; 4720 } 4721 } 4722 return false; 4723 } 4724 4725 private shouldCheckForForbiddenAPI(declaration: ts.SignatureDeclaration | ts.JSDocSignature): boolean { 4726 for (const parameter of declaration.parameters) { 4727 if (ts.isJSDocParameterTag(parameter)) { 4728 continue; 4729 } 4730 const parameterType = this.tsTypeChecker.getTypeAtLocation(parameter); 4731 const parameterTypeString = this.tsTypeChecker.typeToString(parameterType); 4732 4733 if (parameterTypeString === OBJECT_LITERAL) { 4734 return true; 4735 } 4736 } 4737 4738 return false; 4739 } 4740 4741 private checkForForbiddenAPIs(callSignature: ts.Signature, tsCallExpr: ts.CallExpression): void { 4742 if (!callSignature.declaration) { 4743 return; 4744 } 4745 4746 if (!this.shouldCheckForForbiddenAPI(callSignature.declaration)) { 4747 return; 4748 } 4749 4750 const functionSymbol = this.getFunctionSymbol(callSignature.declaration); 4751 const functionDeclaration = functionSymbol?.valueDeclaration; 4752 if (!functionDeclaration) { 4753 return; 4754 } 4755 4756 if (!TypeScriptLinter.isFunctionLike(functionDeclaration)) { 4757 return; 4758 } 4759 4760 switch (TypeScriptLinter.containsForbiddenAPI(functionDeclaration)) { 4761 case REFLECT_LITERAL: 4762 this.incrementCounters(tsCallExpr.parent, FaultID.InteropCallReflect); 4763 break; 4764 case OBJECT_LITERAL: 4765 this.incrementCounters(tsCallExpr.parent, FaultID.InteropCallObjectParam); 4766 break; 4767 default: 4768 break; 4769 } 4770 } 4771 4772 private handleEtsComponentExpression(node: ts.Node): void { 4773 // for all the checks we make EtsComponentExpression is compatible with the CallExpression 4774 const etsComponentExpression = node as ts.CallExpression; 4775 this.handleLibraryTypeCall(etsComponentExpression); 4776 } 4777 4778 private handleImportCall(tsCallExpr: ts.CallExpression): void { 4779 if (tsCallExpr.expression.kind !== ts.SyntaxKind.ImportKeyword) { 4780 return; 4781 } else if (this.options.arkts2) { 4782 this.incrementCounters(tsCallExpr, FaultID.DynamicImport); 4783 } 4784 4785 // relax rule#133 "arkts-no-runtime-import" 4786 const tsArgs = tsCallExpr.arguments; 4787 if (tsArgs.length <= 1 || !ts.isObjectLiteralExpression(tsArgs[1])) { 4788 return; 4789 } 4790 4791 for (const tsProp of tsArgs[1].properties) { 4792 if ( 4793 (ts.isPropertyAssignment(tsProp) || ts.isShorthandPropertyAssignment(tsProp)) && 4794 tsProp.name.getText() === 'assert' 4795 ) { 4796 this.incrementCounters(tsProp, FaultID.ImportAssertion); 4797 break; 4798 } 4799 } 4800 } 4801 4802 private handleRequireCall(tsCallExpr: ts.CallExpression): void { 4803 if ( 4804 ts.isIdentifier(tsCallExpr.expression) && 4805 tsCallExpr.expression.text === 'require' && 4806 ts.isVariableDeclaration(tsCallExpr.parent) 4807 ) { 4808 const tsType = this.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression); 4809 if (TsUtils.isInterfaceType(tsType) && tsType.symbol.name === 'NodeRequire') { 4810 this.incrementCounters(tsCallExpr.parent, FaultID.ImportAssignment); 4811 } 4812 } 4813 } 4814 4815 private handleGenericCallWithNoTypeArgs( 4816 callLikeExpr: ts.CallExpression | ts.NewExpression, 4817 callSignature: ts.Signature 4818 ): void { 4819 4820 /* 4821 * Note: The PR!716 has led to a significant performance degradation. 4822 * Since initial problem was fixed in a more general way, this change 4823 * became redundant. Therefore, it was reverted. See #13721 comments 4824 * for a detailed analysis. 4825 */ 4826 if (this.options.arkts2 && TypeScriptLinter.isInvalidBuiltinGenericConstructorCall(callLikeExpr)) { 4827 const autofix = this.autofixer?.fixGenericCallNoTypeArgs(callLikeExpr as ts.NewExpression); 4828 this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs, autofix); 4829 return; 4830 } 4831 this.checkTypeArgumentsForGenericCallWithNoTypeArgs(callLikeExpr, callSignature); 4832 } 4833 4834 private static isInvalidBuiltinGenericConstructorCall(newExpression: ts.CallExpression | ts.NewExpression): boolean { 4835 if (!ts.isNewExpression(newExpression)) { 4836 return false; 4837 } 4838 const isBuiltin = BUILTIN_GENERIC_CONSTRUCTORS.has(newExpression.expression.getText().replace(/Constructor$/, '')); 4839 return isBuiltin && (!newExpression.typeArguments || newExpression.typeArguments.length === 0); 4840 } 4841 4842 private checkTypeArgumentsForGenericCallWithNoTypeArgs( 4843 callLikeExpr: ts.CallExpression | ts.NewExpression, 4844 callSignature: ts.Signature 4845 ): void { 4846 if (ts.isNewExpression(callLikeExpr) && this.isNonGenericClass(callLikeExpr)) { 4847 return; 4848 } 4849 const tsSyntaxKind = ts.isNewExpression(callLikeExpr) ? 4850 ts.SyntaxKind.Constructor : 4851 ts.SyntaxKind.FunctionDeclaration; 4852 const signFlags = ts.NodeBuilderFlags.WriteTypeArgumentsOfSignature | ts.NodeBuilderFlags.IgnoreErrors; 4853 const signDecl = this.tsTypeChecker.signatureToSignatureDeclaration( 4854 callSignature, 4855 tsSyntaxKind, 4856 undefined, 4857 signFlags 4858 ); 4859 if (!signDecl?.typeArguments) { 4860 return; 4861 } 4862 const resolvedTypeArgs = signDecl.typeArguments; 4863 const providedTypeArgs = callLikeExpr.typeArguments; 4864 const startTypeArg = providedTypeArgs?.length ?? 0; 4865 let shouldReportError = startTypeArg !== resolvedTypeArgs.length; 4866 if (this.options.arkts2 && callLikeExpr.kind === ts.SyntaxKind.NewExpression) { 4867 shouldReportError = this.shouldReportGenericTypeArgsError( 4868 callLikeExpr, 4869 resolvedTypeArgs, 4870 providedTypeArgs, 4871 startTypeArg, 4872 shouldReportError 4873 ); 4874 if (shouldReportError) { 4875 const autofix = this.autofixer?.fixGenericCallNoTypeArgs(callLikeExpr); 4876 this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs, autofix); 4877 } 4878 } else { 4879 this.checkForUnknownTypeInNonArkTS2(callLikeExpr, resolvedTypeArgs, startTypeArg); 4880 } 4881 } 4882 4883 private checkForUnknownTypeInNonArkTS2( 4884 callLikeExpr: ts.CallExpression | ts.NewExpression, 4885 resolvedTypeArgs: ts.NodeArray<ts.TypeNode>, 4886 startTypeArg: number 4887 ): void { 4888 for (let i = startTypeArg; i < resolvedTypeArgs.length; ++i) { 4889 const typeNode = resolvedTypeArgs[i]; 4890 4891 /* 4892 * if compiler infers 'unknown' type there are 2 possible cases: 4893 * 1. Compiler unable to infer type from arguments and use 'unknown' 4894 * 2. Compiler infer 'unknown' from arguments 4895 * We report error in both cases. It is ok because we cannot use 'unknown' 4896 * in ArkTS and already have separate check for it. 4897 */ 4898 if (typeNode.kind === ts.SyntaxKind.UnknownKeyword) { 4899 this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs); 4900 break; 4901 } 4902 } 4903 } 4904 4905 private shouldReportGenericTypeArgsError( 4906 callLikeExpr: ts.CallExpression | ts.NewExpression, 4907 resolvedTypeArgs: ts.NodeArray<ts.TypeNode>, 4908 providedTypeArgs: ts.NodeArray<ts.TypeNode> | undefined, 4909 startTypeArg: number, 4910 initialErrorState: boolean 4911 ): boolean { 4912 const typeParameters = this.getOriginalTypeParameters(callLikeExpr); 4913 if (!typeParameters || typeParameters.length === 0) { 4914 return initialErrorState; 4915 } 4916 const optionalParamsCount = typeParameters.filter((param, index) => { 4917 return param.default && (!providedTypeArgs || index >= providedTypeArgs.length); 4918 }).length; 4919 if (optionalParamsCount === 0) { 4920 return initialErrorState; 4921 } 4922 return startTypeArg + optionalParamsCount !== resolvedTypeArgs.length; 4923 } 4924 4925 private getOriginalTypeParameters( 4926 callLikeExpr: ts.CallExpression | ts.NewExpression 4927 ): ts.TypeParameterDeclaration[] | undefined { 4928 const typeChecker = this.tsTypeChecker; 4929 const expressionType = typeChecker.getTypeAtLocation(callLikeExpr.expression); 4930 const declarations = expressionType.symbol?.declarations; 4931 if (!declarations || declarations.length === 0) { 4932 return undefined; 4933 } 4934 for (const decl of declarations) { 4935 if (ts.isFunctionDeclaration(decl) && decl.typeParameters) { 4936 return [...decl.typeParameters]; 4937 } 4938 if (ts.isMethodDeclaration(decl) && decl.typeParameters) { 4939 return [...decl.typeParameters]; 4940 } 4941 if (ts.isClassDeclaration(decl) && decl.typeParameters) { 4942 return [...decl.typeParameters]; 4943 } 4944 if (ts.isInterfaceDeclaration(decl) && decl.typeParameters) { 4945 return [...decl.typeParameters]; 4946 } 4947 } 4948 return undefined; 4949 } 4950 4951 private isNonGenericClass(expression: ts.NewExpression): boolean { 4952 const declaration = this.tsUtils.getDeclarationNode(expression.expression); 4953 return !!declaration && ts.isClassDeclaration(declaration) && !declaration.typeParameters; 4954 } 4955 4956 static isArrayFromCall(callLikeExpr: ts.CallExpression | ts.NewExpression): boolean { 4957 return ( 4958 ts.isCallExpression(callLikeExpr) && 4959 ts.isPropertyAccessExpression(callLikeExpr.expression) && 4960 callLikeExpr.expression.name.text === STRINGLITERAL_FROM && 4961 ts.isIdentifier(callLikeExpr.expression.expression) && 4962 callLikeExpr.expression.expression.text === STRINGLITERAL_ARRAY 4963 ); 4964 } 4965 4966 private static readonly listFunctionApplyCallApis = [ 4967 'Function.apply', 4968 'Function.call', 4969 'CallableFunction.apply', 4970 'CallableFunction.call' 4971 ]; 4972 4973 private static readonly listFunctionBindApis = ['Function.bind', 'CallableFunction.bind']; 4974 4975 private handleFunctionApplyBindPropCall(tsCallExpr: ts.CallExpression, calleeSym: ts.Symbol): void { 4976 const exprName = this.tsTypeChecker.getFullyQualifiedName(calleeSym); 4977 if (TypeScriptLinter.listFunctionApplyCallApis.includes(exprName)) { 4978 this.incrementCounters(tsCallExpr, FaultID.FunctionApplyCall); 4979 } 4980 if (TypeScriptLinter.listFunctionBindApis.includes(exprName)) { 4981 const faultId = this.options.arkts2 ? FaultID.FunctionBindError : FaultID.FunctionBind; 4982 this.incrementCounters(tsCallExpr, faultId); 4983 } 4984 } 4985 4986 private handleStructIdentAndUndefinedInArgs( 4987 tsCallOrNewExpr: ts.CallExpression | ts.NewExpression, 4988 callSignature: ts.Signature 4989 ): void { 4990 if (!tsCallOrNewExpr.arguments) { 4991 return; 4992 } 4993 for (let argIndex = 0; argIndex < tsCallOrNewExpr.arguments.length; ++argIndex) { 4994 const tsArg = tsCallOrNewExpr.arguments[argIndex]; 4995 const tsArgType = this.tsTypeChecker.getTypeAtLocation(tsArg); 4996 if (!tsArgType) { 4997 continue; 4998 } 4999 const paramIndex = argIndex < callSignature.parameters.length ? argIndex : callSignature.parameters.length - 1; 5000 const tsParamSym = callSignature.parameters[paramIndex]; 5001 if (!tsParamSym) { 5002 continue; 5003 } 5004 const tsParamDecl = tsParamSym.valueDeclaration; 5005 if (tsParamDecl && ts.isParameter(tsParamDecl)) { 5006 let tsParamType = this.tsTypeChecker.getTypeOfSymbolAtLocation(tsParamSym, tsParamDecl); 5007 if (tsParamDecl.dotDotDotToken && this.tsUtils.isGenericArrayType(tsParamType) && tsParamType.typeArguments) { 5008 tsParamType = tsParamType.typeArguments[0]; 5009 } 5010 if (!tsParamType) { 5011 continue; 5012 } 5013 this.checkAssignmentMatching(tsArg, tsParamType, tsArg); 5014 } 5015 } 5016 } 5017 5018 private static readonly LimitedApis = new Map<string, { arr: Array<string> | null; fault: FaultID }>([ 5019 ['global', { arr: LIMITED_STD_GLOBAL_API, fault: FaultID.LimitedStdLibApi }], 5020 ['Object', { arr: LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi }], 5021 ['ObjectConstructor', { arr: LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi }], 5022 ['Reflect', { arr: LIMITED_STD_REFLECT_API, fault: FaultID.LimitedStdLibApi }], 5023 ['ProxyHandler', { arr: LIMITED_STD_PROXYHANDLER_API, fault: FaultID.LimitedStdLibApi }], 5024 [SYMBOL, { arr: null, fault: FaultID.SymbolType }], 5025 [SYMBOL_CONSTRUCTOR, { arr: null, fault: FaultID.SymbolType }] 5026 ]); 5027 5028 private handleStdlibAPICall( 5029 callExpr: ts.CallExpression, 5030 calleeSym: ts.Symbol, 5031 name: string, 5032 parName: string | undefined 5033 ): void { 5034 if (parName === undefined) { 5035 if (LIMITED_STD_GLOBAL_API.includes(name)) { 5036 this.incrementCounters(callExpr, FaultID.LimitedStdLibApi); 5037 return; 5038 } 5039 const escapedName = calleeSym.escapedName; 5040 if (escapedName === 'Symbol' || escapedName === 'SymbolConstructor') { 5041 this.incrementCounters(callExpr, FaultID.SymbolType); 5042 } 5043 return; 5044 } 5045 const lookup = TypeScriptLinter.LimitedApis.get(parName); 5046 if ( 5047 lookup !== undefined && 5048 (lookup.arr === null || lookup.arr.includes(name)) && 5049 (!this.options.useRelaxedRules || !this.supportedStdCallApiChecker.isSupportedStdCallAPI(callExpr, parName, name)) 5050 ) { 5051 this.incrementCounters(callExpr, lookup.fault); 5052 } 5053 } 5054 5055 private handleBuiltinThisArgs( 5056 callExpr: ts.CallExpression, 5057 calleeSym: ts.Symbol, 5058 name: string, 5059 parName: string | undefined 5060 ): void { 5061 if (parName === undefined) { 5062 return; 5063 } 5064 5065 const builtinThisArgsInfos = TypeScriptLinter.funcMap.get(name); 5066 if (!builtinThisArgsInfos) { 5067 return; 5068 } 5069 5070 const sourceFile = calleeSym?.declarations?.[0]?.getSourceFile(); 5071 if (!sourceFile) { 5072 return; 5073 } 5074 5075 const fileName = path.basename(sourceFile.fileName); 5076 const builtinInfos = builtinThisArgsInfos.get(fileName); 5077 if (!(builtinInfos && builtinInfos.size > 0)) { 5078 return; 5079 } 5080 for (const info of builtinInfos) { 5081 const needReport = 5082 info.parent_api.length > 0 && 5083 info.parent_api[0].api_name === parName && 5084 info?.api_func_args?.length === callExpr.arguments.length; 5085 if (needReport) { 5086 this.incrementCounters(callExpr, FaultID.BuiltinThisArgs); 5087 return; 5088 } 5089 } 5090 } 5091 5092 private checkLimitedStdlibApi(node: ts.Identifier, symbol: ts.Symbol): void { 5093 const parName = this.tsUtils.getParentSymbolName(symbol); 5094 const entries = LIMITED_STD_API.get(parName); 5095 if (!entries) { 5096 return; 5097 } 5098 for (const entry of entries) { 5099 if ( 5100 entry.api.includes(symbol.name) && 5101 !this.supportedStdCallApiChecker.isSupportedStdCallAPI(node, parName, symbol.name) 5102 ) { 5103 this.incrementCounters(node, entry.faultId); 5104 return; 5105 } 5106 } 5107 } 5108 5109 private handleLibraryTypeCall(expr: ts.CallExpression | ts.NewExpression): void { 5110 if (!expr.arguments || !this.tscStrictDiagnostics || !this.sourceFile) { 5111 return; 5112 } 5113 5114 const file = path.normalize(this.sourceFile.fileName); 5115 const tscDiagnostics: readonly ts.Diagnostic[] | undefined = this.tscStrictDiagnostics.get(file); 5116 if (!tscDiagnostics?.length) { 5117 return; 5118 } 5119 5120 const isOhModulesEts = TsUtils.isOhModulesEtsSymbol(this.tsUtils.trueSymbolAtLocation(expr.expression)); 5121 const deleteDiagnostics: Set<ts.Diagnostic> = new Set(); 5122 LibraryTypeCallDiagnosticChecker.instance.filterDiagnostics( 5123 tscDiagnostics, 5124 expr, 5125 this.tsUtils.isLibraryType(this.tsTypeChecker.getTypeAtLocation(expr.expression)), 5126 (diagnostic, errorType) => { 5127 5128 /* 5129 * When a diagnostic meets the filter criteria, If it happens in an ets file in the 'oh_modules' directory. 5130 * the diagnostic is downgraded to warning. For other files, downgraded to nothing. 5131 */ 5132 if (isOhModulesEts && errorType !== DiagnosticCheckerErrorType.UNKNOW) { 5133 diagnostic.category = ts.DiagnosticCategory.Warning; 5134 return false; 5135 } 5136 deleteDiagnostics.add(diagnostic); 5137 return true; 5138 } 5139 ); 5140 5141 if (!deleteDiagnostics.size) { 5142 return; 5143 } 5144 5145 this.tscStrictDiagnostics.set( 5146 file, 5147 tscDiagnostics.filter((item) => { 5148 return !deleteDiagnostics.has(item); 5149 }) 5150 ); 5151 } 5152 5153 private checkConstrutorAccess(propertyAccessExpr: ts.PropertyAccessExpression): void { 5154 if (!this.options.arkts2 || !this.useStatic) { 5155 return; 5156 } 5157 5158 if (propertyAccessExpr.name.text === 'constructor') { 5159 this.incrementCounters(propertyAccessExpr, FaultID.NoConstructorOnClass); 5160 } 5161 } 5162 5163 private checkForInterfaceInitialization(newExpression: ts.NewExpression): void { 5164 if (!this.options.arkts2 || !this.useStatic) { 5165 return; 5166 } 5167 const calleeExpr = newExpression.expression; 5168 if (!ts.isIdentifier(calleeExpr)) { 5169 return; 5170 } 5171 5172 const type = this.tsTypeChecker.getTypeAtLocation(calleeExpr); 5173 const typeDeclaration = TsUtils.getDeclaration(type.symbol); 5174 if (typeDeclaration && ts.isInterfaceDeclaration(typeDeclaration) && type.symbol) { 5175 const filePath = typeDeclaration.getSourceFile().fileName; 5176 this.checkIsConstructorIface(calleeExpr, type.symbol.name, path.basename(filePath)); 5177 } 5178 } 5179 5180 private checkIsConstructorIface(node: ts.Node, symbol: string, filePath: string): void { 5181 const constructorIfaceSetInfos = Array.from(TypeScriptLinter.ConstructorIfaceSet); 5182 constructorIfaceSetInfos.some((constructorFuncsInfo) => { 5183 const api_name = constructorFuncsInfo.api_info.parent_api[0].api_name; 5184 if ( 5185 symbol === api_name && 5186 (constructorFuncsInfo.file_path.includes(filePath) || constructorFuncsInfo.import_path.includes(filePath)) 5187 ) { 5188 this.incrementCounters(node, FaultID.ConstructorIfaceFromSdk); 5189 return true; 5190 } 5191 return false; 5192 }); 5193 } 5194 5195 private handleNewExpression(node: ts.Node): void { 5196 const tsNewExpr = node as ts.NewExpression; 5197 5198 this.checkForInterfaceInitialization(tsNewExpr); 5199 this.handleSharedArrayBuffer(tsNewExpr); 5200 this.handleSdkGlobalApi(tsNewExpr); 5201 this.checkCreatingPrimitiveTypes(tsNewExpr); 5202 5203 if (this.options.advancedClassChecks || this.options.arkts2) { 5204 const calleeExpr = tsNewExpr.expression; 5205 const calleeType = this.tsTypeChecker.getTypeAtLocation(calleeExpr); 5206 if ( 5207 !this.tsUtils.isClassTypeExpression(calleeExpr) && 5208 !isStdLibraryType(calleeType) && 5209 !this.tsUtils.isLibraryType(calleeType) && 5210 !this.tsUtils.hasEsObjectType(calleeExpr) 5211 ) { 5212 // missing exact rule 5213 const faultId = this.options.arkts2 ? FaultID.DynamicCtorCall : FaultID.ClassAsObject; 5214 this.incrementCounters(calleeExpr, faultId); 5215 } 5216 } 5217 const sym = this.tsUtils.trueSymbolAtLocation(tsNewExpr.expression); 5218 const callSignature = this.tsTypeChecker.getResolvedSignature(tsNewExpr); 5219 if (callSignature !== undefined) { 5220 if (!this.tsUtils.isLibrarySymbol(sym)) { 5221 this.handleStructIdentAndUndefinedInArgs(tsNewExpr, callSignature); 5222 this.handleGenericCallWithNoTypeArgs(tsNewExpr, callSignature); 5223 } else if (this.options.arkts2) { 5224 this.handleGenericCallWithNoTypeArgs(tsNewExpr, callSignature); 5225 } 5226 } 5227 this.handleSendableGenericTypes(tsNewExpr); 5228 this.handleInstantiatedJsObject(tsNewExpr, sym); 5229 this.handlePromiseNeedVoidResolve(tsNewExpr); 5230 } 5231 5232 handlePromiseNeedVoidResolve(newExpr: ts.NewExpression): void { 5233 if (!this.options.arkts2) { 5234 return; 5235 } 5236 5237 if (!ts.isIdentifier(newExpr.expression) || newExpr.expression.text !== 'Promise') { 5238 return; 5239 } 5240 5241 const typeArg = newExpr.typeArguments?.[0]; 5242 if (!typeArg) { 5243 return; 5244 } 5245 5246 const type = this.tsTypeChecker.getTypeAtLocation(typeArg); 5247 if (!(type.getFlags() & ts.TypeFlags.Void)) { 5248 return; 5249 } 5250 5251 const executor = newExpr.arguments?.[0]; 5252 if (!executor || !ts.isFunctionLike(executor)) { 5253 return; 5254 } 5255 5256 const resolveParam = executor.parameters[0]; 5257 if (resolveParam?.type) { 5258 if (ts.isFunctionTypeNode(resolveParam.type) && 5259 resolveParam.type.parameters.length === 0) { 5260 this.incrementCounters(resolveParam.type,FaultID.PromiseVoidNeedResolveArg); 5261 } 5262 } 5263 if (executor.body) { 5264 ts.forEachChild(executor.body, node => { 5265 if (ts.isCallExpression(node) && 5266 ts.isIdentifier(node.expression) && 5267 node.expression.text === 'resolve' && 5268 node.arguments.length === 0) { 5269 this.incrementCounters(node,FaultID.PromiseVoidNeedResolveArg); 5270 } 5271 }); 5272 } 5273 } 5274 5275 private checkCreatingPrimitiveTypes(tsNewExpr: ts.NewExpression): void { 5276 if (!this.options.arkts2) { 5277 return; 5278 } 5279 const typeStr = this.tsTypeChecker.typeToString(this.tsTypeChecker.getTypeAtLocation(tsNewExpr)); 5280 const primitiveTypes = ['Number', 'String', 'Boolean']; 5281 if (primitiveTypes.includes(typeStr)) { 5282 this.incrementCounters(tsNewExpr, FaultID.CreatingPrimitiveTypes); 5283 } 5284 } 5285 5286 handleInstantiatedJsObject(tsNewExpr: ts.NewExpression, sym: ts.Symbol | undefined): void { 5287 if (this.useStatic && this.options.arkts2) { 5288 if (sym?.declarations?.[0]?.getSourceFile().fileName.endsWith(EXTNAME_JS)) { 5289 const args = tsNewExpr.arguments; 5290 const autofix = this.autofixer?.fixInteropInstantiateExpression(tsNewExpr, args); 5291 this.incrementCounters(tsNewExpr, FaultID.InstantiatedJsOjbect, autofix); 5292 } 5293 } 5294 } 5295 5296 private handleSendableGenericTypes(node: ts.NewExpression): void { 5297 const type = this.tsTypeChecker.getTypeAtLocation(node); 5298 if (!this.tsUtils.isSendableClassOrInterface(type)) { 5299 return; 5300 } 5301 5302 const typeArgs = node.typeArguments; 5303 if (!typeArgs || typeArgs.length === 0) { 5304 return; 5305 } 5306 5307 for (const arg of typeArgs) { 5308 if (!this.tsUtils.isSendableTypeNode(arg)) { 5309 this.incrementCounters(arg, FaultID.SendableGenericTypes); 5310 } 5311 } 5312 } 5313 5314 private handleAsExpression(node: ts.Node): void { 5315 const tsAsExpr = node as ts.AsExpression; 5316 if (tsAsExpr.type.getText() === 'const') { 5317 this.incrementCounters(node, FaultID.ConstAssertion); 5318 } 5319 const targetType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.type).getNonNullableType(); 5320 const exprType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression).getNonNullableType(); 5321 // check for rule#65: 'number as Number' and 'boolean as Boolean' are disabled 5322 if ( 5323 this.tsUtils.isNumberLikeType(exprType) && this.tsUtils.isStdNumberType(targetType) || 5324 TsUtils.isBooleanLikeType(exprType) && this.tsUtils.isStdBooleanType(targetType) 5325 ) { 5326 this.incrementCounters(node, FaultID.TypeAssertion); 5327 } 5328 if ( 5329 !this.tsUtils.isSendableClassOrInterface(exprType) && 5330 !this.tsUtils.isObject(exprType) && 5331 !TsUtils.isAnyType(exprType) && 5332 this.tsUtils.isSendableClassOrInterface(targetType) 5333 ) { 5334 this.incrementCounters(tsAsExpr, FaultID.SendableAsExpr); 5335 } 5336 if (this.tsUtils.isWrongSendableFunctionAssignment(targetType, exprType)) { 5337 this.incrementCounters(tsAsExpr, FaultID.SendableFunctionAsExpr); 5338 } 5339 if ( 5340 this.options.arkts2 && 5341 this.tsUtils.needToDeduceStructuralIdentity(targetType, exprType, tsAsExpr.expression, true) 5342 ) { 5343 if (this.isExemptedAsExpression(tsAsExpr)) { 5344 return; 5345 } 5346 if (!this.tsUtils.isObject(exprType)) { 5347 this.incrementCounters(node, FaultID.StructuralIdentity); 5348 } 5349 } 5350 this.handleAsExpressionImport(tsAsExpr); 5351 this.handleNoTuplesArrays(node, targetType, exprType); 5352 this.handleObjectLiteralAssignmentToClass(tsAsExpr); 5353 this.handleArrayTypeImmutable(tsAsExpr, exprType, targetType); 5354 this.handleNotsLikeSmartTypeOnAsExpression(tsAsExpr); 5355 } 5356 5357 private isExemptedAsExpression(node: ts.AsExpression): boolean { 5358 if (!ts.isElementAccessExpression(node.expression)) { 5359 return false; 5360 } 5361 5362 const sourceType = this.tsTypeChecker.getTypeAtLocation(node.expression); 5363 const targetType = this.tsTypeChecker.getTypeAtLocation(node.type); 5364 const isRecordIndexAccess = (): boolean => { 5365 const exprType = this.tsTypeChecker.getTypeAtLocation(node.expression); 5366 const hasNumberIndex = !!exprType.getNumberIndexType(); 5367 const hasStringIndex = !!exprType.getStringIndexType(); 5368 const hasBooleanIndex = !!exprType.getProperty('true') || !!exprType.getProperty('false'); 5369 5370 return hasNumberIndex || hasStringIndex || hasBooleanIndex; 5371 }; 5372 5373 if (isRecordIndexAccess()) { 5374 const targetSymbol = targetType.getSymbol(); 5375 if (targetSymbol && targetSymbol.getName() === 'Array') { 5376 return true; 5377 } 5378 } 5379 const primitiveFlags = ts.TypeFlags.Number | ts.TypeFlags.String | ts.TypeFlags.Boolean; 5380 const objectFlag = ts.TypeFlags.Object; 5381 return ( 5382 sourceType.isUnion() && 5383 sourceType.types.some((t) => { 5384 return t.flags & primitiveFlags; 5385 }) && 5386 sourceType.types.some((t) => { 5387 return t.flags & objectFlag; 5388 }) 5389 ); 5390 } 5391 5392 private handleAsExpressionImport(tsAsExpr: ts.AsExpression): void { 5393 if (!this.useStatic || !this.options.arkts2) { 5394 return; 5395 } 5396 5397 const type = tsAsExpr.type; 5398 const expression = tsAsExpr.expression; 5399 const restrictedPrimitiveTypes = [ 5400 ts.SyntaxKind.NumberKeyword, 5401 ts.SyntaxKind.BooleanKeyword, 5402 ts.SyntaxKind.StringKeyword, 5403 ts.SyntaxKind.BigIntKeyword, 5404 ts.SyntaxKind.UndefinedKeyword 5405 ]; 5406 this.handleAsExpressionImportNull(tsAsExpr); 5407 const isRestrictedPrimitive = restrictedPrimitiveTypes.includes(type.kind); 5408 const isRestrictedArrayType = 5409 type.kind === ts.SyntaxKind.ArrayType || 5410 ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === 'Array'; 5411 5412 if (!isRestrictedPrimitive && !isRestrictedArrayType) { 5413 return; 5414 } 5415 5416 let identifier: ts.Identifier | undefined; 5417 if (ts.isIdentifier(expression)) { 5418 identifier = expression; 5419 } else if (ts.isPropertyAccessExpression(expression)) { 5420 identifier = ts.isIdentifier(expression.expression) ? expression.expression : undefined; 5421 } 5422 5423 if (identifier) { 5424 const sym = this.tsUtils.trueSymbolAtLocation(identifier); 5425 const decl = TsUtils.getDeclaration(sym); 5426 if (decl?.getSourceFile().fileName.endsWith(EXTNAME_JS)) { 5427 const autofix = this.autofixer?.fixInteropAsExpression(tsAsExpr); 5428 this.incrementCounters(tsAsExpr, FaultID.InterOpConvertImport, autofix); 5429 } 5430 } 5431 } 5432 5433 private handleAsExpressionImportNull(tsAsExpr: ts.AsExpression): void { 5434 const type = tsAsExpr.type; 5435 const isNullAssertion = 5436 type.kind === ts.SyntaxKind.NullKeyword || 5437 ts.isLiteralTypeNode(type) && type.literal.kind === ts.SyntaxKind.NullKeyword || 5438 type.getText() === 'null'; 5439 if (isNullAssertion) { 5440 this.incrementCounters(tsAsExpr, FaultID.InterOpConvertImport); 5441 } 5442 } 5443 5444 private handleSdkConstructorIface(typeRef: ts.TypeReferenceNode): void { 5445 if (!this.options.arkts2 && typeRef?.typeName === undefined && !ts.isQualifiedName(typeRef.typeName)) { 5446 return; 5447 } 5448 const qualifiedName = typeRef.typeName as ts.QualifiedName; 5449 // tsc version diff 5450 const topName = qualifiedName.left?.getText(); 5451 const sdkInfos = this.interfaceMap.get(topName); 5452 if (!sdkInfos) { 5453 return; 5454 } 5455 for (const sdkInfo of sdkInfos) { 5456 if (sdkInfo.api_type !== 'ConstructSignature') { 5457 continue; 5458 } 5459 // sdk api from json has 3 overload. need consider these case. 5460 if (sdkInfo.parent_api[0].api_name === qualifiedName.right.getText()) { 5461 this.incrementCounters(typeRef, FaultID.ConstructorIfaceFromSdk); 5462 break; 5463 } 5464 } 5465 } 5466 5467 private handleSdkConstructorIfaceForCallExpression(callExpr: ts.CallExpression): void { 5468 if (!this.options.arkts2) { 5469 return; 5470 } 5471 let type: ts.Type | undefined; 5472 if (!callExpr.arguments || callExpr.arguments.length === 0) { 5473 if (ts.isPropertyAccessExpression(callExpr.expression)) { 5474 type = this.tsTypeChecker.getTypeAtLocation(callExpr.expression.expression); 5475 } 5476 } 5477 callExpr.arguments.some((args) => { 5478 if (ts.isIdentifier(args)) { 5479 type = this.tsTypeChecker.getTypeAtLocation(args); 5480 } 5481 }); 5482 if (!type) { 5483 return; 5484 } 5485 const decl = TsUtils.getDeclaration(type?.symbol); 5486 if (!decl) { 5487 return; 5488 } 5489 const filePath = TypeScriptLinter.getFileName(decl); 5490 this.checkIsConstructorIface(callExpr, type.symbol.name, filePath); 5491 } 5492 5493 private static getFileName(decl: ts.Declaration): string { 5494 let filePath = ''; 5495 if ( 5496 ts.isImportSpecifier(decl) && 5497 ts.isImportDeclaration(decl.parent.parent.parent) && 5498 ts.isStringLiteral(decl.parent.parent.parent.moduleSpecifier) 5499 ) { 5500 filePath = decl.parent.parent.parent.moduleSpecifier.text; 5501 } else if ( 5502 ts.isImportClause(decl) && 5503 ts.isImportDeclaration(decl.parent) && 5504 ts.isStringLiteral(decl.parent.moduleSpecifier) 5505 ) { 5506 filePath = decl.parent.moduleSpecifier.text; 5507 } else { 5508 filePath = decl.getSourceFile().fileName; 5509 } 5510 return path.basename(filePath); 5511 } 5512 5513 private handleSharedArrayBuffer( 5514 node: ts.TypeReferenceNode | ts.NewExpression | ts.ExpressionWithTypeArguments 5515 ): void { 5516 if (!this.options.arkts2) { 5517 return; 5518 } 5519 5520 const typeNameIdentifier = ts.isTypeReferenceNode(node) ? node.typeName : node.expression; 5521 if (!ts.isIdentifier(typeNameIdentifier) || typeNameIdentifier.getText() !== ESLIB_SHAREDARRAYBUFFER) { 5522 return; 5523 } 5524 const symbol = this.tsUtils.trueSymbolAtLocation(typeNameIdentifier); 5525 if (!symbol) { 5526 return; 5527 } 5528 5529 const isImported = this.sourceFile.statements.some((stmt) => { 5530 if (!ts.isImportDeclaration(stmt)) { 5531 return false; 5532 } 5533 const importClause = stmt.importClause; 5534 if (!importClause?.namedBindings || !ts.isNamedImports(importClause.namedBindings)) { 5535 return false; 5536 } 5537 5538 const elements = importClause.namedBindings.elements.some((element) => { 5539 return element.name.text === ESLIB_SHAREDARRAYBUFFER; 5540 }); 5541 return elements; 5542 }); 5543 if (isImported) { 5544 return; 5545 } 5546 const decls = symbol.getDeclarations(); 5547 const isSharedMemoryEsLib = decls?.some((decl) => { 5548 const srcFileName = decl.getSourceFile().fileName; 5549 return srcFileName.endsWith(ESLIB_SHAREDMEMORY_FILENAME); 5550 }); 5551 5552 if (!isSharedMemoryEsLib || this.hasLocalSharedArrayBufferClass()) { 5553 return; 5554 } 5555 5556 const autofix = this.autofixer?.replaceNode(typeNameIdentifier, 'ArrayBuffer'); 5557 this.incrementCounters(typeNameIdentifier, FaultID.SharedArrayBufferDeprecated, autofix); 5558 } 5559 5560 private hasLocalSharedArrayBufferClass(): boolean { 5561 return this.sourceFile.statements.some((stmt) => { 5562 return ts.isClassDeclaration(stmt) && stmt.name?.text === ESLIB_SHAREDARRAYBUFFER; 5563 }); 5564 } 5565 5566 private handleTypeReference(node: ts.Node): void { 5567 const typeRef = node as ts.TypeReferenceNode; 5568 5569 this.handleBuiltinCtorCallSignature(typeRef); 5570 this.handleSharedArrayBuffer(typeRef); 5571 this.handleSdkGlobalApi(typeRef); 5572 5573 this.handleSdkConstructorIface(typeRef); 5574 5575 const isESValue = TsUtils.isEsValueType(typeRef); 5576 const isPossiblyValidContext = TsUtils.isEsValuePossiblyAllowed(typeRef); 5577 if (isESValue && !isPossiblyValidContext) { 5578 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 5579 this.incrementCounters(node, faultId); 5580 return; 5581 } 5582 5583 const typeName = this.tsUtils.entityNameToString(typeRef.typeName); 5584 const isStdUtilityType = LIMITED_STANDARD_UTILITY_TYPES.includes(typeName); 5585 if (isStdUtilityType) { 5586 this.incrementCounters(node, FaultID.UtilityType); 5587 return; 5588 } 5589 5590 this.checkPartialType(node); 5591 5592 const typeNameType = this.tsTypeChecker.getTypeAtLocation(typeRef.typeName); 5593 if (this.options.arkts2 && (typeNameType.flags & ts.TypeFlags.Void) !== 0) { 5594 this.incrementCounters(typeRef, FaultID.LimitedVoidType); 5595 } 5596 if (this.tsUtils.isSendableClassOrInterface(typeNameType)) { 5597 this.checkSendableTypeArguments(typeRef); 5598 } 5599 5600 this.checkNoEnumProp(typeRef); 5601 if (ts.isQualifiedName(typeRef.typeName)) { 5602 this.handleSdkForConstructorFuncs(typeRef.typeName); 5603 } 5604 } 5605 5606 private checkNoEnumProp(typeRef: ts.TypeReferenceNode): void { 5607 if (!this.options.arkts2) { 5608 return; 5609 } 5610 if (ts.isQualifiedName(typeRef.typeName)) { 5611 const symbol = this.tsTypeChecker.getSymbolAtLocation(typeRef.typeName.right); 5612 5613 if (!symbol) { 5614 return; 5615 } 5616 5617 const declarations = symbol.getDeclarations(); 5618 if (!declarations || declarations.length === 0) { 5619 return; 5620 } 5621 5622 if (ts.isEnumMember(declarations[0])) { 5623 this.incrementCounters(typeRef, FaultID.NoEnumPropAsType); 5624 } 5625 } 5626 } 5627 5628 private checkPartialType(node: ts.Node): void { 5629 const typeRef = node as ts.TypeReferenceNode; 5630 // Using Partial<T> type is allowed only when its argument type is either Class or Interface. 5631 const isStdPartial = this.tsUtils.entityNameToString(typeRef.typeName) === 'Partial'; 5632 if (!isStdPartial) { 5633 return; 5634 } 5635 5636 const hasSingleTypeArgument = !!typeRef.typeArguments && typeRef.typeArguments.length === 1; 5637 let argType; 5638 if (!this.options.useRtLogic) { 5639 const firstTypeArg = !!typeRef.typeArguments && hasSingleTypeArgument && typeRef.typeArguments[0]; 5640 argType = firstTypeArg && this.tsTypeChecker.getTypeFromTypeNode(firstTypeArg); 5641 } else { 5642 argType = hasSingleTypeArgument && this.tsTypeChecker.getTypeFromTypeNode(typeRef.typeArguments[0]); 5643 } 5644 5645 if (argType && !argType.isClassOrInterface()) { 5646 this.incrementCounters(node, FaultID.UtilityType); 5647 } 5648 } 5649 5650 private checkSendableTypeArguments(typeRef: ts.TypeReferenceNode): void { 5651 if (typeRef.typeArguments) { 5652 for (const typeArg of typeRef.typeArguments) { 5653 if (!this.tsUtils.isSendableTypeNode(typeArg)) { 5654 this.incrementCounters(typeArg, FaultID.SendableGenericTypes); 5655 } 5656 } 5657 } 5658 } 5659 5660 private handleMetaProperty(node: ts.Node): void { 5661 const tsMetaProperty = node as ts.MetaProperty; 5662 if (tsMetaProperty.name.text === 'target') { 5663 this.incrementCounters(node, FaultID.NewTarget); 5664 } 5665 } 5666 5667 private handleSpreadOp(node: ts.Node): void { 5668 5669 /* 5670 * spread assignment is disabled 5671 * spread element is allowed only for arrays as rest parameter 5672 */ 5673 if (ts.isSpreadElement(node)) { 5674 const spreadExprType = this.tsUtils.getTypeOrTypeConstraintAtLocation(node.expression); 5675 if ( 5676 spreadExprType && 5677 (this.options.useRtLogic || ts.isCallLikeExpression(node.parent) || ts.isArrayLiteralExpression(node.parent)) && 5678 (this.tsUtils.isOrDerivedFrom(spreadExprType, this.tsUtils.isArray) || 5679 this.tsUtils.isOrDerivedFrom(spreadExprType, this.tsUtils.isCollectionArrayType)) 5680 ) { 5681 return; 5682 } 5683 } 5684 this.incrementCounters(node, FaultID.SpreadOperator); 5685 } 5686 5687 private handleConstructSignature(node: ts.Node): void { 5688 switch (node.parent.kind) { 5689 case ts.SyntaxKind.TypeLiteral: 5690 this.incrementCounters(node, FaultID.ConstructorType); 5691 break; 5692 case ts.SyntaxKind.InterfaceDeclaration: 5693 this.incrementCounters(node, FaultID.ConstructorIface); 5694 break; 5695 default: 5696 } 5697 } 5698 5699 private handleExpressionWithTypeArguments(node: ts.Node): void { 5700 const tsTypeExpr = node as ts.ExpressionWithTypeArguments; 5701 const symbol = this.tsUtils.trueSymbolAtLocation(tsTypeExpr.expression); 5702 5703 if (!!symbol && TsUtils.isEsObjectSymbol(symbol)) { 5704 const faultId = this.options.arkts2 ? FaultID.EsValueTypeError : FaultID.EsValueType; 5705 this.incrementCounters(tsTypeExpr, faultId); 5706 } 5707 this.handleSdkGlobalApi(tsTypeExpr); 5708 } 5709 5710 private handleComputedPropertyName(node: ts.Node): void { 5711 const computedProperty = node as ts.ComputedPropertyName; 5712 if (this.isSendableCompPropName(computedProperty)) { 5713 // cancel the '[Symbol.iterface]' restriction of 'sendable class/interface' in the 'collections.d.ts' file 5714 if (this.tsUtils.isSymbolIteratorExpression(computedProperty.expression)) { 5715 const declNode = computedProperty.parent?.parent; 5716 if (declNode && TsUtils.isArkTSCollectionsClassOrInterfaceDeclaration(declNode)) { 5717 return; 5718 } 5719 } 5720 this.incrementCounters(node, FaultID.SendableComputedPropName); 5721 } else if (!this.tsUtils.isValidComputedPropertyName(computedProperty, false)) { 5722 this.incrementCounters(node, FaultID.ComputedPropertyName); 5723 } 5724 } 5725 5726 private isSendableCompPropName(compProp: ts.ComputedPropertyName): boolean { 5727 const declNode = compProp.parent?.parent; 5728 if (declNode && ts.isClassDeclaration(declNode) && TsUtils.hasSendableDecorator(declNode)) { 5729 return true; 5730 } else if (declNode && ts.isInterfaceDeclaration(declNode)) { 5731 const declNodeType = this.tsTypeChecker.getTypeAtLocation(declNode); 5732 if (this.tsUtils.isSendableClassOrInterface(declNodeType)) { 5733 return true; 5734 } 5735 } 5736 return false; 5737 } 5738 5739 private handleGetAccessor(node: ts.GetAccessorDeclaration): void { 5740 TsUtils.getDecoratorsIfInSendableClass(node)?.forEach((decorator) => { 5741 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 5742 }); 5743 } 5744 5745 private handleSetAccessor(node: ts.SetAccessorDeclaration): void { 5746 TsUtils.getDecoratorsIfInSendableClass(node)?.forEach((decorator) => { 5747 this.incrementCounters(decorator, FaultID.SendableClassDecorator); 5748 }); 5749 } 5750 5751 /* 5752 * issue 13987: 5753 * When variable have no type annotation and no initial value, and 'noImplicitAny' 5754 * option is enabled, compiler attempts to infer type from variable references: 5755 * see https://github.com/microsoft/TypeScript/pull/11263. 5756 * In this case, we still want to report the error, since ArkTS doesn't allow 5757 * to omit both type annotation and initializer. 5758 */ 5759 private proceedVarPropDeclaration( 5760 decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 5761 ): boolean | undefined { 5762 if ( 5763 (ts.isVariableDeclaration(decl) && ts.isVariableStatement(decl.parent.parent) || 5764 ts.isPropertyDeclaration(decl)) && 5765 !decl.initializer 5766 ) { 5767 if ( 5768 ts.isPropertyDeclaration(decl) && 5769 this.tsUtils.skipPropertyInferredTypeCheck(decl, this.sourceFile, this.options.isEtsFileCb) 5770 ) { 5771 return true; 5772 } 5773 5774 this.incrementCounters(decl, FaultID.AnyType); 5775 return true; 5776 } 5777 return undefined; 5778 } 5779 5780 private handleDeclarationInferredType( 5781 decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 5782 ): void { 5783 // The type is explicitly specified, no need to check inferred type. 5784 if (decl.type) { 5785 return; 5786 } 5787 5788 /* 5789 * issue 13161: 5790 * In TypeScript, the catch clause variable must be 'any' or 'unknown' type. Since 5791 * ArkTS doesn't support these types, the type for such variable is simply omitted, 5792 * and we don't report it as an error. See TypeScriptLinter.handleCatchClause() 5793 * for reference. 5794 */ 5795 if (ts.isCatchClause(decl.parent)) { 5796 return; 5797 } 5798 // Destructuring declarations are not supported, do not process them. 5799 if (ts.isArrayBindingPattern(decl.name) || ts.isObjectBindingPattern(decl.name)) { 5800 return; 5801 } 5802 5803 if (this.proceedVarPropDeclaration(decl)) { 5804 return; 5805 } 5806 5807 const type = this.tsTypeChecker.getTypeAtLocation(decl); 5808 if (type) { 5809 this.validateDeclInferredType(type, decl); 5810 } 5811 } 5812 5813 private handleDefiniteAssignmentAssertion(decl: ts.VariableDeclaration | ts.PropertyDeclaration): void { 5814 if (decl.exclamationToken === undefined) { 5815 return; 5816 } 5817 5818 if (decl.kind === ts.SyntaxKind.PropertyDeclaration) { 5819 const parentDecl = decl.parent; 5820 if (parentDecl.kind === ts.SyntaxKind.ClassDeclaration && TsUtils.hasSendableDecorator(parentDecl)) { 5821 this.incrementCounters(decl, FaultID.SendableDefiniteAssignment); 5822 return; 5823 } 5824 } 5825 const faultId = this.options.arkts2 ? FaultID.DefiniteAssignmentError : FaultID.DefiniteAssignment; 5826 this.incrementCounters(decl, faultId); 5827 } 5828 5829 private readonly validatedTypesSet = new Set<ts.Type>(); 5830 5831 private checkAnyOrUnknownChildNode(node: ts.Node): boolean { 5832 if (node.kind === ts.SyntaxKind.AnyKeyword || node.kind === ts.SyntaxKind.UnknownKeyword) { 5833 return true; 5834 } 5835 for (const child of node.getChildren()) { 5836 if (this.checkAnyOrUnknownChildNode(child)) { 5837 return true; 5838 } 5839 } 5840 return false; 5841 } 5842 5843 private handleInferredObjectreference( 5844 type: ts.Type, 5845 decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 5846 ): void { 5847 const typeArgs = this.tsTypeChecker.getTypeArguments(type as ts.TypeReference); 5848 if (typeArgs) { 5849 const haveAnyOrUnknownNodes = this.checkAnyOrUnknownChildNode(decl); 5850 if (!haveAnyOrUnknownNodes) { 5851 for (const typeArg of typeArgs) { 5852 this.validateDeclInferredType(typeArg, decl); 5853 } 5854 } 5855 } 5856 } 5857 5858 private validateDeclInferredType( 5859 type: ts.Type, 5860 decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 5861 ): void { 5862 if (type.aliasSymbol !== undefined) { 5863 return; 5864 } 5865 if (TsUtils.isObjectType(type) && !!(type.objectFlags & ts.ObjectFlags.Reference)) { 5866 this.handleInferredObjectreference(type, decl); 5867 return; 5868 } 5869 if (this.validatedTypesSet.has(type)) { 5870 return; 5871 } 5872 if (type.isUnion()) { 5873 this.validatedTypesSet.add(type); 5874 for (const unionElem of type.types) { 5875 this.validateDeclInferredType(unionElem, decl); 5876 } 5877 } 5878 if (TsUtils.isAnyType(type)) { 5879 this.incrementCounters(decl, FaultID.AnyType); 5880 } else if (TsUtils.isUnknownType(type)) { 5881 this.incrementCounters(decl, FaultID.UnknownType); 5882 } 5883 } 5884 5885 private handleCommentDirectives(sourceFile: ts.SourceFile): void { 5886 5887 /* 5888 * We use a dirty hack to retrieve list of parsed comment directives by accessing 5889 * internal properties of SourceFile node. 5890 */ 5891 /* CC-OFFNXT(no_explicit_any) std lib */ 5892 // Handle comment directive '@ts-nocheck' 5893 const pragmas = (sourceFile as any).pragmas; 5894 if (pragmas && pragmas instanceof Map) { 5895 const noCheckPragma = pragmas.get('ts-nocheck'); 5896 if (noCheckPragma) { 5897 5898 /* 5899 * The value is either a single entry or an array of entries. 5900 * Wrap up single entry with array to simplify processing. 5901 */ 5902 /* CC-OFFNXT(no_explicit_any) std lib */ 5903 const noCheckEntries: any[] = Array.isArray(noCheckPragma) ? noCheckPragma : [noCheckPragma]; 5904 for (const entry of noCheckEntries) { 5905 this.processNoCheckEntry(entry); 5906 } 5907 } 5908 } 5909 5910 /* CC-OFFNXT(no_explicit_any) std lib */ 5911 // Handle comment directives '@ts-ignore' and '@ts-expect-error' 5912 const commentDirectives = (sourceFile as any).commentDirectives; 5913 if (commentDirectives && Array.isArray(commentDirectives)) { 5914 for (const directive of commentDirectives) { 5915 if (directive.range?.pos === undefined || directive.range?.end === undefined) { 5916 continue; 5917 } 5918 5919 const range = directive.range as ts.TextRange; 5920 const kind: ts.SyntaxKind = 5921 sourceFile.text.slice(range.pos, range.pos + 2) === '/*' ? 5922 ts.SyntaxKind.MultiLineCommentTrivia : 5923 ts.SyntaxKind.SingleLineCommentTrivia; 5924 const commentRange: ts.CommentRange = { 5925 pos: range.pos, 5926 end: range.end, 5927 kind 5928 }; 5929 5930 this.incrementCounters(commentRange, FaultID.ErrorSuppression); 5931 } 5932 } 5933 } 5934 5935 /* CC-OFFNXT(no_explicit_any) std lib */ 5936 private processNoCheckEntry(entry: any): void { 5937 if (entry.range?.kind === undefined || entry.range?.pos === undefined || entry.range?.end === undefined) { 5938 return; 5939 } 5940 5941 this.incrementCounters(entry.range as ts.CommentRange, FaultID.ErrorSuppression); 5942 } 5943 5944 private reportThisKeywordsInScope(scope: ts.Block | ts.Expression): void { 5945 const callback = (node: ts.Node): void => { 5946 if (node.kind === ts.SyntaxKind.ThisKeyword) { 5947 this.incrementCounters(node, FaultID.FunctionContainsThis); 5948 } 5949 }; 5950 const stopCondition = (node: ts.Node): boolean => { 5951 const isClassLike = ts.isClassDeclaration(node) || ts.isClassExpression(node); 5952 const isFunctionLike = ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node); 5953 const isModuleDecl = ts.isModuleDeclaration(node); 5954 return isClassLike || isFunctionLike || isModuleDecl; 5955 }; 5956 forEachNodeInSubtree(scope, callback, stopCondition); 5957 } 5958 5959 private handleConstructorDeclaration(node: ts.Node): void { 5960 const ctorDecl = node as ts.ConstructorDeclaration; 5961 this.checkDefaultParamBeforeRequired(ctorDecl); 5962 this.handleTSOverload(ctorDecl); 5963 const paramProperties = ctorDecl.parameters.filter((x) => { 5964 return this.tsUtils.hasAccessModifier(x); 5965 }); 5966 if (paramProperties.length === 0) { 5967 return; 5968 } 5969 let paramTypes: ts.TypeNode[] | undefined; 5970 if (ctorDecl.body) { 5971 paramTypes = this.collectCtorParamTypes(ctorDecl); 5972 } 5973 const autofix = this.autofixer?.fixCtorParameterProperties(ctorDecl, paramTypes); 5974 for (const param of paramProperties) { 5975 this.incrementCounters(param, FaultID.ParameterProperties, autofix); 5976 } 5977 } 5978 5979 private collectCtorParamTypes(ctorDecl: ts.ConstructorDeclaration): ts.TypeNode[] | undefined { 5980 const paramTypes: ts.TypeNode[] = []; 5981 5982 for (const param of ctorDecl.parameters) { 5983 let paramTypeNode = param.type; 5984 if (!paramTypeNode) { 5985 const paramType = this.tsTypeChecker.getTypeAtLocation(param); 5986 paramTypeNode = this.tsTypeChecker.typeToTypeNode(paramType, param, ts.NodeBuilderFlags.None); 5987 } 5988 if (!paramTypeNode || !this.tsUtils.isSupportedType(paramTypeNode)) { 5989 return undefined; 5990 } 5991 paramTypes.push(paramTypeNode); 5992 } 5993 5994 return paramTypes; 5995 } 5996 5997 private handlePrivateIdentifier(node: ts.Node): void { 5998 const ident = node as ts.PrivateIdentifier; 5999 const autofix = this.autofixer?.fixPrivateIdentifier(ident); 6000 this.incrementCounters(node, FaultID.PrivateIdentifier, autofix); 6001 } 6002 6003 private handleIndexSignature(node: ts.Node): void { 6004 if (!this.tsUtils.isAllowedIndexSignature(node as ts.IndexSignatureDeclaration)) { 6005 this.incrementCounters(node, FaultID.IndexMember); 6006 } 6007 } 6008 6009 private handleTypeLiteral(node: ts.Node): void { 6010 const typeLiteral = node as ts.TypeLiteralNode; 6011 const autofix = this.autofixer?.fixTypeliteral(typeLiteral); 6012 this.incrementCounters(node, FaultID.ObjectTypeLiteral, autofix); 6013 } 6014 6015 private scanCapturedVarsInSendableScope(startNode: ts.Node, scope: ts.Node, faultId: FaultID): void { 6016 const callback = (node: ts.Node): void => { 6017 // Namespace import will introduce closure in the es2abc compiler stage 6018 if (!ts.isIdentifier(node) || this.checkNamespaceImportVar(node)) { 6019 return; 6020 } 6021 6022 // The "b" of "A.b" should not be checked since it's load from object "A" 6023 const parent: ts.Node = node.parent; 6024 if (ts.isPropertyAccessExpression(parent) && parent.name === node) { 6025 return; 6026 } 6027 // When overloading function, will misreport 6028 if (ts.isFunctionDeclaration(startNode) && startNode.name === node) { 6029 return; 6030 } 6031 6032 this.checkLocalDecl(node, scope, faultId); 6033 }; 6034 // Type nodes should not checked because no closure will be introduced 6035 const stopCondition = (node: ts.Node): boolean => { 6036 // already existed 'arkts-sendable-class-decoratos' error 6037 if (ts.isDecorator(node) && node.parent === startNode) { 6038 return true; 6039 } 6040 return ts.isTypeReferenceNode(node); 6041 }; 6042 forEachNodeInSubtree(startNode, callback, stopCondition); 6043 } 6044 6045 private checkLocalDecl(node: ts.Identifier, scope: ts.Node, faultId: FaultID): void { 6046 const trueSym = this.tsUtils.trueSymbolAtLocation(node); 6047 // Sendable decorator should be used in method of Sendable classes 6048 if (trueSym === undefined) { 6049 return; 6050 } 6051 6052 // Const enum member will be replaced by the exact value of it, no closure will be introduced 6053 if (TsUtils.isConstEnum(trueSym)) { 6054 return; 6055 } 6056 6057 const declarations = trueSym.getDeclarations(); 6058 if (declarations?.length) { 6059 this.checkLocalDeclWithSendableClosure(node, scope, declarations[0], faultId); 6060 } 6061 } 6062 6063 private checkLocalDeclWithSendableClosure( 6064 node: ts.Identifier, 6065 scope: ts.Node, 6066 decl: ts.Declaration, 6067 faultId: FaultID 6068 ): void { 6069 const declPosition = decl.getStart(); 6070 if ( 6071 decl.getSourceFile().fileName !== node.getSourceFile().fileName || 6072 declPosition !== undefined && declPosition >= scope.getStart() && declPosition < scope.getEnd() 6073 ) { 6074 return; 6075 } 6076 6077 if (this.isFileExportDecl(decl)) { 6078 return; 6079 } 6080 6081 if (this.isTopSendableClosure(decl)) { 6082 return; 6083 } 6084 6085 /** 6086 * The cases in condition will introduce closure if defined in the same file as the Sendable class. The following 6087 * cases are excluded because they are not allowed in ArkTS: 6088 * 1. ImportEqualDecalration 6089 * 2. BindingElement 6090 */ 6091 if ( 6092 ts.isVariableDeclaration(decl) || 6093 ts.isFunctionDeclaration(decl) || 6094 ts.isClassDeclaration(decl) || 6095 ts.isInterfaceDeclaration(decl) || 6096 ts.isEnumDeclaration(decl) || 6097 ts.isModuleDeclaration(decl) || 6098 ts.isParameter(decl) 6099 ) { 6100 this.incrementCounters(node, faultId); 6101 } 6102 } 6103 6104 private isTopSendableClosure(decl: ts.Declaration): boolean { 6105 if (!ts.isSourceFile(decl.parent)) { 6106 return false; 6107 } 6108 if ( 6109 ts.isClassDeclaration(decl) && 6110 this.tsUtils.isSendableClassOrInterface(this.tsTypeChecker.getTypeAtLocation(decl)) 6111 ) { 6112 return true; 6113 } 6114 if (ts.isFunctionDeclaration(decl) && TsUtils.hasSendableDecoratorFunctionOverload(decl)) { 6115 return true; 6116 } 6117 return false; 6118 } 6119 6120 private checkNamespaceImportVar(node: ts.Node): boolean { 6121 // Namespace import cannot be determined by the true symbol 6122 const sym = this.tsTypeChecker.getSymbolAtLocation(node); 6123 const decls = sym?.getDeclarations(); 6124 if (decls?.length) { 6125 if (ts.isNamespaceImport(decls[0])) { 6126 this.incrementCounters(node, FaultID.SendableCapturedVars); 6127 return true; 6128 } 6129 } 6130 return false; 6131 } 6132 6133 private isFileExportDecl(decl: ts.Declaration): boolean { 6134 const sourceFile = decl.getSourceFile(); 6135 if (!this.fileExportDeclCaches) { 6136 this.fileExportDeclCaches = this.tsUtils.searchFileExportDecl(sourceFile); 6137 } 6138 return this.fileExportDeclCaches.has(decl); 6139 } 6140 6141 private handleExportKeyword(node: ts.Node): void { 6142 const parentNode = node.parent; 6143 if (!TypeScriptLinter.inSharedModule(node) || ts.isModuleBlock(parentNode.parent)) { 6144 return; 6145 } 6146 6147 switch (parentNode.kind) { 6148 case ts.SyntaxKind.EnumDeclaration: 6149 case ts.SyntaxKind.InterfaceDeclaration: 6150 case ts.SyntaxKind.FunctionDeclaration: 6151 case ts.SyntaxKind.ClassDeclaration: 6152 if (!this.tsUtils.isShareableType(this.tsTypeChecker.getTypeAtLocation(parentNode))) { 6153 this.incrementCounters((parentNode as ts.NamedDeclaration).name ?? parentNode, FaultID.SharedModuleExports); 6154 } 6155 return; 6156 case ts.SyntaxKind.VariableStatement: 6157 for (const variableDeclaration of (parentNode as ts.VariableStatement).declarationList.declarations) { 6158 if (!this.tsUtils.isShareableEntity(variableDeclaration.name)) { 6159 this.incrementCounters(variableDeclaration.name, FaultID.SharedModuleExports); 6160 } 6161 } 6162 return; 6163 case ts.SyntaxKind.TypeAliasDeclaration: 6164 if (!this.tsUtils.isShareableEntity(parentNode)) { 6165 this.incrementCounters(parentNode, FaultID.SharedModuleExportsWarning); 6166 } 6167 return; 6168 default: 6169 this.incrementCounters(parentNode, FaultID.SharedModuleExports); 6170 } 6171 } 6172 6173 private handleExportDeclaration(node: ts.Node): void { 6174 const exportDecl = node as ts.ExportDeclaration; 6175 6176 if (this.isExportedEntityDeclaredInJs(exportDecl)) { 6177 this.incrementCounters(node, FaultID.InteropJsObjectExport); 6178 return; 6179 } 6180 6181 if (!TypeScriptLinter.inSharedModule(node) || ts.isModuleBlock(node.parent)) { 6182 return; 6183 } 6184 6185 if (exportDecl.exportClause === undefined) { 6186 this.incrementCounters(exportDecl, FaultID.SharedModuleNoWildcardExport); 6187 return; 6188 } 6189 6190 if (ts.isNamespaceExport(exportDecl.exportClause)) { 6191 if (!this.tsUtils.isShareableType(this.tsTypeChecker.getTypeAtLocation(exportDecl.exportClause.name))) { 6192 this.incrementCounters(exportDecl.exportClause.name, FaultID.SharedModuleExports); 6193 } 6194 return; 6195 } 6196 6197 for (const exportSpecifier of exportDecl.exportClause.elements) { 6198 if (!this.tsUtils.isShareableEntity(exportSpecifier.name)) { 6199 this.incrementCounters(exportSpecifier.name, FaultID.SharedModuleExports); 6200 } 6201 } 6202 } 6203 6204 private handleReturnStatement(node: ts.Node): void { 6205 // The return value must match the return type of the 'function' 6206 const returnStat = node as ts.ReturnStatement; 6207 const expr = returnStat.expression; 6208 if (!expr) { 6209 return; 6210 } 6211 const lhsType = this.tsTypeChecker.getContextualType(expr); 6212 if (!lhsType) { 6213 return; 6214 } 6215 this.checkAssignmentMatching(node, lhsType, expr, true); 6216 this.handleObjectLiteralInReturn(returnStat); 6217 this.handleObjectLiteralAssignmentToClass(returnStat); 6218 } 6219 6220 /** 6221 * 'arkts-no-structural-typing' check was missing in some scenarios, 6222 * in order not to cause incompatibility, 6223 * only need to strictly match the type of filling the check again 6224 */ 6225 private checkAssignmentMatching( 6226 field: ts.Node, 6227 lhsType: ts.Type, 6228 rhsExpr: ts.Expression, 6229 isNewStructuralCheck: boolean = false 6230 ): void { 6231 const rhsType = this.tsTypeChecker.getTypeAtLocation(rhsExpr); 6232 this.handleNoTuplesArrays(field, lhsType, rhsType); 6233 this.handleArrayTypeImmutable(field, lhsType, rhsType, rhsExpr); 6234 // check that 'sendable typeAlias' is assigned correctly 6235 if (this.tsUtils.isWrongSendableFunctionAssignment(lhsType, rhsType)) { 6236 this.incrementCounters(field, FaultID.SendableFunctionAssignment); 6237 } 6238 const isStrict = this.tsUtils.needStrictMatchType(lhsType, rhsType); 6239 // 'isNewStructuralCheck' means that this assignment scenario was previously omitted, so only strict matches are checked now 6240 if (isNewStructuralCheck && !isStrict) { 6241 return; 6242 } 6243 if (this.tsUtils.needToDeduceStructuralIdentity(lhsType, rhsType, rhsExpr, isStrict)) { 6244 if (ts.isNewExpression(rhsExpr) && ts.isIdentifier(rhsExpr.expression) && rhsExpr.expression.text === 'Promise') { 6245 const isReturnStatement = ts.isReturnStatement(rhsExpr.parent); 6246 const enclosingFunction = ts.findAncestor(rhsExpr, ts.isFunctionLike); 6247 const isAsyncFunction = 6248 enclosingFunction && 6249 (enclosingFunction.modifiers?.some((m) => { 6250 return m.kind === ts.SyntaxKind.AsyncKeyword; 6251 }) || 6252 false); 6253 if (isReturnStatement && isAsyncFunction) { 6254 return; 6255 } 6256 } 6257 this.incrementCounters(field, FaultID.StructuralIdentity); 6258 } 6259 } 6260 6261 private handleDecorator(node: ts.Node): void { 6262 this.handleExtendDecorator(node); 6263 this.handleEntryDecorator(node); 6264 this.handleProvideDecorator(node); 6265 this.handleLocalBuilderDecorator(node); 6266 6267 const decorator: ts.Decorator = node as ts.Decorator; 6268 this.checkSendableAndConcurrentDecorator(decorator); 6269 this.handleStylesDecorator(decorator); 6270 if (TsUtils.getDecoratorName(decorator) === SENDABLE_DECORATOR) { 6271 const parent: ts.Node = decorator.parent; 6272 if (!parent || !SENDABLE_DECORATOR_NODES.includes(parent.kind)) { 6273 const autofix = this.autofixer?.removeNode(decorator); 6274 this.incrementCounters(decorator, FaultID.SendableDecoratorLimited, autofix); 6275 } 6276 } 6277 this.handleNotSupportCustomDecorators(decorator); 6278 } 6279 6280 private handleProvideDecorator(node: ts.Node): void { 6281 if (!this.options.arkts2) { 6282 return; 6283 } 6284 6285 if (!ts.isDecorator(node)) { 6286 return; 6287 } 6288 6289 if (ts.isCallExpression(node.expression) && ts.isIdentifier(node.expression.expression)) { 6290 if (node.expression.expression.text !== PROVIDE_DECORATOR_NAME || node.expression.arguments.length !== 1) { 6291 return; 6292 } 6293 const arg = node.expression.arguments[0]; 6294 if (!ts.isStringLiteral(arg) && !ts.isObjectLiteralExpression(arg)) { 6295 return; 6296 } 6297 if (ts.isObjectLiteralExpression(arg)) { 6298 const properties = arg.properties; 6299 if (properties.length !== 1) { 6300 return; 6301 } 6302 const property = properties[0] as ts.PropertyAssignment; 6303 if (!ts.isIdentifier(property.name) || !ts.isStringLiteral(property.initializer)) { 6304 return; 6305 } 6306 if (property.name.escapedText !== PROVIDE_ALLOW_OVERRIDE_PROPERTY_NAME) { 6307 return; 6308 } 6309 } 6310 const autofix = this.autofixer?.fixProvideDecorator(node); 6311 this.incrementCounters(node.parent, FaultID.ProvideAnnotation, autofix); 6312 } 6313 } 6314 6315 private isSendableDecoratorValid(decl: ts.FunctionDeclaration | ts.TypeAliasDeclaration): boolean { 6316 if ( 6317 this.compatibleSdkVersion > SENDBALE_FUNCTION_START_VERSION || 6318 this.compatibleSdkVersion === SENDBALE_FUNCTION_START_VERSION && 6319 !SENDABLE_FUNCTION_UNSUPPORTED_STAGES_IN_API12.includes(this.compatibleSdkVersionStage) 6320 ) { 6321 return true; 6322 } 6323 const curDecorator = TsUtils.getSendableDecorator(decl); 6324 if (curDecorator) { 6325 this.incrementCounters(curDecorator, FaultID.SendableBetaCompatible); 6326 } 6327 return false; 6328 } 6329 6330 private handleImportType(node: ts.Node): void { 6331 if (!this.options.arkts2) { 6332 return; 6333 } 6334 this.incrementCounters(node, FaultID.ImportType); 6335 this.incrementCounters(node, FaultID.DynamicImport); 6336 } 6337 6338 private handleVoidExpression(node: ts.Node): void { 6339 if (!this.options.arkts2) { 6340 return; 6341 } 6342 const autofix = this.autofixer?.fixVoidOperator(node as ts.VoidExpression); 6343 this.incrementCounters(node, FaultID.VoidOperator, autofix); 6344 } 6345 6346 private handleRegularExpressionLiteral(node: ts.Node): void { 6347 if (!this.options.arkts2) { 6348 return; 6349 } 6350 const autofix = this.autofixer?.fixRegularExpressionLiteral(node as ts.RegularExpressionLiteral); 6351 this.incrementCounters(node, FaultID.RegularExpressionLiteral, autofix); 6352 } 6353 6354 private handleLimitedVoidType(node: ts.VariableDeclaration): void { 6355 if (!this.options.arkts2) { 6356 return; 6357 } 6358 6359 const typeNode = node.type; 6360 if (typeNode && TsUtils.typeContainsVoid(typeNode)) { 6361 const autofix = this.autofixer?.fixLimitedVoidType(node); 6362 this.incrementCounters(typeNode, FaultID.LimitedVoidType, autofix); 6363 } 6364 } 6365 6366 private handleLimitedVoidWithCall(node: ts.CallExpression): void { 6367 if (!this.options.arkts2) { 6368 return; 6369 } 6370 6371 if (ts.isPropertyAccessExpression(node.parent)) { 6372 return; 6373 } 6374 6375 const signature = this.tsTypeChecker.getResolvedSignature(node); 6376 if (!signature) { 6377 return; 6378 } 6379 6380 const returnType = this.tsTypeChecker.getReturnTypeOfSignature(signature); 6381 if (this.tsTypeChecker.typeToString(returnType) !== 'void') { 6382 return; 6383 } 6384 6385 if (ts.isReturnStatement(node.parent)) { 6386 const functionLike = TypeScriptLinter.findContainingFunction(node); 6387 if (functionLike && TypeScriptLinter.isRecursiveCall(node, functionLike)) { 6388 this.incrementCounters(node, FaultID.LimitedVoidType); 6389 } 6390 return; 6391 } 6392 6393 this.incrementCounters(node, FaultID.LimitedVoidType); 6394 } 6395 6396 private static findContainingFunction(node: ts.Node): ts.FunctionLikeDeclaration | undefined { 6397 while (node) { 6398 if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) { 6399 return node; 6400 } 6401 node = node.parent; 6402 } 6403 return undefined; 6404 } 6405 6406 // Helper function to check if a call is recursive 6407 private static isRecursiveCall(callExpr: ts.CallExpression, fn: ts.FunctionLikeDeclaration): boolean { 6408 return ( 6409 ts.isIdentifier(callExpr.expression) && 6410 ts.isFunctionDeclaration(fn) && 6411 !!fn.name && 6412 fn.name.text === callExpr.expression.text 6413 ); 6414 } 6415 6416 private handleArrayType(arrayType: ts.Node): void { 6417 if (!this.options.arkts2) { 6418 return; 6419 } 6420 6421 if (!arrayType || !ts.isArrayTypeNode(arrayType)) { 6422 return; 6423 } 6424 6425 if (arrayType.elementType.kind === ts.SyntaxKind.VoidKeyword) { 6426 this.incrementCounters(arrayType.elementType, FaultID.LimitedVoidType); 6427 } 6428 } 6429 6430 private handleUnionType(unionType: ts.Node): void { 6431 if (!this.options.arkts2) { 6432 return; 6433 } 6434 6435 if (!unionType || !ts.isUnionTypeNode(unionType)) { 6436 return; 6437 } 6438 6439 const types = unionType.types; 6440 for (const type of types) { 6441 if (type.kind === ts.SyntaxKind.VoidKeyword) { 6442 this.incrementCounters(type, FaultID.LimitedVoidType); 6443 } 6444 } 6445 } 6446 6447 private handleDebuggerStatement(node: ts.Node): void { 6448 if (!this.options.arkts2) { 6449 return; 6450 } 6451 6452 this.incrementCounters(node, FaultID.DebuggerStatement); 6453 } 6454 6455 private handleTSOverload(decl: ts.FunctionDeclaration | ts.MethodDeclaration | ts.ConstructorDeclaration): void { 6456 if (!this.options.arkts2) { 6457 return; 6458 } 6459 if (decl.name) { 6460 const symbol = this.tsTypeChecker.getSymbolAtLocation(decl.name); 6461 if (!symbol) { 6462 return; 6463 } 6464 const declarations = symbol.getDeclarations(); 6465 if (!declarations) { 6466 return; 6467 } 6468 const filterDecl = declarations.filter((name) => { 6469 return ts.isFunctionDeclaration(name) || ts.isMethodDeclaration(name); 6470 }); 6471 const isInternalFunction = decl.name && ts.isIdentifier(decl.name) && interanlFunction.includes(decl.name.text); 6472 if (isInternalFunction && filterDecl.length > 2 || !isInternalFunction && filterDecl.length > 1) { 6473 this.incrementCounters(decl, FaultID.TsOverload); 6474 } 6475 } else if (ts.isConstructorDeclaration(decl) && decl.getText()) { 6476 this.handleTSOverloadUnderConstructorDeclaration(decl); 6477 } 6478 } 6479 6480 private handleTSOverloadUnderConstructorDeclaration(decl: ts.ConstructorDeclaration): void { 6481 const parent = decl.parent; 6482 const constructors = parent.members.filter(ts.isConstructorDeclaration); 6483 const isStruct = decl.getText() && ts.isStructDeclaration(parent); 6484 if ((isStruct ? --constructors.length : constructors.length) > 1) { 6485 this.incrementCounters(decl, FaultID.TsOverload); 6486 } 6487 } 6488 6489 private handleSwitchStatement(node: ts.Node): void { 6490 if (!this.options.arkts2) { 6491 return; 6492 } 6493 const switchStatement = node as ts.SwitchStatement; 6494 6495 this.validateSwitchExpression(switchStatement); 6496 6497 const duplicateCases = this.findDuplicateCases(switchStatement); 6498 if (duplicateCases.length > 0) { 6499 for (const duplicateCase of duplicateCases) { 6500 this.incrementCounters(duplicateCase.expression, FaultID.CaseExpression); 6501 } 6502 } 6503 } 6504 6505 private validateSwitchExpression(switchStatement: ts.SwitchStatement): void { 6506 const expr = switchStatement.expression; 6507 const nodeType = this.tsTypeChecker.getTypeAtLocation(expr); 6508 const { isLiteralInitialized, isFloatLiteral, hasExplicitTypeAnnotation } = this.getDeclarationInfo(expr); 6509 6510 const isUnionType = (nodeType.flags & ts.TypeFlags.Union) !== 0; 6511 6512 const isTypeAllowed = (t: ts.Type): boolean => { 6513 const typeText = this.tsTypeChecker.typeToString(t); 6514 return Boolean( 6515 t.flags & ts.TypeFlags.StringLike || 6516 typeText === 'String' || 6517 t.flags & ts.TypeFlags.NumberLike && (/^\d+$/).test(typeText) || 6518 isLiteralInitialized && !hasExplicitTypeAnnotation && !isFloatLiteral || 6519 t.flags & ts.TypeFlags.EnumLike 6520 ); 6521 }; 6522 6523 let isAllowed = !isUnionType && isTypeAllowed(nodeType); 6524 6525 if (isUnionType) { 6526 const unionType = nodeType as ts.UnionType; 6527 isAllowed = unionType.types.every(isTypeAllowed); 6528 } 6529 6530 if (!isAllowed) { 6531 this.incrementCounters(expr, FaultID.SwitchExpression); 6532 } 6533 } 6534 6535 private getDeclarationInfo(expression: ts.Expression): { 6536 isLiteralInitialized: boolean; 6537 isFloatLiteral: boolean; 6538 hasExplicitTypeAnnotation: boolean; 6539 } { 6540 const symbol = this.tsTypeChecker.getSymbolAtLocation(expression); 6541 const declaration = symbol?.valueDeclaration; 6542 6543 if (!declaration || !ts.isVariableDeclaration(declaration)) { 6544 return { isLiteralInitialized: false, isFloatLiteral: false, hasExplicitTypeAnnotation: false }; 6545 } 6546 6547 const hasExplicitTypeAnnotation = !!declaration.type; 6548 const initializerInfo = TypeScriptLinter.getInitializerInfo(declaration.initializer); 6549 6550 return { 6551 isLiteralInitialized: initializerInfo.isLiteralInitialized, 6552 isFloatLiteral: initializerInfo.isFloatLiteral, 6553 hasExplicitTypeAnnotation 6554 }; 6555 } 6556 6557 private static getInitializerInfo(initializer?: ts.Expression): { 6558 isLiteralInitialized: boolean; 6559 isFloatLiteral: boolean; 6560 } { 6561 if (!initializer) { 6562 return { isLiteralInitialized: false, isFloatLiteral: false }; 6563 } 6564 6565 const isLiteralInitialized = ts.isNumericLiteral(initializer) || ts.isStringLiteral(initializer); 6566 6567 let isFloatLiteral = false; 6568 if (ts.isNumericLiteral(initializer)) { 6569 const literalText = initializer.getText(); 6570 if (!(/^0[xX]/).test(literalText)) { 6571 isFloatLiteral = (/\.|e[-+]|\dE[-+]/i).test(literalText); 6572 } 6573 } 6574 6575 return { isLiteralInitialized, isFloatLiteral }; 6576 } 6577 6578 private findDuplicateCases(switchStatement: ts.SwitchStatement): ts.CaseClause[] { 6579 const seenValues = new Map<string | number | boolean, ts.CaseClause>(); 6580 const duplicates: ts.CaseClause[] = []; 6581 6582 for (const clause of switchStatement.caseBlock.clauses) { 6583 if (ts.isCaseClause(clause) && clause.expression) { 6584 const value = this.getConstantValue(clause.expression); 6585 const key = value !== undefined ? value : clause.expression.getText(); 6586 if (seenValues.has(key)) { 6587 duplicates.push(clause); 6588 } else { 6589 seenValues.set(key, clause); 6590 } 6591 } 6592 } 6593 return duplicates; 6594 } 6595 6596 private getConstantValue(expression: ts.Expression): string | number | boolean | undefined { 6597 if (ts.isLiteralExpression(expression)) { 6598 return ts.isNumericLiteral(expression) ? Number(expression.text) : expression.text; 6599 } 6600 6601 switch (expression.kind) { 6602 case ts.SyntaxKind.TrueKeyword: 6603 return true; 6604 case ts.SyntaxKind.FalseKeyword: 6605 return false; 6606 default: 6607 if (ts.isElementAccessExpression(expression) || ts.isPropertyAccessExpression(expression)) { 6608 const constantValue = this.tsTypeChecker.getConstantValue(expression); 6609 if (constantValue !== undefined) { 6610 return constantValue; 6611 } 6612 } 6613 return undefined; 6614 } 6615 } 6616 6617 private handleLimitedLiteralType(literalTypeNode: ts.LiteralTypeNode): void { 6618 if (!this.options.arkts2 || !literalTypeNode) { 6619 return; 6620 } 6621 const literal = literalTypeNode.literal; 6622 if ( 6623 !( 6624 literal.kind === ts.SyntaxKind.StringLiteral || 6625 literal.kind === ts.SyntaxKind.NullKeyword || 6626 literal.kind === ts.SyntaxKind.UndefinedKeyword 6627 ) 6628 ) { 6629 this.incrementCounters(literalTypeNode, FaultID.LimitedLiteralType); 6630 } 6631 } 6632 6633 private findVariableInitializationValue(identifier: ts.Identifier): number | null { 6634 const symbol = this.tsTypeChecker.getSymbolAtLocation(identifier); 6635 if (!symbol) { 6636 return null; 6637 } 6638 if (this.constVariableInitCache.has(symbol)) { 6639 return this.constVariableInitCache.get(symbol)!; 6640 } 6641 const declarations = symbol.getDeclarations(); 6642 if (declarations && declarations.length > 0) { 6643 const declaration = declarations[0]; 6644 6645 const isConditionOnEnumMember = ts.isEnumMember(declaration) && declaration.initializer; 6646 const isConditionOnVariableDecl = 6647 ts.isVariableDeclaration(declaration) && 6648 declaration.initializer && 6649 (declaration.parent as ts.VariableDeclarationList).flags & ts.NodeFlags.Const; 6650 if (isConditionOnEnumMember || isConditionOnVariableDecl) { 6651 const res = this.evaluateNumericValue(declaration.initializer); 6652 this.constVariableInitCache.set(symbol, res); 6653 return res; 6654 } 6655 } 6656 6657 return null; 6658 } 6659 6660 private evaluateNumericValueFromPrefixUnaryExpression(node: ts.PrefixUnaryExpression): number | null { 6661 if (node.operator === ts.SyntaxKind.MinusToken) { 6662 if (ts.isNumericLiteral(node.operand) || ts.isIdentifier(node.operand) && node.operand.text === 'Infinity') { 6663 return node.operand.text === 'Infinity' ? Number.NEGATIVE_INFINITY : -Number(node.operand.text); 6664 } 6665 const operandValue = this.evaluateNumericValue(node.operand); 6666 if (operandValue !== null) { 6667 return -operandValue; 6668 } 6669 } 6670 return null; 6671 } 6672 6673 private evaluateNumericValueFromAsExpression(node: ts.AsExpression): number | null { 6674 const typeNode = node.type; 6675 if ( 6676 typeNode.kind === ts.SyntaxKind.NumberKeyword || 6677 ts.isTypeReferenceNode(typeNode) && typeNode.typeName.getText() === 'Number' 6678 ) { 6679 return this.evaluateNumericValue(node.expression); 6680 } 6681 return null; 6682 } 6683 6684 private evaluateNumericValue(node: ts.Expression): number | null { 6685 let result: number | null = null; 6686 if (ts.isNumericLiteral(node)) { 6687 result = Number(node.text); 6688 } else if (ts.isPrefixUnaryExpression(node)) { 6689 result = this.evaluateNumericValueFromPrefixUnaryExpression(node); 6690 } else if (ts.isBinaryExpression(node)) { 6691 result = this.evaluateNumericValueFromBinaryExpression(node); 6692 } else if (ts.isPropertyAccessExpression(node)) { 6693 result = this.evaluateNumericValueFromPropertyAccess(node); 6694 } else if (ts.isParenthesizedExpression(node)) { 6695 result = this.evaluateNumericValue(node.expression); 6696 } else if (ts.isAsExpression(node)) { 6697 result = this.evaluateNumericValueFromAsExpression(node); 6698 } else if (ts.isIdentifier(node)) { 6699 if (node.text === 'Infinity') { 6700 return Number.POSITIVE_INFINITY; 6701 } else if (node.text === 'NaN') { 6702 return Number.NaN; 6703 } 6704 const symbol = this.tsTypeChecker.getSymbolAtLocation(node); 6705 return symbol ? this.constVariableInitCache.get(symbol) || null : null; 6706 } 6707 return result; 6708 } 6709 6710 private evaluateNumericValueFromBinaryExpression(node: ts.BinaryExpression): number | null { 6711 const leftValue = this.evaluateNumericValue(node.left); 6712 const rightValue = this.evaluateNumericValue(node.right); 6713 if (leftValue !== null && rightValue !== null) { 6714 switch (node.operatorToken.kind) { 6715 case ts.SyntaxKind.PlusToken: 6716 return leftValue + rightValue; 6717 case ts.SyntaxKind.MinusToken: 6718 return leftValue - rightValue; 6719 case ts.SyntaxKind.AsteriskToken: 6720 return leftValue * rightValue; 6721 case ts.SyntaxKind.SlashToken: 6722 return leftValue / rightValue; 6723 case ts.SyntaxKind.PercentToken: 6724 return leftValue % rightValue; 6725 case ts.SyntaxKind.AsteriskAsteriskToken: 6726 return Math.pow(leftValue, rightValue); 6727 default: 6728 return null; 6729 } 6730 } 6731 return null; 6732 } 6733 6734 private evaluateNumericValueFromPropertyAccess(node: ts.PropertyAccessExpression): number | null { 6735 const numberProperties = ['MIN_SAFE_INTEGER', 'MAX_SAFE_INTEGER', 'NaN', 'NEGATIVE_INFINITY', 'POSITIVE_INFINITY']; 6736 if ( 6737 ts.isIdentifier(node.expression) && 6738 node.expression.text === 'Number' && 6739 numberProperties.includes(node.name.text) 6740 ) { 6741 switch (node.name.text) { 6742 case 'MIN_SAFE_INTEGER': 6743 return Number.MIN_SAFE_INTEGER; 6744 case 'MAX_SAFE_INTEGER': 6745 return Number.MAX_SAFE_INTEGER; 6746 case 'NaN': 6747 return Number.NaN; 6748 case 'NEGATIVE_INFINITY': 6749 return Number.NEGATIVE_INFINITY; 6750 case 'POSITIVE_INFINITY': 6751 return Number.POSITIVE_INFINITY; 6752 default: 6753 return null; 6754 } 6755 } 6756 return this.evaluateNumericValue(node.name); 6757 } 6758 6759 private collectVariableNamesAndCache(node: ts.Node): void { 6760 if (ts.isIdentifier(node)) { 6761 const value = this.findVariableInitializationValue(node); 6762 const symbol = this.tsTypeChecker.getSymbolAtLocation(node); 6763 if (value && symbol) { 6764 this.constVariableInitCache.set(symbol, value); 6765 } 6766 } 6767 ts.forEachChild(node, this.collectVariableNamesAndCache.bind(this)); 6768 } 6769 6770 private handleIndexNegative(node: ts.Node): void { 6771 if (!this.options.arkts2 || !ts.isElementAccessExpression(node)) { 6772 return; 6773 } 6774 const indexNode = node.argumentExpression; 6775 if (indexNode) { 6776 this.collectVariableNamesAndCache(indexNode); 6777 const indexValue = this.evaluateNumericValue(indexNode); 6778 6779 if (indexValue !== null && (indexValue < 0 || isNaN(indexValue))) { 6780 this.incrementCounters(node, FaultID.IndexNegative); 6781 } 6782 } 6783 } 6784 6785 private handleNoTuplesArrays(node: ts.Node, lhsType: ts.Type, rhsType: ts.Type): void { 6786 if (!this.options.arkts2) { 6787 return; 6788 } 6789 if ( 6790 this.tsUtils.isOrDerivedFrom(lhsType, this.tsUtils.isArray) && 6791 this.tsUtils.isOrDerivedFrom(rhsType, TsUtils.isTuple) || 6792 this.tsUtils.isOrDerivedFrom(rhsType, this.tsUtils.isArray) && 6793 this.tsUtils.isOrDerivedFrom(lhsType, TsUtils.isTuple) 6794 ) { 6795 this.incrementCounters(node, FaultID.NoTuplesArrays); 6796 } 6797 } 6798 6799 private handleNoTuplesArraysForPropertyAccessExpression(node: ts.PropertyAccessExpression): void { 6800 if (!this.options.arkts2) { 6801 return; 6802 } 6803 const lhsType = this.tsTypeChecker.getTypeAtLocation(node.expression); 6804 if (this.tsUtils.isOrDerivedFrom(lhsType, TsUtils.isTuple)) { 6805 if (ARRAY_API_LIST.includes(node.name.text)) { 6806 this.incrementCounters(node, FaultID.NoTuplesArrays); 6807 } 6808 } 6809 } 6810 6811 private handleArrayTypeImmutable(node: ts.Node, lhsType: ts.Type, rhsType: ts.Type, rhsExpr?: ts.Expression): void { 6812 if (!this.options.arkts2) { 6813 return; 6814 } 6815 const isArray = 6816 this.tsUtils.isOrDerivedFrom(lhsType, this.tsUtils.isArray) && 6817 this.tsUtils.isOrDerivedFrom(rhsType, this.tsUtils.isArray); 6818 const isTuple = 6819 this.tsUtils.isOrDerivedFrom(lhsType, TsUtils.isTuple) && this.tsUtils.isOrDerivedFrom(rhsType, TsUtils.isTuple); 6820 if (!((isArray || isTuple) && lhsType !== rhsType)) { 6821 return; 6822 } 6823 6824 const rhsTypeStr = this.tsTypeChecker.typeToString(rhsType); 6825 let lhsTypeStr = this.tsTypeChecker.typeToString(lhsType); 6826 if (rhsExpr && (this.isNullOrEmptyArray(rhsExpr) || ts.isArrayLiteralExpression(rhsExpr))) { 6827 return; 6828 } 6829 6830 if (ts.isAsExpression(node) && ts.isArrayLiteralExpression(node.expression)) { 6831 node.expression.elements.forEach((elem) => { 6832 if (elem.kind === ts.SyntaxKind.FalseKeyword || elem.kind === ts.SyntaxKind.TrueKeyword) { 6833 lhsTypeStr = rhsTypeStr.replace(elem.getText(), 'boolean'); 6834 } 6835 }); 6836 } 6837 if (lhsTypeStr !== rhsTypeStr) { 6838 this.incrementCounters(node, FaultID.ArrayTypeImmutable); 6839 } 6840 } 6841 6842 private isNullOrEmptyArray(expr: ts.Expression): boolean { 6843 if (ts.isNewExpression(expr)) { 6844 const constructorSym = this.tsTypeChecker.getSymbolAtLocation(expr.expression); 6845 if (constructorSym?.name === 'Array') { 6846 if (!expr.arguments || expr.arguments.length === 0) { 6847 return true; 6848 } 6849 if (expr.arguments.length === 1) { 6850 const argType = this.tsTypeChecker.getTypeAtLocation(expr.arguments[0]); 6851 return !!(argType.flags & ts.TypeFlags.NumberLike); 6852 } 6853 } 6854 } 6855 6856 return false; 6857 } 6858 6859 private handleExponentOperation(node: ts.Node): void { 6860 if (!this.options.arkts2) { 6861 return; 6862 } 6863 const autofix = this.autofixer?.fixExponent(node.parent); 6864 this.incrementCounters(node, FaultID.ExponentOp, autofix); 6865 } 6866 6867 private handleNonNullExpression(node: ts.Node): void { 6868 if (!this.options.arkts2) { 6869 return; 6870 } 6871 6872 if ( 6873 !ts.isNonNullExpression(node) || 6874 !ts.isNonNullExpression(node.expression) || 6875 ts.isNonNullExpression(node.parent) || 6876 ts.isPropertyAccessExpression(node.parent) || 6877 ts.isNonNullExpression(node.expression.expression) 6878 ) { 6879 return; 6880 } 6881 6882 const statement = ts.findAncestor(node, ts.isExpressionStatement); 6883 if (statement && this.isCustomComponent(statement)) { 6884 this.handleCustomBidirectionalBinding(node, node.expression); 6885 } else { 6886 const autofix = this.autofixer?.fixNativeBidirectionalBinding(node, this.interfacesNeedToImport); 6887 this.incrementCounters(node, FaultID.DoubleExclaBindingNotSupported, autofix); 6888 } 6889 } 6890 6891 private isCustomComponent(statement: ts.ExpressionStatement): boolean { 6892 const callExpr = statement.expression; 6893 if (!ts.isCallExpression(callExpr)) { 6894 return false; 6895 } 6896 6897 const identifier = callExpr.expression; 6898 if (!ts.isIdentifier(identifier)) { 6899 return false; 6900 } 6901 6902 const symbol = this.tsTypeChecker.getSymbolAtLocation(identifier); 6903 if (symbol) { 6904 const decl = this.tsUtils.getDeclarationNode(identifier); 6905 if (decl?.getSourceFile() === statement.getSourceFile()) { 6906 return true; 6907 } 6908 } 6909 6910 return this.interfacesAlreadyImported.has(callExpr.expression.getText()); 6911 } 6912 6913 private handleCustomBidirectionalBinding(firstExpr: ts.NonNullExpression, secondExpr: ts.NonNullExpression): void { 6914 let currentParam: ts.Identifier | undefined; 6915 if (ts.isPropertyAccessExpression(secondExpr.expression)) { 6916 currentParam = secondExpr.expression.name as ts.Identifier; 6917 } 6918 6919 let customParam: ts.Identifier | undefined; 6920 if (ts.isPropertyAssignment(firstExpr.parent)) { 6921 customParam = firstExpr.parent.name as ts.Identifier; 6922 } 6923 6924 if (!currentParam || !customParam) { 6925 return; 6926 } 6927 6928 const originalExpr = firstExpr.parent.parent; 6929 if (!ts.isObjectLiteralExpression(originalExpr)) { 6930 return; 6931 } 6932 6933 const autofix = this.autofixer?.fixCustomBidirectionalBinding(originalExpr, currentParam, customParam); 6934 this.incrementCounters(firstExpr, FaultID.DoubleExclaBindingNotSupported, autofix); 6935 } 6936 6937 private handleDoubleDollar(node: ts.Node): void { 6938 if (!this.options.arkts2) { 6939 return; 6940 } 6941 6942 if ( 6943 ts.isPropertyAccessExpression(node) && 6944 ts.isIdentifier(node.expression) && 6945 node.expression.escapedText === DOUBLE_DOLLAR_IDENTIFIER + THIS_IDENTIFIER 6946 ) { 6947 const autofix = this.autofixer?.fixDoubleDollar(node, this.interfacesNeedToImport); 6948 this.incrementCounters(node, FaultID.DoubleDollarBindingNotSupported, autofix); 6949 } 6950 } 6951 6952 private handleDollarBind(node: ts.Node): void { 6953 if (!this.options.arkts2) { 6954 return; 6955 } 6956 6957 if (!ts.isPropertyAssignment(node) || !ts.isIdentifier(node.initializer)) { 6958 return; 6959 } 6960 6961 const text = node.initializer.getText(); 6962 if (!(/^\$.+$/).test(text)) { 6963 return; 6964 } 6965 6966 const autofix = this.autofixer?.fixDollarBind(node); 6967 this.incrementCounters(node, FaultID.DollarBindingNotSupported, autofix); 6968 } 6969 6970 private handleExtendDecorator(node: ts.Node): void { 6971 if (!this.options.arkts2) { 6972 return; 6973 } 6974 6975 if (!ts.isFunctionDeclaration(node.parent) || !ts.isDecorator(node)) { 6976 return; 6977 } 6978 6979 if (ts.isCallExpression(node.expression) && ts.isIdentifier(node.expression.expression)) { 6980 if (node.expression.expression.text === CustomDecoratorName.Extend) { 6981 const autofix = this.autofixer?.fixExtendDecorator(node, false, this.interfacesNeedToImport); 6982 this.incrementCounters(node.parent, FaultID.ExtendDecoratorNotSupported, autofix); 6983 } else if (node.expression.expression.text === CustomDecoratorName.AnimatableExtend) { 6984 const autofix = this.autofixer?.fixExtendDecorator(node, true, this.interfacesNeedToImport); 6985 this.incrementCounters(node.parent, FaultID.AnimatableExtendDecoratorTransform, autofix); 6986 } 6987 } 6988 } 6989 6990 private handleEntryDecorator(node: ts.Node): void { 6991 if (!this.options.arkts2) { 6992 return; 6993 } 6994 6995 if (!ts.isDecorator(node)) { 6996 return; 6997 } 6998 6999 if (ts.isCallExpression(node.expression) && ts.isIdentifier(node.expression.expression)) { 7000 if (node.expression.expression.escapedText !== ENTRY_DECORATOR_NAME || node.expression.arguments.length !== 1) { 7001 return; 7002 } 7003 const arg = node.expression.arguments[0]; 7004 if (ts.isObjectLiteralExpression(arg)) { 7005 const properties = arg.properties; 7006 if (properties.length !== 1) { 7007 return; 7008 } 7009 if (!ts.isPropertyAssignment(properties[0])) { 7010 return; 7011 } 7012 const property = properties[0]; 7013 if (ts.isStringLiteral(property.initializer)) { 7014 return; 7015 } 7016 } 7017 const autofix = this.autofixer?.fixEntryDecorator(node); 7018 this.incrementCounters(node, FaultID.EntryAnnotation, autofix); 7019 } 7020 } 7021 7022 private handleStructPropertyDecl(propDecl: ts.PropertyDeclaration): void { 7023 if (!this.options.arkts2) { 7024 return; 7025 } 7026 const isStatic = TsUtils.hasModifier(propDecl.modifiers, ts.SyntaxKind.StaticKeyword); 7027 const hasNoInitializer = !propDecl.initializer; 7028 const isOptional = !!propDecl.questionToken; 7029 7030 const defaultSkipTypeCheck = (typeNode: ts.TypeNode | undefined): boolean => { 7031 if (!typeNode) { 7032 return false; 7033 } 7034 7035 const typeText = typeNode.getText(); 7036 if (ts.isLiteralTypeNode(typeNode) || ['boolean', 'number', 'null', 'undefined'].includes(typeText)) { 7037 return true; 7038 } 7039 7040 if (ts.isUnionTypeNode(typeNode)) { 7041 return typeNode.types.some((t) => { 7042 const tText = t.getText(); 7043 return tText === 'undefined'; 7044 }); 7045 } 7046 7047 return false; 7048 }; 7049 7050 const shouldSkipCheck = isOptional || defaultSkipTypeCheck(propDecl.type); 7051 7052 if (isStatic && hasNoInitializer && !shouldSkipCheck) { 7053 this.incrementCounters(propDecl, FaultID.ClassstaticInitialization); 7054 } 7055 } 7056 7057 private handleTaggedTemplatesExpression(node: ts.Node): void { 7058 if (!this.options.arkts2) { 7059 return; 7060 } 7061 this.incrementCounters(node, FaultID.TaggedTemplates); 7062 } 7063 7064 private checkFunctionTypeCompatible(lhsTypeNode: ts.TypeNode | undefined, rhsExpr: ts.Expression): void { 7065 if (this.options.arkts2 && lhsTypeNode && this.tsUtils.isIncompatibleFunctionals(lhsTypeNode, rhsExpr)) { 7066 this.incrementCounters(rhsExpr, FaultID.IncompationbleFunctionType); 7067 } 7068 } 7069 7070 private handleInvalidIdentifier( 7071 decl: 7072 | ts.TypeAliasDeclaration 7073 | ts.StructDeclaration 7074 | ts.VariableDeclaration 7075 | ts.FunctionDeclaration 7076 | ts.MethodSignature 7077 | ts.ClassDeclaration 7078 | ts.PropertyDeclaration 7079 | ts.MethodDeclaration 7080 | ts.ParameterDeclaration 7081 | ts.PropertySignature 7082 | ts.ImportDeclaration 7083 | ts.EnumDeclaration 7084 | ts.EnumMember 7085 | ts.ModuleDeclaration 7086 | ts.InterfaceDeclaration 7087 ): void { 7088 if (!this.options.arkts2) { 7089 return; 7090 } 7091 7092 const checkIdentifier = (identifier: ts.Identifier | undefined): void => { 7093 const text = identifier && ts.isIdentifier(identifier) ? identifier.text : ''; 7094 if (identifier && text && INVALID_IDENTIFIER_KEYWORDS.includes(text)) { 7095 this.incrementCounters(identifier, FaultID.InvalidIdentifier); 7096 } 7097 }; 7098 7099 if (ts.isImportDeclaration(decl)) { 7100 const importClause = decl.importClause; 7101 if (importClause?.namedBindings && ts.isNamedImports(importClause?.namedBindings)) { 7102 importClause.namedBindings.elements.forEach((importSpecifier) => { 7103 checkIdentifier(importSpecifier.name); 7104 }); 7105 } 7106 checkIdentifier(importClause?.name); 7107 } else if (isStructDeclaration(decl)) { 7108 checkIdentifier((decl as ts.StructDeclaration).name); 7109 } else { 7110 checkIdentifier(decl.name as ts.Identifier); 7111 } 7112 } 7113 7114 private handleHeritageClause(node: ts.HeritageClause): void { 7115 this.checkEWTArgumentsForSdkDuplicateDeclName(node); 7116 if (!this.options.arkts2 || !this.useStatic) { 7117 return; 7118 } 7119 if (node.token === ts.SyntaxKind.ExtendsKeyword || node.token === ts.SyntaxKind.ImplementsKeyword) { 7120 node.types.forEach((type) => { 7121 const expr = type.expression; 7122 if (ts.isCallExpression(expr)) { 7123 this.incrementCounters(expr, FaultID.ExtendsExpression); 7124 return; 7125 } 7126 if ( 7127 ts.isIdentifier(expr) && 7128 this.isVariableReference(expr) && 7129 this.tsUtils.isBuiltinClassHeritageClause(node) 7130 ) { 7131 this.incrementCounters(expr, FaultID.ExtendsExpression); 7132 } else if (ts.isIdentifier(expr)) { 7133 this.fixJsImportExtendsClass(node.parent, expr); 7134 } 7135 }); 7136 7137 this.handleMissingSuperCallInExtendedClass(node); 7138 } 7139 } 7140 7141 private isVariableReference(identifier: ts.Identifier): boolean { 7142 const symbol = this.tsTypeChecker.getSymbolAtLocation(identifier); 7143 return !!symbol && (symbol.flags & ts.SymbolFlags.Variable) !== 0; 7144 } 7145 7146 private checkSendableAndConcurrentDecorator(decorator: ts.Decorator): void { 7147 if (!this.options.arkts2 || !this.useStatic) { 7148 return; 7149 } 7150 const decoratorName = TsUtils.getDecoratorName(decorator); 7151 const autofix = this.autofixer?.removeNode(decorator); 7152 if (decoratorName === SENDABLE_DECORATOR) { 7153 this.incrementCounters(decorator, FaultID.LimitedStdLibNoSendableDecorator, autofix); 7154 } 7155 7156 if (decoratorName === CONCURRENT_DECORATOR) { 7157 this.incrementCounters(decorator, FaultID.LimitedStdLibNoDoncurrentDecorator, autofix); 7158 } 7159 } 7160 7161 private checkAsonSymbol(node: ts.Identifier): void { 7162 if (!this.options.arkts2) { 7163 return; 7164 } 7165 7166 if (node.text !== ASON_TEXT) { 7167 return; 7168 } 7169 7170 const parent = node.parent; 7171 switch (parent.kind) { 7172 case ts.SyntaxKind.QualifiedName: 7173 if (!ts.isQualifiedName(parent)) { 7174 return; 7175 } 7176 if (parent.right.text !== node.text) { 7177 return; 7178 } 7179 if (ts.isQualifiedName(parent.parent) && ASON_WHITE_SET.has(parent.parent.right.text)) { 7180 this.checkAsonUsage(parent.left, true); 7181 } else { 7182 this.checkAsonUsage(parent.left, false); 7183 } 7184 break; 7185 case ts.SyntaxKind.PropertyAccessExpression: 7186 if (!ts.isPropertyAccessExpression(parent)) { 7187 return; 7188 } 7189 if (parent.name.text !== node.text) { 7190 return; 7191 } 7192 if (ts.isPropertyAccessExpression(parent.parent) && ASON_WHITE_SET.has(parent.parent.name.text)) { 7193 this.checkAsonUsage(parent.expression, true); 7194 } else { 7195 this.checkAsonUsage(parent.expression, false); 7196 } 7197 break; 7198 default: 7199 } 7200 } 7201 7202 private checkAsonUsage(nodeToCheck: ts.Node, needAutofix: boolean): void { 7203 if (!ts.isIdentifier(nodeToCheck)) { 7204 return; 7205 } 7206 7207 const declaration = this.tsUtils.getDeclarationNode(nodeToCheck); 7208 if (!declaration && nodeToCheck.text === ARKTS_UTILS_TEXT) { 7209 const autofix = 7210 needAutofix && this.autofixer ? this.autofixer.replaceNode(nodeToCheck.parent, JSON_TEXT) : undefined; 7211 this.incrementCounters(nodeToCheck, FaultID.LimitedStdLibNoASON, autofix); 7212 return; 7213 } 7214 7215 if (!declaration) { 7216 return; 7217 } 7218 7219 const sourceFile = declaration.getSourceFile(); 7220 const fileName = path.basename(sourceFile.fileName); 7221 7222 if ( 7223 ASON_MODULES.some((moduleName) => { 7224 return fileName.startsWith(moduleName); 7225 }) 7226 ) { 7227 const autofix = 7228 needAutofix && this.autofixer ? this.autofixer.replaceNode(nodeToCheck.parent, JSON_TEXT) : undefined; 7229 this.incrementCounters(nodeToCheck, FaultID.LimitedStdLibNoASON, autofix); 7230 } 7231 } 7232 7233 private checkCollectionsSymbol(symbol: ts.Symbol, node: ts.Node): void { 7234 const cb = (): void => { 7235 const parent = node.parent; 7236 if (!parent) { 7237 return; 7238 } 7239 if (ts.isPropertyAccessExpression(parent)) { 7240 const autofix = this.autofixer?.replaceNode(parent, parent.name.text); 7241 this.incrementCounters(node, FaultID.NoNeedStdLibSendableContainer, autofix); 7242 } 7243 7244 if (ts.isQualifiedName(parent)) { 7245 const autofix = this.autofixer?.replaceNode(parent, parent.right.text); 7246 this.incrementCounters(node, FaultID.NoNeedStdLibSendableContainer, autofix); 7247 } 7248 7249 if (ts.isImportSpecifier(parent) && ts.isIdentifier(node)) { 7250 const autofix = this.autofixer?.removeImport(node, parent); 7251 this.incrementCounters(node, FaultID.NoNeedStdLibSendableContainer, autofix); 7252 } 7253 }; 7254 7255 this.checkSymbolAndExecute(symbol, COLLECTIONS_TEXT, COLLECTIONS_MODULES, cb); 7256 } 7257 7258 private checkWorkerSymbol(symbol: ts.Symbol, node: ts.Node): void { 7259 const cb = (): void => { 7260 this.incrementCounters(node, FaultID.NoNeedStdlibWorker); 7261 }; 7262 7263 this.checkSymbolAndExecute(symbol, WORKER_TEXT, WORKER_MODULES, cb); 7264 } 7265 7266 private checkConcurrencySymbol(symbol: ts.Symbol, node: ts.Node): void { 7267 const cb = (): void => { 7268 const parent = node.parent; 7269 if (!ts.isPropertyAccessExpression(parent)) { 7270 return; 7271 } 7272 if (parent.name.text === ARKTSUTILS_LOCKS_MEMBER) { 7273 this.incrementCounters(node, FaultID.LimitedStdLibNoImportConcurrency); 7274 } 7275 }; 7276 7277 this.checkSymbolAndExecute(symbol, ARKTSUTILS_LOCKS_MEMBER, ARKTSUTILS_MODULES, cb); 7278 } 7279 7280 private checkSymbolAndExecute(symbol: ts.Symbol, symbolName: string, modules: string[], cb: () => void): void { 7281 void this; 7282 if (symbol.name === symbolName) { 7283 const decl = TsUtils.getDeclaration(symbol); 7284 7285 if (!decl) { 7286 return; 7287 } 7288 7289 const sourceFile = decl.getSourceFile(); 7290 const fileName = path.basename(sourceFile.fileName); 7291 7292 if ( 7293 modules.some((moduleName) => { 7294 return fileName.startsWith(moduleName); 7295 }) 7296 ) { 7297 cb(); 7298 } 7299 } 7300 } 7301 7302 interfacesNeedToAlarm: ts.Identifier[] = []; 7303 interfacesNeedToImport: Set<string> = new Set<string>(); 7304 interfacesAlreadyImported: Set<string> = new Set<string>(); 7305 7306 private handleInterfaceImport(identifier: ts.Identifier): void { 7307 if (!this.options.arkts2) { 7308 return; 7309 } 7310 7311 if (this.shouldSkipIdentifier(identifier)) { 7312 return; 7313 } 7314 7315 const name = identifier.getText(); 7316 if (!this.interfacesNeedToImport.has(name)) { 7317 this.interfacesNeedToImport.add(name); 7318 } 7319 7320 this.interfacesNeedToAlarm.push(identifier); 7321 } 7322 7323 private shouldSkipIdentifier(identifier: ts.Identifier): boolean { 7324 const name = identifier.getText(); 7325 if (!arkuiImportList.has(name)) { 7326 return true; 7327 } 7328 7329 if (skipImportDecoratorName.has(name)) { 7330 return true; 7331 } 7332 7333 const targetPropertyAccess = TypeScriptLinter.findTargetPropertyAccess(identifier.parent); 7334 if (targetPropertyAccess) { 7335 const expr = targetPropertyAccess.expression; 7336 if (this.isDeclarationInSameFile(expr)) { 7337 return true; 7338 } 7339 } 7340 7341 const parent = identifier.parent; 7342 const wrappedSkipComponents = new Set<string>([CustomDecoratorName.AnimatableExtend, CustomDecoratorName.Extend]); 7343 if (ts.isCallExpression(parent)) { 7344 const expr = parent.expression; 7345 if (wrappedSkipComponents.has(expr.getText()) && name !== CustomDecoratorName.AnimatableExtend) { 7346 return true; 7347 } 7348 } 7349 7350 if (this.isDeclarationInSameFile(identifier)) { 7351 return true; 7352 } 7353 7354 return this.interfacesAlreadyImported.has(name); 7355 } 7356 7357 private static findTargetPropertyAccess(node: ts.Node): ts.PropertyAccessExpression | undefined { 7358 while (ts.isPropertyAccessExpression(node)) { 7359 const expr = node.expression; 7360 if (!ts.isPropertyAccessExpression(expr)) { 7361 return node; 7362 } 7363 node = expr; 7364 } 7365 return undefined; 7366 } 7367 7368 private isDeclarationInSameFile(node: ts.Node): boolean { 7369 const symbol = this.tsTypeChecker.getSymbolAtLocation(node); 7370 const decl = TsUtils.getDeclaration(symbol); 7371 if (decl?.getSourceFile() === node.getSourceFile()) { 7372 return true; 7373 } 7374 7375 return false; 7376 } 7377 7378 private processInterfacesToImport(sourceFile: ts.SourceFile): void { 7379 if (!this.options.arkts2) { 7380 return; 7381 } 7382 7383 const autofix = this.autofixer?.fixInterfaceImport( 7384 this.interfacesNeedToImport, 7385 this.interfacesAlreadyImported, 7386 sourceFile 7387 ); 7388 7389 this.interfacesNeedToAlarm.forEach((identifier) => { 7390 const name = identifier.getText(); 7391 const errorMsg = `The ArkUI interface "${name}" should be imported before it is used (arkui-modular-interface)`; 7392 this.incrementCounters(identifier, FaultID.UIInterfaceImport, autofix, errorMsg); 7393 }); 7394 7395 this.interfacesNeedToAlarm = []; 7396 this.interfacesNeedToImport.clear(); 7397 this.interfacesAlreadyImported.clear(); 7398 } 7399 7400 private extractImportedNames(sourceFile: ts.SourceFile): void { 7401 if (!this.options.arkts2) { 7402 return; 7403 } 7404 for (const statement of sourceFile.statements) { 7405 if (!ts.isImportDeclaration(statement)) { 7406 continue; 7407 } 7408 7409 const importClause = statement.importClause; 7410 if (!importClause) { 7411 continue; 7412 } 7413 7414 const namedBindings = importClause.namedBindings; 7415 if (!namedBindings || !ts.isNamedImports(namedBindings)) { 7416 continue; 7417 } 7418 7419 for (const specifier of namedBindings.elements) { 7420 const importedName = specifier.name.getText(sourceFile); 7421 this.interfacesAlreadyImported.add(importedName); 7422 } 7423 } 7424 } 7425 7426 private handleStylesDecorator(node: ts.Decorator): void { 7427 if (!this.options.arkts2) { 7428 return; 7429 } 7430 7431 if (!ts.isFunctionDeclaration(node.parent) && !ts.isMethodDeclaration(node.parent)) { 7432 return; 7433 } 7434 7435 if (!ts.isIdentifier(node.expression) || node.expression.text !== CustomDecoratorName.Styles) { 7436 return; 7437 } 7438 7439 const decl = node.parent; 7440 const declName = decl.name?.getText(); 7441 if (ts.isFunctionDeclaration(decl)) { 7442 const functionCalls = TypeScriptLinter.findDeclarationCalls(this.sourceFile, declName as string); 7443 const autofix = this.autofixer?.fixStylesDecoratorGlobal(decl, functionCalls, this.interfacesNeedToImport); 7444 this.incrementCounters(decl, FaultID.StylesDecoratorNotSupported, autofix); 7445 } 7446 7447 if (ts.isMethodDeclaration(decl)) { 7448 const methodCalls = TypeScriptLinter.findDeclarationCalls(this.sourceFile, declName as string); 7449 const autofix = this.autofixer?.fixStylesDecoratorStruct(decl, methodCalls, this.interfacesNeedToImport); 7450 this.incrementCounters(decl, FaultID.StylesDecoratorNotSupported, autofix); 7451 } 7452 } 7453 7454 private handleStateStyles(node: ts.CallExpression | ts.PropertyAccessExpression): void { 7455 if (!this.options.arkts2) { 7456 return; 7457 } 7458 7459 let args: ts.Expression[] = []; 7460 let startNode: ts.Node | undefined; 7461 if (ts.isCallExpression(node)) { 7462 if (node.expression.getText() !== STATE_STYLES) { 7463 return; 7464 } 7465 startNode = node.expression; 7466 args = Array.from(node.arguments); 7467 } 7468 7469 if (ts.isPropertyAccessExpression(node)) { 7470 if (node.name.getText() !== STATE_STYLES) { 7471 return; 7472 } 7473 if (!ts.isCallExpression(node.parent)) { 7474 return; 7475 } 7476 startNode = node.name; 7477 args = Array.from(node.parent.arguments); 7478 } 7479 7480 if (args.length === 0 || !startNode) { 7481 return; 7482 } 7483 7484 const object = args[0]; 7485 if (!object || !ts.isObjectLiteralExpression(object)) { 7486 return; 7487 } 7488 7489 const properties = object.properties; 7490 if (properties.length === 0) { 7491 return; 7492 } 7493 7494 if (!TypeScriptLinter.hasAnonBlock(properties)) { 7495 return; 7496 } 7497 7498 const autofix = this.autofixer?.fixStateStyles(object, startNode, this.interfacesNeedToImport); 7499 this.incrementCounters(object, FaultID.StateStylesBlockNeedArrowFunc, autofix); 7500 } 7501 7502 private static hasAnonBlock(properties: ts.NodeArray<ts.ObjectLiteralElementLike>): boolean { 7503 let anonBlockCount = 0; 7504 7505 properties.forEach((property) => { 7506 if (ts.isPropertyAssignment(property) && ts.isObjectLiteralExpression(property.initializer)) { 7507 anonBlockCount++; 7508 } 7509 }); 7510 7511 return anonBlockCount !== 0; 7512 } 7513 7514 private handleStringLiteral(node: ts.StringLiteral): void { 7515 if (!this.options.arkts2) { 7516 return; 7517 } 7518 7519 this.checkForConcurrentExpressions(node); 7520 } 7521 7522 private checkForConcurrentExpressions(stringLiteral: ts.StringLiteral): void { 7523 if (!stringLiteral.parent) { 7524 return; 7525 } 7526 7527 if (!ts.isExpressionStatement(stringLiteral.parent)) { 7528 return; 7529 } 7530 7531 const text = stringLiteral.text; 7532 const autofix = this.autofixer?.removeNode(stringLiteral); 7533 7534 if (text === USE_CONCURRENT) { 7535 this.incrementCounters(stringLiteral, FaultID.UseConcurrentDeprecated, autofix); 7536 } 7537 7538 if (text === USE_SHARED) { 7539 this.incrementCounters(stringLiteral, FaultID.UseSharedDeprecated, autofix); 7540 } 7541 } 7542 7543 private static findDeclarationCalls(sourceFile: ts.SourceFile, declName: string): ts.Identifier[] { 7544 const functionCalls: ts.Identifier[] = []; 7545 7546 function traverse(node: ts.Node): void { 7547 const identifier = getIdentifierFromNode(node); 7548 if (identifier && identifier.getText() === declName) { 7549 functionCalls.push(identifier); 7550 } 7551 7552 ts.forEachChild(node, traverse); 7553 } 7554 7555 function getIdentifierFromNode(node: ts.Node): ts.Identifier | undefined { 7556 if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) { 7557 return node.expression; 7558 } 7559 if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) { 7560 if (node.expression.getText() === THIS_IDENTIFIER) { 7561 return undefined; 7562 } 7563 return node.name; 7564 } 7565 return undefined; 7566 } 7567 7568 traverse(sourceFile); 7569 return functionCalls; 7570 } 7571 7572 addObservedDecorator: Set<ts.ClassDeclaration> = new Set<ts.ClassDeclaration>(); 7573 7574 private handleDataObservation(node: ts.PropertyDeclaration): void { 7575 if (!this.options.arkts2) { 7576 return; 7577 } 7578 7579 const decorators = ts.getDecorators(node); 7580 if (!decorators || decorators.length === 0) { 7581 return; 7582 } 7583 const decorator = decorators[0]; 7584 let decoratorName = ''; 7585 if (ts.isIdentifier(decorator.expression)) { 7586 decoratorName = decorator.expression.getText(); 7587 } else if (ts.isCallExpression(decorator.expression)) { 7588 decoratorName = decorator.expression.expression.getText(); 7589 } 7590 if (!observedDecoratorName.has(decoratorName)) { 7591 return; 7592 } 7593 7594 let firstClassDecls: ts.ClassDeclaration[] | undefined; 7595 const expr = node.initializer; 7596 if (expr && ts.isNewExpression(expr)) { 7597 firstClassDecls = this.addFromNewExpression(expr); 7598 } 7599 7600 let secondClassDecls: ts.ClassDeclaration[] | undefined; 7601 const type = node.type; 7602 if (type) { 7603 secondClassDecls = this.addFromTypeNode(type); 7604 } 7605 7606 const classDecls = (firstClassDecls || []).concat(secondClassDecls || []); 7607 if (classDecls.length === 0) { 7608 return; 7609 } 7610 7611 const filteredClassDecls = classDecls.filter((classDecl) => { 7612 if (this.addObservedDecorator.has(classDecl)) { 7613 return false; 7614 } 7615 this.addObservedDecorator.add(classDecl); 7616 return true; 7617 }); 7618 if (filteredClassDecls.length !== 0) { 7619 this.interfacesNeedToImport.add(CustomDecoratorName.Observed); 7620 } 7621 const autofix = this.autofixer?.fixDataObservation(filteredClassDecls); 7622 this.incrementCounters(node, FaultID.DataObservation, autofix); 7623 } 7624 7625 private addFromNewExpression(expr: ts.NewExpression): ts.ClassDeclaration[] | undefined { 7626 const identifier = expr.expression; 7627 if (!ts.isIdentifier(identifier)) { 7628 return undefined; 7629 } 7630 7631 const decl: ts.ClassDeclaration | undefined = this.getClassDeclaration(identifier); 7632 if (!decl) { 7633 return undefined; 7634 } 7635 7636 const classDecls: ts.ClassDeclaration[] = this.getClassHierarchy(decl); 7637 const filteredClassDecls = classDecls.filter((classDecl) => { 7638 if (TypeScriptLinter.hasObservedDecorator(classDecl)) { 7639 return false; 7640 } 7641 return true; 7642 }); 7643 return filteredClassDecls; 7644 } 7645 7646 private addFromTypeNode(type: ts.TypeNode): ts.ClassDeclaration[] | undefined { 7647 const targets: ts.Node[] = []; 7648 if (ts.isUnionTypeNode(type)) { 7649 const types = type.types; 7650 types.forEach((typeNode) => { 7651 if (ts.isTypeReferenceNode(typeNode)) { 7652 targets.push(typeNode.typeName); 7653 } 7654 }); 7655 } else if (ts.isTypeReferenceNode(type)) { 7656 targets.push(type.typeName); 7657 } 7658 7659 const classDecls: ts.ClassDeclaration[] = []; 7660 targets.forEach((target) => { 7661 const decl: ts.ClassDeclaration | undefined = this.getClassDeclaration(target); 7662 if (!decl) { 7663 return; 7664 } 7665 7666 const decls: ts.ClassDeclaration[] = this.getClassHierarchy(decl); 7667 classDecls.push(...decls); 7668 }); 7669 const filteredClassDecls = classDecls.filter((classDecl) => { 7670 if (TypeScriptLinter.hasObservedDecorator(classDecl)) { 7671 return false; 7672 } 7673 return true; 7674 }); 7675 return filteredClassDecls; 7676 } 7677 7678 private static hasObservedDecorator(classDecl: ts.ClassDeclaration): boolean { 7679 return ( 7680 ts.getDecorators(classDecl)?.some((decorator) => { 7681 return decorator.getText() === '@' + CustomDecoratorName.Observed; 7682 }) ?? false 7683 ); 7684 } 7685 7686 private getClassDeclaration(node: ts.Node): ts.ClassDeclaration | undefined { 7687 const symbol = this.tsTypeChecker.getSymbolAtLocation(node); 7688 let decl: ts.Declaration | undefined; 7689 if (symbol) { 7690 decl = this.tsUtils.getDeclarationNode(node); 7691 if (decl?.getSourceFile() !== node.getSourceFile()) { 7692 return undefined; 7693 } 7694 } 7695 7696 if (!decl || !ts.isClassDeclaration(decl)) { 7697 return undefined; 7698 } 7699 7700 return decl; 7701 } 7702 7703 private getClassHierarchy(classDecl: ts.ClassDeclaration): ts.ClassDeclaration[] { 7704 const hierarchy: ts.ClassDeclaration[] = []; 7705 let currentClass: ts.ClassDeclaration | undefined = classDecl; 7706 7707 while (currentClass) { 7708 hierarchy.push(currentClass); 7709 const heritageClause = currentClass.heritageClauses?.find((clause) => { 7710 return clause.token === ts.SyntaxKind.ExtendsKeyword; 7711 }); 7712 const identifier = heritageClause?.types[0]?.expression as ts.Identifier | undefined; 7713 if (!identifier) { 7714 break; 7715 } 7716 currentClass = this.getClassDeclaration(identifier); 7717 } 7718 7719 return hierarchy; 7720 } 7721 7722 private checkArkTSObjectInterop(tsCallExpr: ts.CallExpression): void { 7723 const callSignature = this.tsTypeChecker.getResolvedSignature(tsCallExpr); 7724 if (!callSignature?.declaration) { 7725 return; 7726 } 7727 7728 if (!this.isDeclaredInArkTs2(callSignature)) { 7729 return; 7730 } 7731 7732 if (!this.hasObjectParameter(callSignature, tsCallExpr)) { 7733 return; 7734 } 7735 7736 const functionSymbol = this.getFunctionSymbol(callSignature.declaration); 7737 const functionDeclaration = functionSymbol?.valueDeclaration; 7738 if (!functionDeclaration) { 7739 return; 7740 } 7741 7742 if ( 7743 TypeScriptLinter.isFunctionLike(functionDeclaration) && 7744 TypeScriptLinter.containsForbiddenAPI(functionDeclaration) 7745 ) { 7746 this.incrementCounters(tsCallExpr.parent, FaultID.InteropCallReflect); 7747 } 7748 } 7749 7750 private hasObjectParameter(callSignature: ts.Signature, tsCallExpr: ts.CallExpression): boolean { 7751 for (const [index, param] of callSignature.parameters.entries()) { 7752 const paramType = this.tsTypeChecker.getTypeOfSymbolAtLocation(param, tsCallExpr); 7753 7754 if (!this.tsUtils.isObject(paramType)) { 7755 continue; 7756 } 7757 7758 const argument = tsCallExpr.arguments[index]; 7759 if (!argument) { 7760 continue; 7761 } 7762 7763 if (this.tsTypeChecker.getTypeAtLocation(argument).isClass()) { 7764 return true; 7765 } 7766 } 7767 7768 return false; 7769 } 7770 7771 private static containsForbiddenAPI( 7772 node: ts.FunctionDeclaration | ts.MethodDeclaration | ts.FunctionExpression 7773 ): ForbidenAPICheckResult { 7774 if (!node.body) { 7775 return NONE; 7776 } 7777 return TypeScriptLinter.isForbiddenUsed(node.body); 7778 } 7779 7780 private static isForbiddenUsed(currentNode: ts.Node): ForbidenAPICheckResult { 7781 if (!ts.isCallExpression(currentNode)) { 7782 let found: ForbidenAPICheckResult = NONE; 7783 ts.forEachChild(currentNode, (child) => { 7784 if (found === NONE) { 7785 found = TypeScriptLinter.isForbiddenUsed(child); 7786 } 7787 }); 7788 7789 return found; 7790 } 7791 7792 const expr = currentNode.expression; 7793 if (!ts.isPropertyAccessExpression(expr)) { 7794 return NONE; 7795 } 7796 7797 const obj = expr.expression; 7798 const method = expr.name; 7799 if (!ts.isIdentifier(obj)) { 7800 return NONE; 7801 } 7802 7803 if (obj.text === REFLECT_LITERAL) { 7804 if (REFLECT_PROPERTIES.includes(method.text)) { 7805 return REFLECT_LITERAL; 7806 } 7807 } 7808 7809 if (obj.text === OBJECT_LITERAL) { 7810 if (OBJECT_PROPERTIES.includes(method.text)) { 7811 return OBJECT_LITERAL; 7812 } 7813 } 7814 return NONE; 7815 } 7816 7817 private getFunctionSymbol(declaration: ts.Declaration): ts.Symbol | undefined { 7818 if (TypeScriptLinter.isFunctionLike(declaration)) { 7819 return declaration.name ? this.tsTypeChecker.getSymbolAtLocation(declaration.name) : undefined; 7820 } 7821 return undefined; 7822 } 7823 7824 private static isFunctionLike( 7825 node: ts.Node 7826 ): node is ts.FunctionDeclaration | ts.MethodDeclaration | ts.FunctionExpression { 7827 return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isFunctionExpression(node); 7828 } 7829 7830 private static isThirdPartyBySymbol(symbol: ts.Symbol | undefined, apiList: ApiListItem): boolean { 7831 if (!symbol) { 7832 return false; 7833 } 7834 const declaration = symbol.getDeclarations()?.[0]; 7835 if (declaration && ts.isImportClause(declaration)) { 7836 const importDecl = declaration.parent; 7837 const importPath = TsUtils.removeOrReplaceQuotes(importDecl.moduleSpecifier.getText(), false); 7838 const import_path = TypeScriptLinter.getLocalApiListItemByKey(SdkNameInfo.ImportPath, apiList); 7839 if (import_path.includes(importPath)) { 7840 return true; 7841 } 7842 } 7843 return false; 7844 } 7845 7846 private static getLocalApiListItemByKey(key: string, apiList: ApiListItem): string | string[] { 7847 if (!apiList) { 7848 return ''; 7849 } 7850 if (SdkNameInfo.ImportPath === key) { 7851 return apiList.import_path; 7852 } 7853 return ''; 7854 } 7855 7856 private handleSdkForConstructorFuncs(node: ts.PropertyAccessExpression | ts.QualifiedName): void { 7857 if (!this.options.arkts2) { 7858 return; 7859 } 7860 const rightNode = ts.isPropertyAccessExpression(node) ? node.name : node.right; 7861 const leftNode = ts.isPropertyAccessExpression(node) ? node.expression : node.left; 7862 const constructorFuncsInfos = Array.from(TypeScriptLinter.constructorFuncsSet); 7863 constructorFuncsInfos.some((constructorFuncsInfo) => { 7864 const api_name = constructorFuncsInfo.api_info.api_name; 7865 if (api_name !== rightNode.getText()) { 7866 return; 7867 } 7868 const parentSym = this.tsTypeChecker.getSymbolAtLocation(leftNode); 7869 if (TypeScriptLinter.isThirdPartyBySymbol(parentSym, constructorFuncsInfo)) { 7870 this.incrementCounters(rightNode, FaultID.ConstructorTypesDeprecated); 7871 } 7872 }); 7873 } 7874 7875 private handleQuotedHyphenPropsDeprecated(node: ts.PropertyAccessExpression | ts.PropertyAssignment): void { 7876 if (!this.options.arkts2 || !node) { 7877 return; 7878 } 7879 const literalAsPropertyNameInfos = Array.from(TypeScriptLinter.literalAsPropertyNameTypeSet); 7880 literalAsPropertyNameInfos.some((literalAsPropertyNameInfo) => { 7881 this.localApiListItem = literalAsPropertyNameInfo; 7882 const api_name = literalAsPropertyNameInfo.api_info.api_name; 7883 if (api_name !== (ts.isPropertyAccessExpression(node) ? node.name.text : node.name.getText())) { 7884 return false; 7885 } 7886 const parentSym = this.getFinalSymOnQuotedHyphenPropsDeprecated( 7887 ts.isPropertyAccessExpression(node) ? node.expression : node 7888 ); 7889 if (parentSym && this.shouldWarn(parentSym)) { 7890 this.incrementCounters(node, FaultID.QuotedHyphenPropsDeprecated); 7891 return true; 7892 } 7893 return false; 7894 }); 7895 } 7896 7897 private shouldWarn(symbol: ts.Symbol): boolean { 7898 const parentApiName = this.getLocalApiListItemByKey(SdkNameInfo.ParentApiName); 7899 return symbol && this.isHeritageClauseisThirdPartyBySymbol(symbol) || symbol.name === parentApiName; 7900 } 7901 7902 private getFinalSymOnQuotedHyphenPropsDeprecated(node: ts.Node): ts.Symbol | undefined { 7903 let currentNode = node; 7904 while (currentNode) { 7905 const symbol = this.checkNodeTypeOnQuotedHyphenPropsDeprecated(currentNode); 7906 if (symbol) { 7907 return symbol; 7908 } 7909 currentNode = currentNode.parent; 7910 } 7911 return undefined; 7912 } 7913 7914 private checkNodeTypeOnQuotedHyphenPropsDeprecated(node: ts.Node): ts.Symbol | undefined { 7915 if (ts.isVariableDeclaration(node)) { 7916 return this.getTypeOfVariable(node); 7917 } 7918 7919 if (ts.isPropertySignature(node)) { 7920 return this.tsTypeChecker.getSymbolAtLocation(node); 7921 } 7922 7923 const nodesWithResolvableType = [ 7924 ts.isFunctionDeclaration(node) && node.type, 7925 ts.isMethodDeclaration(node) && node.type, 7926 ts.isTypeReferenceNode(node) && node, 7927 ts.isParameter(node) && node.type 7928 ].filter(Boolean); 7929 7930 for (const typeNode of nodesWithResolvableType) { 7931 return typeNode ? this.resolveTypeNodeSymbol(typeNode) : undefined; 7932 } 7933 7934 if (ts.isIdentifier(node)) { 7935 const symbol = this.tsTypeChecker.getSymbolAtLocation(node); 7936 const declaration = symbol?.getDeclarations()?.[0]; 7937 if (declaration) { 7938 return this.getFinalSymOnQuotedHyphenPropsDeprecated(declaration); 7939 } 7940 } 7941 7942 return undefined; 7943 } 7944 7945 private getTypeOfVariable(variable: ts.VariableDeclaration): ts.Symbol | undefined { 7946 if (variable.type) { 7947 return ts.isArrayTypeNode(variable.type) ? 7948 this.resolveTypeNodeSymbol(variable.type.elementType) : 7949 this.resolveTypeNodeSymbol(variable.type); 7950 } 7951 return variable.initializer ? this.tsTypeChecker.getTypeAtLocation(variable.initializer).getSymbol() : undefined; 7952 } 7953 7954 private resolveTypeNodeSymbol(typeNode: ts.TypeNode): ts.Symbol | undefined { 7955 if (!ts.isTypeReferenceNode(typeNode)) { 7956 return undefined; 7957 } 7958 return this.resolveTypeNoSymbol(typeNode); 7959 } 7960 7961 private resolveTypeNoSymbol(typeNode: ts.TypeReferenceNode): ts.Symbol | undefined { 7962 if (!typeNode.typeName) { 7963 return undefined; 7964 } 7965 7966 if (ts.isQualifiedName(typeNode.typeName)) { 7967 return this.tsTypeChecker.getSymbolAtLocation(typeNode.typeName.right); 7968 } 7969 7970 const symbol = this.tsUtils.trueSymbolAtLocation(typeNode.typeName); 7971 if (symbol?.declarations && symbol.declarations.length > 0) { 7972 const globalDeclaration = symbol.declarations[0]; 7973 if (ts.isTypeAliasDeclaration(globalDeclaration)) { 7974 return this.resolveTypeNodeSymbol(globalDeclaration.type); 7975 } else if (ts.isInterfaceDeclaration(globalDeclaration)) { 7976 return this.processQuotedHyphenPropsDeprecatedOnInterfaceDeclaration(globalDeclaration); 7977 } 7978 } 7979 return this.tsTypeChecker.getTypeAtLocation(typeNode).getSymbol(); 7980 } 7981 7982 private isHeritageClauseisThirdPartyBySymbol(symbol: ts.Symbol): boolean { 7983 const declarations = symbol.getDeclarations(); 7984 if (declarations && declarations.length > 0) { 7985 const firstDeclaration = declarations[0]; 7986 if (ts.isImportSpecifier(firstDeclaration)) { 7987 const importDecl = firstDeclaration.parent.parent.parent; 7988 const importPath = importDecl.moduleSpecifier.getText(); 7989 const import_path = this.getLocalApiListItemByKey(SdkNameInfo.ImportPath); 7990 if (import_path && JSON.stringify(import_path).includes(importPath)) { 7991 return true; 7992 } 7993 } 7994 } 7995 return false; 7996 } 7997 7998 private getLocalApiListItemByKey(key: string): string | string[] { 7999 if (!this.localApiListItem) { 8000 return ''; 8001 } 8002 if (SdkNameInfo.ParentApiName === key) { 8003 return this.localApiListItem.api_info.parent_api[0].api_name; 8004 } else if (SdkNameInfo.ImportPath === key) { 8005 return this.localApiListItem.import_path; 8006 } 8007 return ''; 8008 } 8009 8010 private processQuotedHyphenPropsDeprecatedOnInterfaceDeclaration( 8011 node: ts.InterfaceDeclaration 8012 ): ts.Symbol | undefined { 8013 const heritageSymbol = this.processHeritageClauses(node); 8014 if (heritageSymbol) { 8015 return heritageSymbol; 8016 } 8017 return this.processMembers(node); 8018 } 8019 8020 private processHeritageClauses(node: ts.InterfaceDeclaration): ts.Symbol | undefined { 8021 if (!node.heritageClauses) { 8022 return undefined; 8023 } 8024 for (const heritageClause of node.heritageClauses) { 8025 return this.processHeritageClause(heritageClause); 8026 } 8027 8028 return undefined; 8029 } 8030 8031 private processHeritageClause(heritageClause: ts.HeritageClause): ts.Symbol | undefined { 8032 for (const type of heritageClause.types) { 8033 if (!type.expression) { 8034 return undefined; 8035 } 8036 if (ts.isPropertyAccessExpression(type.expression)) { 8037 return this.processPropertyAccessExpression(type.expression); 8038 } 8039 } 8040 return undefined; 8041 } 8042 8043 private processPropertyAccessExpression(expression: ts.PropertyAccessExpression): ts.Symbol | undefined { 8044 const heritageSymbol = this.tsTypeChecker.getSymbolAtLocation(expression.expression); 8045 if (heritageSymbol && expression.name.text === this.getLocalApiListItemByKey(SdkNameInfo.ParentApiName)) { 8046 return heritageSymbol; 8047 } 8048 return undefined; 8049 } 8050 8051 private processMembers(node: ts.InterfaceDeclaration): ts.Symbol | undefined { 8052 if (!node.members) { 8053 return undefined; 8054 } 8055 for (const member of node.members) { 8056 if (ts.isPropertySignature(member) && member.type) { 8057 return this.resolveTypeNodeSymbol(member.type); 8058 } 8059 } 8060 return undefined; 8061 } 8062 8063 private processApiNodeSdkGlobalApi(apiName: string, errorNode: ts.Node): void { 8064 for (const [key, value] of globalApiAssociatedInfo) { 8065 this.doProcessApiNodeSdkGlobalApi(apiName, errorNode, key, value); 8066 } 8067 } 8068 8069 private doProcessApiNodeSdkGlobalApi(apiName: string, errorNode: ts.Node, key: string, faultId: number): void { 8070 const setApiListItem = TypeScriptLinter.globalApiInfo.get(key); 8071 if (!setApiListItem) { 8072 return; 8073 } 8074 const apiNamesArr = [...setApiListItem]; 8075 const hasSameApiName = apiNamesArr.some((apilistItem) => { 8076 return apilistItem.api_info.api_name === errorNode.getText(); 8077 }); 8078 if (!hasSameApiName) { 8079 return; 8080 } 8081 if (ts.isTypeReferenceNode(errorNode)) { 8082 errorNode = errorNode.typeName; 8083 } 8084 const matchedApi = apiNamesArr.some((sdkInfo) => { 8085 const isSameName = sdkInfo.api_info.api_name === apiName; 8086 const isGlobal = sdkInfo.is_global; 8087 return isSameName && isGlobal; 8088 }); 8089 const checkSymbol = this.isIdentifierFromSDK(errorNode); 8090 const type = this.tsTypeChecker.getTypeAtLocation(errorNode); 8091 const typeName = this.tsTypeChecker.typeToString(type); 8092 8093 if (checkSymbol) { 8094 if (arkTsBuiltInTypeName.has(typeName)) { 8095 return; 8096 } 8097 if (matchedApi) { 8098 this.incrementCounters(errorNode, faultId); 8099 } 8100 } 8101 } 8102 8103 private isIdentifierFromSDK(node: ts.Node): boolean { 8104 const symbol = this.tsTypeChecker.getSymbolAtLocation(node); 8105 if (!symbol) { 8106 return true; 8107 } 8108 8109 // Check if the symbol is from an SDK import 8110 const declarations = symbol.getDeclarations(); 8111 if (!declarations || declarations.length === 0) { 8112 return true; 8113 } 8114 8115 let isLocal = false; 8116 for (const declaration of declarations) { 8117 if (ts.isVariableDeclaration(declaration) || 8118 ts.isTypeAliasDeclaration(declaration) || 8119 ts.isClassDeclaration(declaration) || 8120 ts.isInterfaceDeclaration(declaration) || 8121 ts.isFunctionDeclaration(declaration) || 8122 ts.isEnumDeclaration(declaration)) { 8123 isLocal = true; 8124 break 8125 } 8126 } 8127 8128 if(isLocal) { 8129 return false; 8130 } 8131 8132 return true; 8133 } 8134 8135 private handleSdkGlobalApi( 8136 node: 8137 | ts.TypeReferenceNode 8138 | ts.NewExpression 8139 | ts.VariableDeclaration 8140 | ts.PropertyDeclaration 8141 | ts.ParameterDeclaration 8142 | ts.CallExpression 8143 | ts.BinaryExpression 8144 | ts.ExpressionWithTypeArguments 8145 | ts.Identifier 8146 | ts.MethodDeclaration 8147 ): void { 8148 if (!this.options.arkts2) { 8149 return; 8150 } 8151 switch (node.kind) { 8152 case ts.SyntaxKind.TypeReference: 8153 this.checkTypeReferenceForSdkGlobalApi(node); 8154 break; 8155 case ts.SyntaxKind.NewExpression: 8156 this.checkNewExpressionForSdkGlobalApi(node); 8157 break; 8158 case ts.SyntaxKind.Identifier: 8159 this.checkHeritageClauseForSdkGlobalApi(node); 8160 break; 8161 case ts.SyntaxKind.VariableDeclaration: 8162 case ts.SyntaxKind.PropertyDeclaration: 8163 case ts.SyntaxKind.Parameter: 8164 this.checkDeclarationForSdkGlobalApi(node); 8165 break; 8166 case ts.SyntaxKind.CallExpression: 8167 this.checkCallExpressionForSdkGlobalApi(node); 8168 break; 8169 case ts.SyntaxKind.BinaryExpression: 8170 this.checkBinaryExpressionForSdkGlobalApi(node); 8171 break; 8172 case ts.SyntaxKind.MethodDeclaration: 8173 this.checkMethodDeclarationForSdkGlobalApi(node); 8174 break; 8175 default: 8176 } 8177 } 8178 8179 private checkTypeReferenceForSdkGlobalApi(node: ts.TypeReferenceNode): void { 8180 const typeName = node.typeName; 8181 if (ts.isIdentifier(typeName)) { 8182 this.processApiNodeSdkGlobalApi(typeName.text, node); 8183 } 8184 } 8185 8186 private checkNewExpressionForSdkGlobalApi(node: ts.NewExpression): void { 8187 const expression = node.expression; 8188 if (ts.isIdentifier(expression)) { 8189 this.processApiNodeSdkGlobalApi(expression.text, expression); 8190 } 8191 } 8192 8193 private checkHeritageClauseForSdkGlobalApi(node: ts.Identifier): void { 8194 if (ts.isIdentifier(node)) { 8195 this.processApiNodeSdkGlobalApi(node.text, node); 8196 } 8197 } 8198 8199 private checkDeclarationForSdkGlobalApi( 8200 node: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration 8201 ): void { 8202 const expression = node.initializer; 8203 if (expression && ts.isIdentifier(expression)) { 8204 this.processApiNodeSdkGlobalApi(expression.text, expression); 8205 } 8206 } 8207 8208 private checkCallExpressionForSdkGlobalApi(node: ts.CallExpression): void { 8209 if (ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) { 8210 const expression = node.expression.expression; 8211 8212 this.processApiNodeSdkGlobalApi(expression.text, expression); 8213 } 8214 } 8215 8216 private checkBinaryExpressionForSdkGlobalApi(node: ts.BinaryExpression): void { 8217 const expression = node.right; 8218 if (ts.isIdentifier(expression)) { 8219 this.processApiNodeSdkGlobalApi(expression.text, expression); 8220 } 8221 } 8222 8223 private checkMethodDeclarationForSdkGlobalApi(node: ts.MethodDeclaration): void { 8224 const expression = node.name; 8225 if (ts.isIdentifier(expression)) { 8226 this.processApiNodeSdkGlobalApi(expression.text, expression); 8227 } 8228 } 8229 8230 private checkEWTArgumentsForSdkDuplicateDeclName(node: ts.HeritageClause): void { 8231 if (!this.options.arkts2) { 8232 return; 8233 } 8234 if (node.token === ts.SyntaxKind.ExtendsKeyword || node.token === ts.SyntaxKind.ImplementsKeyword) { 8235 node.types.forEach((type) => { 8236 this.handleSharedArrayBuffer(type); 8237 const expr = type.expression; 8238 if (ts.isIdentifier(expr)) { 8239 this.processApiNodeSdkGlobalApi(expr.text, expr); 8240 } 8241 }); 8242 } 8243 } 8244 8245 private getOriginalSymbol(node: ts.Node): ts.Symbol | undefined { 8246 if (ts.isIdentifier(node)) { 8247 const variableDeclaration = this.findVariableDeclaration(node); 8248 if (variableDeclaration?.initializer) { 8249 return this.getOriginalSymbol(variableDeclaration.initializer); 8250 } 8251 } else if (ts.isNewExpression(node)) { 8252 const constructor = node.expression; 8253 if (ts.isIdentifier(constructor)) { 8254 return this.tsUtils.trueSymbolAtLocation(constructor); 8255 } 8256 } else if (ts.isCallExpression(node)) { 8257 const callee = node.expression; 8258 if (ts.isIdentifier(callee)) { 8259 return this.tsUtils.trueSymbolAtLocation(callee); 8260 } else if (ts.isPropertyAccessExpression(callee)) { 8261 return this.getOriginalSymbol(callee.expression); 8262 } 8263 } else if (ts.isPropertyAccessExpression(node)) { 8264 return this.getOriginalSymbol(node.expression); 8265 } 8266 return this.tsUtils.trueSymbolAtLocation(node); 8267 } 8268 8269 private static isFromJsImport(symbol: ts.Symbol): boolean { 8270 const declaration = symbol.declarations?.[0]; 8271 if (declaration) { 8272 const sourceFile = declaration.getSourceFile(); 8273 return sourceFile.fileName.endsWith(EXTNAME_JS); 8274 } 8275 return false; 8276 } 8277 8278 private hasLocalAssignment(node: ts.Node): boolean { 8279 if (ts.isIdentifier(node)) { 8280 const variableDeclaration = this.findVariableDeclaration(node); 8281 return !!variableDeclaration?.initializer; 8282 } 8283 return false; 8284 } 8285 8286 private isLocalCall(node: ts.Node): boolean { 8287 if (ts.isCallExpression(node)) { 8288 const callee = node.expression; 8289 if (ts.isIdentifier(callee)) { 8290 return this.hasLocalAssignment(callee); 8291 } else if (ts.isPropertyAccessExpression(callee)) { 8292 const objectNode = callee.expression; 8293 return this.hasLocalAssignment(objectNode); 8294 } 8295 } 8296 return false; 8297 } 8298 8299 private handleInterOpImportJsOnTypeOfNode(typeofExpress: ts.TypeOfExpression): void { 8300 if (!this.options.arkts2 || !typeofExpress || !this.useStatic) { 8301 return; 8302 } 8303 const targetNode = typeofExpress.expression; 8304 if (this.hasLocalAssignment(targetNode) || this.isLocalCall(targetNode)) { 8305 return; 8306 } 8307 const targetSymbol = this.getOriginalSymbol(targetNode); 8308 if (targetSymbol && TypeScriptLinter.isFromJsImport(targetSymbol)) { 8309 const autofix = this.autofixer?.fixInterOpImportJsOnTypeOf(typeofExpress); 8310 this.incrementCounters(typeofExpress, FaultID.InterOpImportJsForTypeOf, autofix); 8311 } 8312 } 8313 8314 private handleSdkTypeQuery(decl: ts.PropertyAccessExpression): void { 8315 if (!this.options.arkts2 || !ts.isPropertyAccessExpression(decl)) { 8316 return; 8317 } 8318 8319 if (this.handleSelfPropertyAccess(decl)) { 8320 return; 8321 } 8322 8323 if (ts.isPropertyAccessExpression(decl)) { 8324 const deprecatedProperties = [ 8325 'position', 8326 'subtype', 8327 'movingPhotoEffectMode', 8328 'dynamicRangeType', 8329 'thumbnailVisible' 8330 ]; 8331 8332 const propertyName = ts.isIdentifier(decl.name) ? decl.name.text : ''; 8333 if (deprecatedProperties.includes(propertyName)) { 8334 this.incrementCounters(decl.name, FaultID.SdkTypeQuery); 8335 return; 8336 } 8337 } 8338 8339 this.handleImportApiPropertyAccess(decl); 8340 } 8341 8342 private handleSelfPropertyAccess(decl: ts.PropertyAccessExpression): boolean { 8343 if (!ts.isPropertyAccessExpression(decl.expression)) { 8344 return false; 8345 } 8346 8347 const propertyName = ts.isIdentifier(decl.expression.name) && decl.expression.name.text || ''; 8348 if (propertyName !== 'self') { 8349 return false; 8350 } 8351 8352 this.incrementCounters(decl.name, FaultID.SdkTypeQuery); 8353 return true; 8354 } 8355 8356 private handleImportApiPropertyAccess(decl: ts.PropertyAccessExpression): void { 8357 if (!ts.isPropertyAccessExpression(decl.expression)) { 8358 return; 8359 } 8360 8361 const importApiName = ts.isIdentifier(decl.expression.expression) && decl.expression.expression.text || ''; 8362 const sdkInfos = importApiName && this.interfaceMap.get(importApiName); 8363 if (!sdkInfos) { 8364 return; 8365 } 8366 8367 const apiName = ts.isIdentifier(decl.name) && decl.name.text || ''; 8368 const matchedApi = [...sdkInfos].find((sdkInfo) => { 8369 return sdkInfo.api_name === apiName; 8370 }); 8371 8372 if (matchedApi) { 8373 this.incrementCounters(decl.name, FaultID.SdkTypeQuery); 8374 } 8375 } 8376 8377 /** 8378 * Returns true if the method’s declared return type or body returns Promise<void>. 8379 */ 8380 private hasPromiseVoidReturn(method: ts.MethodDeclaration): boolean { 8381 return ( 8382 this.hasAnnotatedPromiseVoidReturn(method) || this.isAsyncMethod(method) || this.hasBodyPromiseReturn(method) 8383 ); 8384 } 8385 8386 /** 8387 * Checks if the method’s declared return type annotation includes Promise<void>. 8388 */ 8389 private hasAnnotatedPromiseVoidReturn(method: ts.MethodDeclaration): boolean { 8390 void this; 8391 if (!method.type) { 8392 return false; 8393 } 8394 const t = method.type; 8395 // Union type check 8396 if (ts.isUnionTypeNode(t)) { 8397 return t.types.some((u) => { 8398 return this.isSinglePromiseVoid(u); 8399 }); 8400 } 8401 // Single Promise<void> check 8402 return this.isSinglePromiseVoid(t); 8403 } 8404 8405 private isSinglePromiseVoid(n: ts.Node): boolean { 8406 void this; 8407 return ts.isTypeReferenceNode(n) && n.typeName.getText() === PROMISE && n.typeArguments?.[0]?.getText() === VOID; 8408 } 8409 8410 /** 8411 * Checks if the method is declared async (implying Promise return). 8412 */ 8413 private isAsyncMethod(method: ts.MethodDeclaration): boolean { 8414 void this; 8415 return ( 8416 method.modifiers?.some((m) => { 8417 return m.kind === ts.SyntaxKind.AsyncKeyword; 8418 }) ?? false 8419 ); 8420 } 8421 8422 /** 8423 * Scans the method body iteratively for any Promise-returning statements. 8424 */ 8425 private hasBodyPromiseReturn(method: ts.MethodDeclaration): boolean { 8426 if (!method.body) { 8427 return false; 8428 } 8429 8430 let found = false; 8431 const visit = (node: ts.Node): void => { 8432 if (ts.isReturnStatement(node) && node.expression) { 8433 const retType = this.tsTypeChecker.getTypeAtLocation(node.expression); 8434 if (retType.symbol?.getName() === PROMISE) { 8435 found = true; 8436 return; 8437 } 8438 } 8439 ts.forEachChild(node, visit); 8440 }; 8441 ts.forEachChild(method.body, visit); 8442 8443 return found; 8444 } 8445 8446 /** 8447 * Returns true if this method name is onDestroy/onDisconnect and class extends one of the supported Ability subclasses. 8448 */ 8449 private isLifecycleMethodOnAbilitySubclass(method: ts.MethodDeclaration): boolean { 8450 const name = method.name.getText(); 8451 if (name !== ON_DESTROY && name !== ON_DISCONNECT) { 8452 return false; 8453 } 8454 const cls = method.parent; 8455 if (!ts.isClassDeclaration(cls) || !cls.heritageClauses) { 8456 return false; 8457 } 8458 return cls.heritageClauses.some((h) => { 8459 return ( 8460 h.token === ts.SyntaxKind.ExtendsKeyword && 8461 h.types.some((tn) => { 8462 return this.isSupportedAbilityBase(method.name.getText(), tn.expression); 8463 }) 8464 ); 8465 }); 8466 } 8467 8468 /** 8469 * Checks that the base class name and its import source or declaration file are supported, 8470 * and matches the lifecycle method (onDestroy vs onDisconnect). 8471 */ 8472 private isSupportedAbilityBase(methodName: string, baseExprNode: ts.Expression): boolean { 8473 const sym = this.tsTypeChecker.getSymbolAtLocation(baseExprNode); 8474 if (!sym) { 8475 return false; 8476 } 8477 8478 const baseName = sym.getName(); 8479 if (!ASYNC_LIFECYCLE_SDK_LIST.has(baseName)) { 8480 return false; 8481 } 8482 8483 if (methodName === ON_DISCONNECT && baseName !== SERVICE_EXTENSION_ABILITY) { 8484 return false; 8485 } 8486 if (methodName === ON_DESTROY && baseName === SERVICE_EXTENSION_ABILITY) { 8487 return false; 8488 } 8489 8490 const decl = sym.getDeclarations()?.[0]; 8491 if (!decl || !ts.isImportSpecifier(decl)) { 8492 return false; 8493 } 8494 8495 const importDecl = decl.parent.parent.parent; 8496 const moduleName = (importDecl.moduleSpecifier as ts.StringLiteral).text; 8497 const srcFile = decl.getSourceFile().fileName; 8498 8499 return moduleName === ABILITY_KIT || srcFile.endsWith(`${baseName}.${EXTNAME_D_TS}`); 8500 } 8501 8502 /** 8503 * Rule sdk-void-lifecycle-return: 8504 * Flags onDestroy/onDisconnect methods in Ability subclasses 8505 * whose return type includes Promise<void>. 8506 */ 8507 private checkVoidLifecycleReturn(method: ts.MethodDeclaration): void { 8508 if (!this.options.arkts2) { 8509 return; 8510 } 8511 8512 if (!this.isLifecycleMethodOnAbilitySubclass(method)) { 8513 return; 8514 } 8515 8516 if (!this.hasPromiseVoidReturn(method)) { 8517 return; 8518 } 8519 8520 this.incrementCounters(method.name, FaultID.SdkAbilityAsynchronousLifecycle); 8521 } 8522 8523 private handleGetOwnPropertyNames(decl: ts.PropertyAccessExpression): void { 8524 if (this.checkPropertyAccessExpression(decl, GET_OWN_PROPERTY_NAMES_TEXT, TypeScriptLinter.missingAttributeSet)) { 8525 const autofix = this.autofixer?.fixMissingAttribute(decl); 8526 this.incrementCounters(decl, FaultID.BuiltinGetOwnPropertyNames, autofix); 8527 } 8528 } 8529 8530 private handleSymbolIterator(decl: ts.PropertyAccessExpression): void { 8531 if (this.checkPropertyAccessExpression(decl, SYMBOL_ITERATOR, TypeScriptLinter.symbotIterSet)) { 8532 this.incrementCounters(decl, FaultID.BuiltinSymbolIterator); 8533 } 8534 } 8535 8536 private checkPropertyAccessExpression(decl: ts.PropertyAccessExpression, name: string, set: Set<string>): boolean { 8537 if (set.size === 0 || decl.getText() !== name) { 8538 return false; 8539 } 8540 8541 const symbol = this.tsUtils.trueSymbolAtLocation(decl); 8542 const sourceFile = symbol?.declarations?.[0]?.getSourceFile(); 8543 if (!sourceFile) { 8544 return false; 8545 } 8546 8547 const fileName = path.basename(sourceFile.fileName); 8548 return set.has(fileName); 8549 } 8550 8551 private fixJsImportCallExpression(callExpr: ts.CallExpression): void { 8552 if ( 8553 !this.options.arkts2 || 8554 !this.useStatic || 8555 ts.isAwaitExpression(callExpr.parent) || 8556 ts.isTypeOfExpression(callExpr.parent) 8557 ) { 8558 return; 8559 } 8560 8561 const identifier = this.tsUtils.findIdentifierInExpression(callExpr); 8562 if (!identifier) { 8563 return; 8564 } 8565 8566 if (!this.tsUtils.isImportedFromJS(identifier)) { 8567 return; 8568 } 8569 8570 callExpr.arguments.forEach((arg) => { 8571 const type = this.tsTypeChecker.getTypeAtLocation(arg); 8572 if (ts.isArrowFunction(arg)) { 8573 this.incrementCounters(arg, FaultID.InteropJsObjectCallStaticFunc); 8574 } else if (ts.isIdentifier(arg)) { 8575 const sym = this.tsTypeChecker.getSymbolAtLocation(arg); 8576 const decl = sym?.declarations?.[0]; 8577 if ( 8578 decl && 8579 (ts.isFunctionDeclaration(decl) || 8580 ts.isVariableDeclaration(decl) && decl.initializer && ts.isArrowFunction(decl.initializer)) 8581 ) { 8582 this.incrementCounters(arg, FaultID.InteropJsObjectCallStaticFunc); 8583 } 8584 if (type?.isClassOrInterface()) { 8585 this.incrementCounters(arg, FaultID.InteropJsObjectExpandStaticInstance); 8586 } 8587 } else if (ts.isObjectLiteralExpression(arg) || type?.isClassOrInterface()) { 8588 this.incrementCounters(arg, FaultID.InteropJsObjectExpandStaticInstance); 8589 } 8590 }); 8591 } 8592 8593 private fixJsImportExtendsClass( 8594 node: ts.ClassLikeDeclaration | ts.InterfaceDeclaration, 8595 identifier: ts.Identifier 8596 ): void { 8597 if (!this.options.arkts2) { 8598 return; 8599 } 8600 8601 if (!this.tsUtils.isImportedFromJS(identifier)) { 8602 return; 8603 } 8604 8605 const className = node.name?.text; 8606 if (!className) { 8607 return; 8608 } 8609 this.incrementCounters(node, FaultID.InteropJsObjectInheritance); 8610 } 8611 8612 private fixJsImportPropertyAccessExpression(node: ts.Node): void { 8613 if (!this.options.arkts2 || !this.useStatic) { 8614 return; 8615 } 8616 8617 const identifier = this.tsUtils.findIdentifierInExpression(node); 8618 if (!identifier) { 8619 return; 8620 } 8621 8622 // Try direct check first 8623 if (!this.tsUtils.isImportedFromJS(identifier)) { 8624 return; 8625 } 8626 const autofix = this.autofixer?.createReplacementForJsImportPropertyAccessExpression( 8627 node as ts.PropertyAccessExpression 8628 ); 8629 if (!TsUtils.isInsideIfCondition(node)) { 8630 return; 8631 } 8632 this.incrementCounters(node, FaultID.InteropJsObjectConditionJudgment, autofix); 8633 } 8634 8635 private fixJsImportElementAccessExpression(elementAccessExpr: ts.ElementAccessExpression): void { 8636 if (!this.options.arkts2 || !this.useStatic) { 8637 return; 8638 } 8639 if (!TypeScriptLinter.isInForLoopBody(elementAccessExpr)) { 8640 return; 8641 } 8642 const variableDeclaration = ts.isIdentifier(elementAccessExpr.expression) ? 8643 this.tsUtils.findVariableDeclaration(elementAccessExpr.expression) : 8644 undefined; 8645 if (!variableDeclaration?.initializer) { 8646 return; 8647 } 8648 8649 const identifier = ts.isPropertyAccessExpression(variableDeclaration.initializer) ? 8650 (variableDeclaration.initializer.expression as ts.Identifier) : 8651 undefined; 8652 if (!identifier) { 8653 return; 8654 } 8655 8656 if (!this.tsUtils.isImportedFromJS(identifier)) { 8657 return; 8658 } 8659 8660 const autofix = this.autofixer?.fixJsImportElementAccessExpression(elementAccessExpr); 8661 8662 this.incrementCounters(elementAccessExpr, FaultID.InteropJsObjectTraverseJsInstance, autofix); 8663 } 8664 8665 private static isInForLoopBody(node: ts.Node): boolean { 8666 let current: ts.Node | undefined = node.parent; 8667 while (current) { 8668 if (ts.isForStatement(current) || ts.isForInStatement(current) || ts.isForOfStatement(current)) { 8669 return true; 8670 } 8671 current = current.parent; 8672 } 8673 return false; 8674 } 8675 8676 private handleTaskPoolDeprecatedUsages(propertyAccess: ts.PropertyAccessExpression): void { 8677 if (!this.options.arkts2 || !this.useStatic) { 8678 return; 8679 } 8680 const objectExpr = ts.isNewExpression(propertyAccess.expression) ? 8681 propertyAccess.expression.expression : 8682 propertyAccess.expression; 8683 // Step 1: Must be either setCloneList or setTransferList 8684 if (!TypeScriptLinter.isDeprecatedTaskPoolMethodCall(propertyAccess)) { 8685 return; 8686 } 8687 const variableDecl = TsUtils.getDeclaration(this.tsUtils.trueSymbolAtLocation(objectExpr)); 8688 const isNoContinue = 8689 !variableDecl || 8690 !ts.isVariableDeclaration(variableDecl) || 8691 !variableDecl?.initializer || 8692 !ts.isNewExpression(variableDecl.initializer); 8693 if (isNoContinue) { 8694 return; 8695 } 8696 const taskpoolExpr = variableDecl.initializer.expression; 8697 if (!this.isTaskPoolTaskCreation(taskpoolExpr)) { 8698 return; 8699 } 8700 const faultId = 8701 propertyAccess.name.text === DEPRECATED_TASKPOOL_METHOD_SETCLONELIST ? 8702 FaultID.SetCloneListDeprecated : 8703 FaultID.SetTransferListDeprecated; 8704 this.incrementCounters(propertyAccess.name, faultId); 8705 } 8706 8707 private static isDeprecatedTaskPoolMethodCall(propertyAccess: ts.PropertyAccessExpression): boolean { 8708 const methodName = propertyAccess.name.text; 8709 return ( 8710 methodName === DEPRECATED_TASKPOOL_METHOD_SETCLONELIST || 8711 methodName === DEPRECATED_TASKPOOL_METHOD_SETTRANSFERLIST 8712 ); 8713 } 8714 8715 private isTaskPoolTaskCreation(taskpoolExpr: ts.Expression): boolean { 8716 if ( 8717 ts.isIdentifier(taskpoolExpr) || 8718 ts.isPropertyAccessExpression(taskpoolExpr) && taskpoolExpr.name.text === STDLIB_TASK_CLASS_NAME 8719 ) { 8720 const objectExpr = ts.isIdentifier(taskpoolExpr) ? taskpoolExpr : taskpoolExpr.expression; 8721 return this.isTaskPoolReferenceisTaskPoolImportForTaskPoolDeprecatedUsages(objectExpr); 8722 } 8723 return false; 8724 } 8725 8726 private isTaskPoolReferenceisTaskPoolImportForTaskPoolDeprecatedUsages(expr: ts.Expression): boolean { 8727 if (ts.isIdentifier(expr)) { 8728 const sym = this.tsTypeChecker.getSymbolAtLocation(expr); 8729 const importChild = TsUtils.getDeclaration(sym); 8730 if (!importChild) { 8731 return false; 8732 } 8733 if (ts.isImportSpecifier(importChild)) { 8734 return TypeScriptLinter.isTaskPoolImportForTaskPoolDeprecatedUsages(importChild); 8735 } 8736 if (ts.isImportClause(importChild) && importChild.name?.text === STDLIB_TASKPOOL_OBJECT_NAME) { 8737 return TypeScriptLinter.checkModuleSpecifierForTaskPoolDeprecatedUsages(importChild.parent); 8738 } 8739 } 8740 if (ts.isPropertyAccessExpression(expr)) { 8741 return this.isTaskPoolReferenceOnPropertyAccessExpression(expr); 8742 } 8743 return false; 8744 } 8745 8746 private static checkModuleSpecifierForTaskPoolDeprecatedUsages(importDecl: ts.ImportDeclaration): boolean { 8747 if (ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) { 8748 const moduleSpecifier = importDecl.moduleSpecifier; 8749 return TASKPOOL_MODULES.includes(TsUtils.removeOrReplaceQuotes(moduleSpecifier.getText(), false)); 8750 } 8751 return false; 8752 } 8753 8754 private isTaskPoolReferenceOnPropertyAccessExpression(expr: ts.PropertyAccessExpression): boolean { 8755 if (expr.name.text !== STDLIB_TASKPOOL_OBJECT_NAME || !ts.isIdentifier(expr.expression)) { 8756 return false; 8757 } 8758 const sym = this.tsTypeChecker.getSymbolAtLocation(expr.expression); 8759 const importChild = TsUtils.getDeclaration(sym); 8760 if (importChild && ts.isNamespaceImport(importChild)) { 8761 return TypeScriptLinter.checkModuleSpecifierForTaskPoolDeprecatedUsages(importChild.parent.parent); 8762 } 8763 return false; 8764 } 8765 8766 private static isTaskPoolImportForTaskPoolDeprecatedUsages(specifier: ts.ImportSpecifier): boolean { 8767 const specifierName = specifier.propertyName ? specifier.propertyName : specifier.name; 8768 if (STDLIB_TASKPOOL_OBJECT_NAME !== specifierName.text) { 8769 return false; 8770 } 8771 const importDeclaration = specifier.parent.parent.parent; 8772 return TypeScriptLinter.checkModuleSpecifierForTaskPoolDeprecatedUsages(importDeclaration); 8773 } 8774 8775 private handleForOfJsArray(node: ts.ForOfStatement): void { 8776 if (!this.options.arkts2 || !this.useStatic) { 8777 return; 8778 } 8779 8780 const expr = node.expression; 8781 if (!ts.isIdentifier(expr) || !this.tsUtils.isPossiblyImportedFromJS(expr)) { 8782 return; 8783 } 8784 8785 const exprType = this.tsTypeChecker.getTypeAtLocation(expr); 8786 8787 if (!this.tsUtils.isArray(exprType)) { 8788 return; 8789 } 8790 8791 this.incrementCounters(node, FaultID.InteropJsObjectTraverseJsInstance); 8792 } 8793 8794 private checkStdLibConcurrencyImport(importDeclaration: ts.ImportDeclaration): void { 8795 if (!this.options.arkts2) { 8796 return; 8797 } 8798 8799 const importClause = importDeclaration.importClause; 8800 if (!importClause) { 8801 return; 8802 } 8803 8804 const moduleName = (importDeclaration.moduleSpecifier as ts.StringLiteral).text; 8805 const expectedImports = MODULE_IMPORTS[moduleName]; 8806 if (!expectedImports) { 8807 return; 8808 } 8809 8810 const defaultImport = importClause.name; 8811 const namedBindings = importClause.namedBindings; 8812 8813 const namedImports = namedBindings && ts.isNamedImports(namedBindings) ? namedBindings.elements : []; 8814 8815 const defaultIsForbidden = defaultImport && expectedImports.includes(defaultImport.getText()); 8816 const forbiddenNamed = namedImports.filter((spec) => { 8817 const name = spec.propertyName ? spec.propertyName.getText() : spec.name.getText(); 8818 return expectedImports.includes(name); 8819 }); 8820 8821 if ( 8822 TypeScriptLinter.shouldRemoveWholeImport( 8823 defaultIsForbidden, 8824 forbiddenNamed.length, 8825 namedImports.length, 8826 defaultImport 8827 ) 8828 ) { 8829 const autofix = this.autofixer?.removeNode(importDeclaration); 8830 this.incrementCounters(importDeclaration, FaultID.LimitedStdLibNoImportConcurrency, autofix); 8831 return; 8832 } 8833 8834 if (defaultIsForbidden) { 8835 const autofix = this.autofixer?.removeDefaultImport(importDeclaration, defaultImport); 8836 this.incrementCounters(defaultImport, FaultID.LimitedStdLibNoImportConcurrency, autofix); 8837 } 8838 8839 for (const spec of forbiddenNamed) { 8840 const autofix = this.autofixer?.removeImportSpecifier(spec, importDeclaration); 8841 this.incrementCounters(spec, FaultID.LimitedStdLibNoImportConcurrency, autofix); 8842 } 8843 } 8844 8845 private static shouldRemoveWholeImport( 8846 defaultIsForbidden: boolean | undefined, 8847 forbiddenNamedCount: number, 8848 namedImportsCount: number, 8849 defaultImport: ts.Identifier | undefined 8850 ): boolean { 8851 return ( 8852 defaultIsForbidden && forbiddenNamedCount === namedImportsCount || 8853 defaultIsForbidden && namedImportsCount === 0 || 8854 !defaultImport && forbiddenNamedCount === namedImportsCount && namedImportsCount > 0 8855 ); 8856 } 8857 8858 /** 8859 * Checks for missing super() call in child classes that extend a parent class 8860 * with parameterized constructors. If parent class only has parameterized constructors 8861 * and the child class does not call super() in its constructor, report a fault. 8862 * 8863 * This ensures safe and correct subclassing behavior. 8864 * 8865 * @param node The HeritageClause node (extends clause) to analyze. 8866 */ 8867 private handleMissingSuperCallInExtendedClass(node: ts.HeritageClause): void { 8868 if (!this.options.arkts2 || !this.useStatic) { 8869 return; 8870 } 8871 8872 // We are only interested in 'extends' clauses 8873 if (node.token !== ts.SyntaxKind.ExtendsKeyword) { 8874 return; 8875 } 8876 8877 // Get the parent class declaration (what the child class extends) 8878 const parentClass = this.getParentClassDeclaration(node); 8879 if (!parentClass) { 8880 return; 8881 } 8882 8883 // If parent class has a parameterless constructor (or no constructor at all), child is fine 8884 if (TypeScriptLinter.parentHasParameterlessConstructor(parentClass)) { 8885 return; 8886 } 8887 8888 // The child class node (the one extending) 8889 const childClass = node.parent; 8890 if (!ts.isClassDeclaration(childClass)) { 8891 return; 8892 } 8893 8894 // Look for child class constructor 8895 const childConstructor = childClass.members.find(ts.isConstructorDeclaration); 8896 8897 /* 8898 * If child has no constructor → error (super() cannot be called) 8899 * If child constructor exists but does not contain super() → error 8900 */ 8901 if (!childConstructor?.body || !TypeScriptLinter.childHasSuperCall(childConstructor)) { 8902 this.incrementCounters(node, FaultID.MissingSuperCall); 8903 } 8904 } 8905 8906 /** 8907 * Retrieves the parent class declaration node from an extends heritage clause. 8908 */ 8909 private getParentClassDeclaration(node: ts.HeritageClause): ts.ClassDeclaration | undefined { 8910 const parentExpr = node.types[0]?.expression; 8911 if (!parentExpr) { 8912 return undefined; 8913 } 8914 const parentSymbol = this.tsUtils.trueSymbolAtLocation(parentExpr); 8915 return parentSymbol?.declarations?.find(ts.isClassDeclaration); 8916 } 8917 8918 /** 8919 * Determines if a parent class has a parameterless constructor. 8920 * If it has no constructor at all, that counts as parameterless. 8921 */ 8922 private static parentHasParameterlessConstructor(parentClass: ts.ClassDeclaration): boolean { 8923 const constructors = parentClass.members.filter(ts.isConstructorDeclaration); 8924 return ( 8925 constructors.length === 0 || 8926 constructors.some((ctor) => { 8927 return ctor.parameters.length === 0; 8928 }) 8929 ); 8930 } 8931 8932 private static childHasSuperCall(constructor: ts.ConstructorDeclaration): boolean { 8933 let superCalled = false; 8934 8935 if (!constructor.body) { 8936 return false; 8937 } 8938 8939 ts.forEachChild(constructor.body, (stmt) => { 8940 if ( 8941 ts.isExpressionStatement(stmt) && 8942 ts.isCallExpression(stmt.expression) && 8943 stmt.expression.expression.kind === ts.SyntaxKind.SuperKeyword 8944 ) { 8945 superCalled = true; 8946 } 8947 }); 8948 return superCalled; 8949 } 8950 8951 private handleInterOpImportJs(importDecl: ts.ImportDeclaration): void { 8952 if (!this.options.arkts2 || !importDecl || !this.useStatic) { 8953 return; 8954 } 8955 const importClause = importDecl.importClause; 8956 if (!importClause) { 8957 return; 8958 } 8959 const namedBindings = importClause.namedBindings; 8960 let symbol: ts.Symbol | undefined; 8961 let defaultSymbol: ts.Symbol | undefined; 8962 if (importClause.name) { 8963 defaultSymbol = this.tsUtils.trueSymbolAtLocation(importClause.name); 8964 } 8965 if (namedBindings) { 8966 if (ts.isNamedImports(namedBindings) && namedBindings.elements?.length > 0 && namedBindings.elements[0]?.name) { 8967 symbol = this.tsUtils.trueSymbolAtLocation(namedBindings.elements[0].name); 8968 } else if (ts.isNamespaceImport(namedBindings)) { 8969 symbol = this.tsUtils.trueSymbolAtLocation(namedBindings.name); 8970 } 8971 } 8972 const symbolToUse = defaultSymbol || symbol; 8973 if (symbolToUse) { 8974 this.tryAutoFixInterOpImportJs(importDecl, importClause, symbolToUse, defaultSymbol); 8975 } 8976 } 8977 8978 private tryAutoFixInterOpImportJs( 8979 importDecl: ts.ImportDeclaration, 8980 importClause: ts.ImportClause, 8981 symbolToUse: ts.Symbol, 8982 defaultSymbol?: ts.Symbol 8983 ): void { 8984 const declaration = symbolToUse.declarations?.[0]; 8985 if (declaration) { 8986 const sourceFile = declaration.getSourceFile(); 8987 if (sourceFile.fileName.endsWith(EXTNAME_JS)) { 8988 const autofix = this.autofixer?.fixInterOpImportJs( 8989 importDecl, 8990 importClause, 8991 TsUtils.removeOrReplaceQuotes(importDecl.moduleSpecifier.getText(this.sourceFile), false), 8992 defaultSymbol 8993 ); 8994 this.incrementCounters(importDecl, FaultID.InterOpImportJs, autofix); 8995 } 8996 } 8997 } 8998 8999 private findVariableDeclaration(identifier: ts.Identifier): ts.VariableDeclaration | undefined { 9000 const sym = this.tsUtils.trueSymbolAtLocation(identifier); 9001 const decl = TsUtils.getDeclaration(sym); 9002 if ( 9003 decl && 9004 ts.isVariableDeclaration(decl) && 9005 decl.getSourceFile().fileName === identifier.getSourceFile().fileName 9006 ) { 9007 return decl; 9008 } 9009 return undefined; 9010 } 9011 9012 private isFromJSModule(node: ts.Node): boolean { 9013 const symbol = this.tsUtils.trueSymbolAtLocation(node); 9014 if (symbol?.declarations?.[0]) { 9015 const sourceFile = symbol.declarations[0].getSourceFile(); 9016 return sourceFile.fileName.endsWith(EXTNAME_JS); 9017 } 9018 return false; 9019 } 9020 9021 handleInstanceOfExpression(node: ts.BinaryExpression): void { 9022 if (!this.options.arkts2 || !this.useStatic) { 9023 return; 9024 } 9025 9026 const left = node.left; 9027 const right = node.right; 9028 const getNode = (expr: ts.Expression): ts.Node => { 9029 return ts.isPropertyAccessExpression(expr) || ts.isCallExpression(expr) ? expr.expression : expr; 9030 }; 9031 9032 const leftExpr = getNode(left); 9033 const rightExpr = getNode(right); 9034 9035 if (!this.tsUtils.isJsImport(leftExpr) && !this.tsUtils.isJsImport(rightExpr)) { 9036 return; 9037 } 9038 9039 const autofix = this.autofixer?.fixInteropJsInstanceOfExpression(node); 9040 this.incrementCounters(node, FaultID.InteropJsInstanceof, autofix); 9041 } 9042 9043 private checkAutoIncrementDecrement(unaryExpr: ts.PostfixUnaryExpression | ts.PrefixUnaryExpression): void { 9044 if (!this.useStatic || !this.options.arkts2) { 9045 return; 9046 } 9047 9048 if (!ts.isPropertyAccessExpression(unaryExpr.operand)) { 9049 return; 9050 } 9051 9052 const propertyAccess = unaryExpr.operand; 9053 if (!this.tsUtils.isJsImport(propertyAccess.expression)) { 9054 return; 9055 } 9056 9057 const autofix = this.autofixer?.fixUnaryIncrDecr(unaryExpr, propertyAccess); 9058 9059 this.incrementCounters(unaryExpr, FaultID.InteropIncrementDecrement, autofix); 9060 } 9061 9062 private handleObjectLiteralforUnionTypeInterop(node: ts.VariableDeclaration): void { 9063 if (!this.options.arkts2 || !this.useStatic) { 9064 return; 9065 } 9066 9067 if (!node.type || !ts.isUnionTypeNode(node.type)) { 9068 return; 9069 } 9070 9071 if (!node.initializer || node.initializer.kind !== ts.SyntaxKind.ObjectLiteralExpression) { 9072 return; 9073 } 9074 9075 const typeNodes = node.type.types; 9076 9077 const isDefected = typeNodes.some((tNode) => { 9078 if (!ts.isTypeReferenceNode(tNode)) { 9079 return false; 9080 } 9081 const type = this.tsTypeChecker.getTypeAtLocation(tNode); 9082 const symbol = type.getSymbol(); 9083 if (!symbol) { 9084 return false; 9085 } 9086 for (const declaration of symbol.declarations ?? []) { 9087 if (!this.tsUtils.isArkts12File(declaration.getSourceFile())) { 9088 return true; 9089 } 9090 } 9091 return false; 9092 }); 9093 9094 if (isDefected) { 9095 this.incrementCounters(node, FaultID.InteropObjectLiteralAmbiguity); 9096 } 9097 } 9098 9099 private handleObjectLiteralAssignmentToClass( 9100 node: 9101 | ts.VariableDeclaration 9102 | ts.CallExpression 9103 | ts.ReturnStatement 9104 | ts.ArrayLiteralExpression 9105 | ts.PropertyDeclaration 9106 | ts.AsExpression 9107 | ts.BinaryExpression 9108 ): void { 9109 if (!this.options.arkts2 || !this.useStatic) { 9110 return; 9111 } 9112 9113 switch (node.kind) { 9114 case ts.SyntaxKind.VariableDeclaration: 9115 this.checkVariableDeclarationForObjectLiteral(node); 9116 break; 9117 case ts.SyntaxKind.CallExpression: 9118 this.checkCallExpressionForObjectLiteral(node); 9119 break; 9120 case ts.SyntaxKind.ReturnStatement: 9121 this.checkReturnStatementForObjectLiteral(node); 9122 break; 9123 case ts.SyntaxKind.ArrayLiteralExpression: 9124 this.checkArrayLiteralExpressionForObjectLiteral(node); 9125 break; 9126 case ts.SyntaxKind.PropertyDeclaration: 9127 this.checkPropertyDeclarationForObjectLiteral(node); 9128 break; 9129 case ts.SyntaxKind.AsExpression: 9130 this.checkAsExpressionForObjectLiteral(node); 9131 break; 9132 case ts.SyntaxKind.BinaryExpression: 9133 this.checkBinaryExpressionForObjectLiteral(node); 9134 break; 9135 default: 9136 } 9137 } 9138 9139 private reportIfAssignedToNonArkts2Class(type: ts.Type, expr: ts.ObjectLiteralExpression): void { 9140 const symbol = type.getSymbol(); 9141 if (!symbol) { 9142 return; 9143 } 9144 9145 const declarations = symbol.declarations ?? []; 9146 const isClass = declarations.some(ts.isClassDeclaration); 9147 if (!isClass) { 9148 return; 9149 } 9150 9151 const isFromArkTs2 = declarations.some((decl) => { 9152 return this.tsUtils.isArkts12File(decl.getSourceFile()); 9153 }); 9154 9155 if (isFromArkTs2) { 9156 return; 9157 } 9158 9159 const hasConstructor = declarations.some((decl) => { 9160 return ts.isClassDeclaration(decl) && decl.members.some(ts.isConstructorDeclaration); 9161 }); 9162 9163 if (hasConstructor) { 9164 this.incrementCounters(expr, FaultID.InteropObjectLiteralClass); 9165 } 9166 } 9167 9168 private checkVariableDeclarationForObjectLiteral(node: ts.VariableDeclaration): void { 9169 if (!node.initializer || !node.type) { 9170 return; 9171 } 9172 9173 const type = this.tsTypeChecker.getTypeAtLocation(node.type); 9174 9175 const checkObjectLiteral = (expr: ts.Expression): void => { 9176 if (ts.isObjectLiteralExpression(expr)) { 9177 this.reportIfAssignedToNonArkts2Class(type, expr); 9178 } 9179 }; 9180 9181 if (ts.isObjectLiteralExpression(node.initializer)) { 9182 checkObjectLiteral(node.initializer); 9183 } else if (ts.isConditionalExpression(node.initializer)) { 9184 checkObjectLiteral(node.initializer.whenTrue); 9185 checkObjectLiteral(node.initializer.whenFalse); 9186 } 9187 } 9188 9189 private checkCallExpressionForObjectLiteral(node: ts.CallExpression): void { 9190 for (const arg of node.arguments) { 9191 if (ts.isObjectLiteralExpression(arg)) { 9192 const signature = this.tsTypeChecker.getResolvedSignature(node); 9193 const params = signature?.getParameters() ?? []; 9194 const index = node.arguments.indexOf(arg); 9195 const paramSymbol = params[index]; 9196 if (!paramSymbol) { 9197 continue; 9198 } 9199 9200 const paramDecl = paramSymbol.declarations?.[0]; 9201 if (!paramDecl || !ts.isParameter(paramDecl) || !paramDecl.type) { 9202 continue; 9203 } 9204 9205 const type = this.tsTypeChecker.getTypeAtLocation(paramDecl.type); 9206 this.reportIfAssignedToNonArkts2Class(type, arg); 9207 } 9208 } 9209 } 9210 9211 private checkReturnStatementForObjectLiteral(node: ts.ReturnStatement): void { 9212 if (!node.expression || !ts.isObjectLiteralExpression(node.expression)) { 9213 return; 9214 } 9215 const func = ts.findAncestor(node, ts.isFunctionLike); 9216 if (!func?.type) { 9217 return; 9218 } 9219 9220 const returnType = this.tsTypeChecker.getTypeAtLocation(func.type); 9221 this.reportIfAssignedToNonArkts2Class(returnType, node.expression); 9222 } 9223 9224 private checkArrayLiteralExpressionForObjectLiteral(node: ts.ArrayLiteralExpression): void { 9225 for (const element of node.elements) { 9226 if (ts.isObjectLiteralExpression(element)) { 9227 const contextualType = this.tsTypeChecker.getContextualType(node); 9228 if (!contextualType) { 9229 continue; 9230 } 9231 9232 const typeArgs = (contextualType as ts.TypeReference).typeArguments; 9233 const elementType = typeArgs?.[0]; 9234 if (!elementType) { 9235 continue; 9236 } 9237 9238 this.reportIfAssignedToNonArkts2Class(elementType, element); 9239 } 9240 } 9241 } 9242 9243 private checkPropertyDeclarationForObjectLiteral(node: ts.PropertyDeclaration): void { 9244 if (!node.initializer || !ts.isObjectLiteralExpression(node.initializer) || !node.type) { 9245 return; 9246 } 9247 9248 const type = this.tsTypeChecker.getTypeAtLocation(node.type); 9249 this.reportIfAssignedToNonArkts2Class(type, node.initializer); 9250 } 9251 9252 private checkAsExpressionForObjectLiteral(node: ts.AsExpression): void { 9253 if (!ts.isObjectLiteralExpression(node.expression)) { 9254 return; 9255 } 9256 9257 const type = this.tsTypeChecker.getTypeAtLocation(node.type); 9258 this.reportIfAssignedToNonArkts2Class(type, node.expression); 9259 } 9260 9261 private checkBinaryExpressionForObjectLiteral(node: ts.BinaryExpression): void { 9262 if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { 9263 return; 9264 } 9265 if (!ts.isObjectLiteralExpression(node.right)) { 9266 return; 9267 } 9268 9269 const type = this.tsTypeChecker.getTypeAtLocation(node.left); 9270 this.reportIfAssignedToNonArkts2Class(type, node.right); 9271 } 9272 9273 private isObjectLiteralAssignedToArkts12Type(node: ts.Expression, expectedTypeNode?: ts.TypeNode): boolean { 9274 if (node.kind !== ts.SyntaxKind.ObjectLiteralExpression) { 9275 return false; 9276 } 9277 9278 let type: ts.Type; 9279 if (expectedTypeNode) { 9280 type = this.tsTypeChecker.getTypeAtLocation(expectedTypeNode); 9281 } else { 9282 type = this.tsTypeChecker.getContextualType(node) ?? this.tsTypeChecker.getTypeAtLocation(node); 9283 } 9284 9285 if (!type) { 9286 return false; 9287 } 9288 9289 return this.isTypeFromArkts12(type); 9290 } 9291 9292 private isTypeFromArkts12(type: ts.Type): boolean { 9293 const symbol = type?.getSymbol(); 9294 if (!symbol) { 9295 return false; 9296 } 9297 9298 const isFromArkts12 = (symbol.declarations ?? []).some((decl) => { 9299 return this.tsUtils.isArkts12File(decl.getSourceFile()); 9300 }); 9301 9302 if (isFromArkts12) { 9303 return true; 9304 } 9305 return false; 9306 } 9307 9308 private processNestedObjectLiterals(objLiteral: ts.Expression, parentType?: ts.Type): void { 9309 if (!ts.isObjectLiteralExpression(objLiteral)) { 9310 return; 9311 } 9312 9313 objLiteral.properties.forEach((prop) => { 9314 if (!ts.isPropertyAssignment(prop) || !ts.isObjectLiteralExpression(prop.initializer)) { 9315 return; 9316 } 9317 9318 if (this.isObjectLiteralAssignedToArkts12Type(prop.initializer)) { 9319 this.incrementCounters(prop.initializer, FaultID.InteropStaticObjectLiterals); 9320 return; 9321 } 9322 9323 this.checkPropertyTypeFromParent(prop, parentType); 9324 this.processNestedObjectLiterals(prop.initializer); 9325 }); 9326 } 9327 9328 private checkPropertyTypeFromParent(prop: ts.PropertyAssignment, parentType?: ts.Type): void { 9329 if (!parentType) { 9330 return; 9331 } 9332 if (!ts.isObjectLiteralExpression(prop.initializer)) { 9333 return; 9334 } 9335 9336 const propName = prop.name.getText(); 9337 const property = parentType.getProperty(propName); 9338 9339 if (!property?.valueDeclaration) { 9340 return; 9341 } 9342 9343 const propType = this.tsTypeChecker.getTypeOfSymbolAtLocation(property, property.valueDeclaration); 9344 9345 if (this.isTypeFromArkts12(propType)) { 9346 this.incrementCounters(prop.initializer, FaultID.InteropStaticObjectLiterals); 9347 } 9348 } 9349 9350 private handleObjectLiteralAssignment(node: ts.VariableDeclaration): void { 9351 if (this.tsUtils.isArkts12File(node.getSourceFile())) { 9352 return; 9353 } 9354 9355 if (!node.initializer) { 9356 return; 9357 } 9358 9359 if ( 9360 ts.isObjectLiteralExpression(node.initializer) && 9361 this.isObjectLiteralAssignedToArkts12Type(node.initializer, node.type) 9362 ) { 9363 this.incrementCounters(node.initializer, FaultID.InteropStaticObjectLiterals); 9364 return; 9365 } 9366 9367 const parentType = node.type ? 9368 this.tsTypeChecker.getTypeAtLocation(node.type) : 9369 this.tsTypeChecker.getTypeAtLocation(node.initializer); 9370 9371 this.processNestedObjectLiterals(node.initializer, parentType); 9372 } 9373 9374 private handleObjectLiteralInFunctionArgs(node: ts.CallExpression): void { 9375 if (this.tsUtils.isArkts12File(node.getSourceFile())) { 9376 return; 9377 } 9378 const signature = this.tsTypeChecker.getResolvedSignature(node); 9379 if (!signature) { 9380 return; 9381 } 9382 9383 const params = signature.getParameters(); 9384 9385 node.arguments.forEach((arg, index) => { 9386 if (!ts.isObjectLiteralExpression(arg)) { 9387 return; 9388 } 9389 9390 if (index < params.length) { 9391 const param = params[index]; 9392 if (!param.valueDeclaration) { 9393 return; 9394 } 9395 9396 const paramType = this.tsTypeChecker.getTypeOfSymbolAtLocation(param, param.valueDeclaration); 9397 9398 if (this.isTypeFromArkts12(paramType)) { 9399 this.incrementCounters(arg, FaultID.InteropStaticObjectLiterals); 9400 } 9401 } else if (this.isObjectLiteralAssignedToArkts12Type(arg)) { 9402 this.incrementCounters(arg, FaultID.InteropStaticObjectLiterals); 9403 } 9404 }); 9405 } 9406 9407 private handleObjectLiteralInReturn(node: ts.ReturnStatement): void { 9408 if (this.tsUtils.isArkts12File(node.getSourceFile())) { 9409 return; 9410 } 9411 9412 if (!node.expression || !ts.isObjectLiteralExpression(node.expression)) { 9413 return; 9414 } 9415 9416 let current: ts.Node = node; 9417 let functionNode: ts.FunctionLikeDeclaration | undefined; 9418 9419 while (current && !functionNode) { 9420 current = current.parent; 9421 if ( 9422 current && 9423 (ts.isFunctionDeclaration(current) || 9424 ts.isMethodDeclaration(current) || 9425 ts.isFunctionExpression(current) || 9426 ts.isArrowFunction(current)) 9427 ) { 9428 functionNode = current; 9429 } 9430 } 9431 9432 if (functionNode?.type) { 9433 const returnType = this.tsTypeChecker.getTypeAtLocation(functionNode.type); 9434 if (this.isTypeFromArkts12(returnType)) { 9435 this.incrementCounters(node.expression, FaultID.InteropStaticObjectLiterals); 9436 } 9437 } else if (this.isObjectLiteralAssignedToArkts12Type(node.expression)) { 9438 this.incrementCounters(node.expression, FaultID.InteropStaticObjectLiterals); 9439 } 9440 } 9441 9442 private handleLocalBuilderDecorator(node: ts.Node): void { 9443 if (!this.options.arkts2) { 9444 return; 9445 } 9446 if (!ts.isDecorator(node) || !ts.isIdentifier(node.expression)) { 9447 return; 9448 } 9449 const decoratorName = node.expression.getText(); 9450 if (decoratorName === CustomDecoratorName.LocalBuilder) { 9451 const autofix = this.autofixer?.fixBuilderDecorators(node); 9452 this.incrementCounters(node, FaultID.LocalBuilderDecoratorNotSupported, autofix); 9453 } 9454 } 9455 9456 private checkEnumGetMemberValue(node: ts.ElementAccessExpression): void { 9457 if (!this.options.arkts2) { 9458 return; 9459 } 9460 9461 const symbol = this.tsUtils.trueSymbolAtLocation(node.expression); 9462 if (!symbol?.declarations) { 9463 return; 9464 } 9465 9466 for (const decl of symbol.declarations) { 9467 if (ts.isEnumDeclaration(decl) && this.shouldIncrementCounters(node)) { 9468 this.incrementCounters(node, FaultID.UnsupportPropNameFromValue); 9469 return; 9470 } 9471 } 9472 } 9473 9474 private shouldIncrementCounters(node: ts.ElementAccessExpression): boolean { 9475 const indexExpr = node.argumentExpression; 9476 if (!indexExpr) { 9477 return false; 9478 } 9479 if (ts.isStringLiteral(indexExpr) || ts.isNumericLiteral(indexExpr)) { 9480 return true; 9481 } 9482 const type = this.tsTypeChecker.getTypeAtLocation(indexExpr); 9483 const typeString = this.tsTypeChecker.typeToString(type); 9484 return typeString === 'number' || typeString === 'string'; 9485 } 9486 9487 private handleMakeObserved(node: ts.PropertyAccessExpression): void { 9488 if (!this.options.arkts2) { 9489 return; 9490 } 9491 9492 const name = node.name; 9493 if (name.getText() !== MAKE_OBSERVED) { 9494 return; 9495 } 9496 9497 const expr = node.expression; 9498 const symbol = this.tsTypeChecker.getSymbolAtLocation(expr); 9499 const importSpecifier = TsUtils.getDeclaration(symbol); 9500 if (!importSpecifier || !ts.isImportSpecifier(importSpecifier)) { 9501 return; 9502 } 9503 9504 const importDecl = ts.findAncestor(importSpecifier, ts.isImportDeclaration); 9505 if (!importDecl) { 9506 return; 9507 } 9508 9509 const moduleSpecifier = importDecl.moduleSpecifier; 9510 if (!ts.isStringLiteral(moduleSpecifier)) { 9511 return; 9512 } 9513 if (moduleSpecifier.text !== ARKUI_PACKAGE_NAME && moduleSpecifier.text !== ARKUI_STATE_MANAGEMENT) { 9514 return; 9515 } 9516 9517 this.incrementCounters(node, FaultID.MakeObservedIsNotSupported); 9518 } 9519 9520 private handlePropertyDeclarationForProp(node: ts.PropertyDeclaration): void { 9521 if (!this.options.arkts2) { 9522 return; 9523 } 9524 9525 const decorators = ts.getDecorators(node); 9526 if (!decorators || decorators.length === 0) { 9527 return; 9528 } 9529 9530 let decoratorName: string | undefined; 9531 if (ts.isIdentifier(decorators[0].expression)) { 9532 decoratorName = decorators[0].expression.getText(); 9533 } else if (ts.isCallExpression(decorators[0].expression) && ts.isIdentifier(decorators[0].expression.expression)) { 9534 decoratorName = decorators[0].expression.expression.getText(); 9535 } 9536 9537 if (!decoratorName) { 9538 return; 9539 } 9540 9541 const autofix = this.autofixer?.fixPropDecorator(decorators[0], decoratorName); 9542 switch (decoratorName) { 9543 case PropDecoratorName.Prop: 9544 this.incrementCounters(node, FaultID.PropDecoratorNotSupported, autofix); 9545 break; 9546 case PropDecoratorName.StorageProp: 9547 this.incrementCounters(node, FaultID.StoragePropDecoratorNotSupported, autofix); 9548 break; 9549 case PropDecoratorName.LocalStorageProp: 9550 this.incrementCounters(node, FaultID.LocalStoragePropDecoratorNotSupported, autofix); 9551 break; 9552 default: 9553 } 9554 } 9555 9556 private handleVariableDeclarationForProp(node: ts.VariableDeclaration): void { 9557 if (!this.options.arkts2) { 9558 return; 9559 } 9560 9561 const callExpr = node.initializer; 9562 if (!callExpr || !ts.isCallExpression(callExpr)) { 9563 return; 9564 } 9565 9566 const propertyAccessExpr = callExpr.expression; 9567 if (!ts.isPropertyAccessExpression(propertyAccessExpr)) { 9568 return; 9569 } 9570 9571 const storage = propertyAccessExpr.expression; 9572 if ( 9573 !ts.isIdentifier(storage) || 9574 !this.isTargetStorageType(storage, [StorageTypeName.LocalStorage, StorageTypeName.AppStorage]) 9575 ) { 9576 return; 9577 } 9578 9579 const functionName = propertyAccessExpr.name.getText(); 9580 switch (functionName) { 9581 case PropFunctionName.Prop: 9582 this.incrementCounters(node, FaultID.PropFunctionNotSupported); 9583 break; 9584 case PropFunctionName.SetAndProp: 9585 this.incrementCounters(node, FaultID.SetAndPropFunctionNotSupported); 9586 break; 9587 default: 9588 } 9589 } 9590 9591 private isTargetStorageType(storage: ts.Identifier, targetTypes: string[]): boolean { 9592 const decl = this.tsUtils.getDeclarationNode(storage); 9593 if (!decl || decl.getSourceFile() !== storage.getSourceFile()) { 9594 return targetTypes.includes(storage.getText()); 9595 } 9596 9597 if (!ts.isVariableDeclaration(decl)) { 9598 return false; 9599 } 9600 9601 let storageType: ts.Node | undefined; 9602 if (decl.initializer) { 9603 if (ts.isNewExpression(decl.initializer)) { 9604 storageType = decl.initializer.expression; 9605 } else if (ts.isCallExpression(decl.initializer) && ts.isPropertyAccessExpression(decl.initializer.expression)) { 9606 storageType = decl.initializer.expression.expression; 9607 } 9608 } 9609 9610 if (!storageType || !ts.isIdentifier(storageType)) { 9611 return false; 9612 } 9613 9614 return targetTypes.includes(storageType.getText()); 9615 } 9616 9617 private handlePropertyAssignmentForProp(node: ts.PropertyAssignment): void { 9618 if (!this.options.arkts2) { 9619 return; 9620 } 9621 9622 const callExpr = node.parent.parent; 9623 if (!ts.isCallExpression(callExpr)) { 9624 return; 9625 } 9626 9627 const structDecl = TsUtils.getDeclaration(this.tsTypeChecker.getSymbolAtLocation(callExpr.expression)); 9628 if (!structDecl || !ts.isStructDeclaration(structDecl) || !structDecl.name) { 9629 return; 9630 } 9631 9632 const variable = node.name; 9633 if (!ts.isIdentifier(variable)) { 9634 return; 9635 } 9636 9637 const targetNode = TypeScriptLinter.findVariableChangeNodeInStruct(variable, structDecl); 9638 if (!targetNode) { 9639 return; 9640 } 9641 9642 const targetDecl = TsUtils.getDeclaration(this.tsTypeChecker.getSymbolAtLocation(targetNode)); 9643 if (!targetDecl || !ts.isPropertyDeclaration(targetDecl)) { 9644 return; 9645 } 9646 9647 const decorators = ts.getDecorators(targetDecl); 9648 if (!decorators || decorators.length === 0) { 9649 return; 9650 } 9651 9652 const decorator = decorators[0]; 9653 const decoratorName = TsUtils.getDecoratorName(decorator); 9654 if (decoratorName === PropDecoratorName.Prop) { 9655 this.incrementCounters(node, FaultID.PropNeedCallMethodForDeepCopy); 9656 } 9657 } 9658 9659 private static findVariableChangeNodeInStruct( 9660 variable: ts.Identifier, 9661 structDecl: ts.StructDeclaration 9662 ): ts.MemberName | undefined { 9663 let changeNode: ts.MemberName | undefined; 9664 9665 function traverse(node: ts.Node): void { 9666 if (changeNode) { 9667 return; 9668 } 9669 9670 if (ts.isPropertyAccessExpression(node)) { 9671 if ( 9672 node.expression.kind === ts.SyntaxKind.ThisKeyword && 9673 node.name.getText() === variable.getText() && 9674 (ts.findAncestor(node, ts.isPostfixUnaryExpression) || 9675 ts.findAncestor(node, ts.isPrefixUnaryExpression) || 9676 ts.findAncestor(node, ts.isBinaryExpression)) 9677 ) { 9678 changeNode = node.name; 9679 } 9680 } 9681 9682 ts.forEachChild(node, traverse); 9683 } 9684 9685 traverse(structDecl); 9686 return changeNode; 9687 } 9688 9689 private getIdentifierForAwaitExpr(awaitExpr: ts.AwaitExpression): IdentifierAndArguments { 9690 void this; 9691 9692 let ident: undefined | ts.Identifier; 9693 let args: ts.NodeArray<ts.Expression> | undefined; 9694 9695 const expr = awaitExpr.expression; 9696 if (ts.isCallExpression(expr)) { 9697 if (ts.isIdentifier(expr.expression)) { 9698 ident = expr.expression; 9699 } 9700 9701 if (ts.isPropertyAccessExpression(expr.expression)) { 9702 if (ts.isIdentifier(expr.expression.name)) { 9703 ident = expr.expression.name; 9704 } 9705 } 9706 args = expr.arguments; 9707 } else if (ts.isIdentifier(expr)) { 9708 ident = expr; 9709 } 9710 9711 return { ident, args }; 9712 } 9713 9714 private handleAwaitExpression(awaitExpr: ts.AwaitExpression): void { 9715 if (!this.options.arkts2 || !this.useStatic) { 9716 return; 9717 } 9718 const { ident, args } = this.getIdentifierForAwaitExpr(awaitExpr); 9719 if (!ident) { 9720 return; 9721 } 9722 9723 if (!this.tsUtils.isJsImport(ident)) { 9724 return; 9725 } 9726 9727 const declaration = this.tsUtils.getDeclarationNode(ident); 9728 if (!declaration) { 9729 return; 9730 } 9731 9732 if ( 9733 ts.isFunctionDeclaration(declaration) && 9734 TsUtils.hasModifier(declaration.modifiers, ts.SyntaxKind.AsyncKeyword) 9735 ) { 9736 const autofix = this.autofixer?.fixAwaitJsCallExpression(ident, args); 9737 this.incrementCounters(awaitExpr, FaultID.NoAwaitJsPromise, autofix); 9738 return; 9739 } 9740 9741 if (ts.isMethodDeclaration(declaration) && TsUtils.hasModifier(declaration.modifiers, ts.SyntaxKind.AsyncKeyword)) { 9742 const autofix = this.autofixer?.fixAwaitJsMethodCallExpression(ident, args); 9743 this.incrementCounters(awaitExpr, FaultID.NoAwaitJsPromise, autofix); 9744 return; 9745 } 9746 9747 if (!ts.isVariableDeclaration(declaration)) { 9748 return; 9749 } 9750 9751 const type = this.tsTypeChecker.getTypeAtLocation(declaration); 9752 const typeString = this.tsTypeChecker.typeToString(type); 9753 9754 if (typeString.split('<')[0] !== 'Promise') { 9755 return; 9756 } 9757 9758 const autofix = this.autofixer?.fixAwaitJsPromise(ident); 9759 this.incrementCounters(awaitExpr, FaultID.NoAwaitJsPromise, autofix); 9760 } 9761 9762 private handleNotsLikeSmartTypeOnCallExpression(tsCallExpr: ts.CallExpression, callSignature: ts.Signature): void { 9763 if (!this.options.arkts2) { 9764 return; 9765 } 9766 const isContinue = 9767 ts.isCallExpression(tsCallExpr) && 9768 ts.isIdentifier(tsCallExpr.expression) && 9769 !ts.isReturnStatement(tsCallExpr.parent); 9770 if (!isContinue || !tsCallExpr.arguments) { 9771 return; 9772 } 9773 const declaration = callSignature.getDeclaration(); 9774 if (!declaration || !ts.isFunctionDeclaration(declaration)) { 9775 return; 9776 } 9777 const parameterTypes = declaration.parameters?.map((param) => { 9778 const paramType = this.tsTypeChecker.getTypeAtLocation(param); 9779 return this.tsTypeChecker.typeToString(paramType); 9780 }); 9781 tsCallExpr.arguments.forEach((arg, index) => { 9782 if (index >= parameterTypes.length) { 9783 return; 9784 } 9785 const expectedType = parameterTypes[index]; 9786 const actualSym = this.tsTypeChecker.getSymbolAtLocation(arg); 9787 const decl = TsUtils.getDeclaration(actualSym); 9788 if (decl && ts.isParameter(decl) && decl.type) { 9789 const actualType = this.tsTypeChecker.getTypeFromTypeNode(decl.type); 9790 const actualTypeName = this.tsTypeChecker.typeToString(actualType); 9791 if (actualTypeName !== expectedType) { 9792 this.incrementCounters(arg, FaultID.NoTsLikeSmartType); 9793 } 9794 } 9795 }); 9796 } 9797 9798 private handleNotsLikeSmartTypeOnAsExpression(tsAsExpr: ts.AsExpression): void { 9799 if (!this.options.arkts2) { 9800 return; 9801 } 9802 const asType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.type); 9803 const originType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression); 9804 const originTypeStr = this.tsTypeChecker.typeToString(originType); 9805 if (originTypeStr === 'never' && this.tsTypeChecker.typeToString(asType) !== originTypeStr) { 9806 this.incrementCounters(tsAsExpr, FaultID.NoTsLikeSmartType); 9807 } 9808 } 9809 9810 private handleAssignmentNotsLikeSmartType(tsBinaryExpr: ts.BinaryExpression): void { 9811 if (!this.options.arkts2) { 9812 return; 9813 } 9814 9815 if (this.isPriorityInThreadInfo(tsBinaryExpr)) { 9816 this.incrementCounters(tsBinaryExpr, FaultID.NoTsLikeSmartType); 9817 } 9818 } 9819 9820 private isPriorityInThreadInfo(node: ts.BinaryExpression): boolean { 9821 if (!ts.isBinaryExpression(node)) { 9822 return false; 9823 } 9824 9825 // Handle both regular assignment and 'as' type assertion 9826 let right: ts.Expression = ts.isAsExpression(node.right) ? node.right.expression : node.right; 9827 if (!ts.isPropertyAccessExpression(right)) { 9828 return false; 9829 } 9830 9831 const propertyName = right.name; 9832 if (!ts.isIdentifier(propertyName)) { 9833 return false; 9834 } 9835 9836 const object = right.expression; 9837 if (!ts.isIdentifier(object)) { 9838 return false; 9839 } 9840 9841 const symbol = this.tsTypeChecker.getSymbolAtLocation(object); 9842 if (!symbol) { 9843 return false; 9844 } 9845 9846 const type = this.tsTypeChecker.getTypeOfSymbolAtLocation(symbol, object); 9847 const typeString = this.tsTypeChecker.typeToString(type); 9848 9849 for (const [typeName, properties] of Object.entries(ERROR_TASKPOOL_PROP_LIST)) { 9850 if (typeString === typeName && properties.has(propertyName.text)) { 9851 return true; 9852 } 9853 } 9854 9855 return false; 9856 } 9857 9858 private handleNotsLikeSmartType(classDecl: ts.ClassDeclaration): void { 9859 if (!this.options.arkts2) { 9860 return; 9861 } 9862 9863 const className = classDecl.name?.getText(); 9864 const { staticProps, instanceProps } = this.collectClassProperties(classDecl); 9865 9866 classDecl.members.forEach((member) => { 9867 if (!ts.isMethodDeclaration(member) || !member.body) { 9868 return; 9869 } 9870 9871 const methodReturnType = this.tsTypeChecker.getTypeAtLocation(member); 9872 this.checkMethodAndReturnStatements(member.body, className, methodReturnType, staticProps, instanceProps); 9873 }); 9874 } 9875 9876 private checkMethodAndReturnStatements( 9877 body: ts.Block, 9878 className: string | undefined, 9879 methodReturnType: ts.Type, 9880 staticProps: Map<string, ts.Type>, 9881 instanceProps: Map<string, ts.Type> 9882 ): void { 9883 body.forEachChild((node) => { 9884 if (!ts.isReturnStatement(node) || !node.expression) { 9885 return; 9886 } 9887 9888 const isStaticPropertyAccess = (node: ts.Expression, className: string): boolean => { 9889 return ( 9890 ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === className 9891 ); 9892 }; 9893 const isInstancePropertyAccess = (node: ts.Expression): boolean => { 9894 return ts.isPropertyAccessExpression(node) && node.expression.kind === ts.SyntaxKind.ThisKeyword; 9895 }; 9896 9897 if (className && isStaticPropertyAccess(node.expression, className)) { 9898 this.checkPropertyAccess(node, node.expression as ts.PropertyAccessExpression, staticProps, methodReturnType); 9899 return; 9900 } 9901 9902 if (isInstancePropertyAccess(node.expression)) { 9903 this.checkPropertyAccess(node, node.expression as ts.PropertyAccessExpression, instanceProps, methodReturnType); 9904 } 9905 }); 9906 } 9907 9908 private checkPropertyAccess( 9909 returnNode: ts.ReturnStatement, 9910 propAccess: ts.PropertyAccessExpression, 9911 propsMap: Map<string, ts.Type>, 9912 methodReturnType: ts.Type 9913 ): void { 9914 const propName = propAccess.name.getText(); 9915 const propType = propsMap.get(propName); 9916 9917 if (propType && this.isExactlySameType(propType, methodReturnType)) { 9918 return; 9919 } 9920 9921 this.incrementCounters(returnNode, FaultID.NoTsLikeSmartType); 9922 } 9923 9924 private collectClassProperties(classDecl: ts.ClassDeclaration): { 9925 staticProps: Map<string, ts.Type>; 9926 instanceProps: Map<string, ts.Type>; 9927 } { 9928 const result = { 9929 staticProps: new Map<string, ts.Type>(), 9930 instanceProps: new Map<string, ts.Type>() 9931 }; 9932 9933 classDecl.members.forEach((member) => { 9934 if (!ts.isPropertyDeclaration(member)) { 9935 return; 9936 } 9937 9938 const propName = member.name.getText(); 9939 const propType = this.tsTypeChecker.getTypeAtLocation(member); 9940 const isStatic = member.modifiers?.some((m) => { 9941 return m.kind === ts.SyntaxKind.StaticKeyword; 9942 }); 9943 9944 if (isStatic) { 9945 result.staticProps.set(propName, propType); 9946 } else { 9947 result.instanceProps.set(propName, propType); 9948 } 9949 }); 9950 9951 return result; 9952 } 9953 9954 private isExactlySameType(type1: ts.Type, type2: ts.Type): boolean { 9955 if (type2.getCallSignatures().length > 0) { 9956 const returnType = TsUtils.getFunctionReturnType(type2); 9957 return returnType ? 9958 this.tsTypeChecker.typeToString(type1) === this.tsTypeChecker.typeToString(returnType) : 9959 false; 9960 } 9961 return this.tsTypeChecker.typeToString(type1) === this.tsTypeChecker.typeToString(type2); 9962 } 9963 9964 private handleNumericBigintCompare(node: ts.BinaryExpression): void { 9965 if (!this.options.arkts2) { 9966 return; 9967 } 9968 const leftType = this.tsTypeChecker.getTypeAtLocation(node.left); 9969 const rightType = this.tsTypeChecker.getTypeAtLocation(node.right); 9970 9971 const isBigInt = (type: ts.Type): boolean => { 9972 return (type.flags & ts.TypeFlags.BigInt) !== 0 || (type.flags & ts.TypeFlags.BigIntLiteral) !== 0; 9973 }; 9974 const isNumber = (type: ts.Type): boolean => { 9975 return (type.flags & ts.TypeFlags.Number) !== 0 || (type.flags & ts.TypeFlags.NumberLiteral) !== 0; 9976 }; 9977 9978 const isBigIntAndNumberOperand = 9979 isNumber(leftType) && isBigInt(rightType) || isBigInt(leftType) && isNumber(rightType); 9980 if (isBigIntAndNumberOperand) { 9981 this.incrementCounters(node, FaultID.NumericBigintCompare); 9982 } 9983 } 9984 9985 private handleBigIntLiteral(node: ts.BigIntLiteral): void { 9986 if (!this.options.arkts2) { 9987 return; 9988 } 9989 const literalText = node.getText(); 9990 9991 if ((/^0[box]/i).test(literalText)) { 9992 this.incrementCounters(node, FaultID.NondecimalBigint); 9993 } 9994 } 9995 9996 private handleStructDeclarationForLayout(node: ts.StructDeclaration): void { 9997 if (!this.options.arkts2) { 9998 return; 9999 } 10000 10001 if (!node.name) { 10002 return; 10003 } 10004 10005 let hasTargetFunc = false; 10006 10007 const members = node.members; 10008 for (const member of members) { 10009 if (!ts.isMethodDeclaration(member)) { 10010 continue; 10011 } 10012 10013 if (customLayoutFunctionName.has(member.name.getText())) { 10014 hasTargetFunc = true; 10015 break; 10016 } 10017 } 10018 10019 if (!hasTargetFunc) { 10020 return; 10021 } 10022 10023 const decorators = ts.getDecorators(node); 10024 if (decorators) { 10025 for (const decorator of decorators) { 10026 const decoratorName = TsUtils.getDecoratorName(decorator); 10027 if (decoratorName && decoratorName === CustomDecoratorName.CustomLayout) { 10028 return; 10029 } 10030 } 10031 } 10032 10033 const autofix = this.autofixer?.fixCustomLayout(node); 10034 const name = node.name.getText(); 10035 const errorMsg = 10036 `The Custom component "${name}" with custom layout capability needs to add the "@CustomLayout" decorator ` + 10037 '(arkui-custom-layout-need-add-decorator)'; 10038 this.incrementCounters(node.name, FaultID.CustomLayoutNeedAddDecorator, autofix, errorMsg); 10039 } 10040 10041 private handleArkTSPropertyAccess(expr: ts.BinaryExpression): void { 10042 if (!this.useStatic || !this.options.arkts2 || !TypeScriptLinter.isBinaryOperations(expr.operatorToken.kind)) { 10043 return; 10044 } 10045 10046 const processExpression = (expr: ts.Expression): void => { 10047 const symbol = this.tsUtils.trueSymbolAtLocation(expr); 10048 if (this.isJsFileSymbol(symbol) || this.isJsFileExpression(expr)) { 10049 const autofix = this.autofixer?.fixInteropOperators(expr); 10050 this.incrementCounters(expr, FaultID.BinaryOperations, autofix); 10051 } 10052 }; 10053 10054 processExpression(expr.left); 10055 processExpression(expr.right); 10056 } 10057 10058 private static isBinaryOperations(kind: ts.SyntaxKind): boolean { 10059 const binaryOperators: ts.SyntaxKind[] = [ 10060 ts.SyntaxKind.PlusToken, 10061 ts.SyntaxKind.MinusToken, 10062 ts.SyntaxKind.AsteriskToken, 10063 ts.SyntaxKind.SlashToken, 10064 ts.SyntaxKind.PercentToken, 10065 ts.SyntaxKind.AsteriskAsteriskToken 10066 ]; 10067 return binaryOperators.includes(kind); 10068 } 10069 10070 private handleNumericLiteral(node: ts.Node): void { 10071 if (!this.options.arkts2 || !ts.isNumericLiteral(node)) { 10072 return; 10073 } 10074 const isInElementAccessExpression = (node: ts.NumericLiteral): boolean => { 10075 for (let parent = node.parent; parent; parent = parent.parent) { 10076 if (ts.isElementAccessExpression(parent)) { 10077 return true; 10078 } 10079 } 10080 return false; 10081 }; 10082 const isStandardFloatFormat = (): boolean => { 10083 const text = node.getText(); 10084 return (/\.\d*0+$/).test(text); 10085 }; 10086 const isNoNeedFix = 10087 isInElementAccessExpression(node) || 10088 'name' in node.parent && node.parent.name === node || 10089 isStandardFloatFormat(); 10090 if (isNoNeedFix) { 10091 return; 10092 } 10093 const value = parseFloat(node.text); 10094 const nodeText = node.getText(); 10095 const hasScientificOrRadixNotation = (/[a-zA-Z]/).test(nodeText); 10096 const isIntegerWithoutZero = Number.isInteger(value) && !nodeText.endsWith('.0'); 10097 if (isIntegerWithoutZero && !hasScientificOrRadixNotation) { 10098 const autofix = this.autofixer?.fixNumericLiteralIntToNumber(node); 10099 this.incrementCounters(node, FaultID.NumericSemantics, autofix); 10100 } 10101 } 10102 10103 private checkArrayUsageWithoutBound(accessExpr: ts.ElementAccessExpression): void { 10104 if (!this.options.arkts2 || !this.useStatic) { 10105 return; 10106 } 10107 10108 const arrayAccessInfo = this.getArrayAccessInfo(accessExpr); 10109 if (!arrayAccessInfo) { 10110 const accessArgument = accessExpr.argumentExpression; 10111 if (TypeScriptLinter.isFunctionCall(accessArgument)) { 10112 this.incrementCounters(accessExpr, FaultID.RuntimeArrayCheck); 10113 } 10114 return; 10115 } 10116 10117 const { arrayIdent } = arrayAccessInfo; 10118 const arraySym = this.tsUtils.trueSymbolAtLocation(arrayIdent); 10119 if (!arraySym) { 10120 return; 10121 } 10122 10123 const arrayDecl = TypeScriptLinter.findArrayDeclaration(arraySym); 10124 if (arrayDecl && TypeScriptLinter.isArrayCreatedWithOtherArrayLength(arrayDecl)) { 10125 return; 10126 } 10127 10128 const indexExpr = accessExpr.argumentExpression; 10129 const loopVarName = ts.isIdentifier(indexExpr) ? indexExpr.text : undefined; 10130 10131 const { isInSafeContext, isValidBoundCheck, isVarModifiedBeforeAccess } = this.analyzeSafeContext( 10132 accessExpr, 10133 loopVarName, 10134 arraySym 10135 ); 10136 10137 if (isInSafeContext) { 10138 if (!isValidBoundCheck || isVarModifiedBeforeAccess) { 10139 this.incrementCounters(arrayIdent.parent, FaultID.RuntimeArrayCheck); 10140 } 10141 } else { 10142 this.incrementCounters(arrayIdent.parent, FaultID.RuntimeArrayCheck); 10143 } 10144 } 10145 10146 private analyzeSafeContext( 10147 accessExpr: ts.ElementAccessExpression, 10148 loopVarName: string | undefined, 10149 arraySym: ts.Symbol 10150 ): { isInSafeContext: boolean; isValidBoundCheck: boolean; isVarModifiedBeforeAccess: boolean } { 10151 const context = TypeScriptLinter.findSafeContext(accessExpr); 10152 if (!context) { 10153 return { isInSafeContext: false, isValidBoundCheck: false, isVarModifiedBeforeAccess: false }; 10154 } 10155 10156 return this.analyzeContextSafety(context, accessExpr, loopVarName, arraySym); 10157 } 10158 10159 static findSafeContext( 10160 accessExpr: ts.ElementAccessExpression 10161 ): { node: ts.ForStatement | ts.WhileStatement | ts.IfStatement } | void { 10162 let currentNode: ts.Node | undefined = accessExpr; 10163 10164 while (currentNode) { 10165 if (ts.isForStatement(currentNode) || ts.isWhileStatement(currentNode) || ts.isIfStatement(currentNode)) { 10166 return { node: currentNode }; 10167 } 10168 currentNode = currentNode.parent; 10169 } 10170 10171 return undefined; 10172 } 10173 10174 private analyzeContextSafety( 10175 context: { node: ts.ForStatement | ts.WhileStatement | ts.IfStatement }, 10176 accessExpr: ts.ElementAccessExpression, 10177 loopVarName: string | undefined, 10178 arraySym: ts.Symbol 10179 ): { isInSafeContext: boolean; isValidBoundCheck: boolean; isVarModifiedBeforeAccess: boolean } { 10180 const { node } = context; 10181 10182 if (!loopVarName) { 10183 return { 10184 isInSafeContext: true, 10185 isValidBoundCheck: false, 10186 isVarModifiedBeforeAccess: false 10187 }; 10188 } 10189 10190 const analysis = this.analyzeStatementType(node, accessExpr, loopVarName, arraySym); 10191 10192 return { 10193 isInSafeContext: true, 10194 isValidBoundCheck: analysis.isValidBoundCheck, 10195 isVarModifiedBeforeAccess: analysis.isVarModifiedBeforeAccess 10196 }; 10197 } 10198 10199 private analyzeStatementType( 10200 node: ts.ForStatement | ts.WhileStatement | ts.IfStatement, 10201 accessExpr: ts.ElementAccessExpression, 10202 loopVarName: string, 10203 arraySym: ts.Symbol 10204 ): { isValidBoundCheck: boolean; isVarModifiedBeforeAccess: boolean } { 10205 switch (node.kind) { 10206 case ts.SyntaxKind.ForStatement: 10207 return this.analyzeForStatement(node, accessExpr, loopVarName, arraySym); 10208 case ts.SyntaxKind.WhileStatement: 10209 return this.analyzeWhileStatement(node, accessExpr, loopVarName, arraySym); 10210 case ts.SyntaxKind.IfStatement: 10211 return this.analyzeIfStatement(node, accessExpr, loopVarName, arraySym); 10212 default: 10213 return { isValidBoundCheck: false, isVarModifiedBeforeAccess: false }; 10214 } 10215 } 10216 10217 private analyzeForStatement( 10218 forNode: ts.ForStatement, 10219 accessExpr: ts.ElementAccessExpression, 10220 loopVarName: string, 10221 arraySym: ts.Symbol 10222 ): { isValidBoundCheck: boolean; isVarModifiedBeforeAccess: boolean } { 10223 const isValidBoundCheck = forNode.condition ? 10224 this.checkBoundCondition(forNode.condition, loopVarName, arraySym) : 10225 false; 10226 10227 const isVarModifiedBeforeAccess = forNode.statement ? 10228 TypeScriptLinter.checkVarModifiedBeforeNode(forNode.statement, accessExpr, loopVarName) : 10229 false; 10230 10231 return { isValidBoundCheck, isVarModifiedBeforeAccess }; 10232 } 10233 10234 private analyzeWhileStatement( 10235 whileNode: ts.WhileStatement, 10236 accessExpr: ts.ElementAccessExpression, 10237 loopVarName: string, 10238 arraySym: ts.Symbol 10239 ): { isValidBoundCheck: boolean; isVarModifiedBeforeAccess: boolean } { 10240 const isValidBoundCheck = whileNode.expression ? 10241 this.checkBoundCondition(whileNode.expression, loopVarName, arraySym) : 10242 false; 10243 10244 const isVarModifiedBeforeAccess = whileNode.statement ? 10245 TypeScriptLinter.checkVarModifiedBeforeNode(whileNode.statement, accessExpr, loopVarName) : 10246 false; 10247 10248 return { isValidBoundCheck, isVarModifiedBeforeAccess }; 10249 } 10250 10251 private analyzeIfStatement( 10252 ifNode: ts.IfStatement, 10253 accessExpr: ts.ElementAccessExpression, 10254 loopVarName: string, 10255 arraySym: ts.Symbol 10256 ): { isValidBoundCheck: boolean; isVarModifiedBeforeAccess: boolean } { 10257 const isValidBoundCheck = ifNode.expression ? 10258 this.checkBoundCondition(ifNode.expression, loopVarName, arraySym) : 10259 false; 10260 10261 let isVarModifiedBeforeAccess = false; 10262 const statementBlock = ts.isBlock(ifNode.thenStatement) ? ifNode.thenStatement : undefined; 10263 if (statementBlock) { 10264 isVarModifiedBeforeAccess = TypeScriptLinter.checkVarModifiedBeforeNode(statementBlock, accessExpr, loopVarName); 10265 } 10266 10267 return { isValidBoundCheck, isVarModifiedBeforeAccess }; 10268 } 10269 10270 private checkBoundCondition(condition: ts.Expression, varName: string, arraySym: ts.Symbol): boolean { 10271 if (ts.isBinaryExpression(condition)) { 10272 const { left, right, operatorToken } = condition; 10273 10274 if (this.checkVarLessThanArrayLength(left, right, operatorToken, varName, arraySym)) { 10275 return true; 10276 } 10277 10278 if (this.checkArrayLengthGreaterThanVar(left, right, operatorToken, varName, arraySym)) { 10279 return true; 10280 } 10281 10282 if (ts.isIdentifier(left) && left.text === varName && ts.isNumericLiteral(right)) { 10283 const value = parseFloat(right.text); 10284 if ( 10285 operatorToken.kind === ts.SyntaxKind.GreaterThanEqualsToken && value <= 0 || 10286 operatorToken.kind === ts.SyntaxKind.GreaterThanToken && value < 0 10287 ) { 10288 return true; 10289 } 10290 } 10291 10292 return this.checkBoundCondition(left, varName, arraySym) || this.checkBoundCondition(right, varName, arraySym); 10293 } 10294 10295 return false; 10296 } 10297 10298 private checkArrayLengthGreaterThanVar( 10299 left: ts.Expression, 10300 right: ts.Expression, 10301 operatorToken: ts.Token<ts.BinaryOperator>, 10302 varName: string, 10303 arraySym: ts.Symbol 10304 ): boolean { 10305 if ( 10306 ts.isPropertyAccessExpression(left) && 10307 left.name.text === 'length' && 10308 ts.isIdentifier(right) && 10309 right.text === varName 10310 ) { 10311 const leftArraySym = this.tsUtils.trueSymbolAtLocation(left.expression); 10312 if (leftArraySym === arraySym) { 10313 return ( 10314 operatorToken.kind === ts.SyntaxKind.GreaterThanToken || 10315 operatorToken.kind === ts.SyntaxKind.GreaterThanEqualsToken 10316 ); 10317 } 10318 } 10319 return false; 10320 } 10321 10322 private checkVarLessThanArrayLength( 10323 left: ts.Expression, 10324 right: ts.Expression, 10325 operatorToken: ts.Token<ts.BinaryOperator>, 10326 varName: string, 10327 arraySym: ts.Symbol 10328 ): boolean { 10329 return ( 10330 ts.isIdentifier(left) && 10331 left.text === varName && 10332 ts.isPropertyAccessExpression(right) && 10333 right.name.text === 'length' && 10334 (operatorToken.kind === ts.SyntaxKind.LessThanToken || 10335 operatorToken.kind === ts.SyntaxKind.LessThanEqualsToken) && 10336 this.tsUtils.trueSymbolAtLocation(right.expression) === arraySym 10337 ); 10338 } 10339 10340 private static traverseNodesUntilTarget( 10341 node: ts.Node, 10342 targetNode: ts.Node, 10343 varName: string, 10344 scopeStack: { shadowed: boolean; localVars: Set<string> }[], 10345 state: { targetFound: boolean; modified: boolean } 10346 ): void { 10347 if (node === targetNode) { 10348 state.targetFound = true; 10349 return; 10350 } 10351 10352 if (state.targetFound) { 10353 return; 10354 } 10355 10356 const newScope = this.handleNewScope(node, scopeStack); 10357 10358 TypeScriptLinter.getVariablesFromScope(node, varName, scopeStack); 10359 10360 if (this.isVariableModified(node, varName, scopeStack)) { 10361 state.modified = true; 10362 } 10363 10364 ts.forEachChild(node, (child) => { 10365 this.traverseNodesUntilTarget(child, targetNode, varName, scopeStack, state); 10366 }); 10367 10368 if (newScope) { 10369 scopeStack.pop(); 10370 } 10371 } 10372 10373 private static handleNewScope( 10374 node: ts.Node, 10375 scopeStack: { shadowed: boolean; localVars: Set<string> }[] 10376 ): { shadowed: boolean; localVars: Set<string> } | null { 10377 if (ts.isBlock(node) || ts.isFunctionLike(node) || ts.isCatchClause(node)) { 10378 const parentScope = scopeStack[scopeStack.length - 1]; 10379 const newScope = { 10380 shadowed: parentScope.shadowed, 10381 localVars: new Set<string>() 10382 }; 10383 scopeStack.push(newScope); 10384 return newScope; 10385 } 10386 return null; 10387 } 10388 10389 static getVariablesFromScope( 10390 node: ts.Node, 10391 varName: string, 10392 scopeStack: { shadowed: boolean; localVars: Set<string> }[] 10393 ): void { 10394 if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === varName) { 10395 const parent = node.parent; 10396 if ( 10397 ts.isVariableDeclarationList(parent) && 10398 (parent.flags & ts.NodeFlags.Let || parent.flags & ts.NodeFlags.Const) 10399 ) { 10400 scopeStack[scopeStack.length - 1].localVars.add(varName); 10401 } 10402 } 10403 10404 if (ts.isParameter(node) && ts.isIdentifier(node.name) && node.name.text === varName) { 10405 scopeStack[scopeStack.length - 1].localVars.add(varName); 10406 } 10407 } 10408 10409 private static isVariableModified( 10410 node: ts.Node, 10411 varName: string, 10412 scopeStack: { shadowed: boolean; localVars: Set<string> }[] 10413 ): boolean { 10414 if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) { 10415 return false; 10416 } 10417 10418 if (!ts.isIdentifier(node.left) || node.left.text !== varName) { 10419 return false; 10420 } 10421 10422 for (let i = scopeStack.length - 1; i >= 0; i--) { 10423 if (scopeStack[i].localVars.has(varName)) { 10424 return false; 10425 } 10426 } 10427 10428 return true; 10429 } 10430 10431 static checkVarModifiedBeforeNode(container: ts.Node, targetNode: ts.Node, varName: string): boolean { 10432 const scopeStack: { shadowed: boolean; localVars: Set<string> }[] = []; 10433 scopeStack.push({ shadowed: false, localVars: new Set() }); 10434 10435 const state = { 10436 targetFound: false, 10437 modified: false 10438 }; 10439 10440 this.traverseNodesUntilTarget(container, targetNode, varName, scopeStack, state); 10441 return state.modified; 10442 } 10443 10444 static isFunctionCall(node: ts.Node): boolean { 10445 return ts.isCallExpression(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node); 10446 } 10447 10448 private getArrayAccessInfo(expr: ts.ElementAccessExpression): false | ArrayAccess { 10449 if (!ts.isIdentifier(expr.expression)) { 10450 return false; 10451 } 10452 const baseType = this.tsTypeChecker.getTypeAtLocation(expr.expression); 10453 if (!this.tsUtils.isArray(baseType)) { 10454 return false; 10455 } 10456 const accessArgument = expr.argumentExpression; 10457 10458 TypeScriptLinter.isFunctionCall(accessArgument); 10459 10460 const checkNumericType = (node: ts.Node): boolean => { 10461 const argType = this.tsTypeChecker.getTypeAtLocation(node); 10462 return ( 10463 (argType.flags & ts.TypeFlags.NumberLike) !== 0 || 10464 argType.isUnionOrIntersection() && 10465 argType.types.some((t) => { 10466 return t.flags & ts.TypeFlags.NumberLike; 10467 }) 10468 ); 10469 }; 10470 10471 const isEnumMember = (node: ts.Node): boolean => { 10472 if (ts.isPropertyAccessExpression(node)) { 10473 const symbol = this.tsUtils.trueSymbolAtLocation(node); 10474 return !!symbol && (symbol.flags & ts.SymbolFlags.EnumMember) !== 0; 10475 } 10476 return false; 10477 }; 10478 10479 if (TypeScriptLinter.isFunctionCall(accessArgument)) { 10480 return false; 10481 } 10482 10483 if (checkNumericType(accessArgument) || isEnumMember(accessArgument)) { 10484 return { 10485 pos: expr.getEnd(), 10486 accessingIdentifier: accessArgument, 10487 arrayIdent: expr.expression 10488 }; 10489 } 10490 10491 return false; 10492 } 10493 10494 static isArrayCreatedWithOtherArrayLength(decl: ts.VariableDeclaration): boolean { 10495 if (!decl.initializer || !ts.isNewExpression(decl.initializer)) { 10496 return false; 10497 } 10498 10499 const newExpr = decl.initializer; 10500 return ( 10501 newExpr.arguments?.some((arg) => { 10502 return ts.isPropertyAccessExpression(arg) && arg.name.text === 'length'; 10503 }) ?? false 10504 ); 10505 } 10506 10507 static findArrayDeclaration(sym: ts.Symbol): ts.VariableDeclaration | undefined { 10508 const decls = sym.getDeclarations(); 10509 if (!decls) { 10510 return undefined; 10511 } 10512 10513 for (const decl of decls) { 10514 if (ts.isVariableDeclaration(decl)) { 10515 return decl; 10516 } 10517 } 10518 return undefined; 10519 } 10520} 10521