From 3f1b32c5d2329f54d05a1750ccaae637e8c2a74f Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 17 Oct 2024 00:36:54 -0700 Subject: [PATCH 1/2] Expand the ts-command-line choice paramter APIs. --- ...and-alternatives-api_2024-10-17-07-31.json | 10 +++++ ...and-alternatives-api_2024-10-17-07-33.json | 10 +++++ common/reviews/api/ts-command-line.api.md | 20 +++++----- .../src/parameters/BaseClasses.ts | 2 +- .../CommandLineChoiceListParameter.ts | 16 ++++---- .../parameters/CommandLineChoiceParameter.ts | 24 ++++++------ .../src/parameters/CommandLineDefinition.ts | 10 ++--- .../providers/CommandLineParameterProvider.ts | 4 +- .../src/providers/TabCompletionAction.ts | 22 +++++------ .../src/ParameterView/ParameterForm/index.tsx | 37 ++++++++++--------- 10 files changed, 90 insertions(+), 65 deletions(-) create mode 100644 common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-31.json create mode 100644 common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-33.json diff --git a/common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-31.json b/common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-31.json new file mode 100644 index 00000000000..35cac7e696e --- /dev/null +++ b/common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-31.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/ts-command-line", + "comment": "Expand the `alternatives` and `completions` options of `CommandLineChoiceParameter` and `CommandLineChoiceListParameter` to allow readonly arrays and sets.", + "type": "minor" + } + ], + "packageName": "@rushstack/ts-command-line" +} \ No newline at end of file diff --git a/common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-33.json b/common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-33.json new file mode 100644 index 00000000000..ee95f3e47c8 --- /dev/null +++ b/common/changes/@rushstack/ts-command-line/expand-alternatives-api_2024-10-17-07-33.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/ts-command-line", + "comment": "(BREAKING API CHANGE) Change the type of the `alternatives` property of `CommandLineChoiceParameter` and `CommandLineChoiceParameter` from an array to a `ReadonlySet`.", + "type": "minor" + } + ], + "packageName": "@rushstack/ts-command-line" +} \ No newline at end of file diff --git a/common/reviews/api/ts-command-line.api.md b/common/reviews/api/ts-command-line.api.md index bbe3fec5cee..796e59aa2ff 100644 --- a/common/reviews/api/ts-command-line.api.md +++ b/common/reviews/api/ts-command-line.api.md @@ -37,10 +37,10 @@ export abstract class CommandLineAction extends CommandLineParameterProvider { export class CommandLineChoiceListParameter extends CommandLineParameter { // @internal constructor(definition: ICommandLineChoiceListDefinition); - readonly alternatives: ReadonlyArray; + readonly alternatives: ReadonlySet; // @override appendToArgList(argList: string[]): void; - readonly completions: (() => Promise) | undefined; + readonly completions: (() => Promise | ReadonlySet>) | undefined; readonly kind: CommandLineParameterKind.ChoiceList; // @internal _setValue(data: unknown): void; @@ -51,10 +51,10 @@ export class CommandLineChoiceListParameter ext export class CommandLineChoiceParameter extends CommandLineParameter { // @internal constructor(definition: ICommandLineChoiceDefinition); - readonly alternatives: ReadonlyArray; + readonly alternatives: ReadonlySet; // @override appendToArgList(argList: string[]): void; - readonly completions: (() => Promise) | undefined; + readonly completions: (() => Promise | ReadonlySet>) | undefined; readonly defaultValue: TChoice | undefined; // @internal _getSupplementaryNotes(supplementaryNotes: string[]): void; @@ -246,7 +246,7 @@ export abstract class CommandLineParameterWithArgument extends CommandLineParame // @internal constructor(definition: IBaseCommandLineDefinitionWithArgument); readonly argumentName: string; - readonly completions: (() => Promise) | undefined; + readonly completions: (() => Promise | ReadonlySet>) | undefined; } // @public @@ -343,7 +343,7 @@ export interface IBaseCommandLineDefinition { // @public export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLineDefinition { argumentName: string; - completions?: () => Promise; + completions?: (() => Promise | ReadonlySet>) | undefined; } // @public @@ -355,15 +355,15 @@ export interface ICommandLineActionOptions { // @public export interface ICommandLineChoiceDefinition extends IBaseCommandLineDefinition { - alternatives: TChoice[]; - completions?: () => Promise; + alternatives: ReadonlyArray | ReadonlySet; + completions?: (() => Promise | ReadonlySet>) | undefined; defaultValue?: TChoice; } // @public export interface ICommandLineChoiceListDefinition extends IBaseCommandLineDefinition { - alternatives: TChoice[]; - completions?: () => Promise; + alternatives: ReadonlyArray | ReadonlySet; + completions?: (() => Promise | ReadonlySet>) | undefined; } // @public diff --git a/libraries/ts-command-line/src/parameters/BaseClasses.ts b/libraries/ts-command-line/src/parameters/BaseClasses.ts index dc40f017553..d5eea4d4f02 100644 --- a/libraries/ts-command-line/src/parameters/BaseClasses.ts +++ b/libraries/ts-command-line/src/parameters/BaseClasses.ts @@ -280,7 +280,7 @@ export abstract class CommandLineParameterWithArgument extends CommandLineParame public readonly argumentName: string; /** {@inheritDoc IBaseCommandLineDefinitionWithArgument.completions} */ - public readonly completions: (() => Promise) | undefined; + public readonly completions: (() => Promise | ReadonlySet>) | undefined; /** @internal */ public constructor(definition: IBaseCommandLineDefinitionWithArgument) { diff --git a/libraries/ts-command-line/src/parameters/CommandLineChoiceListParameter.ts b/libraries/ts-command-line/src/parameters/CommandLineChoiceListParameter.ts index fa1f6e377d8..8faaf53dbeb 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineChoiceListParameter.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineChoiceListParameter.ts @@ -13,12 +13,12 @@ export class CommandLineChoiceListParameter< TChoice extends string = string > extends CommandLineParameterBase { /** {@inheritDoc ICommandLineChoiceListDefinition.alternatives} */ - public readonly alternatives: ReadonlyArray; + public readonly alternatives: ReadonlySet; private _values: TChoice[] = []; /** {@inheritDoc ICommandLineChoiceListDefinition.completions} */ - public readonly completions: (() => Promise) | undefined; + public readonly completions: (() => Promise | ReadonlySet>) | undefined; /** {@inheritDoc CommandLineParameter.kind} */ public readonly kind: CommandLineParameterKind.ChoiceList = CommandLineParameterKind.ChoiceList; @@ -26,15 +26,17 @@ export class CommandLineChoiceListParameter< /** @internal */ public constructor(definition: ICommandLineChoiceListDefinition) { super(definition); + const { alternatives, completions } = definition; - if (definition.alternatives.length < 1) { + const alternativesSet: Set = alternatives instanceof Set ? alternatives : new Set(alternatives); + if (alternativesSet.size < 1) { throw new Error( `When defining a choice list parameter, the alternatives list must contain at least one value.` ); } - this.alternatives = definition.alternatives; - this.completions = definition.completions; + this.alternatives = alternativesSet; + this.completions = completions; } /** @@ -60,8 +62,8 @@ export class CommandLineChoiceListParameter< const values: string[] | undefined = EnvironmentVariableParser.parseAsList(this.environmentVariable); if (values) { for (const value of values) { - if (!this.alternatives.includes(value as TChoice)) { - const choices: string = '"' + this.alternatives.join('", "') + '"'; + if (!this.alternatives.has(value as TChoice)) { + const choices: string = '"' + Array.from(this.alternatives).join('", "') + '"'; throw new Error( `Invalid value "${value}" for the environment variable` + ` ${this.environmentVariable}. Valid choices are: ${choices}` diff --git a/libraries/ts-command-line/src/parameters/CommandLineChoiceParameter.ts b/libraries/ts-command-line/src/parameters/CommandLineChoiceParameter.ts index 63dbfe3578b..49fbaa62535 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineChoiceParameter.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineChoiceParameter.ts @@ -19,7 +19,7 @@ export interface IRequiredCommandLineChoiceParameter extends CommandLineParameterBase { /** {@inheritDoc ICommandLineChoiceDefinition.alternatives} */ - public readonly alternatives: ReadonlyArray; + public readonly alternatives: ReadonlySet; /** {@inheritDoc ICommandLineStringDefinition.defaultValue} */ public readonly defaultValue: TChoice | undefined; @@ -27,7 +27,7 @@ export class CommandLineChoiceParameter extends private _value: TChoice | undefined = undefined; /** {@inheritDoc ICommandLineChoiceDefinition.completions} */ - public readonly completions: (() => Promise) | undefined; + public readonly completions: (() => Promise | ReadonlySet>) | undefined; /** {@inheritDoc CommandLineParameter.kind} */ public readonly kind: CommandLineParameterKind.Choice = -CommandLineParameterKind.Choice; @@ -35,23 +35,25 @@ export class CommandLineChoiceParameter extends /** @internal */ public constructor(definition: ICommandLineChoiceDefinition) { super(definition); + const { alternatives, defaultValue, completions } = definition; - if (definition.alternatives.length < 1) { + const alternativesSet: Set = alternatives instanceof Set ? alternatives : new Set(alternatives); + if (alternativesSet.size < 1) { throw new Error( `When defining a choice parameter, the alternatives list must contain at least one value.` ); } - if (definition.defaultValue && definition.alternatives.indexOf(definition.defaultValue) === -1) { + if (defaultValue && !alternativesSet.has(defaultValue)) { throw new Error( - `The specified default value "${definition.defaultValue}"` + - ` is not one of the available options: ${definition.alternatives.toString()}` + `The specified default value "${defaultValue}"` + + ` is not one of the available options: ${alternatives.toString()}` ); } - this.alternatives = definition.alternatives; - this.defaultValue = definition.defaultValue; + this.alternatives = alternativesSet; + this.defaultValue = defaultValue; this.validateDefaultValue(!!this.defaultValue); - this.completions = definition.completions; + this.completions = completions; } /** @@ -72,8 +74,8 @@ export class CommandLineChoiceParameter extends // Try reading the environment variable const environmentValue: string | undefined = process.env[this.environmentVariable]; if (environmentValue !== undefined && environmentValue !== '') { - if (!this.alternatives.includes(environmentValue as TChoice)) { - const choices: string = '"' + this.alternatives.join('", "') + '"'; + if (!this.alternatives.has(environmentValue as TChoice)) { + const choices: string = '"' + Array.from(this.alternatives).join('", "') + '"'; throw new Error( `Invalid value "${environmentValue}" for the environment variable` + ` ${this.environmentVariable}. Valid choices are: ${choices}` diff --git a/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts b/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts index 07adab02114..d9cb0b06f05 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts @@ -130,7 +130,7 @@ export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLine * * In a future release, this will be renamed to `getCompletionsAsync` */ - completions?: () => Promise; + completions?: (() => Promise | ReadonlySet>) | undefined; } /** @@ -146,7 +146,7 @@ export interface ICommandLineChoiceDefinition /** * A list of strings (which contain no spaces), of possible options which can be selected */ - alternatives: TChoice[]; + alternatives: ReadonlyArray | ReadonlySet; /** * {@inheritDoc ICommandLineStringDefinition.defaultValue} @@ -159,7 +159,7 @@ export interface ICommandLineChoiceDefinition * This option is only used when `ICommandLineParserOptions.enableTabCompletionAction` * is enabled. */ - completions?: () => Promise; + completions?: (() => Promise | ReadonlySet>) | undefined; } /** @@ -174,7 +174,7 @@ export interface ICommandLineChoiceListDefinition | ReadonlySet; /** * An optional callback that provides a list of custom choices for tab completion. @@ -182,7 +182,7 @@ export interface ICommandLineChoiceListDefinition Promise; + completions?: (() => Promise | ReadonlySet>) | undefined; } /** diff --git a/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts b/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts index 5b0d211c630..3a4e6eb5c08 100644 --- a/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts +++ b/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts @@ -835,11 +835,11 @@ export abstract class CommandLineParameterProvider { let type: string | undefined; switch (kind) { case CommandLineParameterKind.Choice: { - choices = parameter.alternatives as string[]; + choices = Array.from(parameter.alternatives); break; } case CommandLineParameterKind.ChoiceList: { - choices = parameter.alternatives as string[]; + choices = Array.from(parameter.alternatives); action = 'append'; break; } diff --git a/libraries/ts-command-line/src/providers/TabCompletionAction.ts b/libraries/ts-command-line/src/providers/TabCompletionAction.ts index bcb3a3db4f1..e05d3d14a7e 100644 --- a/libraries/ts-command-line/src/providers/TabCompletionAction.ts +++ b/libraries/ts-command-line/src/providers/TabCompletionAction.ts @@ -122,10 +122,10 @@ export class TabCompleteAction extends CommandLineAction { if (completePartialWord) { for (const parameterName of parameterNames) { if (parameterName === secondLastToken) { - const values: ReadonlyArray = await this._getParameterValueCompletionsAsync( + const values: ReadonlySet = await this._getParameterValueCompletionsAsync( parameterNameMap.get(parameterName)! ); - if (values.length > 0) { + if (values.size > 0) { yield* this._completeParameterValues(values, lastToken); return; } @@ -135,10 +135,10 @@ export class TabCompleteAction extends CommandLineAction { } else { for (const parameterName of parameterNames) { if (parameterName === lastToken) { - const values: ReadonlyArray = await this._getParameterValueCompletionsAsync( + const values: ReadonlySet = await this._getParameterValueCompletionsAsync( parameterNameMap.get(parameterName)! ); - if (values.length > 0) { + if (values.size > 0) { yield* values; return; } @@ -174,8 +174,8 @@ export class TabCompleteAction extends CommandLineAction { private async _getParameterValueCompletionsAsync( parameter: CommandLineParameter - ): Promise> { - let choiceParameterValues: ReadonlyArray = []; + ): Promise> { + let choiceParameterValues: ReadonlySet | undefined; if (parameter.kind === CommandLineParameterKind.Choice) { choiceParameterValues = parameter.alternatives; } else if (parameter.kind !== CommandLineParameterKind.Flag) { @@ -190,12 +190,12 @@ export class TabCompleteAction extends CommandLineAction { parameterWithArgumentOrChoices = parameter; } - if (parameterWithArgumentOrChoices?.completions) { - choiceParameterValues = await parameterWithArgumentOrChoices.completions(); - } + const completionValues: ReadonlyArray | ReadonlySet | undefined = + await parameterWithArgumentOrChoices?.completions?.(); + choiceParameterValues = completionValues instanceof Set ? completionValues : new Set(completionValues); } - return choiceParameterValues; + return choiceParameterValues ?? new Set(); } private _getGlobalParameterOffset(tokens: string[]): number { @@ -215,7 +215,7 @@ export class TabCompleteAction extends CommandLineAction { } private *_completeParameterValues( - choiceParameterValues: ReadonlyArray, + choiceParameterValues: ReadonlyArray | ReadonlySet, lastToken: string ): IterableIterator { for (const choiceParameterValue of choiceParameterValues) { diff --git a/vscode-extensions/rush-vscode-command-webview/src/ParameterView/ParameterForm/index.tsx b/vscode-extensions/rush-vscode-command-webview/src/ParameterView/ParameterForm/index.tsx index d1ed08e0544..506151b94a1 100644 --- a/vscode-extensions/rush-vscode-command-webview/src/ParameterView/ParameterForm/index.tsx +++ b/vscode-extensions/rush-vscode-command-webview/src/ParameterView/ParameterForm/index.tsx @@ -173,32 +173,33 @@ export const ParameterForm = (): JSX.Element => { switch (parameter.kind) { case CommandLineParameterKind.Choice: { - const commandLineChoiceParameter: CommandLineChoiceParameter = + const { alternatives, defaultValue }: CommandLineChoiceParameter = parameter as CommandLineChoiceParameter; + const options: { key: string; text: string }[] = []; + for (const alternative of alternatives) { + options.push({ + key: alternative, + text: alternative + }); + } + fieldNode = ( - ({ - key: alternative, - text: alternative - }))} - /> + ); break; } case CommandLineParameterKind.ChoiceList: { - const commandLineChoiceListParameter: CommandLineChoiceListParameter = + const { alternatives }: CommandLineChoiceListParameter = parameter as CommandLineChoiceListParameter; + const options: { key: string; text: string }[] = []; + for (const alternative of alternatives) { + options.push({ + key: alternative, + text: alternative + }); + } fieldNode = ( - ({ - key: alternative, - text: alternative - }))} - /> + ); break; } From b64fa3cd29e5cd5efcf6c308e1ab63ea15e413b9 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Thu, 17 Oct 2024 00:59:42 -0700 Subject: [PATCH 2/2] fixup! Expand the ts-command-line choice paramter APIs. --- common/reviews/api/ts-command-line.api.md | 6 +++--- .../ts-command-line/src/parameters/CommandLineDefinition.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/reviews/api/ts-command-line.api.md b/common/reviews/api/ts-command-line.api.md index 796e59aa2ff..e1e38b3fe19 100644 --- a/common/reviews/api/ts-command-line.api.md +++ b/common/reviews/api/ts-command-line.api.md @@ -343,7 +343,7 @@ export interface IBaseCommandLineDefinition { // @public export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLineDefinition { argumentName: string; - completions?: (() => Promise | ReadonlySet>) | undefined; + completions?: () => Promise | ReadonlySet>; } // @public @@ -356,14 +356,14 @@ export interface ICommandLineActionOptions { // @public export interface ICommandLineChoiceDefinition extends IBaseCommandLineDefinition { alternatives: ReadonlyArray | ReadonlySet; - completions?: (() => Promise | ReadonlySet>) | undefined; + completions?: () => Promise | ReadonlySet>; defaultValue?: TChoice; } // @public export interface ICommandLineChoiceListDefinition extends IBaseCommandLineDefinition { alternatives: ReadonlyArray | ReadonlySet; - completions?: (() => Promise | ReadonlySet>) | undefined; + completions?: () => Promise | ReadonlySet>; } // @public diff --git a/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts b/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts index d9cb0b06f05..7a115dd94ea 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineDefinition.ts @@ -130,7 +130,7 @@ export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLine * * In a future release, this will be renamed to `getCompletionsAsync` */ - completions?: (() => Promise | ReadonlySet>) | undefined; + completions?: () => Promise | ReadonlySet>; } /** @@ -159,7 +159,7 @@ export interface ICommandLineChoiceDefinition * This option is only used when `ICommandLineParserOptions.enableTabCompletionAction` * is enabled. */ - completions?: (() => Promise | ReadonlySet>) | undefined; + completions?: () => Promise | ReadonlySet>; } /** @@ -182,7 +182,7 @@ export interface ICommandLineChoiceListDefinition Promise | ReadonlySet>) | undefined; + completions?: () => Promise | ReadonlySet>; } /**