From 06827021cf17097fea63215f107c3973012bdffa Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 25 May 2021 10:09:54 +0200 Subject: [PATCH 1/2] (feat) svelte-check CLI help Add sade for - more robust / separated parsing of CLI args - adding a --help command --- packages/svelte-check/package.json | 2 + packages/svelte-check/rollup.config.js | 1 + packages/svelte-check/src/index.ts | 145 +++++++++-------------- packages/svelte-check/src/options.ts | 152 +++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 94 deletions(-) create mode 100644 packages/svelte-check/src/options.ts diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 5ac335bd0..3b945c066 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -24,6 +24,7 @@ "glob": "^7.1.6", "import-fresh": "^3.2.1", "minimist": "^1.2.5", + "sade": "^1.7.4", "source-map": "^0.7.3", "svelte-preprocess": "^4.0.0", "typescript": "*" @@ -45,6 +46,7 @@ "@tsconfig/node12": "^1.0.0", "@types/glob": "^7.1.1", "@types/minimist": "^1.2.0", + "@types/sade": "^1.7.2", "rollup": "^2.28.0", "rollup-plugin-cleanup": "^3.0.0", "rollup-plugin-copy": "^3.0.0", diff --git a/packages/svelte-check/rollup.config.js b/packages/svelte-check/rollup.config.js index be2532b77..e6c06b282 100644 --- a/packages/svelte-check/rollup.config.js +++ b/packages/svelte-check/rollup.config.js @@ -62,6 +62,7 @@ export default [ // Dependencies of svelte-language-server // we don't want to bundle and instead require them as dependencies 'typescript', + 'sade', 'svelte', 'svelte/compiler', 'svelte-preprocess', diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index 06e81ea04..3e335a9b2 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -2,24 +2,21 @@ * This code's groundwork is taken from https://github.com/vuejs/vetur/tree/master/vti */ +import { watch } from 'chokidar'; import * as fs from 'fs'; import glob from 'glob'; -import argv from 'minimist'; import * as path from 'path'; -import { SvelteCheck, SvelteCheckOptions } from 'svelte-language-server'; +import { SvelteCheck } from 'svelte-language-server'; import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol'; import { URI } from 'vscode-uri'; +import { parseOptions, SvelteCheckCliOptions } from './options'; import { + DEFAULT_FILTER, + DiagnosticFilter, HumanFriendlyWriter, MachineFriendlyWriter, - Writer, - DiagnosticFilter, - DEFAULT_FILTER + Writer } from './writers'; -import { watch } from 'chokidar'; - -const outputFormats = ['human', 'human-verbose', 'machine'] as const; -type OutputFormat = typeof outputFormats[number]; type Result = { fileCount: number; @@ -160,8 +157,8 @@ class DiagnosticsWatcher { } } -function createFilter(myArgs: argv.ParsedArgs): DiagnosticFilter { - switch (myArgs['threshold']) { +function createFilter(opts: SvelteCheckCliOptions): DiagnosticFilter { + switch (opts.threshold) { case 'error': return (d) => d.severity === DiagnosticSeverity.Error; case 'warning': @@ -173,96 +170,56 @@ function createFilter(myArgs: argv.ParsedArgs): DiagnosticFilter { } } -function instantiateWriter(myArgs: argv.ParsedArgs): Writer { - const outputFormat: OutputFormat = outputFormats.includes(myArgs['output']) - ? myArgs['output'] - : 'human-verbose'; - - const filter = createFilter(myArgs); +function instantiateWriter(opts: SvelteCheckCliOptions): Writer { + const filter = createFilter(opts); - if (outputFormat === 'human-verbose' || outputFormat === 'human') { - return new HumanFriendlyWriter(process.stdout, outputFormat === 'human-verbose', filter); + if (opts.outputFormat === 'human-verbose' || opts.outputFormat === 'human') { + return new HumanFriendlyWriter( + process.stdout, + opts.outputFormat === 'human-verbose', + filter + ); } else { return new MachineFriendlyWriter(process.stdout, filter); } } -function getOptions(myArgs: argv.ParsedArgs, workspacePath: string): SvelteCheckOptions { - let tsconfig = myArgs['tsconfig']; - if (tsconfig && !path.isAbsolute(tsconfig)) { - tsconfig = path.join(workspacePath, tsconfig); - } - - return { - compilerWarnings: stringToObj(myArgs['compiler-warnings']), - diagnosticSources: ( - myArgs['diagnostic-sources']?.split(',')?.map((s: string) => s.trim()) - ), - tsconfig - }; - - function stringToObj(str = '') { - return str - .split(',') - .map((s) => s.trim()) - .filter((s) => !!s) - .reduce((settings, setting) => { - const [name, val] = setting.split(':'); - if (val === 'error' || val === 'ignore') { - settings[name] = val; - } - return settings; - }, >{}); - } -} - -(async () => { - const myArgs = argv(process.argv.slice(1)); - let workspaceUri; - - let workspacePath = myArgs['workspace']; - if (workspacePath) { - if (!path.isAbsolute(workspacePath)) { - workspacePath = path.resolve(process.cwd(), workspacePath); - } - workspaceUri = URI.file(workspacePath); - } else { - workspaceUri = URI.file(process.cwd()); - } - - const writer = instantiateWriter(myArgs); - - const svelteCheck = new SvelteCheck( - workspaceUri.fsPath, - getOptions(myArgs, workspaceUri.fsPath) - ); - const filePathsToIgnore = myArgs['ignore']?.split(',') || []; - - if (myArgs['watch']) { - new DiagnosticsWatcher( - workspaceUri, - svelteCheck, - writer, - filePathsToIgnore, - !!myArgs['tsconfig'] - ); - } else { - if (!myArgs['tsconfig']) { - await openAllDocuments(workspaceUri, filePathsToIgnore, svelteCheck); - } - const result = await getDiagnostics(workspaceUri, writer, svelteCheck); - if ( - result && - result.errorCount === 0 && - (!myArgs['fail-on-warnings'] || result.warningCount === 0) && - (!myArgs['fail-on-hints'] || result.hintCount === 0) - ) { - process.exit(0); +parseOptions(async (opts) => { + try { + const writer = instantiateWriter(opts); + + const svelteCheck = new SvelteCheck(opts.workspaceUri.fsPath, { + compilerWarnings: opts.compilerWarnings, + diagnosticSources: opts.diagnosticSources, + tsconfig: opts.tsconfig + }); + + if (opts.watch) { + new DiagnosticsWatcher( + opts.workspaceUri, + svelteCheck, + writer, + opts.filePathsToIgnore, + !!opts.tsconfig + ); } else { - process.exit(1); + if (!opts.tsconfig) { + await openAllDocuments(opts.workspaceUri, opts.filePathsToIgnore, svelteCheck); + } + const result = await getDiagnostics(opts.workspaceUri, writer, svelteCheck); + if ( + result && + result.errorCount === 0 && + (!opts.failOnWarnings || result.warningCount === 0) && + (!opts.failOnHints || result.hintCount === 0) + ) { + process.exit(0); + } else { + process.exit(1); + } } + } catch (_err) { + console.error(_err); + console.error('svelte-check failed'); } -})().catch((_err) => { - console.error(_err); - console.error('svelte-check failed'); }); diff --git a/packages/svelte-check/src/options.ts b/packages/svelte-check/src/options.ts new file mode 100644 index 000000000..5a7ae8f9b --- /dev/null +++ b/packages/svelte-check/src/options.ts @@ -0,0 +1,152 @@ +import * as path from 'path'; +import sade from 'sade'; +import { URI } from 'vscode-uri'; + +export interface SvelteCheckCliOptions { + workspaceUri: URI; + outputFormat: OutputFormat; + watch: boolean; + tsconfig?: string; + filePathsToIgnore: string[]; + failOnWarnings: boolean; + failOnHints: boolean; + compilerWarnings: Record; + diagnosticSources: Array; + threshold: Threshold; +} + +// eslint-disable max-len +export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) { + const prog = sade('svelte-check', true) + .version('1.x') + .option( + '--workspace', + 'Path to your workspace. All subdirectories except node_modules and those listed in `--ignore` are checked' + ) + .option( + '--output', + 'What output format to use. Options are human, human-verbose, machine.', + 'human-verbose' + ) + .option( + '--watch', + 'Will not exit after one pass but keep watching files for changes and rerun diagnostics', + false + ) + .option( + '--tsconfig', + 'Pass a path to a tsconfig or jsconfig file. The path can be relative to the workspace path or absolute. Doing this means that only files matched by the files/include/exclude pattern of the config file are diagnosed. It also means that errors from TypeScript and JavaScript files are reported.' + ) + .option( + '--ignore', + 'Files/folders to ignore - relative to workspace root, comma-separated, inside quotes. Example: `--ignore "dist,build"`' + ) + .option( + '--fail-on-warnings', + 'Will also exit with error code when there are warnings', + false + ) + .option('--fail-on-hints', 'Will also exit with error code when there are hints', false) + .option( + '--compiler-warnings', + 'A list of Svelte compiler warning codes. Each entry defines whether that warning should be ignored or treated as an error. Warnings are comma-separated, between warning code and error level is a colon; all inside quotes. Example: `--compiler-warnings "css-unused-selector:ignore,unused-export-let:error"`' + ) + .option( + '--diagnostic-sources', + 'A list of diagnostic sources which should run diagnostics on your code. Possible values are `js` (includes TS), `svelte`, `css`. Comma-separated, inside quotes. By default all are active. Example: `--diagnostic-sources "js,svelte"`' + ) + .option( + '--threshold', + 'Filters the diagnostics to display. `error` will output only errors while `warning` will output warnings and errors.', + 'hint' + ) + .action((opts) => { + const workspaceUri = getWorkspaceUri(opts); + cb({ + workspaceUri, + outputFormat: getOutputFormat(opts), + watch: !!opts.watch, + tsconfig: getTsconfig(opts, workspaceUri.fsPath), + filePathsToIgnore: getFilepathsToIgnore(opts), + failOnWarnings: !!opts['fail-on-warnings'], + failOnHints: !!opts['fail-on-hints'], + compilerWarnings: getCompilerWarnings(opts), + diagnosticSources: getDiagnosticSources(opts), + threshold: getThreshold(opts) + }); + }); + + prog.parse(process.argv); +} +// eslint-enable max-len + +const outputFormats = ['human', 'human-verbose', 'machine'] as const; +type OutputFormat = typeof outputFormats[number]; + +function getOutputFormat(opts: Record): OutputFormat { + return outputFormats.includes(opts.output) ? opts.output : 'human-verbose'; +} + +function getWorkspaceUri(opts: Record) { + let workspaceUri; + let workspacePath = opts.workspace; + if (workspacePath) { + if (!path.isAbsolute(workspacePath)) { + workspacePath = path.resolve(process.cwd(), workspacePath); + } + workspaceUri = URI.file(workspacePath); + } else { + workspaceUri = URI.file(process.cwd()); + } + return workspaceUri; +} + +function getTsconfig(myArgs: Record, workspacePath: string) { + let tsconfig: string | undefined = myArgs.tsconfig; + if (tsconfig && !path.isAbsolute(tsconfig)) { + tsconfig = path.join(workspacePath, tsconfig); + } + return tsconfig; +} + +function getCompilerWarnings(opts: Record) { + return stringToObj(opts['compiler-warnings']); + + function stringToObj(str = '') { + return str + .split(',') + .map((s) => s.trim()) + .filter((s) => !!s) + .reduce((settings, setting) => { + const [name, val] = setting.split(':'); + if (val === 'error' || val === 'ignore') { + settings[name] = val; + } + return settings; + }, >{}); + } +} + +const diagnosticSources = ['js', 'css', 'svelte'] as const; +type DiagnosticSource = typeof diagnosticSources[number]; + +function getDiagnosticSources(opts: Record): DiagnosticSource[] { + const sources = opts['diagnostic-sources']; + return sources + ? sources + .split(',') + ?.map((s: string) => s.trim()) + .filter((s: any) => diagnosticSources.includes(s)) + : diagnosticSources; +} + +function getFilepathsToIgnore(opts: Record): string[] { + return opts.ignore?.split(',') || []; +} + +const thresholds = ['hint', 'warning', 'error'] as const; +type Threshold = typeof thresholds[number]; + +function getThreshold(opts: Record): Threshold { + return thresholds.includes(opts.threshold) ? opts.threshold : 'hint'; +} From b413956aec9ba15a336f25cfe2234c50ad8305e6 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 25 May 2021 10:13:29 +0200 Subject: [PATCH 2/2] lint --- packages/svelte-check/src/options.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte-check/src/options.ts b/packages/svelte-check/src/options.ts index 5a7ae8f9b..23c09df33 100644 --- a/packages/svelte-check/src/options.ts +++ b/packages/svelte-check/src/options.ts @@ -11,7 +11,7 @@ export interface SvelteCheckCliOptions { failOnWarnings: boolean; failOnHints: boolean; compilerWarnings: Record; - diagnosticSources: Array; + diagnosticSources: DiagnosticSource[]; threshold: Threshold; }