Skip to content

[ts-command-line] Expand the ts-command-line choice parameter APIs. #4975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
20 changes: 10 additions & 10 deletions common/reviews/api/ts-command-line.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export abstract class CommandLineAction extends CommandLineParameterProvider {
export class CommandLineChoiceListParameter<TChoice extends string = string> extends CommandLineParameter {
// @internal
constructor(definition: ICommandLineChoiceListDefinition<TChoice>);
readonly alternatives: ReadonlyArray<TChoice>;
readonly alternatives: ReadonlySet<TChoice>;
// @override
appendToArgList(argList: string[]): void;
readonly completions: (() => Promise<TChoice[]>) | undefined;
readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
readonly kind: CommandLineParameterKind.ChoiceList;
// @internal
_setValue(data: unknown): void;
Expand All @@ -51,10 +51,10 @@ export class CommandLineChoiceListParameter<TChoice extends string = string> ext
export class CommandLineChoiceParameter<TChoice extends string = string> extends CommandLineParameter {
// @internal
constructor(definition: ICommandLineChoiceDefinition<TChoice>);
readonly alternatives: ReadonlyArray<TChoice>;
readonly alternatives: ReadonlySet<TChoice>;
// @override
appendToArgList(argList: string[]): void;
readonly completions: (() => Promise<TChoice[]>) | undefined;
readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
readonly defaultValue: TChoice | undefined;
// @internal
_getSupplementaryNotes(supplementaryNotes: string[]): void;
Expand Down Expand Up @@ -246,7 +246,7 @@ export abstract class CommandLineParameterWithArgument extends CommandLineParame
// @internal
constructor(definition: IBaseCommandLineDefinitionWithArgument);
readonly argumentName: string;
readonly completions: (() => Promise<string[]>) | undefined;
readonly completions: (() => Promise<ReadonlyArray<string> | ReadonlySet<string>>) | undefined;
}

// @public
Expand Down Expand Up @@ -343,7 +343,7 @@ export interface IBaseCommandLineDefinition {
// @public
export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLineDefinition {
argumentName: string;
completions?: () => Promise<string[]>;
completions?: (() => Promise<ReadonlyArray<string> | ReadonlySet<string>>) | undefined;
}

// @public
Expand All @@ -355,15 +355,15 @@ export interface ICommandLineActionOptions {

// @public
export interface ICommandLineChoiceDefinition<TChoice extends string = string> extends IBaseCommandLineDefinition {
alternatives: TChoice[];
completions?: () => Promise<TChoice[]>;
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;
completions?: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
defaultValue?: TChoice;
}

// @public
export interface ICommandLineChoiceListDefinition<TChoice extends string = string> extends IBaseCommandLineDefinition {
alternatives: TChoice[];
completions?: () => Promise<TChoice[]>;
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;
completions?: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
}

// @public
Expand Down
2 changes: 1 addition & 1 deletion libraries/ts-command-line/src/parameters/BaseClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export abstract class CommandLineParameterWithArgument extends CommandLineParame
public readonly argumentName: string;

/** {@inheritDoc IBaseCommandLineDefinitionWithArgument.completions} */
public readonly completions: (() => Promise<string[]>) | undefined;
public readonly completions: (() => Promise<ReadonlyArray<string> | ReadonlySet<string>>) | undefined;

/** @internal */
public constructor(definition: IBaseCommandLineDefinitionWithArgument) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,30 @@ export class CommandLineChoiceListParameter<
TChoice extends string = string
> extends CommandLineParameterBase {
/** {@inheritDoc ICommandLineChoiceListDefinition.alternatives} */
public readonly alternatives: ReadonlyArray<TChoice>;
public readonly alternatives: ReadonlySet<TChoice>;

private _values: TChoice[] = [];

/** {@inheritDoc ICommandLineChoiceListDefinition.completions} */
public readonly completions: (() => Promise<TChoice[]>) | undefined;
public readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;

/** {@inheritDoc CommandLineParameter.kind} */
public readonly kind: CommandLineParameterKind.ChoiceList = CommandLineParameterKind.ChoiceList;

/** @internal */
public constructor(definition: ICommandLineChoiceListDefinition<TChoice>) {
super(definition);
const { alternatives, completions } = definition;

if (definition.alternatives.length < 1) {
const alternativesSet: Set<TChoice> = 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;
}

/**
Expand All @@ -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}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,41 @@ export interface IRequiredCommandLineChoiceParameter<TChoice extends string = st
*/
export class CommandLineChoiceParameter<TChoice extends string = string> extends CommandLineParameterBase {
/** {@inheritDoc ICommandLineChoiceDefinition.alternatives} */
public readonly alternatives: ReadonlyArray<TChoice>;
public readonly alternatives: ReadonlySet<TChoice>;

/** {@inheritDoc ICommandLineStringDefinition.defaultValue} */
public readonly defaultValue: TChoice | undefined;

private _value: TChoice | undefined = undefined;

/** {@inheritDoc ICommandLineChoiceDefinition.completions} */
public readonly completions: (() => Promise<TChoice[]>) | undefined;
public readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;

/** {@inheritDoc CommandLineParameter.kind} */
public readonly kind: CommandLineParameterKind.Choice = -CommandLineParameterKind.Choice;

/** @internal */
public constructor(definition: ICommandLineChoiceDefinition<TChoice>) {
super(definition);
const { alternatives, defaultValue, completions } = definition;

if (definition.alternatives.length < 1) {
const alternativesSet: Set<TChoice> = 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;
}

/**
Expand All @@ -72,8 +74,8 @@ export class CommandLineChoiceParameter<TChoice extends string = string> 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}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLine
*
* In a future release, this will be renamed to `getCompletionsAsync`
*/
completions?: () => Promise<string[]>;
completions?: (() => Promise<ReadonlyArray<string> | ReadonlySet<string>>) | undefined;
}

/**
Expand All @@ -146,7 +146,7 @@ export interface ICommandLineChoiceDefinition<TChoice extends string = string>
/**
* A list of strings (which contain no spaces), of possible options which can be selected
*/
alternatives: TChoice[];
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;

/**
* {@inheritDoc ICommandLineStringDefinition.defaultValue}
Expand All @@ -159,7 +159,7 @@ export interface ICommandLineChoiceDefinition<TChoice extends string = string>
* This option is only used when `ICommandLineParserOptions.enableTabCompletionAction`
* is enabled.
*/
completions?: () => Promise<TChoice[]>;
completions?: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
}

/**
Expand All @@ -174,15 +174,15 @@ export interface ICommandLineChoiceListDefinition<TChoice extends string = strin
/**
* A list of strings (which contain no spaces), of possible options which can be selected
*/
alternatives: TChoice[];
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;

/**
* An optional callback that provides a list of custom choices for tab completion.
* @remarks
* This option is only used when `ICommandLineParserOptions.enableTabCompletionAction`
* is enabled.
*/
completions?: () => Promise<TChoice[]>;
completions?: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
22 changes: 11 additions & 11 deletions libraries/ts-command-line/src/providers/TabCompletionAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ export class TabCompleteAction extends CommandLineAction {
if (completePartialWord) {
for (const parameterName of parameterNames) {
if (parameterName === secondLastToken) {
const values: ReadonlyArray<string> = await this._getParameterValueCompletionsAsync(
const values: ReadonlySet<string> = await this._getParameterValueCompletionsAsync(
parameterNameMap.get(parameterName)!
);
if (values.length > 0) {
if (values.size > 0) {
yield* this._completeParameterValues(values, lastToken);
return;
}
Expand All @@ -135,10 +135,10 @@ export class TabCompleteAction extends CommandLineAction {
} else {
for (const parameterName of parameterNames) {
if (parameterName === lastToken) {
const values: ReadonlyArray<string> = await this._getParameterValueCompletionsAsync(
const values: ReadonlySet<string> = await this._getParameterValueCompletionsAsync(
parameterNameMap.get(parameterName)!
);
if (values.length > 0) {
if (values.size > 0) {
yield* values;
return;
}
Expand Down Expand Up @@ -174,8 +174,8 @@ export class TabCompleteAction extends CommandLineAction {

private async _getParameterValueCompletionsAsync(
parameter: CommandLineParameter
): Promise<ReadonlyArray<string>> {
let choiceParameterValues: ReadonlyArray<string> = [];
): Promise<ReadonlySet<string>> {
let choiceParameterValues: ReadonlySet<string> | undefined;
if (parameter.kind === CommandLineParameterKind.Choice) {
choiceParameterValues = parameter.alternatives;
} else if (parameter.kind !== CommandLineParameterKind.Flag) {
Expand All @@ -190,12 +190,12 @@ export class TabCompleteAction extends CommandLineAction {
parameterWithArgumentOrChoices = parameter;
}

if (parameterWithArgumentOrChoices?.completions) {
choiceParameterValues = await parameterWithArgumentOrChoices.completions();
}
const completionValues: ReadonlyArray<string> | ReadonlySet<string> | undefined =
await parameterWithArgumentOrChoices?.completions?.();
choiceParameterValues = completionValues instanceof Set ? completionValues : new Set(completionValues);
}

return choiceParameterValues;
return choiceParameterValues ?? new Set();
}

private _getGlobalParameterOffset(tokens: string[]): number {
Expand All @@ -215,7 +215,7 @@ export class TabCompleteAction extends CommandLineAction {
}

private *_completeParameterValues(
choiceParameterValues: ReadonlyArray<string>,
choiceParameterValues: ReadonlyArray<string> | ReadonlySet<string>,
lastToken: string
): IterableIterator<string> {
for (const choiceParameterValue of choiceParameterValues) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
<ControlledComboBox
{...baseControllerProps}
defaultValue={commandLineChoiceParameter.defaultValue}
options={commandLineChoiceParameter.alternatives.map((alternative: string) => ({
key: alternative,
text: alternative
}))}
/>
<ControlledComboBox {...baseControllerProps} defaultValue={defaultValue} options={options} />
);
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 = (
<ControlledComboBox
{...baseControllerProps}
multiSelect={true}
options={commandLineChoiceListParameter.alternatives.map((alternative: string) => ({
key: alternative,
text: alternative
}))}
/>
<ControlledComboBox {...baseControllerProps} multiSelect={true} options={options} />
);
break;
}
Expand Down
Loading