Skip to content

feat(typings): allow to get component info separate to typings #9

Merged
merged 1 commit into from
Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
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