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