Skip to content

Commit

Permalink
Merge pull request #4 from pvasek/fix/not_exported_props_fails
Browse files Browse the repository at this point in the history
If props are not exported generation fails - fixed #3
  • Loading branch information
pvasek authored Apr 5, 2017
2 parents 5d77621 + 70b354e commit 3f94005
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 25 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "react-docgen-typescript",
"version": "0.0.4",
"version": "0.0.5",
"description": "",
"main": "lib/index.js",
"scripts": {
"prepublish": "tsc -d",
"test": "tsc && mocha ./lib/**/__tests__/**.js",
"test:debug": "tsc && mocha --debug ./lib/**/__tests__/**.js",
"example": "tsc && node ./node_modules/react-styleguidist/bin/styleguidist server --config ./examples/react-styleguidist-example/styleguide.config.js"
},
"author": "pvasek",
Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/data/ColumnWithoutExportedProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
/**
* Column properties.
*/
interface IColumnProps extends React.HTMLAttributes<any> {
/** prop1 description */
prop1?: string;
/** prop2 description */
prop2: number;
/**
* prop3 description
*/
prop3: () => void;
/** prop4 description */
prop4: 'option1' | 'option2' | "option3";
}

/**
* Form column.
*/
export class Column extends React.Component<IColumnProps, {}> {
public static defaultProps: Partial<IColumnProps> = {
prop1: 'prop1'
};

render() {
const {prop1} = this.props;
return <div>{prop1}</div>;
}
}

export default Column;
102 changes: 102 additions & 0 deletions src/__tests__/docgenConverter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { assert } from 'chai';
import * as path from 'path';
import { getDocumentation } from '../parser';
import { convertToDocgen, StyleguidistComponent } from "../docgenConverter";

describe('docgenConverter', () => {
it('Should work with class Component', () => {
const result = convertToDocgen({
classes: [
{
name: 'name1',
comment: 'comment1',
extends: 'Component',
propInterface: 'PropsInterface',
}
],
interfaces: [
{
name: 'PropsInterface',
comment: 'props comment',
members: [
{
name: 'prop1',
comment: 'prop1 comment',
isRequired: true,
text: 'prop1 text',
type: 'prop1 type'
}
]
}
]
});

assert.equal('name1', result.displayName);
assert.equal('comment1', result.description);
const prop1Result = result.props['prop1'];
assert.equal('prop1 type', prop1Result.type.name);
assert.equal('prop1 comment', prop1Result.description);
assert.equal(true, prop1Result.required);
});

it('Should work with functional StatelessComponent', () => {
const result = convertToDocgen({
classes: [
{
name: 'name1',
comment: 'comment1',
extends: 'StatelessComponent',
propInterface: 'PropsInterface',
}
],
interfaces: [
{
name: 'PropsInterface',
comment: 'props comment',
members: [
{
name: 'prop1',
comment: 'prop1 comment',
isRequired: true,
text: 'prop1 text',
type: 'prop1 type'
}
]
}
]
});

assert.equal('name1', result.displayName);
assert.equal('comment1', result.description);
const prop1Result = result.props['prop1'];
assert.equal('prop1 type', prop1Result.type.name);
assert.equal('prop1 comment', prop1Result.description);
assert.equal(true, prop1Result.required);
});

it('Should work without props interface', () => {
let result: StyleguidistComponent = null;
const originalWarn = console.warn;
let warnCallCount = 0;
console.warn = () => warnCallCount++;
try {
result = convertToDocgen({
classes: [
{
name: 'name1',
comment: 'comment1',
extends: 'Component',
propInterface: null,
}
],
interfaces: []
});
} finally {
console.warn = originalWarn;
}

assert.equal(1, warnCallCount);
assert.equal('name1', result.displayName);
assert.equal('comment1', result.description);
});
});
14 changes: 14 additions & 0 deletions src/__tests__/parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ describe('parser', () => {
assert.equal(true, i.members[3].isRequired);
});

