Skip to content

Commit

Permalink
Finish docgen plugin (#928)
Browse files Browse the repository at this point in the history
* Finish docgen plugin

* WIP

* Switch to jest config, clean up paths

* More inference fixes :|

* Finish plugin

* rm lock

* better polymoprhic value detection
  • Loading branch information
jquense authored and KyleAMathews committed May 11, 2017
1 parent a0370a9 commit 5f9232d
Show file tree
Hide file tree
Showing 19 changed files with 579 additions and 197 deletions.
15 changes: 15 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const path = require(`path`)
const glob = require(`glob`)

const pkgs = glob.sync(`./packages/*`).map(p => p.replace(/^\./, `<rootDir>`))

const distDirs = pkgs.map(p => path.join(p, `dist`))

module.exports = {
notify: true,
verbose: true,
roots: pkgs,
modulePathIgnorePatterns: distDirs,
coveragePathIgnorePatterns: distDirs,
testPathIgnorePatterns: [`/dist/`, `/node_modules/`, `__tests__/fixtures`],
}
22 changes: 2 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"eslint-plugin-prettier": "^2.0.1",
"eslint-plugin-react": "^7.0.0",
"flow-bin": "^0.42.0",
"glob": "^7.1.1",
"iflow-debug": "^1.0.15",
"iflow-lodash": "^1.1.24",
"iflow-react-router": "^1.2.1",
Expand All @@ -36,25 +37,6 @@
"remotedev-server": "^0.2.2",
"rimraf": "^2.6.1"
},
"jest": {
"verbose": true,
"modulePathIgnorePatterns": [
"<rootDir>/dist/",
"<rootDir>/node_modules/"
],
"coveragePathIgnorePatterns": [
"<rootDir>/dist/"
],
"testPathIgnorePatterns": [
"/dist/",
"/node_modules/"
],
"moduleDirectories": [
"node_modules",
"<rootDir>/node_modules/"
],
"notify": true
},
"private": true,
"scripts": {
"build": "lerna run build",
Expand All @@ -75,4 +57,4 @@
"test_bkup": "npm run lint && npm run test-node && npm run test-integration",
"watch": "lerna run watch --no-sort --stream --concurrency 999"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable */
import React from 'react'

const Baz = () => <div />

const Buz = function() {
return <div />
}

function Foo() {
return <div />
}

class Bar extends React.Component {
static propTypes = {
/**
* An object hash of field errors for the form.
*/
objProp: React.PropTypes.object,

reqProp: React.PropTypes.object.isRequired,

/**
* Callback **that** is called when a validation error occurs.
*/
funcProp: React.PropTypes.func,

stringProp: React.PropTypes.string,

boolProp: React.PropTypes.bool,

'aria-property': React.PropTypes.string,

enumProp: React.PropTypes.oneOf([true, 'john', 5, null, Infinity]),

otherProp: React.PropTypes.instanceOf(Message),

shapeProp: React.PropTypes.shape({
setter: React.PropTypes.func,
name: React.PropTypes.string,
}),

unionProp: React.PropTypes.oneOfType([
React.PropTypes.func,
React.PropTypes.string,
]),

reqUnionProp: React.PropTypes.oneOfType([
React.PropTypes.func,
React.PropTypes.string,
]).isRequired,

customProp(props, name, componentName) {
return React.PropTypes.any.isRequired(props, name, componentName)
},

customIdentifier: someValidator,

customCallExpression: someValidator(),
}
render() {
return <Foo />
}
}

const Qux = React.createClass({
render() {
return <Foo />
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default class extends React.Component {
render() {
return <div />
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import fs from 'fs'
import path from 'path'
import { groupBy } from 'lodash'
import onNodeCreate from '../src/on-node-create'

const readFile = file =>
new Promise((y, n) => {
fs.readFile(
path.join(__dirname, `fixtures`, file),
`utf8`,
(err, content) => (err ? n(err) : y(content))
)
})

describe(`transformer-react-doc-gen: onNodeCreate`, () => {
let loadNodeContent, boundActionCreators, node, createdNodes, updatedNodes
let run = (node, opts = {}) =>
onNodeCreate(
{
node,
loadNodeContent,
boundActionCreators,
},
opts
)

beforeEach(() => {
createdNodes = []
updatedNodes = []
node = {
mediaType: `application/javascript`,
children: [],
id: `node_1`,
__fixture: `classes.js`,
}
loadNodeContent = jest.fn(node => readFile(node.__fixture))
boundActionCreators = {
createNode: jest.fn(n => createdNodes.push(n)),
updateNode: jest.fn(n => updatedNodes.push(n)),
}
})

it(`should only process javascript nodes`, () => {
loadNodeContent = jest.fn(() => new Promise(() => {}))

expect(run({ mediaType: `text/x-foo` })).toBeNull()
expect(run({ mediaType: `application/javascript` })).toBeDefined()

expect(loadNodeContent.mock.calls).toHaveLength(1)
})

it(`should extract all components in a file`, async () => {
await run(node)

let types = groupBy(createdNodes, `type`)
expect(types.ComponentMetadata).toHaveLength(5)
})

it(`should give all components a name`, async () => {
await run(node)

let types = groupBy(createdNodes, `type`)
expect(types.ComponentMetadata.every(c => c.displayName)).toBe(true)
})

it(`should infer a name`, async () => {
node.__fixture = `unnamed.js`
node.absolutePath = path.join(__dirname, `UnnamedExport`)
await run(node)

expect(createdNodes[0].displayName).toEqual(`UnnamedExport`)
})

it(`should extract all propTypes`, async () => {
await run(node)

let types = groupBy(createdNodes, `type`)
expect(types.ComponentProp).toHaveLength(14)
})

it(`should extract create description nodes with markdown types`, async () => {
await run(node)
let types = groupBy(createdNodes, `type`)
expect(
types.ComponentDescription.every(d => d.mediaType === `text/x-markdown`)
).toBe(true)
})

it(`should allow specifying handlers`, async () => {
let handler = jest.fn()
await run(node, {
handlers: [handler],
})

expect(!!handler.mock.calls.length).toBe(true)
})
})
5 changes: 4 additions & 1 deletion packages/gatsby-transformer-react-docgen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
"graphql": "^0.9.4"
},
"dependencies": {
"react-docgen": "^2.14.1"
"babel-types": "^6.24.1",
"common-tags": "^1.4.0",
"react-docgen": "^2.15.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"graphql-type-json": "^0.1.4",
"lodash": "^4.17.4",
"react-docgen": "^2.14.1"
}
}
8 changes: 6 additions & 2 deletions packages/gatsby-transformer-react-docgen/src/Doclets.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const metadata = require(`react-docgen`)

let cleanDocletValue = str => str.trim().replace(/^\{/, ``).replace(/\}$/, ``)

let isLiteral = str => /^('|")/.test(str.trim())

/**
* Remove doclets from string
*/
Expand All @@ -13,7 +17,7 @@ const cleanDoclets = desc => {
*
* @param {ComponentMetadata|PropMetadata} obj
*/
exports.parseDoclets = (obj, propName) => {
export const parseDoclets = (obj, propName) => {
let desc = obj.description || ``
obj.doclets = metadata.utils.docblock.getDoclets(desc) || {}
obj.description = cleanDoclets(desc)
Expand All @@ -27,7 +31,7 @@ exports.parseDoclets = (obj, propName) => {
* @param {Object} props Object Hash of the prop metadata
* @param {String} propName
*/
exports.applyPropDoclets = prop => {
export const applyPropDoclets = prop => {
let doclets = prop.doclets
let value

Expand Down
97 changes: 97 additions & 0 deletions packages/gatsby-transformer-react-docgen/src/extend-node-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const {
GraphQLBoolean,
GraphQLObjectType,
GraphQLList,
GraphQLString,
GraphQLNonNull,
} = require(`graphql`)
const GraphQLJSON = require(`graphql-type-json`)
const { stripIndent, oneLine } = require(`common-tags`)

const PropDefaultValue = new GraphQLObjectType({
name: `PropDefaultValue`,
fields: () => ({
value: { type: GraphQLString },
computed: { type: GraphQLBoolean },
}),
})

const PropTypeValue = new GraphQLObjectType({
name: `PropTypeValue`,
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
value: { type: GraphQLJSON },
raw: { type: GraphQLString },
}),
})

const Method = new GraphQLObjectType({
name: `ComponentMethod`,
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
docblock: { type: GraphQLString },
modifiers: {
type: new GraphQLList(GraphQLString),
description: oneLine`
Modifiers describing the kind and sort of method e.g. "static",
"generator", or "async".
`,
},
params: {
type: new GraphQLList(
new GraphQLObjectType({
name: `ComponentMethodParams`,
fields: () => ({
name: { type: GraphQLString },
type: { type: GraphQLJSON },
}),
})
),
},
returns: { type: new GraphQLList(GraphQLJSON) },
}),
})

function extendComponents() {
return {
composes: {
type: new GraphQLList(new GraphQLNonNull(GraphQLString)),
description: stripIndent`
${oneLine`
A list of additional modules "spread" into this component's
propTypes such as:`}
propTypes = {
name: PropTypes.string,
...AnotherComponent.propTypes,
}
`,
},
methods: {
type: new GraphQLList(Method),
description: `Component methods`,
},
}
}

function extendProp() {
return {
defaultValue: { type: PropDefaultValue },
required: {
type: new GraphQLNonNull(GraphQLBoolean),
description: oneLine`
Describes whether or not the propType is required, i.e. not \`null\`
`,
},
type: {
type: new GraphQLNonNull(PropTypeValue),
resolve: s => s._propType,
},
}
}

export default ({ type }) => {
if (type.name === `ComponentProp`) return extendProp()
if (type.name === `ComponentMetadata`) return extendComponents()
return {}
}
Loading

0 comments on commit 5f9232d

Please sign in to comment.