Skip to content

Commit 4566148

Browse files
hanslKeen Yee Liau
authored andcommitted
feat(@angular/cli): allow flags to have deprecation
The feature comes from the "x-deprecated" field in schemas (any schema that is used to parse arguments), and can be a boolean or a string. The parser now takes a logger and will warn users when encountering a deprecated option. These options will also appear in JSON help.
1 parent 9da4bdc commit 4566148

File tree

7 files changed

+84
-14
lines changed

7 files changed

+84
-14
lines changed

packages/angular/cli/models/architect-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export abstract class ArchitectCommand<
137137
const builderConf = this._architect.getBuilderConfiguration(targetSpec);
138138
const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise();
139139
const targetOptionArray = await parseJsonSchemaToOptions(this._registry, builderDesc.schema);
140-
const overrides = parseArguments(options, targetOptionArray);
140+
const overrides = parseArguments(options, targetOptionArray, this.logger);
141141

142142
if (overrides['--']) {
143143
(overrides['--'] || []).forEach(additional => {

packages/angular/cli/models/command-runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export async function runCommand(
182182
}
183183

184184
try {
185-
const parsedOptions = parser.parseArguments(args, description.options);
185+
const parsedOptions = parser.parseArguments(args, description.options, logger);
186186
Command.setCommandMap(commandMap);
187187
const command = new description.impl({ workspace }, description, logger);
188188

packages/angular/cli/models/interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ export interface Option {
144144
*/
145145
positional?: number;
146146

147+
/**
148+
* Deprecation. If this flag is not false a warning will be shown on the console. Either `true`
149+
* or a string to show the user as a notice.
150+
*/
151+
deprecated?: boolean | string;
152+
147153
/**
148154
* Smart default object.
149155
*/

packages/angular/cli/models/parser.ts

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

1212

@@ -112,12 +112,15 @@ function _getOptionFromName(name: string, options: Option[]): Option | undefined
112112
function _assignOption(
113113
arg: string,
114114
args: string[],
115-
options: Option[],
116-
parsedOptions: Arguments,
117-
_positionals: string[],
118-
leftovers: string[],
119-
ignored: string[],
120-
errors: string[],
115+
{ options, parsedOptions, leftovers, ignored, errors, deprecations }: {
116+
options: Option[],
117+
parsedOptions: Arguments,
118+
positionals: string[],
119+
leftovers: string[],
120+
ignored: string[],
121+
errors: string[],
122+
deprecations: string[],
123+
},
121124
) {
122125
const from = arg.startsWith('--') ? 2 : 1;
123126
let key = arg.substr(from);
@@ -185,6 +188,11 @@ function _assignOption(
185188
const v = _coerce(value, option, parsedOptions[option.name]);
186189
if (v !== undefined) {
187190
parsedOptions[option.name] = v;
191+
192+
if (option.deprecated !== undefined && option.deprecated !== false) {
193+
deprecations.push(`Option ${JSON.stringify(option.name)} is deprecated${
194+
typeof option.deprecated == 'string' ? ': ' + option.deprecated : ''}.`);
195+
}
188196
} else {
189197
let error = `Argument ${key} could not be parsed using value ${JSON.stringify(value)}.`;
190198
if (option.enum) {
@@ -258,9 +266,14 @@ export function parseFreeFormArguments(args: string[]): Arguments {
258266
*
259267
* @param args The argument array to parse.
260268
* @param options List of supported options. {@see Option}.
269+
* @param logger Logger to use to warn users.
261270
* @returns An object that contains a property per option.
262271
*/
263-
export function parseArguments(args: string[], options: Option[] | null): Arguments {
272+
export function parseArguments(
273+
args: string[],
274+
options: Option[] | null,
275+
logger?: logging.Logger,
276+
): Arguments {
264277
if (options === null) {
265278
options = [];
266279
}
@@ -271,6 +284,9 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
271284

272285
const ignored: string[] = [];
273286
const errors: string[] = [];
287+
const deprecations: string[] = [];
288+
289+
const state = { options, parsedOptions, positionals, leftovers, ignored, errors, deprecations };
274290

275291
for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
276292
if (arg == '--') {
@@ -280,22 +296,22 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
280296
}
281297

282298
if (arg.startsWith('--')) {
283-
_assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
299+
_assignOption(arg, args, state);
284300
} else if (arg.startsWith('-')) {
285301
// Argument is of form -abcdef. Starts at 1 because we skip the `-`.
286302
for (let i = 1; i < arg.length; i++) {
287303
const flag = arg[i];
288304
// If the next character is an '=', treat it as a long flag.
289305
if (arg[i + 1] == '=') {
290306
const f = '-' + flag + arg.slice(i + 1);
291-
_assignOption(f, args, options, parsedOptions, positionals, leftovers, ignored, errors);
307+
_assignOption(f, args, state);
292308
break;
293309
}
294310
// Treat the last flag as `--a` (as if full flag but just one letter). We do this in
295311
// the loop because it saves us a check to see if the arg is just `-`.
296312
if (i == arg.length - 1) {
297313
const arg = '-' + flag;
298-
_assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
314+
_assignOption(arg, args, state);
299315
} else {
300316
const maybeOption = _getOptionFromName(flag, options);
301317
if (maybeOption) {
@@ -352,6 +368,10 @@ export function parseArguments(args: string[], options: Option[] | null): Argume
352368
parsedOptions['--'] = [...positionals, ...leftovers];
353369
}
354370

371+
if (deprecations.length > 0 && logger) {
372+
deprecations.forEach(message => logger.warn(message));
373+
}
374+
355375
if (errors.length > 0) {
356376
throw new ParseArgumentException(errors, parsedOptions, ignored);
357377
}

packages/angular/cli/models/parser_spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*
88
*/
9+
import { logging } from '@angular-devkit/core';
910
import { Arguments, Option, OptionType } from './interface';
1011
import { ParseArgumentException, parseArguments } from './parser';
1112

@@ -157,4 +158,41 @@ describe('parseArguments', () => {
157158
}
158159
});
159160
});
161+
162+
it('handles deprecation', () => {
163+
const options = [
164+
{ name: 'bool', aliases: [], type: OptionType.Boolean, description: '' },
165+
{ name: 'depr', aliases: [], type: OptionType.Boolean, description: '', deprecated: true },
166+
{ name: 'deprM', aliases: [], type: OptionType.Boolean, description: '', deprecated: 'ABCD' },
167+
];
168+
169+
const logger = new logging.Logger('');
170+
const messages: string[] = [];
171+
172+
logger.subscribe(entry => messages.push(entry.message));
173+
174+
let result = parseArguments(['--bool'], options, logger);
175+
expect(result).toEqual({ bool: true });
176+
expect(messages).toEqual([]);
177+
178+
result = parseArguments(['--depr'], options, logger);
179+
expect(result).toEqual({ depr: true });
180+
expect(messages.length).toEqual(1);
181+
expect(messages[0]).toMatch(/\bdepr\b/);
182+
messages.shift();
183+
184+
result = parseArguments(['--depr', '--bool'], options, logger);
185+
expect(result).toEqual({ depr: true, bool: true });
186+
expect(messages.length).toEqual(1);
187+
expect(messages[0]).toMatch(/\bdepr\b/);
188+
messages.shift();
189+
190+
result = parseArguments(['--depr', '--bool', '--deprM'], options, logger);
191+
expect(result).toEqual({ depr: true, deprM: true, bool: true });
192+
expect(messages.length).toEqual(2);
193+
expect(messages[0]).toMatch(/\bdepr\b/);
194+
expect(messages[1]).toMatch(/\bdeprM\b/);
195+
expect(messages[1]).toMatch(/\bABCD\b/);
196+
messages.shift();
197+
});
160198
});

packages/angular/cli/models/schematic-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ export abstract class SchematicCommand<
533533
schematicOptions: string[],
534534
options: Option[] | null,
535535
): Promise<Arguments> {
536-
return parseArguments(schematicOptions, options);
536+
return parseArguments(schematicOptions, options, this.logger);
537537
}
538538

539539
private async _loadWorkspace() {

packages/angular/cli/utilities/json-schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ export async function parseJsonSchemaToOptions(
248248
const visible = current.visible === undefined || current.visible === true;
249249
const hidden = !!current.hidden || !visible;
250250

251+
// Deprecated is set only if it's true or a string.
252+
const xDeprecated = current['x-deprecated'];
253+
const deprecated = (xDeprecated === true || typeof xDeprecated == 'string')
254+
? xDeprecated : undefined;
255+
251256
const option: Option = {
252257
name,
253258
description: '' + (current.description === undefined ? '' : current.description),
@@ -258,6 +263,7 @@ export async function parseJsonSchemaToOptions(
258263
aliases,
259264
...format !== undefined ? { format } : {},
260265
hidden,
266+
...deprecated !== undefined ? { deprecated } : {},
261267
...positional !== undefined ? { positional } : {},
262268
};
263269

0 commit comments

Comments
 (0)