Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
52 changes: 51 additions & 1 deletion packages/docgen/lib/get-type-annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,56 @@ function getTypeAnnotation( typeAnnotation ) {
}
}

/**
* Extract wrapped selector functions to reach inside for parameter types.
*
* This function wasn't necessary until we started introducing more TypeScript code into
* the project. With parameter types fully in the JSDoc comments we always have a direct
* match between parameter name and the type. However, when working in TypeScript where
* we rely on the type annotations for the types we introduce a mismatch when wrapping
* functions.
*
* Example:
* export const getThings = createSelector( ( state ) => state.things, ( state ) => state.version );
*
* In this example we would document `state` but its type is buried inside of `createSelector`.
* Because this kind of scenario is tricky to properly parse without asking TypeScript directly
* to give us the actual type of `getThings` we're going to special-case the known instances
* of selector-wrapping to extract the inner function and re-connect the parameter types
* with their descriptions in the JSDoc comments.
*
* If we find more wrapper functions on selectors we should add them below following the
* example of `createSelector` and `createRegsitrySelector`.
*
* @param {ASTNode} token Contains either a function or a call to a function-wrapper.
*
* TODO: Remove the special-casing here once we're able to infer the types from TypeScript itself.
*/
function unwrapWrappedSelectors( token ) {
if ( babelTypes.isFunctionDeclaration( token ) ) {
return token;
}

if ( babelTypes.isArrowFunctionExpression( token ) ) {
return token;
}

if ( babelTypes.isCallExpression( token ) ) {
// createSelector( ( state, queryId ) => state.queries[ queryId ] );
// \--------------------------------------------/ CallExpression.arguments[0]
if ( token.callee.name === 'createSelector' ) {
return token.arguments[ 0 ];
}

// createRegistrySelector( ( selector ) => ( state, queryId ) => select( 'core/queries' ).get( queryId ) );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely comment!

// \-----------------------------------------------------------/ CallExpression.arguments[0].body
// \---------------------------------------------------------------------------/ CallExpression.arguments[0]
if ( token.callee.name === 'createRegistrySelector' ) {
return token.arguments[ 0 ].body;
}
}
}

/**
* @param {ASTNode} token
* @return {babelTypes.ArrowFunctionExpression | babelTypes.FunctionDeclaration} The function token.
Expand All @@ -392,7 +442,7 @@ function getFunctionToken( token ) {
resolvedToken = resolvedToken.declarations[ 0 ].init;
}

return resolvedToken;
return unwrapWrappedSelectors( resolvedToken );
}

function getFunctionNameForError( declarationToken ) {
Expand Down
45 changes: 45 additions & 0 deletions packages/docgen/test/get-type-annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Internal dependencies
*/
const getTypeAnnotation = require( '../lib/get-type-annotation' );
const engine = require( '../lib/engine' );

const getSimpleTypeNode = require( './fixtures/type-annotations/simple-types/get-node' );
const getArraysGenericTypesUnionsAndIntersctionsNode = require( './fixtures/type-annotations/arrays-generic-types-unions-intersections/get-node' );
Expand Down Expand Up @@ -318,4 +319,48 @@ describe( 'Type annotations', () => {
);
} );
} );

describe( 'statically-wrapped function exceptions', () => {
it( 'should find types for inner function with `createSelector`', () => {
const { tokens } = engine(
'test.ts',
`/**
* Returns the number of things
*
* @param state - stores all the things
*/
export const getCount = createSelector( ( state: string[] ) => state.length );
`
);

expect(
getTypeAnnotation(
{ tag: 'param', name: 'state' },
tokens[ 0 ],
0
)
).toBe( 'string[]' );
} );

it( 'should find types for inner function with `createRegistrySelector`', () => {
const { tokens } = engine(
'test.ts',
`/**
* Returns the number of things
*
* @param state - stores all the things
*/
export const getCount = createRegistrySelector( ( select ) => ( state: number ) => state );
`
);

expect(
getTypeAnnotation(
{ tag: 'param', name: 'state' },
tokens[ 0 ],
0
)
).toBe( 'number' );
} );
} );
} );