1/* 2 * Copyright (c) 2023-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 { Logger } from '../lib/Logger'; 17import { logTscDiagnostic } from '../lib/utils/functions/LogTscDiagnostic'; 18import type { CommandLineOptions } from '../lib/CommandLineOptions'; 19import { Command, Option } from 'commander'; 20import * as ts from 'typescript'; 21import * as fs from 'node:fs'; 22import * as path from 'node:path'; 23 24const TS_EXT = '.ts'; 25const TSX_EXT = '.tsx'; 26const ETS_EXT = '.ets'; 27 28let inputFiles: string[]; 29let responseFile = ''; 30function addSrcFile(value: string): void { 31 if (value.startsWith('@')) { 32 responseFile = value; 33 } else { 34 inputFiles.push(value); 35 } 36} 37 38const getFiles = (dir: string): string[] => { 39 const resultFiles: string[] = []; 40 41 const files = fs.readdirSync(dir); 42 for (let i = 0; i < files.length; ++i) { 43 const name = path.join(dir, files[i]); 44 if (fs.statSync(name).isDirectory()) { 45 resultFiles.push(...getFiles(name)); 46 } else { 47 const extension = path.extname(name); 48 if (extension === TS_EXT || extension === TSX_EXT || extension === ETS_EXT) { 49 resultFiles.push(name); 50 } 51 } 52 } 53 54 return resultFiles; 55}; 56 57function addProjectFolder(projectFolder: string, previous: string[]): string[] { 58 return previous.concat([projectFolder]); 59} 60 61function formCommandLineOptions(program: Command): CommandLineOptions { 62 const opts: CommandLineOptions = { 63 inputFiles: inputFiles, 64 warningsAsErrors: false, 65 enableAutofix: false, 66 arkts2: false 67 }; 68 const options = program.opts(); 69 if (options.TSC_Errors) { 70 opts.logTscErrors = true; 71 } 72 if (options.devecoPluginMode) { 73 opts.ideMode = true; 74 } 75 if (options.testMode) { 76 opts.testMode = true; 77 } 78 if (options.projectFolder) { 79 doProjectFolderArg(options.projectFolder, opts); 80 } 81 if (options.project) { 82 doProjectArg(options.project, opts); 83 } 84 if (options.autofix) { 85 opts.enableAutofix = true; 86 } 87 if (options.arkts2) { 88 opts.arkts2 = true; 89 } 90 if (options.warningsAsErrors) { 91 opts.warningsAsErrors = true; 92 } 93 return opts; 94} 95 96export function parseCommandLine(commandLineArgs: string[]): CommandLineOptions { 97 const program = new Command(); 98 program.name('tslinter').description('Linter for TypeScript sources'). 99 version('0.0.1'); 100 program. 101 option('-E, --TSC_Errors', 'show error messages from Tsc'). 102 option('--test-mode', 'run linter as if running TS test files'). 103 option('--deveco-plugin-mode', 'run as IDE plugin'). 104 option('-p, --project <project_file>', 'path to TS project config file'). 105 option('--project-folder <project_folder>', 'path to folder containig TS files to verify', addProjectFolder, []). 106 option('--autofix', 'automatically fix problems found by linter'). 107 option('--arkts-2', 'enable ArkTS 2.0 mode'). 108 addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true)); 109 program.argument('[srcFile...]', 'files to be verified', addSrcFile); 110 111 inputFiles = []; 112 // method parse() eats two first args, so make them dummy 113 let cmdArgs: string[] = ['dummy', 'dummy']; 114 cmdArgs.push(...commandLineArgs); 115 program.parse(cmdArgs); 116 if (responseFile !== '') { 117 try { 118 // eslint-disable-next-line no-param-reassign 119 commandLineArgs = fs. 120 readFileSync(responseFile.slice(1)). 121 toString(). 122 split('\n'). 123 filter((e) => { 124 return e.trimEnd(); 125 }); 126 cmdArgs = ['dummy', 'dummy']; 127 cmdArgs.push(...commandLineArgs); 128 program.parse(cmdArgs); 129 } catch (error) { 130 Logger.error('Failed to read response file: ' + error); 131 process.exit(-1); 132 } 133 } 134 135 return formCommandLineOptions(program); 136} 137 138function doProjectFolderArg(prjFolders: string[], opts: CommandLineOptions): void { 139 for (let i = 0; i < prjFolders.length; i++) { 140 const prjFolderPath = prjFolders[i]; 141 try { 142 opts.inputFiles.push(...getFiles(prjFolderPath)); 143 } catch (error) { 144 Logger.error('Failed to read folder: ' + error); 145 process.exit(-1); 146 } 147 } 148} 149 150function doProjectArg(cfgPath: string, opts: CommandLineOptions): void { 151 // Process project file (tsconfig.json) and retrieve config arguments. 152 const configFile = cfgPath; 153 154 const host: ts.ParseConfigFileHost = ts.sys as ts.System & ts.ParseConfigFileHost; 155 156 const diagnostics: ts.Diagnostic[] = []; 157 158 try { 159 const oldUnrecoverableDiagnostic = host.onUnRecoverableConfigFileDiagnostic; 160 host.onUnRecoverableConfigFileDiagnostic = (diagnostic: ts.Diagnostic): void => { 161 diagnostics.push(diagnostic); 162 }; 163 opts.parsedConfigFile = ts.getParsedCommandLineOfConfigFile(configFile, {}, host); 164 host.onUnRecoverableConfigFileDiagnostic = oldUnrecoverableDiagnostic; 165 166 if (opts.parsedConfigFile) { 167 diagnostics.push(...ts.getConfigFileParsingDiagnostics(opts.parsedConfigFile)); 168 } 169 170 if (diagnostics.length > 0) { 171 // Log all diagnostic messages and exit program. 172 Logger.error('Failed to read config file.'); 173 logTscDiagnostic(diagnostics, Logger.info); 174 process.exit(-1); 175 } 176 } catch (error) { 177 Logger.error('Failed to read config file: ' + error); 178 process.exit(-1); 179 } 180} 181