/* * Copyright (c) 2023-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Logger from '../utils/logger'; import { logTscDiagnostic, decodeAutofixInfo } from './Utils'; import { CommandLineOptions } from './CommandLineOptions'; import { AUTOFIX_ALL } from './Autofixer'; import { Command, Option } from 'commander'; import * as ts from 'typescript'; import * as fs from 'node:fs'; import * as path from 'node:path'; const TS_EXT = '.ts'; const TSX_EXT = '.tsx'; const ETS_EXT = '.ets'; const JSON_EXT = '.json'; const logger = Logger.getLogger(); let inputFiles: string[]; let responseFile = ''; function addSrcFile(value: string, dummy: string) { if(value.startsWith('@')) responseFile = value; else inputFiles.push(value); } const getFiles = (dir: string): string[] => { const resultFiles: string[] = []; const files = fs.readdirSync(dir); for (let i = 0; i < files.length; ++i) { let name = path.join(dir, files[i]); if (fs.statSync(name).isDirectory()) { resultFiles.push(...getFiles(name)); } else { let extension = path.extname(name); if (extension === TS_EXT || extension === TSX_EXT || extension === ETS_EXT) resultFiles.push(name); } } return resultFiles; }; function addProjectFolder(projectFolder: string, previous: any ) { return previous.concat([projectFolder]); } export function parseCommandLine(commandLineArgs: string[]): CommandLineOptions { const opts: CommandLineOptions = { inputFiles: [], warningsAsErrors: false }; const program = new Command(); program .name('tslinter') .description('Linter for TypeScript sources') .version('0.0.1'); program .option('-E, --TSC_Errors', 'show error messages from Tsc') .option('--relax', 'relax mode On') .option('--test-mode', 'run linter as if running TS test files') .option('--deveco-plugin-mode', 'run as IDE plugin') .option('-p, --project ', 'path to TS project config file') .option('--project-folder ', 'path to folder containig TS files to verify', addProjectFolder, []) .option('--autofix [autofix.json]', 'fix errors specified by JSON file (all if file is omitted)', (val: string, prev: string|boolean) => { return val.endsWith(JSON_EXT) ? val : true; }) .addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true)); program .argument('[srcFile...]', 'files to be verified', addSrcFile); opts.strictMode = true; // Default mode of the linter. inputFiles = []; let cmdArgs: string[] = ['dummy', 'dummy']; // method parse() eats two first args, so make them dummy cmdArgs.push(...commandLineArgs); program.parse(cmdArgs); if (responseFile !== '') { try { commandLineArgs = fs.readFileSync(responseFile.slice(1)).toString().split('\n').filter((e) => e.trimEnd()); cmdArgs = ['dummy', 'dummy']; cmdArgs.push(...commandLineArgs); program.parse( cmdArgs); } catch (error: any) { logger.error('Failed to read response file: ' + (error.message ?? error)); process.exit(-1) } } opts.inputFiles = inputFiles; const options = program.opts(); if (options.relax) opts.strictMode = false; if (options.TSC_Errors) opts.logTscErrors = true; if (options.devecoPluginMode) opts.ideMode = true; if (options.testMode) opts.testMode = true; if (options.projectFolder) doProjectFolderArg(options.projectFolder, opts); if (options.project) doProjectArg(options.project, opts); if (options.autofix) doAutofixArg(options.autofix, opts); if (options.warningsAsErrors) opts.warningsAsErrors = true; return opts; } function doProjectFolderArg(prjFolders: string[], opts: CommandLineOptions) { for( let i = 0; i < prjFolders.length; i++ ) { var prjFolderPath = prjFolders[ i ]; try { opts.inputFiles.push(...getFiles(prjFolderPath)); } catch (error: any) { logger.error('Failed to read folder: ' + (error.message ?? error)); process.exit(-1); } } } function doProjectArg(cfgPath: string, opts: CommandLineOptions) { // Process project file (tsconfig.json) and retrieve config arguments. const configFile = cfgPath; const host: ts.ParseConfigFileHost = ts.sys as ts.System & ts.ParseConfigFileHost; const diagnostics: ts.Diagnostic[] = []; try { const oldUnrecoverableDiagnostic = host.onUnRecoverableConfigFileDiagnostic; host.onUnRecoverableConfigFileDiagnostic = (diagnostic: ts.Diagnostic) => { diagnostics.push(diagnostic); }; opts.parsedConfigFile = ts.getParsedCommandLineOfConfigFile(configFile, {}, host); host.onUnRecoverableConfigFileDiagnostic = oldUnrecoverableDiagnostic; if (opts.parsedConfigFile) diagnostics.push(...ts.getConfigFileParsingDiagnostics(opts.parsedConfigFile)); if (diagnostics.length > 0) { // Log all diagnostic messages and exit program. logger.error('Failed to read config file.'); logTscDiagnostic(diagnostics, logger.info); process.exit(-1); } } catch (error: any) { logger.error('Failed to read config file: ' + (error.message ?? error)); process.exit(-1); } } function doAutofixArg(autofixOptVal: string|boolean, opts: CommandLineOptions) { if (typeof autofixOptVal === 'string') { let autofixInfoStr = fs.readFileSync(autofixOptVal).toString(); let autofixInfos = JSON.parse(autofixInfoStr); opts.autofixInfo = autofixInfos.autofixInfo.map((x: string) => decodeAutofixInfo(x)); } else opts.autofixInfo = [AUTOFIX_ALL]; }