mirror of
https://github.com/chenasraf/DefinitelyTyped.git
synced 2026-05-18 01:49:01 +00:00
🤖 Merge PR #61512 feat(node): types for util.parseArgs by @bakkot
* add types for util.parseArgs * make types worse but valid on ancient versions of typescript * add tests for util.parseArgs * add missing config option, whoops * more tests, plus tweak type for strict: false * remove Exact constraint * consistent pluralization * address rbuckton comments * export ParseArgsConfig type * fix typo Co-authored-by: John Gee <john@ruru.gen.nz> Co-authored-by: John Gee <john@ruru.gen.nz>
This commit is contained in:
2
types/node/index.d.ts
vendored
2
types/node/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
// Type definitions for non-npm package Node.js 18.6
|
||||
// Type definitions for non-npm package Node.js 18.7
|
||||
// Project: https://nodejs.org/
|
||||
// Definitions by: Microsoft TypeScript <https://github.com/Microsoft>
|
||||
// DefinitelyTyped <https://github.com/DefinitelyTyped>
|
||||
|
||||
@@ -200,3 +200,81 @@ access('file/that/does/not/exist', (err) => {
|
||||
{
|
||||
util.stripVTControlCharacters('\u001B[4mvalue\u001B[0m'); // $ExpectType string
|
||||
}
|
||||
|
||||
{
|
||||
// util.parseArgs: happy path
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
const config = {
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
foo: { type: 'string' },
|
||||
bar: { type: 'boolean', multiple: true },
|
||||
},
|
||||
} as const;
|
||||
|
||||
// $ExpectType { values: { foo: string | undefined; bar: boolean[] | undefined; }; positionals: string[]; }
|
||||
util.parseArgs(config);
|
||||
}
|
||||
|
||||
{
|
||||
// util.parseArgs: positionals not enabled
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
const config = {
|
||||
options: {
|
||||
foo: { type: 'string' },
|
||||
bar: { type: 'boolean', multiple: true },
|
||||
},
|
||||
} as const;
|
||||
|
||||
// @ts-expect-error
|
||||
util.parseArgs(config).positionals[0];
|
||||
}
|
||||
|
||||
{
|
||||
// util.parseArgs: tokens
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
const config = {
|
||||
tokens: true,
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
foo: { type: 'string' },
|
||||
bar: { type: 'boolean' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
// tslint:disable-next-line:max-line-length
|
||||
// $ExpectType { kind: "positional"; index: number; value: string; } | { kind: "option-terminator"; index: number; } | { kind: "option"; index: number; name: "foo"; rawName: string; value: string; inlineValue: boolean; } | { kind: "option"; index: number; name: "bar"; rawName: string; value: undefined; inlineValue: undefined; }
|
||||
util.parseArgs(config).tokens[0];
|
||||
}
|
||||
|
||||
{
|
||||
// util.parseArgs: strict: false
|
||||
|
||||
// $ExpectType { values: { [longOption: string]: string | boolean | undefined; }; positionals: string[]; }
|
||||
const result = util.parseArgs({
|
||||
strict: false,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
// util.parseArgs: strict: false
|
||||
|
||||
const result = util.parseArgs({
|
||||
strict: false,
|
||||
options: {
|
||||
x: { type: 'string', multiple: true },
|
||||
},
|
||||
});
|
||||
// $ExpectType (string | boolean)[] | undefined
|
||||
result.values.x;
|
||||
// $ExpectType string | boolean | undefined
|
||||
result.values.y;
|
||||
}
|
||||
|
||||
{
|
||||
// util.parseArgs: config not inferred precisely
|
||||
const config = {};
|
||||
|
||||
// $ExpectType { values: { [longOption: string]: string | boolean | (string | boolean)[] | undefined; }; positionals: string[]; tokens?: Token[] | undefined; }
|
||||
const result = util.parseArgs(config);
|
||||
}
|
||||
|
||||
187
types/node/util.d.ts
vendored
187
types/node/util.d.ts
vendored
@@ -1105,6 +1105,193 @@ declare module 'util' {
|
||||
*/
|
||||
encodeInto(src: string, dest: Uint8Array): EncodeIntoResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a high-level API for command-line argument parsing. Takes a
|
||||
* specification for the expected arguments and returns a structured object
|
||||
* with the parsed values and positionals.
|
||||
*
|
||||
* `config` provides arguments for parsing and configures the parser. It
|
||||
* supports the following properties:
|
||||
*
|
||||
* - `args` The array of argument strings. **Default:** `process.argv` with
|
||||
* `execPath` and `filename` removed.
|
||||
* - `options` Arguments known to the parser. Keys of `options` are the long
|
||||
* names of options and values are objects accepting the following properties:
|
||||
*
|
||||
* - `type` Type of argument, which must be either `boolean` (for options
|
||||
* which do not take values) or `string` (for options which do).
|
||||
* - `multiple` Whether this option can be provided multiple
|
||||
* times. If `true`, all values will be collected in an array. If
|
||||
* `false`, values for the option are last-wins. **Default:** `false`.
|
||||
* - `short` A single character alias for the option.
|
||||
*
|
||||
* - `strict`: Whether an error should be thrown when unknown arguments
|
||||
* are encountered, or when arguments are passed that do not match the
|
||||
* `type` configured in `options`. **Default:** `true`.
|
||||
* - `allowPositionals`: Whether this command accepts positional arguments.
|
||||
* **Default:** `false` if `strict` is `true`, otherwise `true`.
|
||||
* - `tokens`: Whether tokens {boolean} Return the parsed tokens. This is useful
|
||||
* for extending the built-in behavior, from adding additional checks through
|
||||
* to reprocessing the tokens in different ways.
|
||||
* **Default:** `false`.
|
||||
*
|
||||
* @returns The parsed command line arguments:
|
||||
*
|
||||
* - `values` A mapping of parsed option names with their string
|
||||
* or boolean values.
|
||||
* - `positionals` Positional arguments.
|
||||
* - `tokens` Detailed parse information (only if `tokens` was specified).
|
||||
*
|
||||
*/
|
||||
export function parseArgs<T extends ParseArgsConfig>(config: T): ParsedResults<T>;
|
||||
|
||||
interface ParseArgsOptionConfig {
|
||||
type: 'string' | 'boolean';
|
||||
short?: string;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
interface ParseArgsOptionsConfig {
|
||||
[longOption: string]: ParseArgsOptionConfig;
|
||||
}
|
||||
|
||||
export interface ParseArgsConfig {
|
||||
strict?: boolean;
|
||||
allowPositionals?: boolean;
|
||||
tokens?: boolean;
|
||||
options?: ParseArgsOptionsConfig;
|
||||
args?: string[];
|
||||
}
|
||||
|
||||
/*
|
||||
IfDefaultsTrue and IfDefaultsFalse are helpers to handle default values for missing boolean properties.
|
||||
TypeScript does not have exact types for objects: https://github.com/microsoft/TypeScript/issues/12936
|
||||
This means it is impossible to distinguish between "field X is definitely not present" and "field X may or may not be present".
|
||||
But we expect users to generally provide their config inline or `as const`, which means TS will always know whether a given field is present.
|
||||
So this helper treats "not definitely present" (i.e., not `extends boolean`) as being "definitely not present", i.e. it should have its default value.
|
||||
This is technically incorrect but is a much nicer UX for the common case.
|
||||
The IfDefaultsTrue version is for things which default to true; the IfDefaultsFalse version is for things which default to false.
|
||||
*/
|
||||
type IfDefaultsTrue<T, IfTrue, IfFalse> = T extends true
|
||||
? IfTrue
|
||||
: T extends false
|
||||
? IfFalse
|
||||
: IfTrue;
|
||||
|
||||
// we put the `extends false` condition first here because `undefined` compares like `any` when `strictNullChecks: false`
|
||||
type IfDefaultsFalse<T, IfTrue, IfFalse> = T extends false
|
||||
? IfFalse
|
||||
: T extends true
|
||||
? IfTrue
|
||||
: IfFalse;
|
||||
|
||||
type ExtractOptionValue<T extends ParseArgsConfig, O extends ParseArgsOptionConfig> = IfDefaultsTrue<
|
||||
T['strict'],
|
||||
O['type'] extends 'string' ? string : O['type'] extends 'boolean' ? boolean : string | boolean,
|
||||
string | boolean
|
||||
>;
|
||||
|
||||
type ParsedValues<T extends ParseArgsConfig> =
|
||||
& IfDefaultsTrue<T['strict'], unknown, { [longOption: string]: undefined | string | boolean }>
|
||||
& (T['options'] extends ParseArgsOptionsConfig
|
||||
? {
|
||||
-readonly [LongOption in keyof T['options']]: IfDefaultsFalse<
|
||||
T['options'][LongOption]['multiple'],
|
||||
undefined | Array<ExtractOptionValue<T, T['options'][LongOption]>>,
|
||||
undefined | ExtractOptionValue<T, T['options'][LongOption]>
|
||||
>;
|
||||
}
|
||||
: {});
|
||||
|
||||
type ParsedPositionals<T extends ParseArgsConfig> = IfDefaultsTrue<
|
||||
T['strict'],
|
||||
IfDefaultsFalse<T['allowPositionals'], string[], []>,
|
||||
IfDefaultsTrue<T['allowPositionals'], string[], []>
|
||||
>;
|
||||
|
||||
type PreciseTokenForOptions<
|
||||
K extends string,
|
||||
O extends ParseArgsOptionConfig,
|
||||
> = O['type'] extends 'string'
|
||||
? {
|
||||
kind: 'option';
|
||||
index: number;
|
||||
name: K;
|
||||
rawName: string;
|
||||
value: string;
|
||||
inlineValue: boolean;
|
||||
}
|
||||
: O['type'] extends 'boolean'
|
||||
? {
|
||||
kind: 'option';
|
||||
index: number;
|
||||
name: K;
|
||||
rawName: string;
|
||||
value: undefined;
|
||||
inlineValue: undefined;
|
||||
}
|
||||
: OptionToken & { name: K };
|
||||
|
||||
type TokenForOptions<
|
||||
T extends ParseArgsConfig,
|
||||
K extends keyof T['options'] = keyof T['options'],
|
||||
> = K extends unknown
|
||||
? T['options'] extends ParseArgsOptionsConfig
|
||||
? PreciseTokenForOptions<K & string, T['options'][K]>
|
||||
: OptionToken
|
||||
: never;
|
||||
|
||||
type ParsedOptionToken<T extends ParseArgsConfig> = IfDefaultsTrue<T['strict'], TokenForOptions<T>, OptionToken>;
|
||||
|
||||
type ParsedPositionalToken<T extends ParseArgsConfig> = IfDefaultsTrue<
|
||||
T['strict'],
|
||||
IfDefaultsFalse<T['allowPositionals'], { kind: 'positional'; index: number; value: string }, never>,
|
||||
IfDefaultsTrue<T['allowPositionals'], { kind: 'positional'; index: number; value: string }, never>
|
||||
>;
|
||||
|
||||
type ParsedTokens<T extends ParseArgsConfig> = Array<
|
||||
ParsedOptionToken<T> | ParsedPositionalToken<T> | { kind: 'option-terminator'; index: number }
|
||||
>;
|
||||
|
||||
type PreciseParsedResults<T extends ParseArgsConfig> = IfDefaultsFalse<
|
||||
T['tokens'],
|
||||
{
|
||||
values: ParsedValues<T>;
|
||||
positionals: ParsedPositionals<T>;
|
||||
tokens: ParsedTokens<T>;
|
||||
},
|
||||
{
|
||||
values: ParsedValues<T>;
|
||||
positionals: ParsedPositionals<T>;
|
||||
}
|
||||
>;
|
||||
|
||||
type OptionToken =
|
||||
| { kind: 'option'; index: number; name: string; rawName: string; value: string; inlineValue: boolean }
|
||||
| {
|
||||
kind: 'option';
|
||||
index: number;
|
||||
name: string;
|
||||
rawName: string;
|
||||
value: undefined;
|
||||
inlineValue: undefined;
|
||||
};
|
||||
|
||||
type Token =
|
||||
| OptionToken
|
||||
| { kind: 'positional'; index: number; value: string }
|
||||
| { kind: 'option-terminator'; index: number };
|
||||
|
||||
// If ParseArgsConfig extends T, then the user passed config constructed elsewhere.
|
||||
// So we can't rely on the `"not definitely present" implies "definitely not present"` assumption mentioned above.
|
||||
type ParsedResults<T extends ParseArgsConfig> = ParseArgsConfig extends T
|
||||
? {
|
||||
values: { [longOption: string]: undefined | string | boolean | Array<string | boolean> };
|
||||
positionals: string[];
|
||||
tokens?: Token[];
|
||||
}
|
||||
: PreciseParsedResults<T>;
|
||||
}
|
||||
declare module 'util/types' {
|
||||
export * from 'util/types';
|
||||
|
||||
Reference in New Issue
Block a user