Tslint: How to get the resolved types of the arguments of a callback function

Created on 4 May 2017  路  8Comments  路  Source: palantir/tslint

Hi. Long time user so thanks for creating a great project!

I'm working on a new rule which basically tells when you are typing arguments of a callback function when this is not needed because the compiler figured out the type on its own (following a typescript coding standard that you should only explicitly type when it has added value).

I have the following piece of test code.

interface TestInterface {
    readonly prop: number,
}

const testVariable: TestInterface = {prop: 1}

const chain = <T>(input: T) => ({
    chainFunction1: (callback1: (arg: T) => T) => ({
        chainFunction2: () => callback1(input),
    }),
})

const result = chain(testVariable)
    .chainFunction1(arg => arg)
    .chainFunction2()

Typescript naturally knows what the type of arg is:

image

Now I'm trying to access that resolved type in the compiler API.

I'm able to access the resolved type of outer function using the TypeChecker of typescript, but so far I'm unable to access the type of the arguments of the callback. I'm looking for help in achieving this.

(PS: If questions about creating new rules should be placed elsewhere or formatted differently please let me know!)

Question

All 8 comments

Unfortunately the TypeChecker has no method to retrieve the contextual type of a specific parameter, so you have to get the contextual type of the function expression and loop though the parameters:

declare var node: ts.FunctionExpression | ts.ArrowFunction;
const type = tc.getContextualType(node);
if (type !== undefined) {
    for (const param of type.callSignatures[0].parameters) {
        param.type; // do whatever you want with the type
    }
}

It's great that you want to tackle this rule. I guess this will find a lot of unnecessary type annotations.
There are even plenty of them in this repo. For example:

ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { return ts.forEachChild(node, cb); });
                                                  ~~~~~~~ [unnecessary type declaration]

Hey @ajafff. Thanks for the quick and detailed response. I'll try your suggestion this afternoon when I continue work on this topic.

Also it's good to know you recognise the use case. It's especially relevant my team because we rely on the chaining methods of Lodash a lot. As these are quite complex to type the typings are continuously improved. Sometimes typings that were once needed (because the type checker could not figure out the argument types by it self) are now obsolete. It's nice to catch these cases.

Ok, so I'm working on this rule and using your input I was able to retrieve the types of the parameters in my example code. But unfortunately an other piece of code gave me a false negative, despite appearing to me as having comparable syntax (an arrow function in a Lodash chain):

import {chain} from 'lodash'

interface TestInterface {}

const testArray: TestInterface[] = []

chain(testArray)
    .flatMap(testElement => testElement)

Typescript once again perfectly types the testElement as having the type TestInterface. Yet the contextual type of the ArrowFunction has zero callSignatures, resulting in my previous setup not bing able to find the type of the testElement parameter.

Is that problem specific to ArrowFunction does it occur for a FunctionExpression, too?

/cc @andy-hanson maybe you can share your experience about using the type checker?

Good question, just tried out:

chain(testArray).flatMap(function(testElement) { return testElement })

giving met the same 0 callSignatures

Just a guess: the declared type of chain has iteratee: ListIterator<T, Many<TResult>>|string, so you may be looking at a union type instead, and have to break it into its constituent types.

On that note, you may find this function from strictTypePredicatesRule.ts helpful:

/** Type predicate to test for a union type. */
function isUnionType(type: ts.Type): type is ts.UnionType {
    return Lint.isTypeFlagSet(type, ts.TypeFlags.Union);
}

Maybe this belongs in tsutils?

@andy-hanson haha spot on with the declared type of chain :)

Using your insight I was indeed able to fix this false negative. On to the next false negative. This seems like quite a complicated problem so there's a good chance you'll see me coming back here.

Indeed I found the isUnionType function. It's actually part of 4 different rules so moving it to tsutils might make sense

Looks like this was answered - thanks for the discussion everyone!

Was this page helpful?
0 / 5 - 0 ratings