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 fs from 'node:fs';
17import * as os from 'node:os';
18import * as path from 'node:path';
19import * as readline from 'node:readline';
20import type { CommandLineOptions } from '../lib/CommandLineOptions';
21import { lint } from '../lib/LinterRunner';
22import { Logger } from '../lib/Logger';
23import type { ProblemInfo } from '../lib/ProblemInfo';
24import { TypeScriptLinter } from '../lib/TypeScriptLinter';
25import { parseCommandLine } from './CommandLineParser';
26import { compileLintOptions } from './Compiler';
27import type { LintOptions } from '../lib/LintOptions';
28
29export function run(): void {
30  const commandLineArgs = process.argv.slice(2);
31  if (commandLineArgs.length === 0) {
32    Logger.info('Command line error: no arguments');
33    process.exit(-1);
34  }
35
36  const cmdOptions = parseCommandLine(commandLineArgs);
37
38  if (cmdOptions.testMode) {
39    TypeScriptLinter.testMode = true;
40  }
41
42  TypeScriptLinter.initGlobals();
43
44  if (!cmdOptions.ideMode) {
45    const compileOptions = compileLintOptions(cmdOptions);
46    const result = lint(compileOptions, getEtsLoaderPath(compileOptions));
47    process.exit(result.errorNodes > 0 ? 1 : 0);
48  } else {
49    runIDEMode(cmdOptions);
50  }
51}
52
53function getTempFileName(): string {
54  return path.join(os.tmpdir(), Math.floor(Math.random() * 10000000).toString() + '_linter_tmp_file.ts');
55}
56
57function showJSONMessage(problems: ProblemInfo[][]): void {
58  const jsonMessage = problems[0].map((x) => {
59    return {
60      line: x.line,
61      column: x.column,
62      start: x.start,
63      end: x.end,
64      type: x.type,
65      suggest: x.suggest,
66      rule: x.rule,
67      severity: x.severity,
68      autofix: x.autofix
69    };
70  });
71  Logger.info(`{"linter messages":${JSON.stringify(jsonMessage)}}`);
72}
73
74function runIDEMode(cmdOptions: CommandLineOptions): void {
75  TypeScriptLinter.ideMode = true;
76  const tmpFileName = getTempFileName();
77  // read data from stdin
78  const writeStream = fs.createWriteStream(tmpFileName, { flags: 'w' });
79  const rl = readline.createInterface({
80    input: process.stdin,
81    output: writeStream,
82    terminal: false
83  });
84
85  rl.on('line', (line: string) => {
86    fs.appendFileSync(tmpFileName, line + '\n');
87  });
88  rl.once('close', () => {
89    // end of input
90    writeStream.close();
91    cmdOptions.inputFiles = [tmpFileName];
92    if (cmdOptions.parsedConfigFile) {
93      cmdOptions.parsedConfigFile.fileNames.push(tmpFileName);
94    }
95    const compileOptions = compileLintOptions(cmdOptions);
96    const result = lint(compileOptions, getEtsLoaderPath(compileOptions));
97    const problems = Array.from(result.problemsInfos.values());
98    if (problems.length === 1) {
99      showJSONMessage(problems);
100    } else {
101      Logger.error('Unexpected error: could not lint file');
102    }
103    fs.unlinkSync(tmpFileName);
104  });
105}
106
107export function getEtsLoaderPath(compileOptions: LintOptions): string | undefined {
108  const tsProgram = compileOptions.tscCompiledProgram.getProgram();
109  return tsProgram.getCompilerOptions().etsLoaderPath;
110}
111