Skip to content
This repository has been archived by the owner on Feb 16, 2021. It is now read-only.

Commit

Permalink
fix #24, fix #25
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Jan 16, 2016
1 parent d442848 commit 7cbd32f
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
stage: 0,
loose: true,
optional: ["runtime"]
}
95 changes: 95 additions & 0 deletions lib/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
var fs = require('fs');
var t = require('../').t;
var toObject = require('tcomb-doc').toObject;
var parseComments = require('get-comments');
var parseJSDocs = require('doctrine').parse;

// (path: t.String) => { description: t.String, tags: Array<Tag> }
function getComments(path) {
var source = fs.readFileSync(path, 'utf8');
var comments = parseComments(source, true);
var values = comments.map(function (comment) {
return comment.value;
});
return parseJSDocs(values.join('\n'), { unwrap: true });
}

// (comments: t.Object) => { component, props: {} }
function getDescriptions(comments) {
var ret = {
component: comments.description,
props: {}
};
comments.tags.forEach(function (tag) {
if (tag.name) {
ret.props[tag.name] = tag.description;
}
});
return ret;
}

// (exports: t.Object) => ReactComponent
function getComponent(defaultExport) {
if (defaultExport['default']) { // eslint-disable-line dot-notation
defaultExport = defaultExport['default']; // eslint-disable-line dot-notation
}
return defaultExport.propTypes ? defaultExport : null;
}

// (component: ReactComponent) => t.String
function getComponentName(component) {
return component.name;
}

// (component: ReactComponent) => TcombType
function getPropsType(component) {
var propTypes = component.propTypes;
var props = {};
Object.keys(propTypes).forEach(function (k) {
if (k !== '__strict__' && k !== '__subtype__') {
props[k] = propTypes[k].tcomb;
}
});
if (propTypes.hasOwnProperty('__subtype__')) {
return t.refinement(t.struct(props), propTypes.__subtype__.predicate);
}
return t.struct(props);
}

// (component: ReactComponent) => t.Object
function getDefaultProps(component) {
return component.defaultProps || {};
}

// (path: t.String) => { name, description, props }
function parse(path) {
if (t.Array.is(path)) {
return path.map(parse).filter(Boolean);
}
var component = getComponent(require(path));
if (component) {
var comments = getComments(path);
var descriptions = getDescriptions(comments);
var type = toObject(getPropsType(component));
var props = type.kind === 'refinement' ? type.type.props : type.props;
var defaultProps = getDefaultProps(component);
var name = getComponentName(component);
for (var prop in props) {
if (props.hasOwnProperty(prop)) {
if (defaultProps.hasOwnProperty(prop)) {
props[prop].defaultValue = defaultProps[prop];
}
if (descriptions.props.hasOwnProperty(prop)) {
props[prop].description = descriptions.props[prop];
}
}
}
return {
name: name,
description: descriptions.component,
props: props
};
}
}

module.exports = parse;
17 changes: 17 additions & 0 deletions lib/toMarkdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var t = require('../').t;

function getProps(props) {
return Object.keys(props).map(function (k) {
var prop = props[k];
return '- `' + k + ': ' + prop.name + '` ' + (prop.required ? '' : '(optional' + (t.Nil.is(prop.defaultValue) ? '' : ', default: `' + JSON.stringify(prop.defaultValue) + '`') + ')') + ' ' + ( prop.description || '');
}).join('\n');
}

function toMarkdown(json) {
if (t.Array.is(json)) {
return json.map(toMarkdown).join('\n');
}
return '# ' + json.name + '\n' + (json.description ? '\n' + json.description + '\n' : '') + '\n**Props**\n\n' + getProps(json.props) + '\n';
}

