Skip to content

Commit f7f5b28

Browse files
committed
feat(@angular/cli): if parsing comes accross an obvious error throw it
We accumulate errors this way, and throw only once at the end, with messages for all errors.
1 parent d69af5a commit f7f5b28

File tree

4 files changed

+65
-29
lines changed

4 files changed

+65
-29
lines changed

packages/angular/cli/commands/definitions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
"type": "object",
3131
"properties": {
3232
"help": {
33-
"type": ["boolean", "string"],
34-
"description": "Shows a help message. Use '--help=json' as a value to output the help in JSON format.",
33+
"enum": [true, false, "json", "JSON"],
34+
"description": "Shows a help message. You can pass the format as a value.",
3535
"default": false
3636
}
3737
}

packages/angular/cli/models/command.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,6 @@ export abstract class Command<T extends BaseCommandOptions = BaseCommandOptions>
153153
return this.printHelp(options);
154154
} else if (options.help === 'json' || options.help === 'JSON') {
155155
return this.printJsonHelp(options);
156-
} else if (options.help !== false && options.help !== undefined) {
157-
// The user entered an invalid type of help, maybe?
158-
this.logger.fatal(
159-
`--help was provided, but format ${JSON.stringify(options.help)} was not understood.`,
160-
);
161-
162-
return 1;
163156
} else {
164157
return await this.run(options);
165158
}

packages/angular/cli/models/parser.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,21 @@
66
* found in the LICENSE file at https://angular.io/license
77
*
88
*/
9-
import { strings } from '@angular-devkit/core';
9+
import { BaseException, strings } from '@angular-devkit/core';
1010
import { Arguments, Option, OptionType, Value } from './interface';
1111

1212

