Skip to content

Commit

Permalink
Merge pull request #6 from storybookjs/next
Browse files Browse the repository at this point in the history
Merge changes from main
  • Loading branch information
brandonseydel authored Nov 7, 2019
2 parents 1c1b0d5 + 4b2d80e commit 428ae87
Show file tree
Hide file tree
Showing 20 changed files with 874 additions and 419 deletions.
3 changes: 2 additions & 1 deletion addons/docs/src/frameworks/react/extractProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ Object.keys(PropTypes).forEach(typeName => {

export const getPropDefs: PropDefGetter = (type, section) => {
let processedType = type;

// eslint-disable-next-line react/forbid-foreign-prop-types
if (!hasDocgen(type) && !type.propTypes) {
if (isForwardRef(type) || type.render) {
processedType = type.render().type;
}
if (isMemo(type)) {
// (typeof type.type === 'function')?
processedType = type.type().type;
}
}
Expand Down
16 changes: 16 additions & 0 deletions addons/docs/src/lib/DocgenInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface DocgenInfo {
type?: {
name: string;
value?: {
name?: string;
raw?: string;
};
};
flowType?: any;
tsType?: any;
required: boolean;
description?: string;
defaultValue?: {
value: string;
};
}
7 changes: 5 additions & 2 deletions addons/docs/src/lib/docgenUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ export const extractPropsFromDocgen: PropDefGetter = (component, section) => {
propKeys.forEach(propKey => {
const docgenInfoProp = docgenInfoProps[propKey];

const propDef = typeSystemHandler(propKey, docgenInfoProp);
props[propKey] = propDef;
const result = typeSystemHandler(propKey, docgenInfoProp);

if (!result.ignore) {
props[propKey] = result.propDef;
}
});

return Object.values(props);
Expand Down
177 changes: 170 additions & 7 deletions addons/docs/src/lib/jsdoc-parser.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,187 @@
import doctrine from 'doctrine';
import doctrine, { Annotation } from 'doctrine';
import { isNil } from 'lodash';
import { DocgenInfo } from './DocgenInfo';

export interface JsDocAst {
export type ParseJsDoc = (docgenInfo: DocgenInfo) => JsDocParsingResult;

export interface JsDocParsingResult {
ignore: boolean;
description?: string;
extractedTags?: ExtractedJsDocTags;
}

export interface ExtractedJsDocParamTag {
name: string;
type?: doctrine.Type;
description?: string;
tags: any[];
raw: doctrine.Tag;
getPrettyName: () => string;
getTypeName: () => string;
}

export function parseComment(comment: string): JsDocAst {
export interface ExtractedJsDocReturnsTag {
type?: doctrine.Type;
description?: string;
raw: doctrine.Tag;
getTypeName: () => string;
}

export interface ExtractedJsDocTags {
params?: ExtractedJsDocParamTag[];
returns?: ExtractedJsDocReturnsTag;
ignore: boolean;
}

function parse(content: string): Annotation {
let ast;

try {
ast = doctrine.parse(comment, {
tags: ['param', 'arg', 'argument', 'returns'],
ast = doctrine.parse(content, {
tags: ['param', 'arg', 'argument', 'returns', 'ignore'],
sloppy: true,
});
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
console.error(e);

throw new Error('Cannot parse JSDoc tags.');
}

return ast;
}

export const parseJsDoc: ParseJsDoc = (docgenInfo: DocgenInfo) => {
const jsDocAst = parse(docgenInfo.description);
const extractedTags = extractJsDocTags(jsDocAst);

if (extractedTags.ignore) {
// There is no point in doing other stuff since this prop will not be rendered.
return {
ignore: true,
};
}

return {
ignore: false,
// Always use the parsed description to ensure JSDoc is removed from the description.
description: jsDocAst.description,
extractedTags,
};
};

function extractJsDocTags(ast: doctrine.Annotation): ExtractedJsDocTags {
const extractedTags: ExtractedJsDocTags = {
params: null,
returns: null,
ignore: false,
};

for (let i = 0; i < ast.tags.length; i += 1) {
const tag = ast.tags[i];

// arg & argument are aliases for param.
if (tag.title === 'param' || tag.title === 'arg' || tag.title === 'argument') {
const paramName = tag.name;

// When the @param doesn't have a name but have a type and a description, "null-null" is returned.
if (!isNil(paramName) && paramName !== 'null-null') {
if (isNil(extractedTags.params)) {
extractedTags.params = [];
}

extractedTags.params.push({
name: tag.name,
type: tag.type,
description: tag.description,
raw: tag,
getPrettyName: () => {
if (paramName.includes('null')) {
// There is a few cases in which the returned param name contains "null".
// - @param {SyntheticEvent} event- Original SyntheticEvent
// - @param {SyntheticEvent} event.\n@returns {string}
return paramName.replace('-null', '').replace('.null', '');
}

return tag.name;
},
getTypeName: () => {
return !isNil(tag.type) ? extractJsDocTypeName(tag.type) : null;
},
});
}
} else if (tag.title === 'returns') {
if (!isNil(tag.type)) {
extractedTags.returns = {
type: tag.type,
description: tag.description,
raw: tag,
getTypeName: () => {
return extractJsDocTypeName(tag.type);
},
};
}
} else if (tag.title === 'ignore') {
extractedTags.ignore = true;
// Once we reach an @ignore tag, there is no point in parsing the other tags since we will not render the prop.
break;
}
}

return extractedTags;
}

// FIXME: type argument should be doctrine.Type instead of any.
function extractJsDocTypeName(type: any): string {
if (type.type === 'NameExpression') {
return type.name;
}

if (type.type === 'RecordType') {
const recordFields = type.fields.map((field: doctrine.type.FieldType) => {
if (!isNil(field.value)) {
const valueTypeName = extractJsDocTypeName(field.value);

return `${field.key}: ${valueTypeName}`;
}

return field.key;
});

return `({${recordFields.join(', ')}})`;
}

if (type.type === 'UnionType') {
const unionElements = type.elements.map(extractJsDocTypeName);

return `(${unionElements.join('|')})`;
}

// Only support untyped array: []. Might add more support later if required.
if (type.type === 'ArrayType') {
return '[]';
}

if (type.type === 'TypeApplication') {
if (!isNil(type.expression)) {
if (type.expression.name === 'Array') {
const arrayType = extractJsDocTypeName(type.applications[0]);

return `${arrayType}[]`;
}
}
}

if (
type.type === 'NullableType' ||
type.type === 'NonNullableType' ||
type.type === 'OptionalType'
) {
return extractJsDocTypeName(type.expression);
}

if (type.type === 'AllLiteral') {
return 'any';
}

return null;
}
Loading

0 comments on commit 428ae87

Please sign in to comment.