module.exports = toMarkdown;
25 changes: 17 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"name": "tcomb-react",
"version": "0.8.11",
"version": "0.8.12",
"description": "Type checking for React components",
"main": "index.js",
"files": [
"index.js",
"lib"
],
"scripts": {
"lint": "eslint index.js test",
"test": "npm run lint && npm run mocha",
"mocha": "mocha"
"lint": "eslint index.js lib test",
"test": "mocha --compilers js:babel/register"
},
"repository": {
"type": "git",
Expand All @@ -19,14 +22,20 @@
},
"homepage": "https://github.com/gcanti/tcomb-react",
"dependencies": {
"doctrine": "0.7.2",
"get-comments": "1.0.1",
"tcomb-doc": "^0.4.0",
"tcomb-validation": "^2.2.0",
"react": ">=0.13.0"
},
"devDependencies": {
"babel-eslint": "^3.1.23",
"eslint": "^0.22.1",
"eslint-plugin-react": "^2.7.1",
"mocha": "^2.2.5"
"babel": "5.8.34",
"babel-core": "5.8.34",
"babel-runtime": "5.8.34",
"babel-eslint": "3.1.30",
"eslint": "0.22.1",
"eslint-plugin-react": "2.7.1",
"mocha": "2.3.2"
},
"tags": [
"tcomb",
Expand Down
33 changes: 33 additions & 0 deletions test/fixtures/parse/1/Actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { props, t } from '../../../../.'
import User from './User'

/**
* Component description here
* name and surname must be both nil or both specified
* @param name - name description
* @param surname - surname description
*/

const Props = t.refinement(t.struct({
name: t.maybe(User.meta.props.name),
surname: t.maybe(User.meta.props.surname)
}), (x) => t.Nil.is(x.name) === t.Nil.is(x.surname))

@props(Props)
export default class Card extends React.Component {

static defaultProps = {
name: 'Giulio',
surname: 'Canti'
}

render() {
return (
<div>
<p>{this.props.name}</p>
<p>{this.props.surname}</p>
</div>
)
}
}
6 changes: 6 additions & 0 deletions test/fixtures/parse/1/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { t } from '../../../../.'

export default t.struct({
name: t.String,
surname: t.String
}, 'User')
20 changes: 20 additions & 0 deletions test/fixtures/parse/1/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Card",
"description": "Component description here\nname and surname must be both nil or both specified",
"props": {
"name": {
"kind": "irreducible",
"name": "String",
"required": false,
"defaultValue": "Giulio",
"description": "name description"
},
"surname": {
"kind": "irreducible",
"name": "String",
"required": false,
"defaultValue": "Canti",
"description": "surname description"
}
}
}
9 changes: 9 additions & 0 deletions test/fixtures/toMarkdown/1/expected.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Card

Component description here
name and surname must be both nil or both specified

**Props**

- `name: String` (optional, default: `"Giulio"`) name description
- `surname: String` (optional, default: `"Canti"`) surname description
44 changes: 44 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ var library = require('../index');
var getPropTypes = library.propTypes;
var ReactElement = library.ReactElement;
var ReactNode = library.ReactNode;
var path = require('path');
var fs = require('fs');
var parse = require('../lib/parse');
var toMarkdown = require('../lib/toMarkdown');

function throwsWithMessage(f, message) {
assert.throws(f, function (err) {
Expand Down Expand Up @@ -219,3 +223,43 @@ describe('pre-defined types', function () {
});

});

var skipTests = {
'.DS_Store': 1
};

describe('parse', function () {
var fixturesDir = path.join(__dirname, 'fixtures/parse');
fs.readdirSync(fixturesDir).map(function (caseName) {
if ((caseName in skipTests)) {
return;
}
it(caseName, function () {
var fixtureDir = path.join(fixturesDir, caseName);
var filepath = path.join(fixtureDir, 'Actual.js');
var expected = require(path.join(fixtureDir, 'expected.json'));
assert.deepEqual(JSON.stringify(parse(filepath)), JSON.stringify(expected));
});
});
});

function trim(str) {
return str.replace(/^\s+|\s+$/, '');
}

describe('toMarkdown', function () {
var fixturesDir = path.join(__dirname, 'fixtures/toMarkdown');
fs.readdirSync(fixturesDir).map(function (caseName) {
if ((caseName in skipTests)) {
return;
}
it(caseName, function () {
var actualFixtureDir = path.join(__dirname, 'fixtures/parse', caseName);
var filepath = path.join(actualFixtureDir, 'Actual.js');
var fixtureDir = path.join(fixturesDir, caseName);
const expected = fs.readFileSync(path.join(fixtureDir, 'expected.md')).toString();
assert.equal(trim(toMarkdown(parse(filepath))), trim(expected));
});
});
});

0 comments on commit 7cbd32f

Please sign in to comment.