I basically have this:
function foo(){
// literally pass the arguments object into the pragmatik.parse method
// the purpose of pragmatik.parse is to handle more complex variadic functions
const [a,b,c,d,e] = pragmatik.parse(arguments);
// now we have the correct arguments in the expected location, using array destructuring
}
so we have the pragmatik.parse method:
function parse(args){
// return parsed arguments
}
now I want to use TypeScript to define types, all I know is that arguments is an Object:
function parse(args: Object){
}
so my question is: does TypeScript give a definition or type for an arguments object in JS? Sorry this is a bit meta, but please bear with me, what I am asking about is sane.
My Webstorm IDE suggests that this might be IArguments, which is provided by: lib/es6/d.ts, which is somewhere out there. Maybe someone can verify this is correct, but I am fairly certain.
So the answer would be:
function parse(args: IArguments){
}
and the full signature would be:
function parse(args: IArguments) : Array<any> {
}
since the parse method returns a generic array
You can pick exact type of arguments with tsargs package from npm
Eg:
import { ArgsN } from 'tsargs';
function foo(a: boolean, b: number, c: string) {}
const argsABC: ArgsN<typeof foo> = [ true, 123, 'Hello' ];
In your case:
import { ArgsN } from 'tsargs';
function parse<T extends (...args: any[]) => any>(args: IArguments): ArgsN<T> {
// return parsed arguments
}
// ...
const args = parse<typeof foo>(arguments);
// args -> [ boolean, number, string ];
Related
I am trying to access parameter names in method decorator.
function log(filter: string[] = []) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const originalFunc = descriptor.value;
descriptor.value = async function(...args: any[]) {
this.logger.log(
`Start ${propertyKey} function`,
`${this.constructor.name}.${propertyKey}`,
);
// filter is parameter names: to not log them
this.logger.verbose(
`${propertyKey} function Input Parameters is ${args}`,
);
const result = await originalFunc.apply(this, args);
this.logger.verbose(`${propertyKey} function returned: ${result}`);
this.logger.log(`End ${propertyKey} function`);
return result;
};
return descriptor;
};
}
What I am trying to do is to write a logger decorator. this decorator would log method name with its args, and its result after the method has been ran.
This is no problem, but I also want to filter on what param gets logged.
We don't know what params we would get, or how many cause this decorator would be used on variety of methods.
One way I could think of is to get the param names. for example: if the method has three params arbitraryMethod(name: string, age: number, married: boolean) , I would like to get this as something like:
argNames = ['name', 'age', 'married'];
argValues = ['Jack', 23, false];
Or get it in this way:
const args {
name: 'Jack',
age: 23,
married: false
};
There is some other way to filter, which is to get indexes instead of param names, and filter by it, but it wouldn't be elegant and nice and probably a little hard to use. So I avoid using this approach.
I am open to any other solution, if you have it.
Thanks in advance.
You can access to the array of parameters when you are inside the decorator once you want to use the original function, as all of them are inside the args[] parameter
descriptor.value = function (...args: any[]) {
// now you can access to args[0, 1...]
// Call the original function if needed
return originalValue.apply(this, args);
};
Hope this helps,
I'm working on some code that auto-generates type hints based on function calls (GraphQL Nexus stuff).
Then, in one of it's functions, it expects an anonymous type based on those auto-generated attributes:
export const Item = {
isTypeOf(data) { // data's type is an anonymous type, here it would be '{ name: string }'
...
}
definition(t) {
t.string('name')
}
}
However, this data argument may contain more variables than the ones defined by the function calls. In my case, I need to access a kind property, but I can't call the t._type_ function because it would have undesired side-effects.
I also can't just pass the type as { kind: string } since isTypeOf type expects it to have at least all defined properties on it's arguments.
I could just use { name: string, kind: string } in this example, but my actual code contains more complex objects, and I would lose all the benefits of the auto-generated typing stuff.
Is there any way for me to in-line extend an argument with anonymous type? I was thinking something like an initial or default keyword to get an argument's own type, and use it like this:
isTypeOf(data: initial & { kind: string })
isTypeOf(data: default & { kind: string })
Typescript does not care about additional keys in objects passed to function (unless created in the parameter list).
If you only want to accept objects with given property and not return it, use the definition of isTypeOf.
If you need to return the same type and possibly extend it use the definition of definition.
export const Item = {
isTypeOf(data: { kind: string }): boolean {
return data.kind == '...';
},
definition<T extends { string: (key: string): void }>(t): T & { defined: true } {
t.string('name');
(t as T & { defined: true }).defined = true;
return t;
},
};
const myType = {
kind: 'hello',
name: 'World',
string(key: string): {},
};
Item.isTypeOf(myType); // OK
Item.isTypeOf({ kind: 'hello', name: 'World' }); // Not OK
Item.definition(myType); // OK
Item.definition({ string(key: string): {} }); // OK
In my Angular 8.2/Typescript 3.4.3 app, I need to generate an array.map callback dynamically, but the TS linter is barking about that function. Here is an example snippet I contrived to illustrate:
export type Role = [string, number];
export type BaseRole = [string, number[]];
const roles: BaseRole[] = [
[ 'hero', [100, 200, 300]],
[ 'villain', [300, 600, 900]],
[ 'minion', [20, 40, 60]]
];
function roleMapper(i: number): Function {
return ([role, strengths]) => [role, strengths[i]];
}
function getRolesAtLevel (level): Role[] {
return roles.map(roleMapper(level)); // <-- Linter warning occurs here
}
let myRoles = getRolesAtLevel(1);
Above I want to map an array of "BaseRoles" down to "Roles" based on user input. The linter complains about return roles.map(roleMapper(level)); (line 16) with the follow message:
Argument of type 'Function' is not assignable to parameter of type '(value: [string, number[]], index: number, array: [string, number[]][]) => [string, number]'.
I observe that the type '(value ... index ... array)' would be that of a map callback. The function I've provided is typed Function and supports that interface, so my questions are:
Must I be explicit about the interface of the function when using the Function type?
Is there another notation that I should have used to designate a "map" callback?
Thanks..
Yes, you should probably be more explicit about types. Function is too wide of a type to be useful for your purposes, since the compiler has no idea anymore what will come out of it. I'd suggest you write roleMapper() as follows:
function roleMapper(i: number) {
return ([role, strengths]: BaseRole): Role => [role, strengths[i]];
}
Here we are telling the compiler that the returned function takes a BaseRole and returns a Role. The compiler infers a return type equivalent to (br: BaseRole) => Role, which will then allow the rest of your code to function as you intend:
function getRolesAtLevel(level: number): Role[] {
return roles.map(roleMapper(level)); // okay
}
Okay, hope that helps; good luck!
Link to code
In Flow type checker, how to define function response type based on argument object property. Here is an example:
function test(argument) {
if (argument.responseType === "string") {
return "Some string value"
}
return { some: { json: "object" } };
}
Is it possible to add Flow typings to this code?
I know that it is possible to write:
declare export function test(argument: { responseType: string}): string | { some: { json: string } };
But that's not enough. I don't want an Union Type response. It has to be exactly string or object based on the argument provided.
Flow allows function overloading by providing several definitions to the same function. Together with literal types next should work:
declare function test(argument: {responseType: 'string'}): string;
declare function test(argument: {}): {some: {json: string}};
Here is a Flow try example code
I don’t understand the flow error I’m currently getting. I have a Javascript object of objects (dataObject) that I want to convert to an array of objects, so I do so using Object.values(dataObject). Then, I iterate through each object in the array with the following:
const dataObjectArray = Object.values(dataObject);
return dataObjectArray((data: DataObject) => {
const { typeA, typeB } = data;
return {
TYPE_A: typeA,
TYPE_B: typeB,
};
});
But I get the following flowtype error:
I’m not sure how to match up the types. Currently my DataObject flow type is
type DataObject = {
typeA: string,
typeB: string,
};
Any help would be appreciated. Thanks!
The type definition for the Object.values function has no way to know that the argument passed to it is an object where the values are all the same type. You could just as easily be doing Object.values({foo: 4, bar: "str"}). The type definition is
(any) => Array<mixed>
meaning that you are doing .map on a value of type Array<mixed>.
That means if you want to use it as object, your method will not work. Assuming your "object of objects" is typed as
type DataObjects = {
[string]: DataObject,
}
You'd likely be better off doing
function values(objs: DataObjects): Array<DataObject> {
return Object.keys(objs).map(key => objs[key]);
}
If you prefer to use Object.values() (probably more efficient) and have typing right, you can use a helper function like this:
function objectToValues<A, B>(obj:{[key: A]: B} ): Array<B> {
return ((Object.values(obj): any): Array<B>)
}