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