13af6ab5fSopenharmony_ci/*
23af6ab5fSopenharmony_ci * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
33af6ab5fSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
43af6ab5fSopenharmony_ci * you may not use this file except in compliance with the License.
53af6ab5fSopenharmony_ci * You may obtain a copy of the License at
63af6ab5fSopenharmony_ci *
73af6ab5fSopenharmony_ci * http://www.apache.org/licenses/LICENSE-2.0
83af6ab5fSopenharmony_ci *
93af6ab5fSopenharmony_ci * Unless required by applicable law or agreed to in writing, software
103af6ab5fSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
113af6ab5fSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
123af6ab5fSopenharmony_ci * See the License for the specific language governing permissions and
133af6ab5fSopenharmony_ci * limitations under the License.
143af6ab5fSopenharmony_ci */
153af6ab5fSopenharmony_ci
163af6ab5fSopenharmony_ciimport { Logger } from '../lib/Logger';
173af6ab5fSopenharmony_ciimport { logTscDiagnostic } from '../lib/utils/functions/LogTscDiagnostic';
183af6ab5fSopenharmony_ciimport type { CommandLineOptions } from '../lib/CommandLineOptions';
193af6ab5fSopenharmony_ciimport { Command, Option } from 'commander';
203af6ab5fSopenharmony_ciimport * as ts from 'typescript';
213af6ab5fSopenharmony_ciimport * as fs from 'node:fs';
223af6ab5fSopenharmony_ciimport * as path from 'node:path';
233af6ab5fSopenharmony_ci
243af6ab5fSopenharmony_ciconst TS_EXT = '.ts';
253af6ab5fSopenharmony_ciconst TSX_EXT = '.tsx';
263af6ab5fSopenharmony_ciconst ETS_EXT = '.ets';
273af6ab5fSopenharmony_ci
283af6ab5fSopenharmony_cilet inputFiles: string[];
293af6ab5fSopenharmony_cilet responseFile = '';
303af6ab5fSopenharmony_cifunction addSrcFile(value: string): void {
313af6ab5fSopenharmony_ci  if (value.startsWith('@')) {
323af6ab5fSopenharmony_ci    responseFile = value;
333af6ab5fSopenharmony_ci  } else {
343af6ab5fSopenharmony_ci    inputFiles.push(value);
353af6ab5fSopenharmony_ci  }
363af6ab5fSopenharmony_ci}
373af6ab5fSopenharmony_ci
383af6ab5fSopenharmony_ciconst getFiles = (dir: string): string[] => {
393af6ab5fSopenharmony_ci  const resultFiles: string[] = [];
403af6ab5fSopenharmony_ci
413af6ab5fSopenharmony_ci  const files = fs.readdirSync(dir);
423af6ab5fSopenharmony_ci  for (let i = 0; i < files.length; ++i) {
433af6ab5fSopenharmony_ci    const name = path.join(dir, files[i]);
443af6ab5fSopenharmony_ci    if (fs.statSync(name).isDirectory()) {
453af6ab5fSopenharmony_ci      resultFiles.push(...getFiles(name));
463af6ab5fSopenharmony_ci    } else {
473af6ab5fSopenharmony_ci      const extension = path.extname(name);
483af6ab5fSopenharmony_ci      if (extension === TS_EXT || extension === TSX_EXT || extension === ETS_EXT) {
493af6ab5fSopenharmony_ci        resultFiles.push(name);
503af6ab5fSopenharmony_ci      }
513af6ab5fSopenharmony_ci    }
523af6ab5fSopenharmony_ci  }
533af6ab5fSopenharmony_ci
543af6ab5fSopenharmony_ci  return resultFiles;
553af6ab5fSopenharmony_ci};
563af6ab5fSopenharmony_ci
573af6ab5fSopenharmony_cifunction addProjectFolder(projectFolder: string, previous: string[]): string[] {
583af6ab5fSopenharmony_ci  return previous.concat([projectFolder]);
593af6ab5fSopenharmony_ci}
603af6ab5fSopenharmony_ci
613af6ab5fSopenharmony_cifunction formCommandLineOptions(program: Command): CommandLineOptions {
623af6ab5fSopenharmony_ci  const opts: CommandLineOptions = {
633af6ab5fSopenharmony_ci    inputFiles: inputFiles,
643af6ab5fSopenharmony_ci    warningsAsErrors: false,
653af6ab5fSopenharmony_ci    enableAutofix: false,
663af6ab5fSopenharmony_ci    arkts2: false
673af6ab5fSopenharmony_ci  };
683af6ab5fSopenharmony_ci  const options = program.opts();
693af6ab5fSopenharmony_ci  if (options.TSC_Errors) {
703af6ab5fSopenharmony_ci    opts.logTscErrors = true;
713af6ab5fSopenharmony_ci  }
723af6ab5fSopenharmony_ci  if (options.devecoPluginMode) {
733af6ab5fSopenharmony_ci    opts.ideMode = true;
743af6ab5fSopenharmony_ci  }
753af6ab5fSopenharmony_ci  if (options.testMode) {
763af6ab5fSopenharmony_ci    opts.testMode = true;
773af6ab5fSopenharmony_ci  }
783af6ab5fSopenharmony_ci  if (options.projectFolder) {
793af6ab5fSopenharmony_ci    doProjectFolderArg(options.projectFolder, opts);
803af6ab5fSopenharmony_ci  }
813af6ab5fSopenharmony_ci  if (options.project) {
823af6ab5fSopenharmony_ci    doProjectArg(options.project, opts);
833af6ab5fSopenharmony_ci  }
843af6ab5fSopenharmony_ci  if (options.autofix) {
853af6ab5fSopenharmony_ci    opts.enableAutofix = true;
863af6ab5fSopenharmony_ci  }
873af6ab5fSopenharmony_ci  if (options.arkts2) {
883af6ab5fSopenharmony_ci    opts.arkts2 = true;
893af6ab5fSopenharmony_ci  }
903af6ab5fSopenharmony_ci  if (options.warningsAsErrors) {
913af6ab5fSopenharmony_ci    opts.warningsAsErrors = true;
923af6ab5fSopenharmony_ci  }
933af6ab5fSopenharmony_ci  return opts;
943af6ab5fSopenharmony_ci}
953af6ab5fSopenharmony_ci
963af6ab5fSopenharmony_ciexport function parseCommandLine(commandLineArgs: string[]): CommandLineOptions {
973af6ab5fSopenharmony_ci  const program = new Command();
983af6ab5fSopenharmony_ci  program.name('tslinter').description('Linter for TypeScript sources').
993af6ab5fSopenharmony_ci    version('0.0.1');
1003af6ab5fSopenharmony_ci  program.
1013af6ab5fSopenharmony_ci    option('-E, --TSC_Errors', 'show error messages from Tsc').
1023af6ab5fSopenharmony_ci    option('--test-mode', 'run linter as if running TS test files').
1033af6ab5fSopenharmony_ci    option('--deveco-plugin-mode', 'run as IDE plugin').
1043af6ab5fSopenharmony_ci    option('-p, --project <project_file>', 'path to TS project config file').
1053af6ab5fSopenharmony_ci    option('--project-folder <project_folder>', 'path to folder containig TS files to verify', addProjectFolder, []).
1063af6ab5fSopenharmony_ci    option('--autofix', 'automatically fix problems found by linter').
1073af6ab5fSopenharmony_ci    option('--arkts-2', 'enable ArkTS 2.0 mode').
1083af6ab5fSopenharmony_ci    addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true));
1093af6ab5fSopenharmony_ci  program.argument('[srcFile...]', 'files to be verified', addSrcFile);
1103af6ab5fSopenharmony_ci
1113af6ab5fSopenharmony_ci  inputFiles = [];
1123af6ab5fSopenharmony_ci  // method parse() eats two first args, so make them dummy
1133af6ab5fSopenharmony_ci  let cmdArgs: string[] = ['dummy', 'dummy'];
1143af6ab5fSopenharmony_ci  cmdArgs.push(...commandLineArgs);
1153af6ab5fSopenharmony_ci  program.parse(cmdArgs);
1163af6ab5fSopenharmony_ci  if (responseFile !== '') {
1173af6ab5fSopenharmony_ci    try {
1183af6ab5fSopenharmony_ci      // eslint-disable-next-line no-param-reassign
1193af6ab5fSopenharmony_ci      commandLineArgs = fs.
1203af6ab5fSopenharmony_ci        readFileSync(responseFile.slice(1)).
1213af6ab5fSopenharmony_ci        toString().
1223af6ab5fSopenharmony_ci        split('\n').
1233af6ab5fSopenharmony_ci        filter((e) => {
1243af6ab5fSopenharmony_ci          return e.trimEnd();
1253af6ab5fSopenharmony_ci        });
1263af6ab5fSopenharmony_ci      cmdArgs = ['dummy', 'dummy'];
1273af6ab5fSopenharmony_ci      cmdArgs.push(...commandLineArgs);
1283af6ab5fSopenharmony_ci      program.parse(cmdArgs);
1293af6ab5fSopenharmony_ci    } catch (error) {
1303af6ab5fSopenharmony_ci      Logger.error('Failed to read response file: ' + error);
1313af6ab5fSopenharmony_ci      process.exit(-1);
1323af6ab5fSopenharmony_ci    }
1333af6ab5fSopenharmony_ci  }
1343af6ab5fSopenharmony_ci
1353af6ab5fSopenharmony_ci  return formCommandLineOptions(program);
1363af6ab5fSopenharmony_ci}
1373af6ab5fSopenharmony_ci
1383af6ab5fSopenharmony_cifunction doProjectFolderArg(prjFolders: string[], opts: CommandLineOptions): void {
1393af6ab5fSopenharmony_ci  for (let i = 0; i < prjFolders.length; i++) {
1403af6ab5fSopenharmony_ci    const prjFolderPath = prjFolders[i];
1413af6ab5fSopenharmony_ci    try {
1423af6ab5fSopenharmony_ci      opts.inputFiles.push(...getFiles(prjFolderPath));
1433af6ab5fSopenharmony_ci    } catch (error) {
1443af6ab5fSopenharmony_ci      Logger.error('Failed to read folder: ' + error);
1453af6ab5fSopenharmony_ci      process.exit(-1);
1463af6ab5fSopenharmony_ci    }
1473af6ab5fSopenharmony_ci  }
1483af6ab5fSopenharmony_ci}
1493af6ab5fSopenharmony_ci
1503af6ab5fSopenharmony_cifunction doProjectArg(cfgPath: string, opts: CommandLineOptions): void {
1513af6ab5fSopenharmony_ci  // Process project file (tsconfig.json) and retrieve config arguments.
1523af6ab5fSopenharmony_ci  const configFile = cfgPath;
1533af6ab5fSopenharmony_ci
1543af6ab5fSopenharmony_ci  const host: ts.ParseConfigFileHost = ts.sys as ts.System & ts.ParseConfigFileHost;
1553af6ab5fSopenharmony_ci
1563af6ab5fSopenharmony_ci  const diagnostics: ts.Diagnostic[] = [];
1573af6ab5fSopenharmony_ci
1583af6ab5fSopenharmony_ci  try {
1593af6ab5fSopenharmony_ci    const oldUnrecoverableDiagnostic = host.onUnRecoverableConfigFileDiagnostic;
1603af6ab5fSopenharmony_ci    host.onUnRecoverableConfigFileDiagnostic = (diagnostic: ts.Diagnostic): void => {
1613af6ab5fSopenharmony_ci      diagnostics.push(diagnostic);
1623af6ab5fSopenharmony_ci    };
1633af6ab5fSopenharmony_ci    opts.parsedConfigFile = ts.getParsedCommandLineOfConfigFile(configFile, {}, host);
1643af6ab5fSopenharmony_ci    host.onUnRecoverableConfigFileDiagnostic = oldUnrecoverableDiagnostic;
1653af6ab5fSopenharmony_ci
1663af6ab5fSopenharmony_ci    if (opts.parsedConfigFile) {
1673af6ab5fSopenharmony_ci      diagnostics.push(...ts.getConfigFileParsingDiagnostics(opts.parsedConfigFile));
1683af6ab5fSopenharmony_ci    }
1693af6ab5fSopenharmony_ci
1703af6ab5fSopenharmony_ci    if (diagnostics.length > 0) {
1713af6ab5fSopenharmony_ci      // Log all diagnostic messages and exit program.
1723af6ab5fSopenharmony_ci      Logger.error('Failed to read config file.');
1733af6ab5fSopenharmony_ci      logTscDiagnostic(diagnostics, Logger.info);
1743af6ab5fSopenharmony_ci      process.exit(-1);
1753af6ab5fSopenharmony_ci    }
1763af6ab5fSopenharmony_ci  } catch (error) {
1773af6ab5fSopenharmony_ci    Logger.error('Failed to read config file: ' + error);
1783af6ab5fSopenharmony_ci    process.exit(-1);
1793af6ab5fSopenharmony_ci  }
1803af6ab5fSopenharmony_ci}
181