Skip to content

Commit d0162dd

Browse files
authored
(feat) svelte-check CLI help (#1027)
Add sade for - more robust / separated parsing of CLI args - adding a --help command
1 parent 17e5664 commit d0162dd

File tree

4 files changed

+206
-94
lines changed

4 files changed

+206
-94
lines changed

packages/svelte-check/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"glob": "^7.1.6",
2525
"import-fresh": "^3.2.1",
2626
"minimist": "^1.2.5",
27+
"sade": "^1.7.4",
2728
"source-map": "^0.7.3",
2829
"svelte-preprocess": "^4.0.0",
2930
"typescript": "*"
@@ -45,6 +46,7 @@
4546
"@tsconfig/node12": "^1.0.0",
4647
"@types/glob": "^7.1.1",
4748
"@types/minimist": "^1.2.0",
49+
"@types/sade": "^1.7.2",
4850
"rollup": "^2.28.0",
4951
"rollup-plugin-cleanup": "^3.0.0",
5052
"rollup-plugin-copy": "^3.0.0",

packages/svelte-check/rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default [
6262
// Dependencies of svelte-language-server
6363
// we don't want to bundle and instead require them as dependencies
6464
'typescript',
65+
'sade',
6566
'svelte',
6667
'svelte/compiler',
6768
'svelte-preprocess',

packages/svelte-check/src/index.ts

Lines changed: 51 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@
22
* This code's groundwork is taken from https://github.com/vuejs/vetur/tree/master/vti
33
*/
44

5+
import { watch } from 'chokidar';
56
import * as fs from 'fs';
67
import glob from 'glob';
7-
import argv from 'minimist';
88
import * as path from 'path';
9-
import { SvelteCheck, SvelteCheckOptions } from 'svelte-language-server';
9+
import { SvelteCheck } from 'svelte-language-server';
1010
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol';
1111
import { URI } from 'vscode-uri';
12+
import { parseOptions, SvelteCheckCliOptions } from './options';
1213
import {
14+
DEFAULT_FILTER,
15+
DiagnosticFilter,
1316
HumanFriendlyWriter,
1417
MachineFriendlyWriter,
15-
Writer,
16-
DiagnosticFilter,
17-
DEFAULT_FILTER
18+
Writer
1819
} from './writers';
19-
import { watch } from 'chokidar';
20-
21-
const outputFormats = ['human', 'human-verbose', 'machine'] as const;
22-
type OutputFormat = typeof outputFormats[number];
2320

2421
type Result = {
2522
fileCount: number;
@@ -160,8 +157,8 @@ class DiagnosticsWatcher {
160157
}
161158
}
162159

163-
function createFilter(myArgs: argv.ParsedArgs): DiagnosticFilter {
164-
switch (myArgs['threshold']) {
160+
function createFilter(opts: SvelteCheckCliOptions): DiagnosticFilter {
161+
switch (opts.threshold) {
165162
case 'error':
166163
return (d) => d.severity === DiagnosticSeverity.Error;
167164
case 'warning':
@@ -173,96 +170,56 @@ function createFilter(myArgs: argv.ParsedArgs): DiagnosticFilter {
173170
}
174171
}
175172

176-
function instantiateWriter(myArgs: argv.ParsedArgs): Writer {
177-
const outputFormat: OutputFormat = outputFormats.includes(myArgs['output'])
178-
? myArgs['output']
179-
: 'human-verbose';
180-
181-
const filter = createFilter(myArgs);
173+
function instantiateWriter(opts: SvelteCheckCliOptions): Writer {
174+
const filter = createFilter(opts);
182175

183-
if (outputFormat === 'human-verbose' || outputFormat === 'human') {
184-
return new HumanFriendlyWriter(process.stdout, outputFormat === 'human-verbose', filter);
176+
if (opts.outputFormat === 'human-verbose' || opts.outputFormat === 'human') {
177+
return new HumanFriendlyWriter(
178+
process.stdout,
179+
opts.outputFormat === 'human-verbose',
180+
filter
181+
);
185182
} else {
186183
return new MachineFriendlyWriter(process.stdout, filter);
187184
}
188185
}
189186

190-
function getOptions(myArgs: argv.ParsedArgs, workspacePath: string): SvelteCheckOptions {
191-
let tsconfig = myArgs['tsconfig'];
192-
if (tsconfig && !path.isAbsolute(tsconfig)) {
193-
tsconfig = path.join(workspacePath, tsconfig);
194-
}
195-
196-
return {
197-
compilerWarnings: stringToObj(myArgs['compiler-warnings']),
198-
diagnosticSources: <any>(
199-
myArgs['diagnostic-sources']?.split(',')?.map((s: string) => s.trim())
200-
),
201-
tsconfig
202-
};
203-
204-
function stringToObj(str = '') {
205-
return str
206-
.split(',')
207-
.map((s) => s.trim())
208-
.filter((s) => !!s)
209-
.reduce((settings, setting) => {
210-
const [name, val] = setting.split(':');
211-
if (val === 'error' || val === 'ignore') {
212-
settings[name] = val;
213-
}
214-
return settings;
215-
}, <Record<string, 'error' | 'ignore'>>{});
216-
}
217-
}
218-
219-
(async () => {
220-
const myArgs = argv(process.argv.slice(1));
221-
let workspaceUri;
222-
223-
let workspacePath = myArgs['workspace'];
224-
if (workspacePath) {
225-
if (!path.isAbsolute(workspacePath)) {
226-
workspacePath = path.resolve(process.cwd(), workspacePath);
227-
}
228-
workspaceUri = URI.file(workspacePath);
229-
} else {
230-
workspaceUri = URI.file(process.cwd());
231-
}
232-
233-
const writer = instantiateWriter(myArgs);
234-
235-
const svelteCheck = new SvelteCheck(
236-
workspaceUri.fsPath,
237-
getOptions(myArgs, workspaceUri.fsPath)
238-
);
239-
const filePathsToIgnore = myArgs['ignore']?.split(',') || [];
240-
241-
if (myArgs['watch']) {
242-
new DiagnosticsWatcher(
243-
workspaceUri,
244-
svelteCheck,
245-
writer,
246-
filePathsToIgnore,
247-
!!myArgs['tsconfig']
248-
);
249-
} else {
250-
if (!myArgs['tsconfig']) {
251-
await openAllDocuments(workspaceUri, filePathsToIgnore, svelteCheck);
252-
}
253-
const result = await getDiagnostics(workspaceUri, writer, svelteCheck);
254-
if (
255-
result &&
256-
result.errorCount === 0 &&
257-
(!myArgs['fail-on-warnings'] || result.warningCount === 0) &&
258-
(!myArgs['fail-on-hints'] || result.hintCount === 0)
259-
) {
260-
process.exit(0);
187+
parseOptions(async (opts) => {
188+
try {
189+
const writer = instantiateWriter(opts);
190+
191+
const svelteCheck = new SvelteCheck(opts.workspaceUri.fsPath, {
192+
compilerWarnings: opts.compilerWarnings,
193+
diagnosticSources: opts.diagnosticSources,
194+
tsconfig: opts.tsconfig
195+
});
196+
197+
if (opts.watch) {
198+
new DiagnosticsWatcher(
199+
opts.workspaceUri,
200+
svelteCheck,
201+
writer,
202+
opts.filePathsToIgnore,
203+
!!opts.tsconfig
204+
);
261205
} else {
262-
process.exit(1);
206+
if (!opts.tsconfig) {
207+
await openAllDocuments(opts.workspaceUri, opts.filePathsToIgnore, svelteCheck);
208+
}
209+
const result = await getDiagnostics(opts.workspaceUri, writer, svelteCheck);
210+
if (
211+
result &&
212+
result.errorCount === 0 &&
213+
(!opts.failOnWarnings || result.warningCount === 0) &&
214+
(!opts.failOnHints || result.hintCount === 0)
215+
) {
216+
process.exit(0);
217+
} else {
218+
process.exit(1);
219+
}
263220
}
221+
} catch (_err) {
222+
console.error(_err);
223+
console.error('svelte-check failed');
264224
}
265-
})().catch((_err) => {
266-
console.error(_err);
267-
console.error('svelte-check failed');
268225
});
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import * as path from 'path';
2+
import sade from 'sade';
3+
import { URI } from 'vscode-uri';
4+
5+
export interface SvelteCheckCliOptions {
6+
workspaceUri: URI;
7+
outputFormat: OutputFormat;
8+
watch: boolean;
9+
tsconfig?: string;
10+
filePathsToIgnore: string[];
11+
failOnWarnings: boolean;
12+
failOnHints: boolean;
13+
compilerWarnings: Record<string, 'error' | 'ignore'>;
14+
diagnosticSources: DiagnosticSource[];
15+
threshold: Threshold;
16+
}
17+
18+
// eslint-disable max-len
19+
export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) {
20+
const prog = sade('svelte-check', true)
21+
.version('1.x')
22+
.option(
23+
'--workspace',
24+
'Path to your workspace. All subdirectories except node_modules and those listed in `--ignore` are checked'
25+
)
26+
.option(
27+
'--output',
28+
'What output format to use. Options are human, human-verbose, machine.',
29+
'human-verbose'
30+
)
31+
.option(
32+
'--watch',
33+
'Will not exit after one pass but keep watching files for changes and rerun diagnostics',
34+
false
35+
)
36+
.option(
37+
'--tsconfig',
38+
'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.'
39+
)
40+
.option(
41+
'--ignore',
42+
'Files/folders to ignore - relative to workspace root, comma-separated, inside quotes. Example: `--ignore "dist,build"`'
43+
)
44+
.option(
45+
'--fail-on-warnings',
46+
'Will also exit with error code when there are warnings',
47+
false
48+
)
49+
.option('--fail-on-hints', 'Will also exit with error code when there are hints', false)
50+
.option(
51+
'--compiler-warnings',
52+
'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"`'
53+
)
54+
.option(
55+
'--diagnostic-sources',
56+
'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"`'
57+
)
58+
.option(
59+
'--threshold',
60+
'Filters the diagnostics to display. `error` will output only errors while `warning` will output warnings and errors.',
61+
'hint'
62+
)
63+
.action((opts) => {
64+
const workspaceUri = getWorkspaceUri(opts);
65+
cb({
66+
workspaceUri,
67+
outputFormat: getOutputFormat(opts),
68+
watch: !!opts.watch,
69+
tsconfig: getTsconfig(opts, workspaceUri.fsPath),
70+
filePathsToIgnore: getFilepathsToIgnore(opts),
71+
failOnWarnings: !!opts['fail-on-warnings'],
72+
failOnHints: !!opts['fail-on-hints'],
73+
compilerWarnings: getCompilerWarnings(opts),
74+
diagnosticSources: getDiagnosticSources(opts),
75+
threshold: getThreshold(opts)
76+
});
77+
});
78+
79+
prog.parse(process.argv);
80+
}
81+
// eslint-enable max-len
82+
83+
const outputFormats = ['human', 'human-verbose', 'machine'] as const;
84+
type OutputFormat = typeof outputFormats[number];
85+
86+
function getOutputFormat(opts: Record<string, any>): OutputFormat {
87+
return outputFormats.includes(opts.output) ? opts.output : 'human-verbose';
88+
}
89+
90+
function getWorkspaceUri(opts: Record<string, any>) {
91+
let workspaceUri;
92+
let workspacePath = opts.workspace;
93+
if (workspacePath) {
94+
if (!path.isAbsolute(workspacePath)) {
95+
workspacePath = path.resolve(process.cwd(), workspacePath);
96+
}
97+
workspaceUri = URI.file(workspacePath);
98+
} else {
99+
workspaceUri = URI.file(process.cwd());
100+
}
101+
return workspaceUri;
102+
}
103+
104+
function getTsconfig(myArgs: Record<string, any>, workspacePath: string) {
105+
let tsconfig: string | undefined = myArgs.tsconfig;
106+
if (tsconfig && !path.isAbsolute(tsconfig)) {
107+
tsconfig = path.join(workspacePath, tsconfig);
108+
}
109+
return tsconfig;
110+
}
111+
112+
function getCompilerWarnings(opts: Record<string, any>) {
113+
return stringToObj(opts['compiler-warnings']);
114+
115+
function stringToObj(str = '') {
116+
return str
117+
.split(',')
118+
.map((s) => s.trim())
119+
.filter((s) => !!s)
120+
.reduce((settings, setting) => {
121+
const [name, val] = setting.split(':');
122+
if (val === 'error' || val === 'ignore') {
123+
settings[name] = val;
124+
}
125+
return settings;
126+
}, <Record<string, 'error' | 'ignore'>>{});
127+
}
128+
}
129+
130+
const diagnosticSources = ['js', 'css', 'svelte'] as const;
131+
type DiagnosticSource = typeof diagnosticSources[number];
132+
133+
function getDiagnosticSources(opts: Record<string, any>): DiagnosticSource[] {
134+
const sources = opts['diagnostic-sources'];
135+
return sources
136+
? sources
137+
.split(',')
138+
?.map((s: string) => s.trim())
139+
.filter((s: any) => diagnosticSources.includes(s))
140+
: diagnosticSources;
141+
}
142+
143+
function getFilepathsToIgnore(opts: Record<string, any>): string[] {
144+
return opts.ignore?.split(',') || [];
145+
}
146+
147+
const thresholds = ['hint', 'warning', 'error'] as const;
148+
type Threshold = typeof thresholds[number];
149+
150+
function getThreshold(opts: Record<string, any>): Threshold {
151+
return thresholds.includes(opts.threshold) ? opts.threshold : 'hint';
152+
}

0 commit comments

Comments
 (0)