13+
export class ParseArgumentException extends BaseException {
14+
constructor(
15+
comments: string[],
16+
public readonly parsed: Arguments,
17+
public readonly ignored: string[],
18+
) {
19+
super(`One or more errors occured while parsing arguments:\n ${comments.join('\n ')}`);
20+
}
21+
}
22+
23+
1324
function _coerceType(str: string | undefined, type: OptionType, v?: Value): Value | undefined {
1425
switch (type) {
1526
case OptionType.Any:
@@ -103,6 +114,8 @@ function _assignOption(
103114
parsedOptions: Arguments,
104115
_positionals: string[],
105116
leftovers: string[],
117+
ignored: string[],
118+
errors: string[],
106119
) {
107120
let key = arg.substr(2);
108121
let option: Option | null = null;
@@ -169,7 +182,15 @@ function _assignOption(
169182
if (v !== undefined) {
170183
parsedOptions[option.name] = v;
171184
} else {
172-
leftovers.push(arg);
185+
let error = `Argument ${key} could not be parsed using value ${JSON.stringify(value)}.`;
186+
if (option.enum) {
187+
error += ` Valid values are: ${option.enum.map(x => JSON.stringify(x)).join(', ')}.`;
188+
} else {
189+
error += `Valid type(s) is: ${(option.types || [option.type]).join(', ')}`;
190+
}
191+
192+
errors.push(error);
193+
ignored.push(arg);
173194
}
174195
}
175196
}
@@ -244,6 +265,9 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
244265
const positionals: string[] = [];
245266
const parsedOptions: Arguments = {};
246267

268+
const ignored: string[] = [];
269+
const errors: string[] = [];
270+
247271
for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
248272
if (!arg) {
249273
break;
@@ -256,15 +280,16 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
256280
}
257281

258282
if (arg.startsWith('--')) {
259-
_assignOption(arg, args, options, parsedOptions, positionals, leftovers);
283+
_assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
260284
} else if (arg.startsWith('-')) {
261285
// Argument is of form -abcdef. Starts at 1 because we skip the `-`.
262286
for (let i = 1; i < arg.length; i++) {
263287
const flag = arg[i];
264288
// Treat the last flag as `--a` (as if full flag but just one letter). We do this in
265289
// the loop because it saves us a check to see if the arg is just `-`.
266290
if (i == arg.length - 1) {
267-
_assignOption('--' + flag, args, options, parsedOptions, positionals, leftovers);
291+
const arg = '--' + flag;
292+
_assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
268293
} else {
269294
const maybeOption = _getOptionFromName(flag, options);
270295
if (maybeOption) {
@@ -318,5 +343,9 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
318343
parsedOptions['--'] = [...positionals, ...leftovers];
319344
}
320345

346+
if (errors.length > 0) {
347+
throw new ParseArgumentException(errors, parsedOptions, ignored);
348+
}
349+
321350
return parsedOptions;
322351
}

packages/angular/cli/models/parser_spec.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
*/
99
import { Arguments, Option, OptionType } from './interface';
10-
import { parseArguments } from './parser';
10+
import { ParseArgumentException, parseArguments } from './parser';
1111

1212
describe('parseArguments', () => {
1313
const options: Option[] = [
@@ -32,10 +32,11 @@ describe('parseArguments', () => {
3232
description: '' },
3333
];
3434

35-
const tests: { [test: string]: Partial<Arguments> } = {
35+
const tests: { [test: string]: Partial<Arguments> | ['!!!', Partial<Arguments>, string[]] } = {
3636
'--bool': { bool: true },
37-
'--bool=1': { '--': ['--bool=1'] },
38-
'--bool=yellow': { '--': ['--bool=yellow'] },
37+
'--bool=1': ['!!!', {}, ['--bool=1']],
38+
'-- --bool=1': { '--': ['--bool=1'] },
39+
'--bool=yellow': ['!!!', {}, ['--bool=yellow']],
3940
'--bool=true': { bool: true },
4041
'--bool=false': { bool: false },
4142
'--no-bool': { bool: false },
@@ -45,7 +46,8 @@ describe('parseArguments', () => {
4546
'--b true': { bool: true },
4647
'--b false': { bool: false },
4748
'--bool --num': { bool: true, num: 0 },
48-
'--bool --num=true': { bool: true, '--': ['--num=true'] },
49+
'--bool --num=true': ['!!!', { bool: true }, ['--num=true']],
50+
'-- --bool --num=true': { '--': ['--bool', '--num=true'] },
4951
'--bool=true --num': { bool: true, num: 0 },
5052
'--bool true --num': { bool: true, num: 0 },
5153
'--bool=false --num': { bool: false, num: 0 },
@@ -85,40 +87,52 @@ describe('parseArguments', () => {
8587
'--t2=true': { t2: true },
8688
'--t2': { t2: true },
8789
'--no-t2': { t2: false },
88-
'--t2=yellow': { '--': ['--t2=yellow'] },
90+
'--t2=yellow': ['!!!', {}, ['--t2=yellow']],
8991
'--no-t2=true': { '--': ['--no-t2=true'] },
9092
'--t2=123': { t2: 123 },
9193
'--t3=a': { t3: 'a' },
9294
'--t3': { t3: 0 },
9395
'--t3 true': { t3: true },
9496
'--e1 hello': { e1: 'hello' },
9597
'--e1=hello': { e1: 'hello' },
96-
'--e1 yellow': { p1: 'yellow', '--': ['--e1'] },
97-
'--e1=yellow': { '--': ['--e1=yellow'] },
98-
'--e1': { '--': ['--e1'] },
99-
'--e1 true': { p1: 'true', '--': ['--e1'] },
100-
'--e1=true': { '--': ['--e1=true'] },
98+
'--e1 yellow': ['!!!', { p1: 'yellow' }, ['--e1']],
99+
'--e1=yellow': ['!!!', {}, ['--e1=yellow']],
100+
'--e1': ['!!!', {}, ['--e1']],
101+
'--e1 true': ['!!!', { p1: 'true' }, ['--e1']],
102+
'--e1=true': ['!!!', {}, ['--e1=true']],
101103
'--e2 hello': { e2: 'hello' },
102104
'--e2=hello': { e2: 'hello' },
103105
'--e2 yellow': { p1: 'yellow', e2: '' },
104-
'--e2=yellow': { '--': ['--e2=yellow'] },
106+
'--e2=yellow': ['!!!', {}, ['--e2=yellow']],
105107
'--e2': { e2: '' },
106108
'--e2 true': { p1: 'true', e2: '' },
107-
'--e2=true': { '--': ['--e2=true'] },
109+
'--e2=true': ['!!!', {}, ['--e2=true']],
108110
'--e3 json': { e3: 'json' },
109111
'--e3=json': { e3: 'json' },
110112
'--e3 yellow': { p1: 'yellow', e3: true },
111-
'--e3=yellow': { '--': ['--e3=yellow'] },
113+
'--e3=yellow': ['!!!', {}, ['--e3=yellow']],
112114
'--e3': { e3: true },
113115
'--e3 true': { e3: true },
114116
'--e3=true': { e3: true },
115117
};
116118

117119
Object.entries(tests).forEach(([str, expected]) => {
118120
it(`works for ${str}`, () => {
119-
const actual = parseArguments(str.split(/\s+/), options);
121+
try {
122+
const actual = parseArguments(str.split(/\s+/), options);
120123

121-
expect(actual).toEqual(expected as Arguments);
124+
expect(Array.isArray(expected)).toBe(false);
125+
expect(actual).toEqual(expected as Arguments);
126+
} catch (e) {
127+
if (!(e instanceof ParseArgumentException)) {
128+
throw e;
129+
}
130+
131+
// The expected values are an array.
132+
expect(Array.isArray(expected)).toBe(true);
133+
expect(e.parsed).toEqual(expected[1] as Arguments);
134+
expect(e.ignored).toEqual(expected[2] as string[]);
135+
}
122136
});
123137
});
124138
});

0 commit comments

Comments
 (0)