From 3cbba61dce9106a07955801d672e442ebbb6c3dc Mon Sep 17 00:00:00 2001 From: Garen Torikian Date: Mon, 5 Nov 2012 15:15:18 -0800 Subject: [PATCH] Add support for function extractor --- lib/panino.js | 1 - lib/panino/parsers.js | 2 +- .../javascript/jsd/function_extractor.js | 189 ++++++++++++++++++ lib/panino/plugins/parsers/jsd_parser.js | 5 +- lib/panino/renderers.js | 2 +- package.json | 3 +- 6 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 lib/panino/plugins/parsers/javascript/jsd/function_extractor.js diff --git a/lib/panino.js b/lib/panino.js index 1fc4bc1..b56e186 100644 --- a/lib/panino.js +++ b/lib/panino.js @@ -486,7 +486,6 @@ Panino.render = function render(name, ast, buildOptions, callback) { renderers[name](ast, options, callback); }; - /** * Panino.cli -> cli **/ diff --git a/lib/panino/parsers.js b/lib/panino/parsers.js index 53c307b..ddb7751 100644 --- a/lib/panino/parsers.js +++ b/lib/panino/parsers.js @@ -1,5 +1,5 @@ 'use strict'; // Simple "shared" parsers object. -// Used to avoid circular dependencies (between cli and ndoc). +// Used to avoid circular dependencies (between cli and Panino). module.exports = {}; diff --git a/lib/panino/plugins/parsers/javascript/jsd/function_extractor.js b/lib/panino/plugins/parsers/javascript/jsd/function_extractor.js new file mode 100644 index 0000000..cc89366 --- /dev/null +++ b/lib/panino/plugins/parsers/javascript/jsd/function_extractor.js @@ -0,0 +1,189 @@ +(function() { + var util = require('util'); + var esprima, getFunctions, traverse; + + esprima = require('esprima'); + + traverse = function(object, visitor, master) { + var parent; + parent = master === 'undefined' ? [] : master; + if (visitor.call(null, object, parent) === false) { + return; + } + return Object.keys(object).forEach(function(key) { + var child, path; + child = object[key]; + path = [object]; + path.push(parent); + if (typeof child === 'object' && child !== null) { + return traverse(child, visitor, path); + } + }); + }; + + getFunctions = function(tree, code) { + var matched = false, list = []; + traverse(tree, function(node, path) { + var parent; + if (node.type === 'FunctionDeclaration') { + return list.push({ + name: node.id.name, + params: node.params, + range: node.range, + blockStart: node.body.range[0], + end: node.body.range[1] + }); + } else if (node.type === 'FunctionExpression') { + parent = path[0]; + if (parent.type === 'AssignmentExpression') { + if (typeof parent.left.range !== 'undefined') { + if (parent.left.type === "MemberExpression") { + + // for: foo.doSomething = function + if (parent.left.object.name !== undefined) { + var namespace = parent.left.object.name; + + if (parent.left.property.name !== undefined) { + var memberName = parent.left.property.name; + matched = true; + } + + // for: foo["doSomething"] = function() + else if (parent.left.property && parent.left.property.type === "Literal") { + var namespace = parent.left.object.name; + var memberName = parent.left.property.value; + matched = true; + } + } + + // for: this.doSomething = function + else if (parent.left.object.type === "ThisExpression") { + var namespace = "thiz"; + if (parent.left.property.name !== undefined) { + var memberName = parent.left.property.name; + matched = true; + } + + // for this[variable] = function() + else if (parent.left.property.type === "CallExpression") { + // no op + matched = true; + } + } + + // for: Function.prototype.doSomething = function() + else if (parent.left.object.object !== undefined && parent.left.object.object.type === "Identifier") { + var namespace = parent.left.object.object.type; + var memberName = parent.left.property.name; + var isPrototype = true; + var prototyping = "prototype"; + matched = true; + } + + // for: this.htmlElement.onmouseover = function() + else if (parent.left.type === "MemberExpression" && parent.left.object.type === "MemberExpression") { + var namespace ="thiz"; + var memberName = parent.left.property.name; + + var isPrototype = true; + var prototyping = parent.left.object.property.name; + matched = true; + } + + // for: (boolType ? "name" : "name2").doSomething = function() + else if (parent.left.object !== undefined && parent.left.object.type === "ConditionalExpression") { + // no op + matched = true; + } + } + else if (parent.left.type === "Identifier") { + var memberName = parent.left.name; + matched = true; + } + + if (!matched) { + console.error("Never found a matching arrangement!"); + console.error(util.inspect(parent.left, null, 5)); + } + else { + return list.push({ + namespace: namespace, + name: memberName, + isPrototype: isPrototype, + prototyping: prototyping, + params: node.params, + range: node.range, + blockStart: node.body.range[0], + end: node.body.range[1] + }); + } + } + } else if (parent.type === 'VariableDeclarator') { + return list.push({ + name: parent.id.name, + params: node.params, + range: node.range, + blockStart: node.body.range[0], + end: node.body.range[1] + }); + } else if (parent.type === 'CallExpression') { + return list.push({ + name: parent.id ? parent.id.name : '[Anonymous]', + params: node.params, + range: node.range, + blockStart: node.body.range[0], + end: node.body.range[1] + }); + } else if (typeof parent.length === 'number') { + return list.push({ + name: parent.id ? parent.id.name : '[Anonymous]', + params: node.params, + range: node.range, + blockStart: node.body.range[0], + end: node.body.range[1] + }); + } else if (typeof parent.key !== 'undefined') { + if (parent.key.type === 'Identifier') { + if (parent.value === node && parent.key.name) { + return list.push({ + name: parent.key.name, + params: node.params, + range: node.range, + blockStart: node.body.range[0], + end: node.body.range[1] + }); + } + } + } + } + }); + return list; + }; + + exports.parse = function(code) { + var functions, tree; + tree = esprima.parse(code, { + loc: true, + range: true + }); + functions = getFunctions(tree, code); + + functions = functions.filter(function(fn) { + return fn.name !== '[Anonymous]'; + }); + + return functions; + }; + + exports.interpret = function(code, tree) { + var functions; + + functions = getFunctions(tree, code); + + functions = functions.filter(function(fn) { + return fn.name !== '[Anonymous]'; + }); + + return functions; + }; +}).call(this); \ No newline at end of file diff --git a/lib/panino/plugins/parsers/jsd_parser.js b/lib/panino/plugins/parsers/jsd_parser.js index 2059c18..ed96c9e 100644 --- a/lib/panino/plugins/parsers/jsd_parser.js +++ b/lib/panino/plugins/parsers/jsd_parser.js @@ -22,6 +22,7 @@ var util = require('util'); // 3rd-party var _ = require('underscore'); var esprima = require('esprima'); +var functionExtractor = require("function-extractor"); // internal var Panino = require(__dirname + '/../../../panino'); @@ -53,7 +54,9 @@ var process_jsd = function(source, file, options, callback) { // start parsing a la JSDuck //try { - ast = esprima.parse(source, {comment: true, range: true, raw: true}); + ast = esprima.parse(source, {comment: true, range: true, raw: true, loc: true}); + + //var functions = functionExtractor.interpret(source, ast); docs = JSParser.parse(ast, source); diff --git a/lib/panino/renderers.js b/lib/panino/renderers.js index 3f2d82d..c8b8542 100644 --- a/lib/panino/renderers.js +++ b/lib/panino/renderers.js @@ -1,5 +1,5 @@ 'use strict'; // Simple "shared" renderers object. -// Used to avoid circular dependencies (between cli and panino). +// Used to avoid circular dependencies (between cli and Panino). module.exports = {}; diff --git a/package.json b/package.json index 4a5e73a..90c981a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "functional-docs": "", "esprima": "", "StringScanner": "", - "colors": "" + "colors": "", + "function-extractor": "" }, "devDependencies" : { "jison": "~0.3.0" },