-
Notifications
You must be signed in to change notification settings - Fork 486
Change document-exported to traverse the code #533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,93 @@ | ||
var traverse = require('babel-traverse').default, | ||
isJSDocComment = require('../../lib/is_jsdoc_comment'); | ||
|
||
isJSDocComment = require('../../lib/is_jsdoc_comment'), | ||
t = require('babel-types'), | ||
nodePath = require('path'), | ||
fs = require('fs'); | ||
|
||
/** | ||
* Iterate through the abstract syntax tree, finding ES6-style exports, | ||
* and inserting blank comments into documentation.js's processing stream. | ||
* Through inference steps, these comments gain more information and are automatically | ||
* documented as well as we can. | ||
* @param {Function} parseToAst funtiont that parses to an ast | ||
* @param {Object} ast the babel-parsed syntax tree | ||
* @param {Object} data the name of the file | ||
* @param {Function} addComment a method that creates a new comment if necessary | ||
* @returns {Array<Object>} comments | ||
* @private | ||
*/ | ||
function walkExported(ast, addComment) { | ||
function walkExported(parseToAst, ast, data, addComment) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is questionable if passing in |
||
var newResults = []; | ||
var filename = data.file; | ||
|
||
function addBlankComment(data, path, node) { | ||
return addComment(data, '', node.loc, path, node.loc, true); | ||
} | ||
|
||
function getComments(data, path) { | ||
if (!hasJSDocComment(path)) { | ||
return [addBlankComment(data, path, path.node)]; | ||
} | ||
return path.node.leadingComments.filter(isJSDocComment).map(function (comment) { | ||
return addComment(data, comment.value, comment.loc, path, path.node.loc, true); | ||
}).filter(Boolean); | ||
} | ||
|
||
function addBlankComment(path, node) { | ||
return addComment('', node.loc, path, node.loc, true); | ||
function addComments(data, path, overrideName) { | ||
var comments = getComments(data, path); | ||
if (overrideName) { | ||
comments.forEach(function (comment) { | ||
comment.name = overrideName; | ||
}); | ||
} | ||
newResults.push.apply(newResults, comments); | ||
} | ||
|
||
traverse(ast, { | ||
enter: function (path) { | ||
if (path.isExportDeclaration()) { | ||
if (!hasJSDocComment(path)) { | ||
if (!path.node.declaration) { | ||
return; | ||
} | ||
const node = path.node.declaration; | ||
newResults.push(addBlankComment(path, node)); | ||
ExportDeclaration: function (path) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could probably have an enter+skip to reduce the amount of traversal done here. |
||
var declaration = path.get('declaration'); | ||
if (t.isDeclaration(declaration)) { | ||
traverseExportedSubtree(declaration, data, addComments); | ||
} | ||
|
||
if (path.isExportDefaultDeclaration()) { | ||
if (declaration.isDeclaration()) { | ||
traverseExportedSubtree(declaration, data, addComments); | ||
} else if (declaration.isIdentifier()) { | ||
var binding = declaration.scope.getBinding(declaration.node.name); | ||
traverseExportedSubtree(binding.path, data, addComments); | ||
} | ||
} else if ((path.isClassProperty() || path.isClassMethod()) && | ||
!hasJSDocComment(path) && inExportedClass(path)) { | ||
newResults.push(addBlankComment(path, path.node)); | ||
} else if ((path.isObjectProperty() || path.isObjectMethod()) && | ||
!hasJSDocComment(path) && inExportedObject(path)) { | ||
newResults.push(addBlankComment(path, path.node)); | ||
} | ||
|
||
if (t.isExportNamedDeclaration(path)) { | ||
var specifiers = path.get('specifiers'); | ||
var source = path.node.source; | ||
var exportKind = path.node.exportKind; | ||
specifiers.forEach(function (specifier) { | ||
var specData = data; | ||
var local, exported; | ||
if (t.isExportDefaultSpecifier(specifier)) { | ||
local ='default'; | ||
} else { // ExportSpecifier | ||
local = specifier.node.local.name; | ||
} | ||
exported = specifier.node.exported.name; | ||
|
||
var bindingPath; | ||
if (source) { | ||
var tmp = findExportDeclaration(parseToAst, local, exportKind, filename, source.value); | ||
bindingPath = tmp.ast; | ||
specData = tmp.data; | ||
} else if (exportKind === 'value') { | ||
bindingPath = path.scope.getBinding(local).path; | ||
} else if (exportKind === 'type') { | ||
bindingPath = findLocalType(path.scope, local); | ||
} else { | ||
throw new Error('Unreachable'); | ||
} | ||
|
||
traverseExportedSubtree(bindingPath, specData, addComments, exported); | ||
}); | ||
} | ||
} | ||
}); | ||
|
@@ -46,18 +99,155 @@ function hasJSDocComment(path) { | |
return path.node.leadingComments && path.node.leadingComments.some(isJSDocComment); | ||
} | ||
|
||
function inExportedClass(path) { | ||
var c = path.parentPath.parentPath; | ||
return c.isClass() && c.parentPath.isExportDeclaration(); | ||
function traverseExportedSubtree(path, data, addComments, overrideName) { | ||
var attachCommentPath = path; | ||
if (path.parentPath && path.parentPath.isExportDeclaration()) { | ||
attachCommentPath = path.parentPath; | ||
} | ||
addComments(data, attachCommentPath, overrideName); | ||
|
||
if (path.isVariableDeclaration()) { | ||
// TODO: How does JSDoc handle multiple declarations? | ||
path = path.get('declarations')[0].get('init'); | ||
if (!path) { | ||
return; | ||
} | ||
} | ||
|
||
if (path.isClass() || path.isObjectExpression()) { | ||
path.traverse({ | ||
Property: function (path) { | ||
addComments(data, path); | ||
path.skip(); | ||
}, | ||
Method: function (path) { | ||
addComments(data, path); | ||
path.skip(); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
function inExportedObject(path) { | ||
// ObjectExpression -> VariableDeclarator -> VariableDeclaration -> ExportNamedDeclaration | ||
var p = path.parentPath.parentPath; | ||
if (!p.isVariableDeclarator()) { | ||
return false; | ||
var dataCache = Object.create(null); | ||
|
||
function getCachedData(parseToAst, path) { | ||
var value = dataCache[path]; | ||
if (!value) { | ||
var input = fs.readFileSync(path, 'utf-8'); | ||
var ast = parseToAst(input, path); | ||
value = { | ||
data: { | ||
file: path, | ||
source: input | ||
}, | ||
ast: ast | ||
}; | ||
dataCache[path] = value; | ||
} | ||
return p.parentPath.parentPath.isExportDeclaration(); | ||
return value; | ||
} | ||
|
||
// Loads a module and finds the exported declaration. | ||
function findExportDeclaration(parseToAst, name, exportKind, referrer, filename) { | ||
var depPath = nodePath.resolve(nodePath.dirname(referrer), filename); | ||
var tmp = getCachedData(parseToAst, depPath); | ||
var ast = tmp.ast; | ||
var data = tmp.data; | ||
|
||
var rv; | ||
traverse(ast, { | ||
Statement: function (path) { | ||
path.skip(); | ||
}, | ||
ExportDeclaration: function (path) { | ||
if (name === 'default' && path.isExportDefaultDeclaration()) { | ||
rv = path.get('declaration'); | ||
path.stop(); | ||
} else if (path.isExportNamedDeclaration()) { | ||
var declaration = path.get('declaration'); | ||
if (t.isDeclaration(declaration)) { | ||
var bindingName; | ||
if (declaration.isFunctionDeclaration() || declaration.isClassDeclaration() || | ||
declaration.isTypeAlias()) { | ||
bindingName = declaration.node.id.name; | ||
} else if (declaration.isVariableDeclaration()) { | ||
// TODO: Multiple declarations. | ||
bindingName = declaration.node.declarations[0].id.name; | ||
} | ||
if (name === bindingName) { | ||
rv = declaration; | ||
path.stop(); | ||
} else { | ||
path.skip(); | ||
} | ||
return; | ||
} | ||
|
||
// export {x as y} | ||
// export {x as y} from './file.js' | ||
var specifiers = path.get('specifiers'); | ||
var source = path.node.source; | ||
for (var i = 0; i < specifiers.length; i++) { | ||
var specifier = specifiers[i]; | ||
var local, exported; | ||
if (t.isExportDefaultSpecifier(specifier)) { | ||
// export x from ... | ||
local = 'default'; | ||
exported = specifier.node.exported.name; | ||
} else { | ||
// ExportSpecifier | ||
local = specifier.node.local.name; | ||
exported = specifier.node.exported.name; | ||
} | ||
if (exported === name) { | ||
if (source) { | ||
// export {local as exported} from './file.js'; | ||
var tmp = findExportDeclaration(parseToAst, local, exportKind, depPath, source.value); | ||
rv = tmp.ast; | ||
data = tmp.data; | ||
if (!rv) { | ||
throw new Error(`${name} is not exported by ${depPath}`); | ||
} | ||
} else { | ||
// export {local as exported} | ||
if (exportKind === 'value') { | ||
rv = path.scope.getBinding(local).path; | ||
} else { | ||
rv = findLocalType(path.scope, local); | ||
} | ||
if (!rv) { | ||
throw new Error(`${depPath} has no binding for ${name}`); | ||
} | ||
} | ||
path.stop(); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
|
||
return {ast: rv, data: data}; | ||
} | ||
|
||
// Since we cannot use scope.getBinding for types this walks the current scope looking for a | ||
// top-level type alias. | ||
function findLocalType(scope, local) { | ||
var rv; | ||
scope.path.traverse({ | ||
Statement: function (path) { | ||
path.skip(); | ||
}, | ||
TypeAlias: function (path) { | ||
if (path.node.id.name === local) { | ||
rv = path; | ||
path.stop(); | ||
} else { | ||
path.skip(); | ||
} | ||
} | ||
}); | ||
return rv; | ||
} | ||
|
||
module.exports = walkExported; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
functiont -> function