• 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 { LinterConfig } from './LinterConfig';
23import type { LinterOptions } from './LinterOptions';
24import type { LintRunResult } from './LintRunResult';
25import { Logger } from './Logger';
26import type { ProblemInfo } from './ProblemInfo';
27import { ProblemSeverity } from './ProblemSeverity';
28import { FaultID } from './Problems';
29import { TypeScriptLinter } from './TypeScriptLinter';
30import { getTscDiagnostics } from './ts-diagnostics/GetTscDiagnostics';
31import { transformTscDiagnostics } from './ts-diagnostics/TransformTscDiagnostics';
32import {
33  ARKTS_IGNORE_DIRS_NO_OH_MODULES,
34  ARKTS_IGNORE_DIRS_OH_MODULES,
35  ARKTS_IGNORE_FILES
36} from './utils/consts/ArktsIgnorePaths';
37import { mergeArrayMaps } from './utils/functions/MergeArrayMaps';
38import { clearPathHelperCache, pathContainsDirectory } from './utils/functions/PathHelper';
39import { LibraryTypeCallDiagnosticChecker } from './utils/functions/LibraryTypeCallDiagnosticChecker';
40
41export function consoleLog(linterOptions: LinterOptions, ...args: unknown[]): void {
42  if (linterOptions.ideMode) {
43    return;
44  }
45  let outLine = '';
46  for (let k = 0; k < args.length; k++) {
47    outLine += `${args[k]} `;
48  }
49  Logger.info(outLine);
50}
51
52function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] {
53  let inputFiles = cmdOptions.inputFiles;
54  if (!cmdOptions.parsedConfigFile) {
55    return inputFiles;
56  }
57
58  inputFiles = cmdOptions.parsedConfigFile.fileNames;
59  if (cmdOptions.inputFiles.length <= 0) {
60    return inputFiles;
61  }
62
63  /*
64   * Apply linter only to the project source files that are specified
65   * as a command-line arguments. Other source files will be discarded.
66   */
67  const cmdInputsResolvedPaths = cmdOptions.inputFiles.map((x) => {
68    return path.resolve(x);
69  });
70  const configInputsResolvedPaths = inputFiles.map((x) => {
71    return path.resolve(x);
72  });
73  inputFiles = configInputsResolvedPaths.filter((x) => {
74    return cmdInputsResolvedPaths.some((y) => {
75      return x === y;
76    });
77  });
78
79  return inputFiles;
80}
81
82function countProblems(linter: TypeScriptLinter | InteropTypescriptLinter): [number, number] {
83  let errorNodesTotal = 0;
84  let warningNodes = 0;
85  for (let i = 0; i < FaultID.LAST_ID; i++) {
86    switch (faultsAttrs[i].severity) {
87      case ProblemSeverity.ERROR:
88        errorNodesTotal += linter.nodeCounters[i];
89        break;
90      case ProblemSeverity.WARNING:
91        warningNodes += linter.nodeCounters[i];
92        break;
93      default:
94    }
95  }
96
97  return [errorNodesTotal, warningNodes];
98}
99
100export function lint(config: LinterConfig, etsLoaderPath: string | undefined): LintRunResult {
101  const { cmdOptions, tscCompiledProgram } = config;
102  const tsProgram = tscCompiledProgram.getProgram();
103  const options = cmdOptions.linterOptions;
104
105  // Prepare list of input files for linter and retrieve AST for those files.
106  let inputFiles = prepareInputFilesList(cmdOptions);
107  inputFiles = inputFiles.filter((input) => {
108    return shouldProcessFile(options, input);
109  });
110  const srcFiles: ts.SourceFile[] = [];
111  for (const inputFile of inputFiles) {
112    const srcFile = tsProgram.getSourceFile(inputFile);
113    if (srcFile) {
114      srcFiles.push(srcFile);
115    }
116  }
117
118  const tscStrictDiagnostics = getTscDiagnostics(tscCompiledProgram, srcFiles);
119  LibraryTypeCallDiagnosticChecker.instance.rebuildTscDiagnostics(tscStrictDiagnostics);
120  const linter = !options.interopCheckMode ?
121    new TypeScriptLinter(tsProgram.getTypeChecker(), options, tscStrictDiagnostics) :
122    new InteropTypescriptLinter(tsProgram.getTypeChecker(), tsProgram.getCompilerOptions(), options, etsLoaderPath);
123  const { errorNodes, problemsInfos } = lintFiles(srcFiles, linter);
124  LibraryTypeCallDiagnosticChecker.instance.clear();
125  consoleLog(options, '\n\n\nFiles scanned: ', srcFiles.length);
126  consoleLog(options, '\nFiles with problems: ', errorNodes);
127
128  const [errorNodesTotal, warningNodes] = countProblems(linter);
129  logTotalProblemsInfo(errorNodesTotal, warningNodes, linter);
130  logProblemsPercentageByFeatures(linter);
131
132  freeMemory();
133
134  return {
135    errorNodes: errorNodesTotal,
136    problemsInfos: mergeArrayMaps(problemsInfos, transformTscDiagnostics(tscStrictDiagnostics))
137  };
138}
139
140function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter | InteropTypescriptLinter): LintRunResult {
141  let problemFiles = 0;
142  const problemsInfos: Map<string, ProblemInfo[]> = new Map();
143
144  for (const srcFile of srcFiles) {
145    const prevVisitedNodes = linter.totalVisitedNodes;
146    const prevErrorLines = linter.totalErrorLines;
147    const prevWarningLines = linter.totalWarningLines;
148    linter.errorLineNumbersString = '';
149    linter.warningLineNumbersString = '';
150    const nodeCounters: number[] = [];
151
152    for (let i = 0; i < FaultID.LAST_ID; i++) {
153      nodeCounters[i] = linter.nodeCounters[i];
154    }
155
156    linter.lint(srcFile);
157    // save results and clear problems array
158    problemsInfos.set(path.normalize(srcFile.fileName), [...linter.problemsInfos]);
159    linter.problemsInfos.length = 0;
160
161    // print results for current file
162    const fileVisitedNodes = linter.totalVisitedNodes - prevVisitedNodes;
163    const fileErrorLines = linter.totalErrorLines - prevErrorLines;
164    const fileWarningLines = linter.totalWarningLines - prevWarningLines;
165
166    problemFiles = countProblemFiles(
167      nodeCounters,
168      problemFiles,
169      srcFile,
170      fileVisitedNodes,
171      fileErrorLines,
172      fileWarningLines,
173      linter
174    );
175  }
176
177  return {
178    errorNodes: problemFiles,
179    problemsInfos: problemsInfos
180  };
181}
182
183// eslint-disable-next-line max-lines-per-function, max-params
184function countProblemFiles(
185  nodeCounters: number[],
186  filesNumber: number,
187  tsSrcFile: ts.SourceFile,
188  fileNodes: number,
189  fileErrorLines: number,
190  fileWarningLines: number,
191  linter: TypeScriptLinter | InteropTypescriptLinter
192): number {
193  let errorNodes = 0;
194  let warningNodes = 0;
195  for (let i = 0; i < FaultID.LAST_ID; i++) {
196    const nodeCounterDiff = linter.nodeCounters[i] - nodeCounters[i];
197    switch (faultsAttrs[i].severity) {
198      case ProblemSeverity.ERROR:
199        errorNodes += nodeCounterDiff;
200        break;
201      case ProblemSeverity.WARNING:
202        warningNodes += nodeCounterDiff;
203        break;
204      default:
205    }
206  }
207  if (errorNodes > 0) {
208    // eslint-disable-next-line no-param-reassign
209    filesNumber++;
210    const errorRate = (errorNodes / fileNodes * 100).toFixed(2);
211    const warningRate = (warningNodes / fileNodes * 100).toFixed(2);
212    consoleLog(linter.options, tsSrcFile.fileName, ': ', '\n\tError lines: ', linter.errorLineNumbersString);
213    consoleLog(linter.options, tsSrcFile.fileName, ': ', '\n\tWarning lines: ', linter.warningLineNumbersString);
214    consoleLog(
215      linter.options,
216      '\n\tError constructs (%): ',
217      errorRate,
218      '\t[ of ',
219      fileNodes,
220      ' constructs ], \t',
221      fileErrorLines,
222      ' lines'
223    );
224    consoleLog(
225      linter.options,
226      '\n\tWarning constructs (%): ',
227      warningRate,
228      '\t[ of ',
229      fileNodes,
230      ' constructs ], \t',
231      fileWarningLines,
232      ' lines'
233    );
234  }
235
236  return filesNumber;
237}
238
239function logTotalProblemsInfo(
240  errorNodes: number,
241  warningNodes: number,
242  linter: TypeScriptLinter | InteropTypescriptLinter
243): void {
244  const errorRate = (errorNodes / linter.totalVisitedNodes * 100).toFixed(2);
245  const warningRate = (warningNodes / linter.totalVisitedNodes * 100).toFixed(2);
246  consoleLog(linter.options, '\nTotal error constructs (%): ', errorRate);
247  consoleLog(linter.options, '\nTotal warning constructs (%): ', warningRate);
248  consoleLog(linter.options, '\nTotal error lines:', linter.totalErrorLines, ' lines\n');
249  consoleLog(linter.options, '\nTotal warning lines:', linter.totalWarningLines, ' lines\n');
250}
251
252function logProblemsPercentageByFeatures(linter: TypeScriptLinter | InteropTypescriptLinter): void {
253  consoleLog(linter.options, '\nPercent by features: ');
254  for (let i = 0; i < FaultID.LAST_ID; i++) {
255    const nodes = linter.nodeCounters[i];
256    const lines = linter.lineCounters[i];
257    const pecentage = (nodes / linter.totalVisitedNodes * 100).toFixed(2).padEnd(7, ' ');
258
259    consoleLog(linter.options, faultDesc[i].padEnd(55, ' '), pecentage, '[', nodes, ' constructs / ', lines, ' lines]');
260  }
261}
262
263function shouldProcessFile(options: LinterOptions, fileFsPath: string): boolean {
264  if (
265    ARKTS_IGNORE_FILES.some((ignore) => {
266      return path.basename(fileFsPath) === ignore;
267    })
268  ) {
269    return false;
270  }
271
272  if (
273    ARKTS_IGNORE_DIRS_NO_OH_MODULES.some((ignore) => {
274      return pathContainsDirectory(path.resolve(fileFsPath), ignore);
275    })
276  ) {
277    return false;
278  }
279
280  return (
281    !pathContainsDirectory(path.resolve(fileFsPath), ARKTS_IGNORE_DIRS_OH_MODULES) ||
282    !!options.isFileFromModuleCb?.(fileFsPath)
283  );
284}
285
286function freeMemory(): void {
287  clearPathHelperCache();
288}
289