Created
November 13, 2009 08:48
-
-
Save hns/233686 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Token = org.mozilla.javascript.Token; | |
function parse(source, name) { | |
var exportedFunction; | |
var exportedName; | |
var exported = []; | |
var jsdocs = []; | |
var seen = {}; | |
var checkAssignment = function(node, root, exported) { | |
if (node.type == Token.ASSIGN) { | |
if (node.left.type == Token.GETPROP) { | |
var target = node.left.target; | |
var name = node.left.property.string; | |
var propname = nodeToString(node.left); | |
if (propname.indexOf('exports.') == 0 && exported.indexOf(name) < 0) { | |
addDocItem(name, root.jsDoc); | |
exported.push(name); | |
if (node.right.type == Token.FUNCTION) { | |
exportedFunction = node.right; | |
exportedName = name; | |
} | |
return; | |
} else if (target.type == Token.THIS) { | |
if (root.parent && root.parent.parent && root.parent.parent.parent | |
&& root.parent.parent.parent == exportedFunction) { | |
addDocItem(exportedName + ".instance." + name, root.jsDoc); | |
/* if (node.right.type == Token.FUNCTION) { | |
exportedFunction = node.right; | |
exportedName = exportedName + ".prototype." + name; | |
} */ | |
} else if (exported.indexOf(name) > -1) { | |
addDocItem(name, root.jsDoc); | |
} | |
} | |
} else if (node.left.type == Token.GETELEM | |
&& node.left.target.type == Token.NAME | |
&& node.left.target.string == "exports" | |
&& node.left.element.type == Token.STRING) { | |
// exports["foo"] = bar | |
addDocItem(node.left.element.value, root.jsDoc); | |
} | |
} | |
}; | |
var addDocItem = function(name, jsdoc) { | |
if (!seen[name]) { | |
jsdocs.push({name: name, jsdoc: jsdoc ? extractTags(String(jsdoc)) : {}}); | |
seen[name] = true; | |
} | |
}; | |
parseScript(source, name, function(node) { | |
// loop through all comments looking for dangling jsdocs | |
if (node.type == Token.SCRIPT && node.comments) { | |
for each (var comment in node.comments.toArray()) { | |
if (comment.commentType == Token.CommentType.JSDOC) { | |
if (/@fileoverview\s/.test(comment.value)) { | |
Object.defineProperty(jsdocs, "fileoverview", { | |
value: extractTags(String(comment.value)) | |
}); | |
break; | |
} | |
// check for top level module doc | |
// log.info("found jsdoc comment: " + comment.value); | |
} | |
} | |
} | |
// export("foo") (helma-only) | |
if (node.type == Token.CALL && node.target.type == Token.NAME && node.target.string == "export") { | |
for each (var arg in node.arguments.toArray()) { | |
if (arg.type == Token.STRING) exported.push(arg.value); | |
} | |
} | |
// check for Object.defineProperty(foo, bar, {}) | |
if (node.type == Token.CALL && node.target.type == Token.GETPROP) { | |
var getprop = node.target; | |
if (getprop.target.type == Token.NAME && getprop.target.string == "Object" | |
&& getprop.property.string == "defineProperty") { | |
var args = node.arguments ? node.arguments.toArray() : []; | |
var target = nodeToString(args[0]).split('.'); | |
// rhino puts jsdoc on the first name of the third argument object literal (property descriptor) | |
var jsdoc = args[2] && args[2].elements && args[2].elements.get(0).left.jsDoc; | |
if (exported.indexOf(target[0]) > -1 || standardObjects.indexOf(target[0]) > -1) { | |
target.push(nodeToString(args[1])); | |
addDocItem(target.join('.'), jsdoc); | |
} else if (target[0] == 'this' && exportedFunction != null) { | |
target[0] = exportedName; | |
target.push('instance', nodeToString(args[1])); | |
addDocItem(target.join('.'), jsdoc); | |
} | |
} | |
} | |
// exported function | |
if (node.type == Token.FUNCTION && exported.indexOf(node.name) > -1) { | |
addDocItem(node.name, node.jsDoc); | |
exportedFunction = node; | |
exportedName = node.name; | |
} | |
// var foo = exports.foo = bar | |
if ((node.type == Token.VAR || node.type == Token.LET) && node.variables) { | |
for each (var n in node.variables.toArray()) { | |
if (n.target.type == Token.NAME && exported.indexOf(n.target.string) > -1) { | |
addDocItem(n.target.string, node.jsDoc); | |
} else if (n.initializer && n.initializer.type == Token.ASSIGN) { | |
checkAssignment(n.initializer, node, exported); | |
} | |
} | |
} | |
// exports.foo = bar | |
if (node.type == Token.ASSIGN) { | |
checkAssignment(node, node, exported); | |
} | |
return true; | |
}); | |
return jsdocs; | |
} | |
/** | |
* Remove slash-star comment wrapper from a raw comment string. | |
* @type String | |
*/ | |
function unwrapComment(/**String*/comment) { | |
return comment ? comment.replace(/(^\/\*\*|\*\/$)/g, "").replace(/^\s*\* ?/gm, "") : ""; | |
} | |
/** | |
* Parse a JSDoc comment into an array of tags, with tags being represented | |
* as [tagname, tagtext]. | |
* @param {String} comment the raw JSDoc comment | |
* @return {Array} an array of tags. | |
*/ | |
function extractTags(/**String*/comment) { | |
if (comment.indexOf("/**") == 0) { | |
comment = unwrapComment(comment); | |
} | |
var tags = comment.split(/(^|[\r\n])\s*@/) | |
.filter(function($){return $.match(/\S/)}); | |
return tags.map(function(tag, idx) { | |
if (idx == 0 && comment.indexOf('@') != 0) { | |
return ['desc', tag.trim()]; | |
} else { | |
var space = tag.search(/\s/); | |
return space > -1 ? | |
[tag.substring(0, space), tag.substring(space + 1).trim()] : | |
[tag, '']; | |
} | |
}) | |
} | |
/** | |
* Utility function to test whether a node is a Name node | |
* (a node of type org.mozilla.javascript.ast.Name) | |
* @param node {Object} an AST node | |
* @return {Boolean} true if node is a name node | |
*/ | |
function isName(node) { | |
return node instanceof org.mozilla.javascript.ast.Name; | |
} | |
/** | |
* Utility function to get the name value of a node, or the empty | |
* string if it is not a name node. | |
* @param node an AST node | |
* @return {String} the name value of the node | |
*/ | |
function getName(node) { | |
return isName(node) ? node.getString() : ""; | |
} | |
function getTypeName(node) { | |
return node ? org.mozilla.javascript.Token.typeToName(node.getType()) : "" ; | |
} | |
function nodeToString(node) { | |
if (node.type == Token.GETPROP) { | |
return [nodeToString(node.target), node.property.string].join('.'); | |
} else if (node.type == Token.NAME) { | |
return node.string; | |
} else if (node.type == Token.STRING) { | |
return node.value; | |
} else if (node.type == Token.THIS) { | |
return "this"; | |
} else { | |
return getTypeName(node); | |
} | |
}; | |
function parseScript(source, name, visitorFunction) { | |
var ast = getParser().parse(source, name, 0); | |
ast.visit(new org.mozilla.javascript.ast.NodeVisitor({ | |
visit: visitorFunction | |
})); | |
} | |
function getParser() { | |
var ce = new org.mozilla.javascript.CompilerEnvirons(); | |
ce.setRecordingComments(true); | |
ce.setRecordingLocalJsDocComments(true); | |
ce.initFromContext(org.mozilla.javascript.Context.getCurrentContext()); | |
return new org.mozilla.javascript.Parser(ce, ce.getErrorReporter()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment