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