Reputation: 923
Here is the types for arg, Node.js console commands parser:
declare const flagSymbol: unique symbol;
declare function arg<T extends arg.Spec>(spec: T, options?: arg.Options): arg.Result<T>;
declare namespace arg {
export function flag<T>(fn: T): T & { [flagSymbol]: true };
export const COUNT: Handler<number> & { [flagSymbol]: true };
export type Handler <T = any> = (value: string, name: string, previousValue?: T) => T;
export interface Spec {
[key: string]: string | Handler | [Handler];
}
export type Result<T extends Spec> = { _: string[] } & {
[K in keyof T]?: T[K] extends Handler
? ReturnType<T[K]>
: T[K] extends [Handler]
? Array<ReturnType<T[K][0]>>
: never
};
export interface Options {
argv?: string[];
permissive?: boolean;
stopAtPositional?: boolean;
}
}
export = arg;
I need to annotate parsing results:
import parseConsoleArgument from 'arg';
export function cli(rawConsoleCommandData: Array<string>): void {
const consoleCommandArguments: /* ??? */ = parseConsoleArgument(
{}, { argv: rawConsoleCommandData.slice(2)}
);
}
Logically correct type is arg.Result
. However, I can not access to it. Below code
import parseConsoleArgument from 'arg';
export function cli(rawConsoleCommandData: Array<string>): void {
const consoleCommandArguments: arg.Result = parseConsoleArgument(
{}, { argv: rawConsoleCommandData.slice(2)}
);
}
gives TS2503: Cannot find namespace "arg"
.
Upvotes: 1
Views: 156
Reputation: 249546
Not sure why you want to annotate the variable, its type will be inferred based on the result of parseConsoleArgument
. So it will already be of the appropriate type. If your team requires annotations on all variables you should challenge that IMO, there are lots of types that are very difficult to annotate without duplicating a lot of code. This is one of those cases, lets see why.
Accessing the Result
is not an issue, you just don't access it through the arg
namespace, you need to access it through the module alias you used:
const consoleCommandArguments: parseConsoleArgument.Result<{}> = parseConsoleArgument(
{}, { argv: rawConsoleCommandData.slice(2)}
);
This works, but you see you have a type parameter for parseConsoleArgument.Result
that I used {}
for. This might not seem like an issue until you start adding arguments to be parsed:
export function cli(rawConsoleCommandData: Array<string>): void {
const consoleCommandArguments: parseConsoleArgument.Result<{}> = parseConsoleArgument(
{ p: Number, p2: Boolean }, { argv: rawConsoleCommandData.slice(2)}
);
consoleCommandArguments.p // err
}
If we remove the explicit annotation we don't get an error, and p
is a number
as one might expect. This happens because parseConsoleArgument
is a generic function that extracts type information from it's first parameter and uses conditional types to create a new type that has { p: number, p2: Boolean }
If we want to type this manually we would need to write:
export function cli(rawConsoleCommandData: Array<string>): void {
const consoleCommandArguments : parseConsoleArgument.Result<{
p: typeof Number,
p2: typeof Boolean
}> = parseConsoleArgument(
{ p: Number, p2: Boolean }, { argv: rawConsoleCommandData.slice(2)}
);
consoleCommandArguments.p // ok
}
Which works but is ugly and much longer than the original (needlessly so) and requires us to change two places to add a new parameter. I would let the compiler infer the type, it is pretty good at it. Anyone wanting to see the actual type can just hover over the variable and they will see the type in a tooltip
Upvotes: 1