Skip to content

Commit d34c4f2

Browse files
fix(cli): refactor CLI argument parser (#3765)
This commit refactors the argument parser we use for our CLI module. Doing so fixes an issue with certain flags supported by Jest which can be passed multiple times. For instance, in the following example: ``` jest --coverage --reporters="default" --reporters="jest-junit" ``` all of the values for the `--reporters` flag ("default" and "jest-junit") should be collected into an array of values, instead of simply recording whichever value is farther to the right (Stencil's behavior before this commit). To support passing such arguments to the `stencil test` subcommand this commit adds a new recursive-descent parser in `src/cli/parse-flags.ts` to replace the somewhat ad-hoc approach that was there previously. It parses the following grammar: ``` CLIArguments → "" | CLITerm ( " " CLITerm )* ; CLITerm → EqualsArg | AliasEqualsArg | AliasArg | NegativeDashArg | NegativeArg | SimpleArg ; EqualsArg → "--" ArgName "=" CLIValue ; AliasEqualsArg → "-" AliasName "=" CLIValue ; AliasArg → "-" AliasName ( " " CLIValue )? ; NegativeDashArg → "--no-" ArgName ; NegativeArg → "--no" ArgName ; SimpleArg → "--" ArgName ( " " CLIValue )? ; ArgName → /^[a-zA-Z-]+$/ ; AliasName → /^[a-z]{1}$/ ; CLIValue → '"' /^[a-zA-Z0-9]+$/ '"' | /^[a-zA-Z0-9]+$/ ; ``` The regexes are a little fuzzy, but this is sort of an informal presentation, and additionally there are other constraints implemented in the code which handles all of these different terms. Refactoring this to use a proper parser (albeit a pretty simple one) allows our implementation to much more clearly conform to this defined grammar, and should hopefully both help us avoid other bugs in the future and be easier to maintain. See #3712 for more details on the issues with the `--reporters` flag in particular.
1 parent aa7c214 commit d34c4f2

File tree

6 files changed

+530
-213
lines changed

6 files changed

+530
-213
lines changed

src/cli/config-flags.ts

+44-23
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { LogLevel, TaskCommand } from '@stencil/core/declarations';
33
/**
44
* All the Boolean options supported by the Stencil CLI
55
*/
6-
export const BOOLEAN_CLI_ARGS = [
6+
export const BOOLEAN_CLI_FLAGS = [
77
'build',
88
'cache',
99
'checkVersion',
@@ -88,7 +88,7 @@ export const BOOLEAN_CLI_ARGS = [
8888
/**
8989
* All the Number options supported by the Stencil CLI
9090
*/
91-
export const NUMBER_CLI_ARGS = [
91+
export const NUMBER_CLI_FLAGS = [
9292
'port',
9393
// JEST CLI ARGS
9494
'maxConcurrency',
@@ -98,7 +98,7 @@ export const NUMBER_CLI_ARGS = [
9898
/**
9999
* All the String options supported by the Stencil CLI
100100
*/
101-
export const STRING_CLI_ARGS = [
101+
export const STRING_CLI_FLAGS = [
102102
'address',
103103
'config',
104104
'docsApi',
@@ -138,8 +138,9 @@ export const STRING_CLI_ARGS = [
138138
'testURL',
139139
'timers',
140140
'transform',
141+
] as const;
141142

142-
// ARRAY ARGS
143+
export const STRING_ARRAY_CLI_FLAGS = [
143144
'collectCoverageOnlyFrom',
144145
'coveragePathIgnorePatterns',
145146
'coverageReporters',
@@ -169,7 +170,7 @@ export const STRING_CLI_ARGS = [
169170
* `maxWorkers` is an argument which is used both by Stencil _and_ by Jest,
170171
* which means that we need to support parsing both string and number values.
171172
*/
172-
export const STRING_NUMBER_CLI_ARGS = ['maxWorkers'] as const;
173+
export const STRING_NUMBER_CLI_FLAGS = ['maxWorkers'] as const;
173174

174175
/**
175176
* All the LogLevel-type options supported by the Stencil CLI
@@ -178,7 +179,7 @@ export const STRING_NUMBER_CLI_ARGS = ['maxWorkers'] as const;
178179
* but this approach lets us make sure that we're handling all
179180
* our arguments in a type-safe way.
180181
*/
181-
export const LOG_LEVEL_CLI_ARGS = ['logLevel'] as const;
182+
export const LOG_LEVEL_CLI_FLAGS = ['logLevel'] as const;
182183

183184
/**
184185
* A type which gives the members of a `ReadonlyArray<string>` as
@@ -187,26 +188,39 @@ export const LOG_LEVEL_CLI_ARGS = ['logLevel'] as const;
187188
*/
188189
type ArrayValuesAsUnion<T extends ReadonlyArray<string>> = T[number];
189190

190-
export type BooleanCLIArg = ArrayValuesAsUnion<typeof BOOLEAN_CLI_ARGS>;
191-
export type StringCLIArg = ArrayValuesAsUnion<typeof STRING_CLI_ARGS>;
192-
export type NumberCLIArg = ArrayValuesAsUnion<typeof NUMBER_CLI_ARGS>;
193-
export type StringNumberCLIArg = ArrayValuesAsUnion<typeof STRING_NUMBER_CLI_ARGS>;
194-
export type LogCLIArg = ArrayValuesAsUnion<typeof LOG_LEVEL_CLI_ARGS>;
191+
export type BooleanCLIFlag = ArrayValuesAsUnion<typeof BOOLEAN_CLI_FLAGS>;
192+
export type StringCLIFlag = ArrayValuesAsUnion<typeof STRING_CLI_FLAGS>;
193+
export type StringArrayCLIFlag = ArrayValuesAsUnion<typeof STRING_ARRAY_CLI_FLAGS>;
194+
export type NumberCLIFlag = ArrayValuesAsUnion<typeof NUMBER_CLI_FLAGS>;
195+
export type StringNumberCLIFlag = ArrayValuesAsUnion<typeof STRING_NUMBER_CLI_FLAGS>;
196+
export type LogCLIFlag = ArrayValuesAsUnion<typeof LOG_LEVEL_CLI_FLAGS>;
195197

196-
type KnownCLIArg = BooleanCLIArg | StringCLIArg | NumberCLIArg | StringNumberCLIArg | LogCLIArg;
198+
export type KnownCLIFlag =
199+
| BooleanCLIFlag
200+
| StringCLIFlag
201+
| StringArrayCLIFlag
202+
| NumberCLIFlag
203+
| StringNumberCLIFlag
204+
| LogCLIFlag;
197205

198-
type AliasMap = Partial<Record<KnownCLIArg, string>>;
206+
type AliasMap = Partial<Record<string, KnownCLIFlag>>;
199207

200208
/**
201209
* For a small subset of CLI options we support a short alias e.g. `'h'` for `'help'`
202210
*/
203-
export const CLI_ARG_ALIASES: AliasMap = {
204-
config: 'c',
205-
help: 'h',
206-
port: 'p',
207-
version: 'v',
211+
export const CLI_FLAG_ALIASES: AliasMap = {
212+
c: 'config',
213+
h: 'help',
214+
p: 'port',
215+
v: 'version',
208216
};
209217

218+
/**
219+
* A regular expression which can be used to match a CLI flag for one of our
220+
* short aliases.
221+
*/
222+
export const CLI_FLAG_REGEX = new RegExp(`^-[chpv]{1}$`);
223+
210224
/**
211225
* Given two types `K` and `T` where `K` extends `ReadonlyArray<string>`,
212226
* construct a type which maps the strings in `K` as keys to values of type `T`.
@@ -223,31 +237,37 @@ type ObjectFromKeys<K extends ReadonlyArray<string>, T> = {
223237
* Type containing the possible Boolean configuration flags, to be included
224238
* in ConfigFlags, below
225239
*/
226-
type BooleanConfigFlags = ObjectFromKeys<typeof BOOLEAN_CLI_ARGS, boolean>;
240+
type BooleanConfigFlags = ObjectFromKeys<typeof BOOLEAN_CLI_FLAGS, boolean>;
227241

228242
/**
229243
* Type containing the possible String configuration flags, to be included
230244
* in ConfigFlags, below
231245
*/
232-
type StringConfigFlags = ObjectFromKeys<typeof STRING_CLI_ARGS, string>;
246+
type StringConfigFlags = ObjectFromKeys<typeof STRING_CLI_FLAGS, string>;
247+
248+
/**
249+
* Type containing the possible String Array configuration flags. This is
250+
* one of the 'constituent types' for `ConfigFlags`.
251+
*/
252+
type StringArrayConfigFlags = ObjectFromKeys<typeof STRING_ARRAY_CLI_FLAGS, string[]>;
233253

234254
/**
235255
* Type containing the possible numeric configuration flags, to be included
236256
* in ConfigFlags, below
237257
*/
238-
type NumberConfigFlags = ObjectFromKeys<typeof NUMBER_CLI_ARGS, number>;
258+
type NumberConfigFlags = ObjectFromKeys<typeof NUMBER_CLI_FLAGS, number>;
239259

240260
/**
241261
* Type containing the configuration flags which may be set to either string
242262
* or number values.
243263
*/
244-
type StringNumberConfigFlags = ObjectFromKeys<typeof STRING_NUMBER_CLI_ARGS, string | number>;
264+
type StringNumberConfigFlags = ObjectFromKeys<typeof STRING_NUMBER_CLI_FLAGS, string | number>;
245265

246266
/**
247267
* Type containing the possible LogLevel configuration flags, to be included
248268
* in ConfigFlags, below
249269
*/
250-
type LogLevelFlags = ObjectFromKeys<typeof LOG_LEVEL_CLI_ARGS, LogLevel>;
270+
type LogLevelFlags = ObjectFromKeys<typeof LOG_LEVEL_CLI_FLAGS, LogLevel>;
251271

252272
/**
253273
* The configuration flags which can be set by the user on the command line.
@@ -266,6 +286,7 @@ type LogLevelFlags = ObjectFromKeys<typeof LOG_LEVEL_CLI_ARGS, LogLevel>;
266286
export interface ConfigFlags
267287
extends BooleanConfigFlags,
268288
StringConfigFlags,
289+
StringArrayConfigFlags,
269290
NumberConfigFlags,
270291
StringNumberConfigFlags,
271292
LogLevelFlags {

0 commit comments

Comments
 (0)