Skip to content

Commit

Permalink
feat(typings): allow to get component info separate to typings, suppo…
Browse files Browse the repository at this point in the history
…rt cn extended components
  • Loading branch information
Heymdall committed Jun 21, 2017
1 parent fd8f76c commit a6469e0
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 120 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ module.exports = {
rules: {
'func-names': 0
}
};
};
2 changes: 1 addition & 1 deletion gulp-tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function createTasks(packageName, options = {}) {

const typingFiles = components
.pipe(clone())
.pipe(componentTypings(packageName));
.pipe(componentTypings());

return es
.merge(packages, typingFiles)
Expand Down
4 changes: 2 additions & 2 deletions gulp/component-typings.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ const getReactComponentDefinitionsContent = require('../typings/index');
* @param {String} libraryName Library name, will be used in typescript declarations.
* @returns {Function}
*/
function componentTypings(libraryName) {
function componentTypings() {
function transform(file, encoding, callback) {
if (file.isStream()) {
callback();
return;
}
const componentName = path.parse(file.path).name;
getReactComponentDefinitionsContent(file.path, libraryName).then((definitionsContent) => {
getReactComponentDefinitionsContent(file.path).then((definitionsContent) => {
if (!definitionsContent) {
console.warn(`Unable to create typings for ${file.path}`);
return callback(null);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"gulp-typescript": "^3.1.5",
"react-component-info": "^1.0.0",
"react-docgen": "^2.15.0",
"react-docgen-displayname-handler": "^1.0.0",
"recast": "^0.12.5",
"resolve": "^1.3.3",
"through2": "^2.0.3",
Expand Down
71 changes: 41 additions & 30 deletions typings/create-resolver.js → react-doc/create-resolver.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
const path = require('path');
const babylon = require('react-docgen/dist/babylon').default;
const resolve = require('resolve').sync;
const isExportsOrModuleAssignment = require('react-docgen/dist/utils/isExportsOrModuleAssignment').default;
const isReactComponentClass = require('react-docgen/dist/utils/isReactComponentClass').default;
const isReactCreateClassCall = require('react-docgen/dist/utils/isReactCreateClassCall').default;
const isStatelessComponent = require('react-docgen/dist/utils/isStatelessComponent').default;
const normalizeClassDefinition = require('react-docgen/dist/utils/normalizeClassDefinition').default;
const resolveExportDeclaration = require('./resolve-export-declaration');
const resolveToValue = require('react-docgen/dist/utils/resolveToValue').default;
const resolveHOC = require('react-docgen/dist/utils/resolveHOC').default;
const resolveToModule = require('react-docgen/dist/utils/resolveToModule').default;
const getSourceFileContent = require('./get-source-file-content');
const resolveExportDeclaration = require('./docgen/resolve-export-declaration');
const isDecoratedBy = require('./docgen/is-decorated-by');

const ERROR_MULTIPLE_DEFINITIONS = 'Multiple exported component definitions found.';

function ignore() {
return false;
}

function isComponentDefinition(path) {
return isReactCreateClassCall(path) || isReactComponentClass(path) || isStatelessComponent(path);
}
Expand All @@ -26,7 +26,7 @@ function resolveDefinition(definition, types) {
if (types.ObjectExpression.check(resolvedPath.node)) {
return resolvedPath;
}
} else if (isReactComponentClass(definition)) {
} else if (isReactComponentClass(definition) || isDecoratedBy(definition, 'cn')) {
normalizeClassDefinition(definition);
return definition;
} else if (isStatelessComponent(definition)) {
Expand All @@ -35,12 +35,9 @@ function resolveDefinition(definition, types) {
return null;
}

function findExportedComponentDefinition(
ast,
recast,
filePath
) {
function findExportedComponentDefinition(ast, recast, filePath) {
const types = recast.types.namedTypes;
const importedModules = {};
let definition;

function exportDeclaration(nodePath) {
Expand All @@ -49,16 +46,26 @@ function findExportedComponentDefinition(
.reduce((acc, def) => {
if (isComponentDefinition(def)) {
acc.push(def);
} else {
const resolved = resolveToValue(resolveHOC(def));
if (isComponentDefinition(resolved)) {
acc.push(resolved);
return acc;
}
return acc;
}

const resolved = resolveToValue(resolveHOC(def));
if (isComponentDefinition(resolved)) {
acc.push(resolved);
return acc;
}

if (isDecoratedBy(def, 'cn') && def.get('superClass')) {
const superClass = def.get('superClass');
const src = getSourceFileContent(importedModules[superClass.value.name], filePath);
filePath = src.filePath; // update file path, so we can correctly resolve imports
linkedFile = recast.parse(src.content, { esprima: babylon });
return acc;
}
if (def.get('value') && def.get('value').value) {
// if we found reexported file - parse it with recast and return
const src = getSourceFileContent(def.get('value').value, filePath);
filePath = src.filePath; // update file path, so we can correctly resolve imports
linkedFile = recast.parse(src.content, { esprima: babylon });
}
return acc;
Expand All @@ -80,23 +87,27 @@ function findExportedComponentDefinition(
}

recast.visit(ast, {
visitFunctionDeclaration: ignore,
visitFunctionExpression: ignore,
visitClassDeclaration: ignore,
visitClassExpression: ignore,
visitIfStatement: ignore,
visitWithStatement: ignore,
visitSwitchStatement: ignore,
visitCatchCause: ignore,
visitWhileStatement: ignore,
visitDoWhileStatement: ignore,
visitForStatement: ignore,
visitForInStatement: ignore,

visitExportDeclaration: exportDeclaration,
visitExportNamedDeclaration: exportDeclaration,
visitExportDefaultDeclaration: exportDeclaration,
visitImportDeclaration(node) {
const specifiers = node.value.specifiers;
const moduleName = resolveToModule(node);

if (moduleName !== 'react' && moduleName !== 'prop-types') {
// resolve path to file here, because this is the only place where we've got actual source path
// but skip `react` and `prop-types` modules, because dockgen winn not be able to detect types otherwise
node.value.source.value = resolve(
node.value.source.value,
{ basedir: path.dirname(filePath), extensions: ['.js', '.jsx'] }
);
}

if (specifiers && specifiers.length > 0) {
importedModules[specifiers[0].local.name] = node.value.source.value;
}
return false;
},
visitAssignmentExpression(path) {
// Ignore anything that is not `exports.X = ...;` or
// `module.exports = ...;`
Expand Down
10 changes: 10 additions & 0 deletions react-doc/docgen/is-decorated-by.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function isDecoratedBy(path, decoratorName = 'cn') {
const decorators = path.get('decorators');
if (decorators && decorators.value) {
return decorators.value
.some(decorator => decorator.expression.callee && decorator.expression.callee.name === decoratorName);
}
return false;
}

module.exports = isDecoratedBy;
File renamed without changes.
48 changes: 48 additions & 0 deletions react-doc/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const path = require('path');
const reactDocGen = require('react-docgen');
const getSourceFileContent = require('./get-source-file-content');
const createResolver = require('./create-resolver');
const createDisplayNameHandler = require('react-docgen-displayname-handler').createDisplayNameHandler;

const documentation = {};
const defaultHandlers = [
reactDocGen.handlers.propTypeHandler,
reactDocGen.handlers.propTypeCompositionHandler,
reactDocGen.handlers.propDocBlockHandler,
reactDocGen.handlers.flowTypeHandler,
reactDocGen.handlers.flowTypeDocBlockHandler,
reactDocGen.handlers.defaultPropsHandler,
reactDocGen.handlers.componentDocblockHandler,
reactDocGen.handlers.displayNameHandler,
reactDocGen.handlers.componentMethodsHandler,
reactDocGen.handlers.componentMethodsJsDocHandler
];

function getReactComponentInfo(filePath, parentPath) {
if (documentation[filePath]) {
return documentation[filePath];
}

const src = getSourceFileContent(filePath, parentPath);
const content = src.content;
filePath = src.filePath;
const info = reactDocGen.parse(
content,
createResolver(filePath),
defaultHandlers.concat(createDisplayNameHandler(filePath))
);
info.filePath = filePath;
if (info.composes) {
info.composes = info.composes
.map(relativePath => getReactComponentInfo(relativePath, path.dirname(filePath)));
} else {
info.composes = [];
}
// extends props for composed components
const composeProps = info.composes.reduce((prev, item) => Object.assign({}, prev, item.props), {});
info.props = Object.assign(composeProps || {}, info.props || {}); // own props should have higher priority
documentation[filePath] = info;
return info;
}

module.exports = getReactComponentInfo;
32 changes: 0 additions & 32 deletions typings/get-react-component-info.js

This file was deleted.

11 changes: 6 additions & 5 deletions typings/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
const path = require('path');
const getReactComponentInfo = require('./get-react-component-info');
const getReactComponentInfo = require('../react-doc');
const getReactComponentDefinitionsContent = require('./stringify-component-definition');
const formatTs = require('./format-ts');


function getFormattedReactComponentDefinitionsContent(filePath, projectName) {
function getFormattedReactComponentDefinitionsContent(filePath) {
return new Promise((resolve) => {
const componentName = path.parse(filePath).name;
try {
const componentInfo = getReactComponentInfo(filePath);
const definitionsContent = getReactComponentDefinitionsContent(componentInfo, componentName, projectName);
// filter public methods
componentInfo.methods = componentInfo.methods
.filter(({ docblock }) => docblock && docblock.indexOf('@public') !== -1);
const definitionsContent = getReactComponentDefinitionsContent(componentInfo);
formatTs(definitionsContent)
.then(resolve);
} catch (e) {
Expand Down
Loading

0 comments on commit a6469e0

Please sign in to comment.