• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import * as path from 'node:path';
17import type * as ts from 'typescript';
18import type { CommandLineOptions } from './CommandLineOptions';
19import { faultsAttrs } from './FaultAttrs';
20import { faultDesc } from './FaultDesc';
21import { InteropTypescriptLinter } from './InteropTypescriptLinter';
22import type { LintOptions } from './LintOptions';
23import type { LintRunResult } from './LintRunResult';
24import type { ProblemInfo } from './ProblemInfo';
25import { ProblemSeverity } from './ProblemSeverity';
26import { FaultID } from './Problems';
27import { TypeScriptLinter, consoleLog } from './TypeScriptLinter';
28import { getTscDiagnostics } from './ts-diagnostics/GetTscDiagnostics';
29import { transformTscDiagnostics } from './ts-diagnostics/TransformTscDiagnostics';
30import {
31  ARKTS_IGNORE_DIRS_NO_OH_MODULES,
32  ARKTS_IGNORE_DIRS_OH_MODULES,
33  ARKTS_IGNORE_FILES
34} from './utils/consts/ArktsIgnorePaths';
35import { mergeArrayMaps } from './utils/functions/MergeArrayMaps';
36import { pathContainsDirectory } from './utils/functions/PathHelper';
37
38function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] {
39  let inputFiles = cmdOptions.inputFiles;
40  if (!cmdOptions.parsedConfigFile) {
41    return inputFiles;
42  }
43
44  inputFiles = cmdOptions.parsedConfigFile.fileNames;
45  if (cmdOptions.inputFiles.length <= 0) {
46    return inputFiles;
47  }
48
49  /*
50   * Apply linter only to the project source files that are specified
51   * as a command-line arguments. Other source files will be discarded.
52   */
53  const cmdInputsResolvedPaths = cmdOptions.inputFiles.map((x) => {
54    return path.resolve(x);
55  });
56  const configInputsResolvedPaths = inputFiles.map((x) => {
57    return path.resolve(x);
58  });
59  inputFiles = configInputsResolvedPaths.filter((x) => {
60    return cmdInputsResolvedPaths.some((y) => {
61      return x === y;
62    });
63  });
64
65  return inputFiles;
66}
67
68function countProblems(linter: TypeScriptLinter | InteropTypescriptLinter): [number, number] {
69  let errorNodesTotal = 0;
70  let warningNodes = 0;
71  for (let i = 0; i < FaultID.LAST_ID; i++) {
72    switch (faultsAttrs[i].severity) {
73      case ProblemSeverity.ERROR:
74        errorNodesTotal += linter.nodeCounters[i];
75        break;
76      case ProblemSeverity.WARNING:
77        warningNodes += linter.nodeCounters[i];
78        break;
79      default:
80    }
81  }
82
83  return [errorNodesTotal, warningNodes];
84}
85
86export function lint(options: LintOptions): LintRunResult {
87  const cmdOptions = options.cmdOptions;
88  const cancellationToken = options.cancellationToken;
89  const tscCompiledProgram = options.tscCompiledProgram;
90  const tsProgram = tscCompiledProgram.getProgram();
91
92  // Prepare list of input files for linter and retrieve AST for those files.
93  let inputFiles = prepareInputFilesList(cmdOptions);
94  inputFiles = inputFiles.filter((input) => {
95    return shouldProcessFile(options, input);
96  });
97  const srcFiles: ts.SourceFile[] = [];
98  for (const inputFile of inputFiles) {
99    const srcFile = tsProgram.getSourceFile(inputFile);
100    if (srcFile) {
101      srcFiles.push(srcFile);
102    }
103  }
104
105  const tscStrictDiagnostics = getTscDiagnostics(tscCompiledProgram, srcFiles);
106  const linter = options.isEtsFile ?
107    new TypeScriptLinter(
108      tsProgram.getTypeChecker(),
109      cmdOptions.enableAutofix,
110      cancellationToken,
111      options.incrementalLintInfo,
112      tscStrictDiagnostics,
113      options.reportAutofixCb,
114      options.isEtsFileCb
115    ) :
116    new InteropTypescriptLinter(
117      tsProgram.getTypeChecker(),
118      tsProgram.getCompilerOptions(),
119      options.incrementalLintInfo
120    );
121  const { errorNodes, problemsInfos } = lintFiles(srcFiles, linter);
122
123  consoleLog('\n\n\nFiles scanned: ', srcFiles.length);
124  consoleLog('\nFiles with problems: ', errorNodes);
125
126  const [errorNodesTotal, warningNodes] = countProblems(linter);
127
128  logTotalProblemsInfo(errorNodesTotal, warningNodes, linter);
129  logProblemsPercentageByFeatures(linter);
130
131  return {
132    errorNodes: errorNodesTotal,
133    problemsInfos: mergeArrayMaps(problemsInfos, transformTscDiagnostics(tscStrictDiagnostics))
134  };
135}
136
137function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter | InteropTypescriptLinter): LintRunResult {
138  let problemFiles = 0;
139  const problemsInfos: Map<string, ProblemInfo[]> = new Map();
140
141  for (const srcFile of srcFiles) {
142    const prevVisitedNodes = linter.totalVisitedNodes;
143    const prevErrorLines = linter.totalErrorLines;
144    const prevWarningLines = linter.totalWarningLines;
145    linter.errorLineNumbersString = '';
146    linter.warningLineNumbersString = '';
147    const nodeCounters: number[] = [];
148
149    for (let i = 0; i < FaultID.LAST_ID; i++) {
150      nodeCounters[i] = linter.nodeCounters[i];
151    }
152
153    linter.lint(srcFile);
154    // save results and clear problems array
155    problemsInfos.set(path.normalize(srcFile.fileName), [...linter.problemsInfos]);
156    linter.problemsInfos.length = 0;
157
158    // print results for current file
159    const fileVisitedNodes = linter.totalVisitedNodes - prevVisitedNodes;
160    const fileErrorLines = linter.totalErrorLines - prevErrorLines;
161    const fileWarningLines = linter.totalWarningLines - prevWarningLines;
162
163    problemFiles = countProblemFiles(
164      nodeCounters,
165      problemFiles,
166      srcFile,
167      fileVisitedNodes,
168      fileErrorLines,
169      fileWarningLines,
170      linter
171    );
172  }
173
174  return {
175    errorNodes: problemFiles,
176    problemsInfos: problemsInfos
177  };
178}
179
180// eslint-disable-next-line max-lines-per-function, max-params
181function countProblemFiles(
182  nodeCounters: number[],
183  filesNumber: number,
184  tsSrcFile: ts.SourceFile,
185  fileNodes: number,
186  fileErrorLines: number,
187  fileWarningLines: number,
188  linter: TypeScriptLinter | InteropTypescriptLinter
189): number {
190  let errorNodes = 0;
191  let warningNodes = 0;
192  for (let i = 0; i < FaultID.LAST_ID; i++) {
193    const nodeCounterDiff = linter.nodeCounters[i] - nodeCounters[i];
194    switch (faultsAttrs[i].severity) {
195      case ProblemSeverity.ERROR:
196        errorNodes += nodeCounterDiff;
197        break;
198      case ProblemSeverity.WARNING:
199        warningNodes += nodeCounterDiff;
200        break;
201      default:
202    }
203  }
204  if (errorNodes > 0) {
205    // eslint-disable-next-line no-param-reassign
206    filesNumber++;
207    const errorRate = (errorNodes / fileNodes * 100).toFixed(2);
208    const warningRate = (warningNodes / fileNodes * 100).toFixed(2);
209    consoleLog(tsSrcFile.fileName, ': ', '\n\tError lines: ', linter.errorLineNumbersString);
210    consoleLog(tsSrcFile.fileName, ': ', '\n\tWarning lines: ', linter.warningLineNumbersString);
211    consoleLog(
212      '\n\tError constructs (%): ',
213      errorRate,
214      '\t[ of ',
215      fileNodes,
216      ' constructs ], \t',
217      fileErrorLines,
218      ' lines'
219    );
220    consoleLog(
221      '\n\tWarning constructs (%): ',
222      warningRate,
223      '\t[ of ',
224      fileNodes,
225      ' constructs ], \t',
226      fileWarningLines,
227      ' lines'
228    );
229  }
230
231  return filesNumber;
232}
233
234function logTotalProblemsInfo(
235  errorNodes: number,
236  warningNodes: number,
237  linter: TypeScriptLinter | InteropTypescriptLinter
238): void {
239  const errorRate = (errorNodes / linter.totalVisitedNodes * 100).toFixed(2);
240  const warningRate = (warningNodes / linter.totalVisitedNodes * 100).toFixed(2);
241  consoleLog('\nTotal error constructs (%): ', errorRate);
242  consoleLog('\nTotal warning constructs (%): ', warningRate);
243  consoleLog('\nTotal error lines:', linter.totalErrorLines, ' lines\n');
244  consoleLog('\nTotal warning lines:', linter.totalWarningLines, ' lines\n');
245}
246
247function logProblemsPercentageByFeatures(linter: TypeScriptLinter | InteropTypescriptLinter): void {
248  consoleLog('\nPercent by features: ');
249  for (let i = 0; i < FaultID.LAST_ID; i++) {
250    const nodes = linter.nodeCounters[i];
251    const lines = linter.lineCounters[i];
252    const pecentage = (nodes / linter.totalVisitedNodes * 100).toFixed(2).padEnd(7, ' ');
253
254    consoleLog(faultDesc[i].padEnd(55, ' '), pecentage, '[', nodes, ' constructs / ', lines, ' lines]');
255  }
256}
257
258function shouldProcessFile(options: LintOptions, fileFsPath: string): boolean {
259  if (
260    ARKTS_IGNORE_FILES.some((ignore) => {
261      return path.basename(fileFsPath) === ignore;
262    })
263  ) {
264    return false;
265  }
266
267  if (
268    ARKTS_IGNORE_DIRS_NO_OH_MODULES.some((ignore) => {
269      return pathContainsDirectory(path.resolve(fileFsPath), ignore);
270    })
271  ) {
272    return false;
273  }
274
275  return (
276    !pathContainsDirectory(path.resolve(fileFsPath), ARKTS_IGNORE_DIRS_OH_MODULES) ||
277    !!options.isFileFromModuleCb?.(fileFsPath)
278  );
279}
280