Skip to content
2 changes: 1 addition & 1 deletion packages/docgen/lib/get-jsdoc-from-token.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ module.exports = ( token ) => {
token,
0
);
if ( potentialTypeAnnotation !== '' ) {
if ( potentialTypeAnnotation ) {
jsdoc.tags.push( {
tag: 'type',
type: potentialTypeAnnotation,
Expand Down
78 changes: 35 additions & 43 deletions packages/docgen/lib/get-type-annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,10 @@ function getFunctionToken( token ) {

function getFunctionNameForError( declarationToken ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This particular function has been such a headache to maintain with its many edge cases. Thanks for taking the time to improve it!

let namedFunctionToken = declarationToken;
if ( babelTypes.isExportNamedDeclaration( declarationToken ) ) {
if (
babelTypes.isExportNamedDeclaration( declarationToken ) ||
babelTypes.isExportDefaultDeclaration( declarationToken )
) {
namedFunctionToken = declarationToken.declaration;
}

Expand Down Expand Up @@ -451,7 +454,7 @@ function getQualifiedObjectPatternTypeAnnotation( tag, paramType ) {
* @param {CommentTag} tag The documented parameter.
* @param {ASTNode} declarationToken The function the parameter is documented on.
* @param {number} paramIndex The parameter index.
* @return {null | string} The parameter's type annotation.
* @return {string?} The parameter's type annotation.
*/
function getParamTypeAnnotation( tag, declarationToken, paramIndex ) {
const functionToken = getFunctionToken( declarationToken );
Expand All @@ -469,58 +472,51 @@ function getParamTypeAnnotation( tag, declarationToken, paramIndex ) {
);
}

const isQualifiedName = tag.name.includes( '.' );
if ( babelTypes.isAssignmentPattern( paramToken ) ) {
paramToken = paramToken.left;
}

try {
if ( babelTypes.isAssignmentPattern( paramToken ) ) {
paramToken = paramToken.left;
}
if (
! paramToken.typeAnnotation ||
! paramToken.typeAnnotation.typeAnnotation
) {
return;
}

const paramType = paramToken.typeAnnotation.typeAnnotation;
const paramType = paramToken.typeAnnotation.typeAnnotation;
const isQualifiedName = tag.name.includes( '.' );

if (
babelTypes.isIdentifier( paramToken ) ||
babelTypes.isRestElement( paramToken ) ||
( ( babelTypes.isArrayPattern( paramToken ) ||
babelTypes.isObjectPattern( paramToken ) ) &&
! isQualifiedName )
) {
return getTypeAnnotation( paramType );
} else if ( babelTypes.isArrayPattern( paramToken ) ) {
return getQualifiedArrayPatternTypeAnnotation( tag, paramType );
} else if ( babelTypes.isObjectPattern( paramToken ) ) {
return getQualifiedObjectPatternTypeAnnotation( tag, paramType );
}
} catch ( e ) {
throw new Error(
`Could not find type for parameter '${
tag.name
}' in function '${ getFunctionNameForError( declarationToken ) }'.`
);
if (
babelTypes.isIdentifier( paramToken ) ||
babelTypes.isRestElement( paramToken ) ||
( ( babelTypes.isArrayPattern( paramToken ) ||
babelTypes.isObjectPattern( paramToken ) ) &&
! isQualifiedName )
) {
return getTypeAnnotation( paramType );
} else if ( babelTypes.isArrayPattern( paramToken ) ) {
return getQualifiedArrayPatternTypeAnnotation( tag, paramType );
} else if ( babelTypes.isObjectPattern( paramToken ) ) {
return getQualifiedObjectPatternTypeAnnotation( tag, paramType );
}
}

/**
* @param {ASTNode} declarationToken A function token.
* @return {null | string} The function's return type annoation.
* @return {string?} The function's return type annotation.
*/
function getReturnTypeAnnotation( declarationToken ) {
const functionToken = getFunctionToken( declarationToken );

try {
return getTypeAnnotation( functionToken.returnType.typeAnnotation );
} catch ( e ) {
throw new Error(
`Could not find return type for function '${ getFunctionNameForError(
declarationToken
) }'.`
);
if ( ! functionToken.returnType ) {
return;
}

return getTypeAnnotation( functionToken.returnType.typeAnnotation );
}

/**
* @param {ASTNode} declarationToken
* @return {string} The type annotation for the variable.
* @return {string?} The type annotation for the variable.
*/
function getVariableTypeAnnotation( declarationToken ) {
let resolvedToken = declarationToken;
Expand All @@ -541,7 +537,6 @@ function getVariableTypeAnnotation( declarationToken ) {
return getTypeAnnotation( resolvedToken.typeAnnotation.typeAnnotation );
} catch ( e ) {
// assume it's a fully undocumented variable, there's nothing we can do about that but fail silently.
return '';
}
}

Expand All @@ -550,7 +545,7 @@ module.exports =
* @param {CommentTag} tag A comment tag.
* @param {ASTNode} token A function token.
* @param {number | null} index The index of the parameter or `null` if not a param tag.
* @return {null | string} The type annotation for the given tag or null if the tag has no type annotation.
* @return {[string]} The type annotation for the given tag or null if the tag has no type annotation.
*/
function ( tag, token, index ) {
// If the file is using JSDoc type annotations, use the JSDoc.
Expand All @@ -568,8 +563,5 @@ module.exports =
case 'type': {
return getVariableTypeAnnotation( token );
}
default: {
return '';
}
}
};
74 changes: 54 additions & 20 deletions packages/docgen/lib/markdown/formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const formatTag = ( title, tags, formatter, docs ) => {
docs.push( '\n' );
docs.push( `*${ title }*` );
docs.push( '\n' );
docs.push( ...tags.map( formatter ) );
docs.push( ...tags.map( ( tag ) => `\n${ formatter( tag ) }` ) );
}
};

Expand Down Expand Up @@ -103,54 +103,88 @@ module.exports = (
formatTag(
'Related',
getSymbolTagsByName( symbol, 'see', 'link' ),
( tag ) =>
`\n- ${ tag.name.trim() }${
tag.description ? ' ' : ''
}${ tag.description.trim() }`,
( tag ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for breaking out the conditional logic in the functions in this file. It improves the readability a lot.

const name = tag.name.trim();
const desc = tag.description.trim();

// prettier-ignore
return desc
? `- ${ name } ${ desc }`
: `- ${ name }`;
},
docs
);
formatExamples( getSymbolTagsByName( symbol, 'example' ), docs );
formatTag(
'Type',
getSymbolTagsByName( symbol, 'type' ),
( tag ) =>
`\n- ${ getTypeOutput( tag ) }${ cleanSpaces(
` ${ tag.name } ${ tag.description }`
) }`,
( tag ) => {
const type = tag.type && getTypeOutput( tag );
const desc = cleanSpaces(
`${ tag.name } ${ tag.description }`
);

// prettier-ignore
return type
? `- ${ type }${ desc }`
: `- ${ desc }`;
},
docs
);
formatTag(
'Parameters',
getSymbolTagsByName( symbol, 'param' ),
( tag ) =>
`\n- *${ tag.name }* ${ getTypeOutput(
tag
) }: ${ cleanSpaces( tag.description ) }`,
( tag ) => {
const name = tag.name;
const type = tag.type && getTypeOutput( tag );
const desc = cleanSpaces( tag.description );

return type
? `- *${ name }* ${ type }: ${ desc }`
: `- *${ name }* ${ desc }`;
},
docs
);
formatTag(
'Returns',
getSymbolTagsByName( symbol, 'return' ),
( tag ) => {
return `\n- ${ getTypeOutput( tag ) }: ${ cleanSpaces(
const type = tag.type && getTypeOutput( tag );
const desc = cleanSpaces(
`${ tag.name } ${ tag.description }`
) }`;
);

// prettier-ignore
return type
? `- ${ type }: ${ desc }`
: `- ${ desc }`;
},
docs
);
formatTag(
'Type Definition',
getSymbolTagsByName( symbol, 'typedef' ),
( tag ) => `\n- *${ tag.name }* ${ getTypeOutput( tag ) }`,
( tag ) => {
const name = tag.name;
const type = getTypeOutput( tag );

return `- *${ name }* ${ type }`;
},
docs
);
formatTag(
'Properties',
getSymbolTagsByName( symbol, 'property' ),
( tag ) =>
`\n- *${ tag.name }* ${ getTypeOutput(
tag
) }: ${ cleanSpaces( tag.description ) }`,
( tag ) => {
const name = tag.name;
const type = tag.type && getTypeOutput( tag );
const desc = cleanSpaces( tag.description );

// prettier-ignore
return type
? `- *${ name }* ${ type }: ${ desc }`
: `- *${ name }* ${ desc }`
},
docs
);
docs.push( '\n' );
Expand Down
14 changes: 5 additions & 9 deletions packages/docgen/test/get-type-annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe( 'Type annotations', () => {
};
const node = {};
const result = getTypeAnnotation( tag, node, 0 );
expect( result ).toBe( '' );
expect( result ).toBeFalsy();
} );

const paramTag = {
Expand Down Expand Up @@ -196,16 +196,12 @@ describe( 'Type annotations', () => {
describe( 'missing types', () => {
const node = getMissingTypesNode();

it( 'should throw an error if there is no return type', () => {
expect( () => getTypeAnnotation( returnTag, node, 0 ) ).toThrow(
"Could not find return type for function 'fn'."
);
it( 'should return empty value if there is no return type', () => {
expect( getTypeAnnotation( returnTag, node, 0 ) ).toBeFalsy();
} );

it( 'should throw an error if there is no param type', () => {
expect( () => getTypeAnnotation( paramTag, node, 0 ) ).toThrow(
"Could not find type for parameter 'foo' in function 'fn'."
);
it( 'should return empty value if there is no param type', () => {
expect( getTypeAnnotation( paramTag, node, 0 ) ).toBeFalsy();
} );
} );

Expand Down