it('Should parse class-based components with unexported props interface', () => {
const fileName = path.join(__dirname, '../../src/__tests__/data/ColumnWithoutExportedProps.tsx'); // it's running in ./temp
const result = getDocumentation(fileName);
assert.ok(result.classes);
assert.ok(result.interfaces);
assert.equal(1, result.classes.length);
assert.equal(0, result.interfaces.length);

const c = result.classes[0];
assert.equal('Column', c.name);
assert.equal('Form column.', c.comment);
assert.equal('Component', c.extends);
});

it('Should parse functional components', () => {
const fileName = path.join(__dirname, '../../src/__tests__/data/Row.tsx'); // it's running in ./temp
const result = getDocumentation(fileName);
Expand Down
54 changes: 35 additions & 19 deletions src/docgenConverter.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import { FileDoc } from './parser';
import { FileDoc, InterfaceDoc, MemberDoc } from './parser';

export function convertToDocgen(doc: FileDoc) {
export interface StyleguidistProps {
[key: string]: PropItem;
}

export interface StyleguidistComponent {
displayName: string;
description: string;
props: StyleguidistProps;
}

export function convertToDocgen(doc: FileDoc): StyleguidistComponent {
const reactClasses = doc.classes.filter(i => i.extends === 'Component' || i.extends === 'StatelessComponent');

if (reactClasses.length === 0) {
return null;
}
const comp = reactClasses[0];
const reactInterfaces = doc.interfaces.filter(i => i.name === comp.propInterface);
if (reactInterfaces.length === 0) {
return null;

let props: any = {};
if (reactInterfaces.length !== 0) {
props = getProps(reactInterfaces[0]);
} else {
console.warn('REACT-DOCGEN-TYPESCRIPT It seems that your props type is not exported. Add \'export\' keyword to your props definition.');
}
const props = reactInterfaces[0];

return {
displayName: comp.name,
description: comp.comment,
props: props.members.reduce((acc, i) => {
const item: PropItem = {
description: i.comment,
type: {name: i.type},
defaultValue: null,
required: i.isRequired
};
if (i.values) {
item.description = item.description + ' (one of the following:' + i.values.join(',') + ')';
}

acc[i.name] = item;
return acc;
}, {})
props: props
}
}

Expand All @@ -54,6 +54,22 @@ export interface Docgen {
props: PropsObject;
}

function getProps(props: InterfaceDoc): StyleguidistProps {
return props.members.reduce((acc, i) => {
const item: PropItem = {
description: i.comment,
type: {name: i.type},
defaultValue: null,
required: i.isRequired
};
if (i.values) {
item.description = item.description + ' (one of the following:' + i.values.join(',') + ')';
}

acc[i.name] = item;
return acc;
}, {});
}
/*
{
"props": {
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { parse } from './propTypesParser';
import { StyleguidistComponent, StyleguidistProps } from './docgenConverter';

export {
parse,
StyleguidistComponent,
StyleguidistProps,
}
9 changes: 6 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function getDocumentation(fileName: string, options: ts.CompilerOptions =
};
classes.push(classObj);
}
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
const classNode = node as ts.ClassDeclaration;
const symbol = checker.getSymbolAtLocation(classNode.name);

Expand All @@ -78,11 +78,14 @@ export function getDocumentation(fileName: string, options: ts.CompilerOptions =
.filter(i => i.kind === ts.SyntaxKind.Identifier)
.map((i: ts.Identifier) => i.text);

const componentIndex = list.indexOf('Component');
const propsIndex = componentIndex === -1 ? -1 : (componentIndex + 1);

classes.push({
name: symbol.name,
comment: ts.displayPartsToString(symbol.getDocumentationComment()),
extends: list.length > 0 && list.indexOf('Component') > -1 ? 'Component' : null,
propInterface: list.length > 1 ? list[1] : null,
extends: list.length > 0 && componentIndex > -1 ? 'Component' : null,
propInterface: list.length > propsIndex ? list[propsIndex] : null,
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/propTypesParser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getDocumentation } from './parser';
import { convertToDocgen } from './docgenConverter';
import { convertToDocgen, StyleguidistComponent } from './docgenConverter';

/**
* Parser given file and return documentation in format compatibe with react-docgen.
*/
export function parse(filePath: string) {
export function parse(filePath: string): StyleguidistComponent {
const doc = getDocumentation(filePath);
return convertToDocgen(doc);
}

0 comments on commit 3f94005

Please sign in to comment.