• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { MigrationTool } from 'homecheck';
17import * as fs from 'node:fs';
18import * as os from 'node:os';
19import * as path from 'node:path';
20import * as readlineSync from 'readline-sync';
21import * as readline from 'node:readline';
22import type { CommandLineOptions } from '../lib/CommandLineOptions';
23import { getHomeCheckConfigInfo, transferIssues2ProblemInfo } from '../lib/HomeCheck';
24import { lint } from '../lib/LinterRunner';
25import { Logger } from '../lib/Logger';
26import type { ProblemInfo } from '../lib/ProblemInfo';
27import * as statistic from '../lib/statistics/scan/ProblemStatisticsCommonFunction';
28import type { ScanTaskRelatedInfo } from '../lib/statistics/scan/ScanTaskRelatedInfo';
29import { StatisticsReportInPutInfo } from '../lib/statistics/scan/StatisticsReportInPutInfo';
30import { TimeRecorder } from '../lib/statistics/scan/TimeRecorder';
31import { logStatistics } from '../lib/statistics/StatisticsLogger';
32import { compileLintOptions, getEtsLoaderPath } from '../lib/ts-compiler/Compiler';
33import { processSyncErr, processSyncOut } from '../lib/utils/functions/ProcessWrite';
34import { parseCommandLine } from './CommandLineParser';
35
36export function run(): void {
37  const commandLineArgs = process.argv.slice(2);
38  if (commandLineArgs.length === 0) {
39    Logger.info('Command line error: no arguments');
40    process.exit(-1);
41  }
42
43  const cmdOptions = parseCommandLine(commandLineArgs);
44  if (cmdOptions.linterOptions.migratorMode && cmdOptions.linterOptions.autofixCheck) {
45    const shouldRun = readlineSync.question('Do you want to run the linter and apply migration? (y/n): ').toLowerCase();
46    if (shouldRun !== 'y') {
47      console.log('Linting canceled by user.');
48      process.exit(0);
49    }
50  }
51
52  if (cmdOptions.devecoPluginModeDeprecated) {
53    runIdeModeDeprecated(cmdOptions);
54  } else if (cmdOptions.linterOptions.ideInteractive) {
55    runIdeInteractiveMode(cmdOptions);
56  } else {
57    const compileOptions = compileLintOptions(cmdOptions);
58    const result = lint(compileOptions, new TimeRecorder());
59    logStatistics(result.projectStats);
60    process.exit(result.hasErrors ? 1 : 0);
61  }
62}
63
64async function runIdeInteractiveMode(cmdOptions: CommandLineOptions): Promise<void> {
65  cmdOptions.followSdkSettings = true;
66  cmdOptions.disableStrictDiagnostics = true;
67  const timeRecorder = new TimeRecorder();
68  const scanTaskRelatedInfo = {} as ScanTaskRelatedInfo;
69  const compileOptions = compileLintOptions(cmdOptions);
70  scanTaskRelatedInfo.cmdOptions = cmdOptions;
71  scanTaskRelatedInfo.timeRecorder = timeRecorder;
72  scanTaskRelatedInfo.compileOptions = compileOptions;
73  await executeScanTask(scanTaskRelatedInfo);
74
75  const statisticsReportInPutInfo = scanTaskRelatedInfo.statisticsReportInPutInfo;
76  statisticsReportInPutInfo.statisticsReportName = 'scan-problems-statistics.json';
77  statisticsReportInPutInfo.totalProblemNumbers = getTotalProblemNumbers(scanTaskRelatedInfo.mergedProblems);
78  statisticsReportInPutInfo.cmdOptions = cmdOptions;
79  statisticsReportInPutInfo.timeRecorder = timeRecorder;
80
81  if (!cmdOptions.linterOptions.migratorMode && statisticsReportInPutInfo.cmdOptions.linterOptions.projectFolderList) {
82    await statistic.generateScanProbelemStatisticsReport(statisticsReportInPutInfo);
83  }
84
85  const mergedProblems = scanTaskRelatedInfo.mergedProblems;
86  const reportData = Object.fromEntries(mergedProblems);
87  const reportName: string = 'scan-report.json';
88  await statistic.generateReportFile(reportName, reportData, cmdOptions.outputFilePath);
89  for (const [filePath, problems] of mergedProblems) {
90    const reportLine = JSON.stringify({ filePath, problems }) + '\n';
91    await processSyncOut(reportLine);
92  }
93  await processSyncErr('{"content":"report finish","messageType":1,"indictor":1}\n');
94  process.exit(0);
95}
96
97function getTotalProblemNumbers(mergedProblems: Map<string, ProblemInfo[]>): number {
98  let totalProblemNumbers: number = 0;
99  for (const problems of mergedProblems.values()) {
100    totalProblemNumbers += problems.length;
101  }
102  return totalProblemNumbers;
103}
104
105async function executeScanTask(scanTaskRelatedInfo: ScanTaskRelatedInfo): Promise<void> {
106  const cmdOptions = scanTaskRelatedInfo.cmdOptions;
107  scanTaskRelatedInfo.statisticsReportInPutInfo = new StatisticsReportInPutInfo();
108  scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToNumbersMap = new Map();
109  scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToAutoFixedNumbersMap = new Map();
110  scanTaskRelatedInfo.mergedProblems = new Map<string, ProblemInfo[]>();
111  if (cmdOptions.linterOptions.arkts2 && cmdOptions.homecheck) {
112    await executeHomeCheckTask(scanTaskRelatedInfo);
113  }
114
115  if (!cmdOptions.skipLinter) {
116    executeLintTask(scanTaskRelatedInfo);
117  }
118}
119
120async function executeHomeCheckTask(scanTaskRelatedInfo: ScanTaskRelatedInfo): Promise<void> {
121  const cmdOptions = scanTaskRelatedInfo.cmdOptions;
122  const { ruleConfigInfo, projectConfigInfo } = getHomeCheckConfigInfo(cmdOptions);
123  let migrationTool: MigrationTool | null = new MigrationTool(ruleConfigInfo, projectConfigInfo);
124  await migrationTool.buildCheckEntry();
125  scanTaskRelatedInfo.timeRecorder.startScan();
126  scanTaskRelatedInfo.timeRecorder.setHomeCheckCountStatus(true);
127  const result = await migrationTool.start();
128  migrationTool = null;
129  scanTaskRelatedInfo.homeCheckResult = transferIssues2ProblemInfo(result);
130  for (const [filePath, problems] of scanTaskRelatedInfo.homeCheckResult) {
131    if (!scanTaskRelatedInfo.mergedProblems.has(filePath)) {
132      scanTaskRelatedInfo.mergedProblems.set(filePath, []);
133    }
134    statistic.accumulateRuleNumbers(
135      problems,
136      scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToNumbersMap,
137      scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToAutoFixedNumbersMap
138    );
139    scanTaskRelatedInfo.statisticsReportInPutInfo.arkOnePointOneProblemNumbers +=
140      statistic.getArktsOnePointOneProlemNumbers(problems);
141    scanTaskRelatedInfo.mergedProblems.get(filePath)!.push(...problems);
142  }
143}
144
145function executeLintTask(scanTaskRelatedInfo: ScanTaskRelatedInfo): void {
146  const cmdOptions = scanTaskRelatedInfo.cmdOptions;
147  const compileOptions = scanTaskRelatedInfo.compileOptions;
148  const homeCheckResult = scanTaskRelatedInfo.homeCheckResult;
149  if (!scanTaskRelatedInfo.timeRecorder.getHomeCheckCountStatus()) {
150    scanTaskRelatedInfo.timeRecorder.startScan();
151  }
152  const result = lint(
153    compileOptions,
154    scanTaskRelatedInfo.timeRecorder,
155    getEtsLoaderPath(compileOptions),
156    homeCheckResult
157  );
158  for (const [filePath, problems] of result.problemsInfos) {
159    statistic.accumulateRuleNumbers(
160      problems,
161      scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToNumbersMap,
162      scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToAutoFixedNumbersMap
163    );
164    scanTaskRelatedInfo.statisticsReportInPutInfo.arkOnePointOneProblemNumbers +=
165      statistic.getArktsOnePointOneProlemNumbers(problems);
166    mergeLintProblems(filePath, problems, scanTaskRelatedInfo.mergedProblems, cmdOptions);
167  }
168}
169
170function mergeLintProblems(
171  filePath: string,
172  problems: ProblemInfo[],
173  mergedProblems: Map<string, ProblemInfo[]>,
174  cmdOptions: CommandLineOptions
175): void {
176  if (!mergedProblems.has(filePath)) {
177    mergedProblems.set(filePath, []);
178  }
179  let filteredProblems = problems;
180  mergedProblems.get(filePath)!.push(...filteredProblems);
181
182  if (cmdOptions.scanWholeProjectInHomecheck) {
183    for (const file of mergedProblems.keys()) {
184      if (cmdOptions.inputFiles.includes(file)) {
185        continue;
186      }
187      const totalProblems = mergedProblems.get(file);
188      if (totalProblems === undefined) {
189        continue;
190      }
191      filteredProblems = totalProblems.filter((problem) => {
192        return problem.rule.includes('s2d');
193      });
194      if (filteredProblems.length > 0) {
195        mergedProblems.set(file, filteredProblems);
196      } else {
197        mergedProblems.delete(file);
198      }
199    }
200  }
201}
202
203function getTempFileName(): string {
204  return path.join(os.tmpdir(), Math.floor(Math.random() * 10000000).toString() + '_linter_tmp_file.ts');
205}
206
207function showJSONMessage(problems: ProblemInfo[][]): void {
208  const jsonMessage = problems[0].map((x) => {
209    return {
210      line: x.line,
211      column: x.column,
212      start: x.start,
213      end: x.end,
214      type: x.type,
215      suggest: x.suggest,
216      rule: x.rule,
217      severity: x.severity,
218      autofix: x.autofix
219    };
220  });
221  Logger.info(`{"linter messages":${JSON.stringify(jsonMessage)}}`);
222}
223
224function runIdeModeDeprecated(cmdOptions: CommandLineOptions): void {
225  const tmpFileName = getTempFileName();
226  // read data from stdin
227  const writeStream = fs.createWriteStream(tmpFileName, { flags: 'w' });
228  const rl = readline.createInterface({
229    input: process.stdin,
230    output: writeStream,
231    terminal: false
232  });
233
234  rl.on('line', (line: string) => {
235    fs.appendFileSync(tmpFileName, line + '\n');
236  });
237  rl.once('close', () => {
238    // end of input
239    writeStream.close();
240    cmdOptions.inputFiles = [tmpFileName];
241    if (cmdOptions.parsedConfigFile) {
242      cmdOptions.parsedConfigFile.fileNames.push(tmpFileName);
243    }
244    const compileOptions = compileLintOptions(cmdOptions);
245    const result = lint(compileOptions, new TimeRecorder());
246    const problems = Array.from(result.problemsInfos.values());
247    if (problems.length === 1) {
248      showJSONMessage(problems);
249    } else {
250      Logger.error('Unexpected error: could not lint file');
251    }
252    fs.unlinkSync(tmpFileName);
253  });
254}
255