From c058a60ed146a2da7899c98ab2f37c9d7c1b311c Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Fri, 5 Jul 2013 09:11:27 -0700 Subject: [PATCH] hook up node visitors; gratuitous refactor of 'runtime' module (formerly 'vm'); move runtime-specific code includes updated Rhino jar: https://github.com/hegemonic/rhino/commit/b63c00d5 --- Jake/templates/package.json.tmpl | 2 +- jsdoc.js | 10 +- lib/jsdoc/fs.js | 4 +- lib/jsdoc/path.js | 6 +- lib/jsdoc/plugins.js | 7 +- lib/jsdoc/src/parser.js | 57 ++--- lib/jsdoc/src/visitor.js | 46 ++-- lib/jsdoc/util/include.js | 4 +- lib/jsdoc/util/{vm.js => runtime.js} | 58 +++-- {nodejs => node}/bin/jsdoc | 11 +- {nodejs => node}/jsdoc/fs.js | 0 node/jsdoc/src/parser.js | 1 + {nodejs => node}/jsdoc/util/include.js | 0 package.json | 4 +- rhino/js.jar | Bin 1131653 -> 1131550 bytes .../rhino => rhino/jsdoc/src}/astbuilder.js | 6 +- rhino/jsdoc/src/parser.js | 34 +++ rhino/jsdoc/src/visitor.js | 56 +++++ test/jasmine-jsdoc.js | 10 +- test/specs/documentation/modules.js | 6 +- test/specs/jsdoc/src/handlers.js | 9 +- test/specs/jsdoc/src/parser.js | 208 ++++++++++++++++-- test/specs/jsdoc/util/runtime.js | 62 ++++++ test/specs/jsdoc/util/vm.js | 66 ------ test/specs/rhino/src/parser.js | 154 +++++++++++++ test/specs/rhino/src/visitor.js | 88 ++++++++ test/specs/tags/overviewtag.js | 7 +- 27 files changed, 718 insertions(+), 198 deletions(-) rename lib/jsdoc/util/{vm.js => runtime.js} (53%) rename {nodejs => node}/bin/jsdoc (66%) rename {nodejs => node}/jsdoc/fs.js (100%) create mode 100644 node/jsdoc/src/parser.js rename {nodejs => node}/jsdoc/util/include.js (100%) rename {lib/jsdoc/src/rhino => rhino/jsdoc/src}/astbuilder.js (71%) create mode 100644 rhino/jsdoc/src/parser.js create mode 100644 rhino/jsdoc/src/visitor.js create mode 100644 test/specs/jsdoc/util/runtime.js delete mode 100644 test/specs/jsdoc/util/vm.js create mode 100644 test/specs/rhino/src/parser.js create mode 100644 test/specs/rhino/src/visitor.js diff --git a/Jake/templates/package.json.tmpl b/Jake/templates/package.json.tmpl index d5159473..4d6773d7 100644 --- a/Jake/templates/package.json.tmpl +++ b/Jake/templates/package.json.tmpl @@ -28,7 +28,7 @@ "underscore": "1.4.2", "wrench": "1.3.9" }, - "bin": "./nodejs/bin/jsdoc", + "bin": "./node/bin/jsdoc", "bugs": "https://github.com/jsdoc3/jsdoc/issues", "author": { "name": "Michael Mathews", diff --git a/jsdoc.js b/jsdoc.js index b1f27885..9b2b614a 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -72,9 +72,9 @@ require('lib/jsdoc/util/global').env = { // initialize the environment for the current JavaScript VM (function(args) { - var vm = require('jsdoc/util/vm').vm; + var runtime = require('jsdoc/util/runtime').getRuntime(); // TODO: may need to move this file to support Node.js - require('initialize')[vm](args); + require('initialize')[runtime](args); })( Array.prototype.slice.call(arguments, 0) ); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// @@ -87,7 +87,11 @@ require('lib/jsdoc/util/global').env = { require('lib/jsdoc/util/global').app = { jsdoc: { scanner: new (require('jsdoc/src/scanner').Scanner)(), - parser: new (require('jsdoc/src/parser').Parser)(), + // TODO: allow the config file to specify the parser module + parser: (function() { + var modulePath = require('jsdoc/util/runtime').getModulePath('jsdoc/src/parser'); + return new ( require(modulePath) ).Parser(); + })(), name: require('jsdoc/name') } }; diff --git a/lib/jsdoc/fs.js b/lib/jsdoc/fs.js index ee365a8d..1993807a 100644 --- a/lib/jsdoc/fs.js +++ b/lib/jsdoc/fs.js @@ -4,11 +4,11 @@ */ var fs = exports.fs = require('fs'); -var vm = require('jsdoc/util/vm'); +var runtime = require('jsdoc/util/runtime'); // export the VM-specific implementations of the extra methods // TODO: document extra methods here -var extras = vm.getModule('fs'); +var extras = require( runtime.getModulePath('fs') ); Object.keys(extras).forEach(function(extra) { exports[extra] = extras[extra]; }); diff --git a/lib/jsdoc/path.js b/lib/jsdoc/path.js index 4ddb031d..0be64476 100644 --- a/lib/jsdoc/path.js +++ b/lib/jsdoc/path.js @@ -5,7 +5,7 @@ var fs = require('fs'); var path = require('path'); -var vm = require('jsdoc/util/vm'); +var runtime = require('jsdoc/util/runtime'); function prefixReducer(previousPath, current) { @@ -71,7 +71,7 @@ exports.commonPrefix = function(paths) { * @param {string} path The path to convert. * @return {string} A URI that meets the operating system's requirements, or the original path. */ -var pathToUri = vm.getModule('jsdoc').pathToUri; +var pathToUri = require( runtime.getModulePath('jsdoc') ).pathToUri; // TODO: do we need this? if so, any way to stop exporting it? /** @@ -82,7 +82,7 @@ var pathToUri = vm.getModule('jsdoc').pathToUri; * @param {string} uri The URI to convert. * @return {string} A path that meets the operating system's requirements. */ -exports._uriToPath = vm.getModule('jsdoc').uriToPath; +exports._uriToPath = require( runtime.getModulePath('jsdoc') ).uriToPath; /** * Retrieve the fully qualified path to the requested resource. diff --git a/lib/jsdoc/plugins.js b/lib/jsdoc/plugins.js index 5e2637bc..963f0a97 100644 --- a/lib/jsdoc/plugins.js +++ b/lib/jsdoc/plugins.js @@ -36,10 +36,15 @@ exports.installPlugins = function(plugins, p) { plugin.defineTags(dictionary); } - //...add a node visitor + //...add a Rhino node visitor (deprecated in JSDoc 3.3) if (plugin.nodeVisitor) { parser.addNodeVisitor(plugin.nodeVisitor); } + + //...add a Mozilla Parser API node visitor + if (plugin.astNodeVisitor) { + parser.addAstNodeVisitor(plugin.astNodeVisitor); + } } } }; diff --git a/lib/jsdoc/src/parser.js b/lib/jsdoc/src/parser.js index 01e958f6..f0d02031 100644 --- a/lib/jsdoc/src/parser.js +++ b/lib/jsdoc/src/parser.js @@ -24,13 +24,33 @@ var SCHEMA = 'javascript:'; * @example Create a new parser. * var jsdocParser = new (require('jsdoc/src/parser').Parser)(); */ -var Parser = exports.Parser = function() { +var Parser = exports.Parser = function(builderInstance, visitorInstance, walkerInstance) { this.clear(); - // TODO: require the appropriate AstBuilder based on a config setting - this._astBuilder = new (require('jsdoc/src/rhino/astbuilder'))(); - this._visitor = new (require('jsdoc/src/visitor'))(this); - this._walker = new (require('jsdoc/src/walker')).Walker(); + // TODO: replace with a runtime-agnostic default builder + var runtime = require('jsdoc/util/runtime'); + this._astBuilder = builderInstance || + new ( require(runtime.getModulePath('jsdoc/src/astbuilder')) )(); + this._visitor = visitorInstance || new (require('jsdoc/src/visitor')).Visitor(this); + this._walker = walkerInstance || new (require('jsdoc/src/walker')).Walker(); + + Object.defineProperties(this, { + astBuilder: { + get: function() { + return this._astBuilder; + } + }, + visitor: { + get: function() { + return this._visitor; + } + }, + walker: { + get: function() { + return this._walker; + } + } + }); }; util.inherits(Parser, require('events').EventEmitter); @@ -42,12 +62,6 @@ Parser.prototype.clear = function() { meta: {} } }; - this._visitor = null; -}; - -// TODO: docs -Parser.prototype.getVisitor = function() { - return this._visitor; }; // TODO: docs @@ -134,9 +148,6 @@ Parser.prototype.fireProcessingComplete = function(doclets) { }; // TODO: docs -/** - * @returns {Array} The accumulated results of any calls to parse. - */ Parser.prototype.results = function() { return this._resultBuffer; }; @@ -149,20 +160,14 @@ Parser.prototype.addResult = function(o) { this._resultBuffer.push(o); }; -// TODO: update docs -/** - * Adds a node visitor to use in parsing - */ -Parser.prototype.addNodeVisitor = function(visitor) { - this._visitor._addRhinoNodeVisitor(visitor); +// TODO: docs +Parser.prototype.addAstNodeVisitor = function(visitor) { + this._visitor.addAstNodeVisitor(visitor); }; // TODO: docs -/** - * Get the node visitors used in parsing - */ -Parser.prototype.getVisitors = function() { - return this._visitor._getRhinoNodeVisitors(); +Parser.prototype.getAstNodeVisitors = function() { + return this._visitor.getAstNodeVisitors(); }; // TODO: docs @@ -424,7 +429,6 @@ Parser.prototype.getBasename = function(name) { if (name !== undefined) { return name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1'); } - return name; }; // TODO: docs @@ -433,7 +437,6 @@ function definedInScope(doclet, basename) { hasOwnProp.call(doclet.meta.vars, basename); } - // TODO: docs /** * Given a node, determine what the node is a member of. diff --git a/lib/jsdoc/src/visitor.js b/lib/jsdoc/src/visitor.js index 51961903..7b7f3f28 100644 --- a/lib/jsdoc/src/visitor.js +++ b/lib/jsdoc/src/visitor.js @@ -68,13 +68,11 @@ function JsdocCommentFound(comment, filename) { // TODO: docs -var Visitor = module.exports = function(parser) { +var Visitor = exports.Visitor = function(parser) { this._parser = parser; // Mozilla Parser API node visitors added by plugins this._nodeVisitors = []; - // Rhino node visitors added by plugins (deprecated in JSDoc 3.3) - this._rhinoNodeVisitors = []; // built-in visitors this._visitors = [ this.visitNodeComments, @@ -83,12 +81,12 @@ var Visitor = module.exports = function(parser) { }; // TODO: docs -Visitor.prototype.addNodeVisitor = function(visitor) { +Visitor.prototype.addAstNodeVisitor = function(visitor) { this._nodeVisitors.push(visitor); }; // TODO: docs -Visitor.prototype.removeNodeVisitor = function(visitor) { +Visitor.prototype.removeAstNodeVisitor = function(visitor) { var idx = this._nodeVisitors.indexOf(visitor); if (idx !== -1) { this._nodeVisitors.splice(idx, 1); @@ -96,20 +94,10 @@ Visitor.prototype.removeNodeVisitor = function(visitor) { }; // TODO: docs -Visitor.prototype.getNodeVisitors = function() { +Visitor.prototype.getAstNodeVisitors = function() { return this._nodeVisitors; }; -// TODO: docs (deprecated) -Visitor.prototype._addRhinoNodeVisitor = function(visitor) { - this._rhinoNodeVisitors.push(visitor); -}; - -// TODO: docs (deprecated) -Visitor.prototype._getRhinoNodeVisitors = function() { - return this._rhinoNodeVisitors; -}; - // TODO: docs; visitor signature is (node, parser, filename) Visitor.prototype.visit = function(node, filename) { var i; @@ -203,18 +191,10 @@ Visitor.prototype.visitNodeComments = function(node, parser, filename) { // TODO: docs Visitor.prototype.visitNode = function(node, parser, filename) { - var e = this.makeSymbolFoundEvent(node, parser, filename); + var i; + var l; - function callNodeVisitors(visitors, nodeToVisit) { - if (visitors) { - for (var i = 0, l = visitors.length; i < l; i++) { - visitors[i].visitNode(nodeToVisit, e, parser, filename); - if (e.stopPropagation) { - break; - } - } - } - } + var e = this.makeSymbolFoundEvent(node, parser, filename); if (!node.nodeId) { Object.defineProperty(node, 'nodeId', { @@ -222,9 +202,13 @@ Visitor.prototype.visitNode = function(node, parser, filename) { }); } - callNodeVisitors(this._nodeVisitors, node); - if (e.code && e.code.node) { - callNodeVisitors(this._rhinoNodeVisitors, e.code.node); // TODO: check this!! + if (this._nodeVisitors && this._nodeVisitors.length) { + for (i = 0, l = this._nodeVisitors.length; i < l; i++) { + this._nodeVisitors[i].visitNode(node, e, parser, filename); + if (e.stopPropagation) { + break; + } + } } if (!e.preventDefault && e.comment && isValidJsdoc(e.comment)) { @@ -234,7 +218,7 @@ Visitor.prototype.visitNode = function(node, parser, filename) { // add the node to the parser's lookup table parser.addDocletRef(e); - for (var i = 0, l = e.finishers.length; i < l; i++) { + for (i = 0, l = e.finishers.length; i < l; i++) { e.finishers[i].call(parser, e); } diff --git a/lib/jsdoc/util/include.js b/lib/jsdoc/util/include.js index 54d1efa4..a877a1f9 100644 --- a/lib/jsdoc/util/include.js +++ b/lib/jsdoc/util/include.js @@ -1,5 +1,5 @@ var path = require('path'); -var vm = require('jsdoc/util/vm'); +var runtime = require('jsdoc/util/runtime'); /** * Read and execute a JavaScript file in global scope. @@ -11,7 +11,7 @@ module.exports = function(filepath) { filepath = path.resolve(__dirname, filepath); try { - vm.getModule('jsdoc/util/include')(filepath); + require( runtime.getModulePath('jsdoc/util/include') )(filepath); } catch (e) { console.log('Cannot include ' + filepath + ': ' + e); diff --git a/lib/jsdoc/util/vm.js b/lib/jsdoc/util/runtime.js similarity index 53% rename from lib/jsdoc/util/vm.js rename to lib/jsdoc/util/runtime.js index eb7048b1..ce3c0786 100644 --- a/lib/jsdoc/util/vm.js +++ b/lib/jsdoc/util/runtime.js @@ -1,16 +1,18 @@ /*global Packages: true */ /** - * Helper functions to enable JSDoc to run on multiple JavaScript virtual machines. - * @module jsdoc/util/vm + * Helper functions to enable JSDoc to run on multiple JavaScript runtimes. + * + * @module jsdoc/util/runtime + * @private */ var os = require('os'); // These strings represent directory names; do not modify them! /** @private */ -const RHINO = exports.RHINO = 'rhino'; +var RHINO = exports.RHINO = 'rhino'; /** @private */ -const NODEJS = exports.NODEJS = 'nodejs'; +var NODE = exports.NODE = 'node'; // hacky conversion from Windows path to URI function pathToUri(filepath) { @@ -33,51 +35,63 @@ function pathToUri(filepath) { } /** - * The JavaScript VM that is executing JSDoc: + * The JavaScript runtime that is executing JSDoc: * - * + `module:jsdoc/util/vm.RHINO`: Mozilla Rhino. - * + `module:jsdoc/util/vm.NODEJS`: Node.js (not currently supported). + * + `module:jsdoc/util/runtime~RHINO`: Mozilla Rhino. + * + `module:jsdoc/util/runtime~NODE`: Node.js (not currently supported). + * + * @private */ -var vm = exports.vm = (function() { +var runtime = (function() { if (Packages && typeof Packages === 'object' && Object.prototype.toString.call(Packages) === '[object JavaPackage]') { return RHINO; } else if ( require && require.main && module && (require.main === module) ) { - return NODEJS; + return NODE; } else { - // unknown VM - throw new Error('Unable to identify the current JavaScript VM.'); + // unknown runtime + throw new Error('Unable to identify the current JavaScript runtime.'); } })(); /** - * Load the VM-specific implementation of a module. + * Get the require path for the runtime-specific implementation of a module. * - * @param {string} modulePath - The relative path to the module. Use the same format as when calling + * @param {string} partialPath - The partial path to the module. Use the same format as when calling * `require()`. - * @return {object} An object containing VM-specific functions for the requested module. + * @return {object} The require path for the runtime-specific implementation of the module. */ -exports.getModule = function(modulePath) { - modulePath = [__dirname, vm, modulePath].join('/').replace(/ /g, '%20'); +exports.getModulePath = function(partialPath) { + var modulePath = [__dirname, runtime, partialPath].join('/').replace(/ /g, '%20'); if (os.platform() === 'win32') { modulePath = pathToUri(modulePath); } - return require(modulePath); + return modulePath; +}; + +/** + * Retrieve the identifier for the current JavaScript runtime. + * + * @private + * @return {string} The runtime identifier. + */ +exports.getRuntime = function() { + return runtime; }; /** * Check whether Mozilla Rhino is running JSDoc. - * @return {boolean} Set to `true` if the current VM is Mozilla Rhino. + * @return {boolean} Set to `true` if the current runtime is Mozilla Rhino. */ exports.isRhino = function() { - return vm === RHINO; + return runtime === RHINO; }; /** * Check whether Node.js is running JSDoc. (Node.js is not currently supported.) - * @return {boolean} Set to `true` if the current VM is Node.js. + * @return {boolean} Set to `true` if the current runtime is Node.js. */ -exports.isNodejs = function() { - return vm === NODEJS; +exports.isNode = function() { + return runtime === NODE; }; diff --git a/nodejs/bin/jsdoc b/node/bin/jsdoc similarity index 66% rename from nodejs/bin/jsdoc rename to node/bin/jsdoc index c53efc4d..d2f56ad1 100755 --- a/nodejs/bin/jsdoc +++ b/node/bin/jsdoc @@ -7,19 +7,12 @@ var fs = require('fs'); var os = require('os'); var path = require('path'); -var spawnProc = require('child_process').spawn; var args = process.argv.slice(2); var script = path.normalize( path.join( path.dirname(fs.realpathSync(process.argv[1])), '..', '..', 'jsdoc' ) ); -var jsdoc; - -if (process.platform === 'win32') { - jsdoc = spawnProc(script + '.cmd', args, {stdio: 'inherit'}); -} -else { - jsdoc = spawnProc(script, args, {stdio: 'inherit'}); -} +var command = process.platform === 'win32' ? script + '.cmd' : script; +var jsdoc = require('child_process').spawn(command, args, {stdio: 'inherit'}); jsdoc.on('close', function(code) { process.exit(code); diff --git a/nodejs/jsdoc/fs.js b/node/jsdoc/fs.js similarity index 100% rename from nodejs/jsdoc/fs.js rename to node/jsdoc/fs.js diff --git a/node/jsdoc/src/parser.js b/node/jsdoc/src/parser.js new file mode 100644 index 00000000..1b146d91 --- /dev/null +++ b/node/jsdoc/src/parser.js @@ -0,0 +1 @@ +module.exports = require('jsdoc/src/parser'); diff --git a/nodejs/jsdoc/util/include.js b/node/jsdoc/util/include.js similarity index 100% rename from nodejs/jsdoc/util/include.js rename to node/jsdoc/util/include.js diff --git a/package.json b/package.json index 17546150..820a8ef3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jsdoc", "version": "3.2.0-dev", - "revision": "1366409063892", + "revision": "1373039791834", "description": "An API documentation generator for JavaScript.", "keywords": [ "documentation", "javascript" ], "licenses": [ @@ -28,7 +28,7 @@ "underscore": "1.4.2", "wrench": "1.3.9" }, - "bin": "./nodejs/bin/jsdoc", + "bin": "./node/bin/jsdoc", "bugs": "https://github.com/jsdoc3/jsdoc/issues", "author": { "name": "Michael Mathews", diff --git a/rhino/js.jar b/rhino/js.jar index 30e26793a86a6a004e3e7f7f39c9969e97727a93..244aa614d837794b4d04e6a4b67eb3dbcdd4970f 100644 GIT binary patch delta 26250 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

feSC^^F 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 C>|tmC delta 26481 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 R`}79VUa*$uBvQO;`X9bkA$|Y= diff --git a/lib/jsdoc/src/rhino/astbuilder.js b/rhino/jsdoc/src/astbuilder.js similarity index 71% rename from lib/jsdoc/src/rhino/astbuilder.js rename to rhino/jsdoc/src/astbuilder.js index bb934b24..9a6c0cde 100644 --- a/lib/jsdoc/src/rhino/astbuilder.js +++ b/rhino/jsdoc/src/astbuilder.js @@ -1,7 +1,7 @@ /*global Packages: true */ /** * Creates an Esprima-compatible AST using Rhino's JavaScript parser. - * @module jsdoc/src/rhino/astbuilder + * @module rhino/jsdoc/src/astbuilder */ var AstBuilder = module.exports = function() { @@ -11,3 +11,7 @@ var AstBuilder = module.exports = function() { AstBuilder.prototype.build = function(sourceCode, sourceName) { return this._builder.build(sourceCode, sourceName); }; + +AstBuilder.prototype.getRhinoNodes = function() { + return this._builder.getRhinoNodes(); +}; diff --git a/rhino/jsdoc/src/parser.js b/rhino/jsdoc/src/parser.js new file mode 100644 index 00000000..b3c16177 --- /dev/null +++ b/rhino/jsdoc/src/parser.js @@ -0,0 +1,34 @@ +// TODO: module docs + +// TODO: docs +var Parser = exports.Parser = function() { + var astBuilder; + var visitor; + + var runtime = require('jsdoc/util/runtime'); + if ( !runtime.isRhino() ) { + throw new Error('You must run JSDoc on Mozilla Rhino to use the Rhino parser.'); + } + + astBuilder = new ( require(runtime.getModulePath('jsdoc/src/astbuilder')) )(); + visitor = new ( require(runtime.getModulePath('jsdoc/src/visitor')) ).Visitor(this); + + Parser.super_.call(this, astBuilder, visitor); +}; +require('util').inherits(Parser, require('jsdoc/src/parser').Parser); + +// TODO: update docs +/** + * Adds a node visitor to use in parsing + */ +Parser.prototype.addNodeVisitor = function(visitor) { + this._visitor.addRhinoNodeVisitor(visitor); +}; + +// TODO: docs +/** + * Get the node visitors used in parsing + */ +Parser.prototype.getNodeVisitors = function() { + return this._visitor.getRhinoNodeVisitors(); +}; diff --git a/rhino/jsdoc/src/visitor.js b/rhino/jsdoc/src/visitor.js new file mode 100644 index 00000000..a12fadff --- /dev/null +++ b/rhino/jsdoc/src/visitor.js @@ -0,0 +1,56 @@ +// TODO: module docs + +// TODO: docs +var Visitor = exports.Visitor = function(parser) { + var runtime = require('jsdoc/util/runtime'); + if ( !runtime.isRhino() ) { + throw new Error('You must run JSDoc on Mozilla Rhino to use the Rhino node visitor.'); + } + + Visitor.super_.call(this, parser); + + // Rhino node visitors added by plugins (deprecated in JSDoc 3.3) + this._rhinoNodeVisitors = []; + // Rhino nodes retrieved from the org.jsdoc.AstBuilder instance + this._rhinoNodes = null; + + this.addAstNodeVisitor({ + visitNode: this._visitRhinoNode.bind(this) + }); +}; +require('util').inherits(Visitor, require('jsdoc/src/visitor').Visitor); + +// TODO: docs (deprecated) +Visitor.prototype.addRhinoNodeVisitor = function(visitor) { + this._rhinoNodeVisitors.push(visitor); +}; + +// TODO: docs (deprecated) +Visitor.prototype.getRhinoNodeVisitors = function() { + return this._rhinoNodeVisitors; +}; + +// TODO: docs (deprecated) +Visitor.prototype._visitRhinoNode = function(astNode, e, parser, filename) { + var rhinoNode; + + var visitors = this._rhinoNodeVisitors; + // if there are no visitors, bail out before we retrieve all the nodes + if (!visitors.length) { + return; + } + + if (!this._rhinoNodes) { + this._rhinoNodes = parser.astBuilder.getRhinoNodes(); + } + + rhinoNode = this._rhinoNodes ? this._rhinoNodes.get(astNode.nodeId) : null; + if (rhinoNode) { + for (var i = 0, l = visitors.length; i < l; i++) { + visitors[i].visitNode(rhinoNode, e, parser, filename); + if (e.stopPropagation) { + break; + } + } + } +}; diff --git a/test/jasmine-jsdoc.js b/test/jasmine-jsdoc.js index f77b0f5f..79064337 100644 --- a/test/jasmine-jsdoc.js +++ b/test/jasmine-jsdoc.js @@ -110,10 +110,12 @@ jasmine.asyncSpecDone = function() { }; jasmine.getDocSetFromFile = function(filename, parser) { - var sourceCode = fs.readFileSync(__dirname + '/' + filename, 'utf8'), - testParser = parser || new (require('jsdoc/src/parser')).Parser(), - indexAll = require('jsdoc/borrow').indexAll, - doclets; + var sourceCode = fs.readFileSync(__dirname + '/' + filename, 'utf8'); + var runtime = require('jsdoc/util/runtime'); + var testParser = parser || + new ( require(runtime.getModulePath('jsdoc/src/parser')) ).Parser(); + var indexAll = require('jsdoc/borrow').indexAll; + var doclets; require('jsdoc/src/handlers').attachTo(testParser); diff --git a/test/specs/documentation/modules.js b/test/specs/documentation/modules.js index 52481150..9d31e402 100644 --- a/test/specs/documentation/modules.js +++ b/test/specs/documentation/modules.js @@ -1,7 +1,9 @@ /*global beforeEach: true, describe: true, env: true, expect: true, it: true */ describe("module names", function() { - var parser = require('jsdoc/src/parser'), - srcParser = null, doclets; + var runtime = require('jsdoc/util/runtime'); + var parser = require( runtime.getModulePath('jsdoc/src/parser') ); + var srcParser = null; + var doclets; beforeEach(function() { env.opts._ = [__dirname + '/test/fixtures/modules/']; diff --git a/test/specs/jsdoc/src/handlers.js b/test/specs/jsdoc/src/handlers.js index f43ac711..66f0368b 100644 --- a/test/specs/jsdoc/src/handlers.js +++ b/test/specs/jsdoc/src/handlers.js @@ -1,8 +1,9 @@ /*global describe: true, expect: true, it: true */ describe("jsdoc/src/handlers", function() { - var jsdoc = {src: { parser: require('jsdoc/src/parser')}}, - testParser = new jsdoc.src.parser.Parser(), - handlers = require('jsdoc/src/handlers'); + var runtime = require('jsdoc/util/runtime'); + var parser = require( runtime.getModulePath('jsdoc/src/parser') ); + var testParser = new parser.Parser(); + var handlers = require('jsdoc/src/handlers'); handlers.attachTo(testParser); @@ -52,4 +53,4 @@ describe("jsdoc/src/handlers", function() { describe("symbolFound handler", function() { //TODO }); -}); \ No newline at end of file +}); diff --git a/test/specs/jsdoc/src/parser.js b/test/specs/jsdoc/src/parser.js index d75f554a..6babbef8 100644 --- a/test/specs/jsdoc/src/parser.js +++ b/test/specs/jsdoc/src/parser.js @@ -1,15 +1,16 @@ -/*global beforeEach: true, describe: true, expect: true, it: true, jasmine: true, xdescribe: true, xit: true */ +/*global beforeEach: true, describe: true, expect: true, it: true, jasmine: true, spyOn: true, +xit: true */ describe("jsdoc/src/parser", function() { - var jsdoc = {src: { parser: require('jsdoc/src/parser')}}; + var jsdoc = { src: { parser: require('jsdoc/src/parser') } }; it("should exist", function() { expect(jsdoc.src.parser).toBeDefined(); - expect(typeof jsdoc.src.parser).toEqual("object"); + expect(typeof jsdoc.src.parser).toBe('object'); }); it("should export a 'Parser' constructor", function() { expect(jsdoc.src.parser.Parser).toBeDefined(); - expect(typeof jsdoc.src.parser.Parser).toEqual("function"); + expect(typeof jsdoc.src.parser.Parser).toBe('function'); }); describe("Parser", function() { @@ -19,14 +20,69 @@ describe("jsdoc/src/parser", function() { parser = new jsdoc.src.parser.Parser(); } - it("should have a 'parse' function", function() { - expect(jsdoc.src.parser.Parser.prototype.parse).toBeDefined(); - expect(typeof jsdoc.src.parser.Parser.prototype.parse).toEqual("function"); + newParser(); + + it('should have an "astBuilder" property', function() { + expect(parser.astBuilder).toBeDefined(); }); - it("should have a 'results' function", function() { - expect(jsdoc.src.parser.Parser.prototype.results).toBeDefined(); - expect(typeof jsdoc.src.parser.Parser.prototype.results).toEqual("function"); + it('should have a "visitor" property', function() { + expect(parser.visitor).toBeDefined(); + }); + + it('should have a "walker" property', function() { + expect(parser.walker).toBeDefined(); + }); + + it('should accept an astBuilder, visitor, and walker as arguments', function() { + var astBuilder = {}; + var visitor = {}; + var walker = {}; + + var myParser = new jsdoc.src.parser.Parser(astBuilder, visitor, walker); + + expect(myParser.astBuilder).toBe(astBuilder); + expect(myParser.visitor).toBe(visitor); + expect(myParser.walker).toBe(walker); + }); + + it('should have a "parse" method', function() { + expect(parser.parse).toBeDefined(); + expect(typeof parser.parse).toBe('function'); + }); + + it('should have a "results" method', function() { + expect(parser.results).toBeDefined(); + expect(typeof parser.results).toBe('function'); + }); + + it('should have an "addAstNodeVisitor" method', function() { + expect(parser.addAstNodeVisitor).toBeDefined(); + expect(typeof parser.addAstNodeVisitor).toBe('function'); + }); + + it('should have a "getAstNodeVisitors" method', function() { + expect(parser.getAstNodeVisitors).toBeDefined(); + expect(typeof parser.getAstNodeVisitors).toBe('function'); + }); + + describe('astBuilder', function() { + // TODO: enable + xit('should contain an appropriate astBuilder by default', function() { + expect( parser.astBuilder instanceof (require('jsdoc/src/astbuilder')) ).toBe(true); + }); + }); + + describe('visitor', function() { + it('should contain an appropriate visitor by default', function() { + expect( parser.visitor instanceof (require('jsdoc/src/visitor')).Visitor ).toBe(true); + }); + }); + + describe('walker', function() { + it('should contain an appropriate walker by default', function() { + expect( parser.walker instanceof (require('jsdoc/src/walker')).Walker ).toBe(true); + }); }); describe("parse", function() { @@ -70,6 +126,70 @@ describe("jsdoc/src/parser", function() { expect(spy).toHaveBeenCalled(); }); + it('should call AST node visitors', function() { + var Syntax = require('jsdoc/src/syntax').Syntax; + + var args; + + var sourceCode = ['javascript:/** foo */var foo;']; + var visitor = { + visitNode: function(node, e, parser, sourceName) { + if (e && e.code && !args) { + args = Array.prototype.slice.call(arguments); + } + } + }; + + require('jsdoc/src/handlers').attachTo(parser); + parser.addAstNodeVisitor(visitor); + parser.parse(sourceCode); + + expect(args).toBeDefined(); + expect( Array.isArray(args) ).toBe(true); + expect(args.length).toBe(4); + + // args[0]: AST node + expect(args[0].type).toBeDefined(); + expect(args[0].type).toBe(Syntax.VariableDeclarator); + + // args[1]: JSDoc event + expect(typeof args[1]).toBe('object'); + expect(args[1].code).toBeDefined(); + expect(args[1].code.name).toBeDefined(); + expect(args[1].code.name).toBe('foo'); + + // args[2]: parser + expect(typeof args[2]).toBe('object'); + expect(args[2] instanceof jsdoc.src.parser.Parser).toBe(true); + + // args[3]: current source name + expect( String(args[3]) ).toBe('[[string0]]'); + }); + + it('should reflect changes made by AST node visitors', function() { + var doclet; + + var sourceCode = ['javascript:/** foo */var foo;']; + var visitor = { + visitNode: function(node, e, parser, sourceName) { + if (e && e.code && e.code.name === 'foo') { + e.code.name = 'bar'; + } + } + }; + + require('jsdoc/src/handlers').attachTo(parser); + parser.addAstNodeVisitor(visitor); + parser.parse(sourceCode); + + doclet = parser.results()[0]; + + expect(doclet).toBeDefined(); + expect(typeof doclet).toBe('object'); + expect(doclet.name).toBeDefined(); + expect(doclet.name).toBe('bar'); + }); + it("should fire 'parseComplete' events after it finishes parsing files", function() { var spy = jasmine.createSpy(), sourceCode = ['javascript:var bar = false;']; @@ -101,15 +221,31 @@ describe("jsdoc/src/parser", function() { }); }); - describe("results", function() { + describe('results', function() { beforeEach(newParser); - xit("contains an empty array before files are parsed", function() { - // TODO + it('returns an empty array before files are parsed', function() { + var results = parser.results(); + + expect(results).toBeDefined(); + expect( Array.isArray(results) ).toBe(true); + expect(results.length).toBe(0); }); - xit("contains an array of doclets after files are parsed", function() { + it('returns an array of doclets after files are parsed', function() { + var source = 'javascript:var foo;'; + var results; + require('jsdoc/src/handlers').attachTo(parser); + + parser.parse(source); + results = parser.results(); + + expect(results).toBeDefined(); + expect(results[0]).toBeDefined(); + expect(typeof results[0]).toBe('object'); + expect(results[0].name).toBeDefined(); + expect(results[0].name).toBe('foo'); }); it("should reflect comment changes made by 'jsdocCommentFound' handlers", function() { @@ -129,5 +265,47 @@ describe("jsdoc/src/parser", function() { }); }); }); + + describe('addAstNodeVisitor', function() { + function visitorA() {} + function visitorB() {} + + var visitors; + + beforeEach(newParser); + + it('should work with a single node visitor', function() { + parser.addAstNodeVisitor(visitorA); + + visitors = parser.getAstNodeVisitors(); + + expect(visitors.length).toBe(1); + expect(visitors[0]).toBe(visitorA); + }); + + it('should work with multiple node visitors', function() { + parser.addAstNodeVisitor(visitorA); + parser.addAstNodeVisitor(visitorB); + + visitors = parser.getAstNodeVisitors(); + + expect(visitors.length).toBe(2); + expect(visitors[0]).toBe(visitorA); + expect(visitors[1]).toBe(visitorB); + }); + }); + + describe('getAstNodeVisitors', function() { + beforeEach(newParser); + + it('should return an empty array by default', function() { + var visitors = parser.getAstNodeVisitors(); + + expect( Array.isArray(visitors) ).toBe(true); + expect(visitors.length).toBe(0); + }); + + // other functionality is covered by the addNodeVisitors tests + }); }); -}); \ No newline at end of file +}); diff --git a/test/specs/jsdoc/util/runtime.js b/test/specs/jsdoc/util/runtime.js new file mode 100644 index 00000000..2efb1a81 --- /dev/null +++ b/test/specs/jsdoc/util/runtime.js @@ -0,0 +1,62 @@ +/*global describe: true, expect: true, it: true, xit: true */ +describe("jsdoc/util/runtime", function() { + var runtime = require('jsdoc/util/runtime'); + var isRhino; + var isNode; + + it("should exist", function() { + expect(runtime).toBeDefined(); + expect(typeof runtime).toEqual('object'); + }); + + it("should export a 'RHINO' constant", function() { + expect(runtime.RHINO).toBeDefined(); + expect(typeof runtime.RHINO).toEqual('string'); + }); + + it("should export a 'NODE' constant", function() { + expect(runtime.NODE).toBeDefined(); + expect(typeof runtime.NODE).toEqual('string'); + }); + it("should export an 'isRhino' function", function() { + expect(runtime.isRhino).toBeDefined(); + expect(typeof runtime.isRhino).toEqual('function'); + }); + + it("should export an 'isNode' function", function() { + expect(runtime.isNode).toBeDefined(); + expect(typeof runtime.isNode).toEqual('function'); + }); + + describe("isRhino", function() { + isRhino = runtime.isRhino(); + + it("should return a boolean", function() { + expect(typeof isRhino).toEqual('boolean'); + }); + + it("should return the opposite value from isNode()", function() { + if (isNode === undefined) { + isNode = runtime.isNode(); + } + + expect(!isRhino).toBe(isNode); + }); + }); + + describe("isNode", function() { + isNode = runtime.isNode(); + + it("should return a boolean", function() { + expect(typeof isNode).toEqual('boolean'); + }); + + it("should return the opposite value from isRhino()", function() { + if (isRhino === undefined) { + isRhino = runtime.isRhino(); + } + + expect(!isNode).toBe(isRhino); + }); + }); +}); diff --git a/test/specs/jsdoc/util/vm.js b/test/specs/jsdoc/util/vm.js deleted file mode 100644 index a141a4cc..00000000 --- a/test/specs/jsdoc/util/vm.js +++ /dev/null @@ -1,66 +0,0 @@ -/*global describe: true, expect: true, it: true, xit: true */ -describe("jsdoc/util/vm", function() { - var vm = require('jsdoc/util/vm'); - - it("should exist", function() { - expect(vm).toBeDefined(); - expect(typeof vm).toEqual('object'); - }); - - it("should export a 'RHINO' constant", function() { - expect(vm.RHINO).toBeDefined(); - expect(typeof vm.RHINO).toEqual('string'); - }); - - it("should export a 'NODEJS' constant", function() { - expect(vm.NODEJS).toBeDefined(); - expect(typeof vm.NODEJS).toEqual('string'); - }); - - it("should export a 'vm' property", function() { - expect(vm.vm).toBeDefined(); - expect(typeof vm.vm).toEqual('string'); - }); - - it("should export an 'isRhino' function", function() { - expect(vm.isRhino).toBeDefined(); - expect(typeof vm.isRhino).toEqual('function'); - }); - - it("should export an 'isNodejs' function", function() { - expect(vm.isNodejs).toBeDefined(); - expect(typeof vm.isNodejs).toEqual('function'); - }); - - - describe("vm", function() { - it("should match either 'vm.RHINO' or 'vm.NODEJS'", function() { - expect(vm.vm).toEqual(vm.RHINO || vm.NODEJS); - }); - - xit("should return the correct value for the current VM", function() { - // TODO: is there a reasonable test that doesn't just replicate getVm()? - }); - }); - - describe("isRhino", function() { - it("should return a boolean", function() { - expect( typeof vm.isRhino() ).toEqual('boolean'); - }); - - it("should reflect the value of 'vm.vm'", function() { - expect( vm.isRhino() ).toEqual(vm.vm === vm.RHINO ? true : false); - }); - }); - - describe("isNodejs", function() { - it("should return a boolean", function() { - expect( typeof vm.isNodejs() ).toEqual('boolean'); - }); - - it("should reflect the value of 'vm.vm'", function() { - expect( vm.isNodejs() ).toEqual(vm.vm === vm.NODEJS ? true : false); - }); - }); - -}); diff --git a/test/specs/rhino/src/parser.js b/test/specs/rhino/src/parser.js new file mode 100644 index 00000000..5926606d --- /dev/null +++ b/test/specs/rhino/src/parser.js @@ -0,0 +1,154 @@ +/*global beforeEach: true, describe: true, expect: true, it: true, jasmine: true, spyOn: true */ +describe('rhino/jsdoc/src/parser', function() { + var jsdoc = { + src: { + parser: (function() { + var runtime = require('jsdoc/util/runtime'); + return require( runtime.getModulePath('jsdoc/src/parser') ); + })() + } + }; + + it('should exist', function() { + expect(jsdoc.src.parser).toBeDefined(); + expect(typeof jsdoc.src.parser).toBe('object'); + }); + + it('should export a "Parser" constructor', function() { + expect(jsdoc.src.parser.Parser).toBeDefined(); + expect(typeof jsdoc.src.parser.Parser).toBe('function'); + }); + + describe('Parser', function() { + var parser; + + function newParser() { + parser = new jsdoc.src.parser.Parser(); + } + + newParser(); + + it('should inherit from jsdoc/src/parser', function() { + var parent = require('jsdoc/src/parser').Parser; + expect(parser instanceof parent).toBe(true); + }); + + it('should have an "addNodeVisitor" method', function() { + expect(parser.addNodeVisitor).toBeDefined(); + expect(typeof parser.addNodeVisitor).toBe('function'); + }); + + it('should have a "getNodeVisitors" method', function() { + expect(parser.getNodeVisitors).toBeDefined(); + expect(typeof parser.getNodeVisitors).toBe('function'); + }); + + describe('addNodeVisitor', function() { + function visitorA() {} + function visitorB() {} + + var visitors; + + beforeEach(newParser); + + it('should work with a single Rhino node visitor', function() { + parser.addNodeVisitor(visitorA); + + visitors = parser.getNodeVisitors(); + + expect(visitors.length).toBe(1); + expect(visitors[0]).toBe(visitorA); + }); + + it('should work with multiple Rhino node visitors', function() { + parser.addNodeVisitor(visitorA); + parser.addNodeVisitor(visitorB); + + visitors = parser.getNodeVisitors(); + + expect(visitors.length).toBe(2); + expect(visitors[0]).toBe(visitorA); + expect(visitors[1]).toBe(visitorB); + }); + }); + + describe('getNodeVisitors', function() { + beforeEach(newParser); + + it('should return an empty array by default', function() { + var visitors = parser.getNodeVisitors(); + + expect( Array.isArray(visitors) ).toBe(true); + expect(visitors.length).toBe(0); + }); + + // other functionality is covered by the addNodeVisitors tests + }); + + describe('parse', function() { + beforeEach(newParser); + + var sourceCode = ['javascript:/** foo */var foo;']; + + it('should call Rhino node visitors', function() { + var args; + + var visitor = { + visitNode: function(rhinoNode, e, parser, sourceName) { + if (e && e.code && !args) { + args = Array.prototype.slice.call(arguments); + } + } + }; + + require('jsdoc/src/handlers').attachTo(parser); + parser.addNodeVisitor(visitor); + parser.parse(sourceCode); + + expect(args).toBeDefined(); + expect( Array.isArray(args) ).toBe(true); + expect(args.length).toBe(4); + + // args[0]: Rhino node + expect(args[0].toSource).toBeDefined(); + expect( String(args[0].toSource()) ).toBe('foo'); + + // args[1]: JSDoc event + expect(typeof args[1]).toBe('object'); + expect(args[1].code).toBeDefined(); + expect(args[1].code.name).toBeDefined(); + expect( String(args[1].code.name) ).toBe('foo'); + + // args[2]: parser + expect(typeof args[2]).toBe('object'); + expect(args[2] instanceof jsdoc.src.parser.Parser).toBe(true); + + // args[3]: current source name + expect( String(args[3]) ).toBe('[[string0]]'); + }); + + it('should reflect changes made by Rhino node visitors', function() { + var doclet; + + var visitor = { + visitNode: function(rhinoNode, e, parser, sourceName) { + if (e && e.code && e.code.name === 'foo') { + e.code.name = 'bar'; + } + } + }; + + require('jsdoc/src/handlers').attachTo(parser); + parser.addNodeVisitor(visitor); + parser.parse(sourceCode); + + doclet = parser.results()[0]; + + expect(doclet).toBeDefined(); + expect(typeof doclet).toBe('object'); + expect(doclet.name).toBeDefined(); + expect(doclet.name).toBe('bar'); + }); + }); + }); +}); diff --git a/test/specs/rhino/src/visitor.js b/test/specs/rhino/src/visitor.js new file mode 100644 index 00000000..1c26ae23 --- /dev/null +++ b/test/specs/rhino/src/visitor.js @@ -0,0 +1,88 @@ +/*global beforeEach: true, describe: true, expect: true, it: true */ +describe('rhino/jsdoc/src/visitor', function() { + var runtime = require('jsdoc/util/runtime'); + var jsdoc = { + src: { + visitor: require( runtime.getModulePath('jsdoc/src/visitor') ) + } + }; + + it('should exist', function() { + expect(jsdoc.src.visitor).toBeDefined(); + expect(typeof jsdoc.src.visitor).toBe('object'); + }); + + it('should export a "Visitor" constructor', function() { + expect(jsdoc.src.visitor.Visitor).toBeDefined(); + expect(typeof jsdoc.src.visitor.Visitor).toBe('function'); + }); + + describe('Visitor', function() { + var parser; + var visitor; + + function newVisitor() { + parser = new ( require(runtime.getModulePath('jsdoc/src/parser')) ).Parser(); + visitor = new jsdoc.src.visitor.Visitor(parser); + } + + newVisitor(); + + it('should inherit from jsdoc/src/visitor', function() { + var parent = require('jsdoc/src/visitor').Visitor; + expect(visitor instanceof parent).toBe(true); + }); + + it('should have an "addRhinoNodeVisitor" method', function() { + expect(visitor.addRhinoNodeVisitor).toBeDefined(); + expect(typeof visitor.addRhinoNodeVisitor).toBe('function'); + }); + + it('should have a "getRhinoNodeVisitors" method', function() { + expect(visitor.getRhinoNodeVisitors).toBeDefined(); + expect(typeof visitor.getRhinoNodeVisitors).toBe('function'); + }); + + describe('addRhinoNodeVisitor', function() { + function visitorA() {} + function visitorB() {} + + var visitors; + + beforeEach(newVisitor); + + it('should work with a single Rhino node visitor', function() { + visitor.addRhinoNodeVisitor(visitorA); + + visitors = visitor.getRhinoNodeVisitors(); + + expect(visitors.length).toBe(1); + expect(visitors[0]).toBe(visitorA); + }); + + it('should work with multiple Rhino node visitors', function() { + visitor.addRhinoNodeVisitor(visitorA); + visitor.addRhinoNodeVisitor(visitorB); + + visitors = visitor.getRhinoNodeVisitors(); + + expect(visitors.length).toBe(2); + expect(visitors[0]).toBe(visitorA); + expect(visitors[1]).toBe(visitorB); + }); + }); + + describe('getRhinoNodeVisitors', function() { + beforeEach(newVisitor); + + it('should return an empty array by default', function() { + var visitors = visitor.getRhinoNodeVisitors(); + + expect( Array.isArray(visitors) ).toBe(true); + expect(visitors.length).toBe(0); + }); + + // other functionality is covered by the addNodeVisitors tests + }); + }); +}); diff --git a/test/specs/tags/overviewtag.js b/test/specs/tags/overviewtag.js index 6087be5b..925412a9 100644 --- a/test/specs/tags/overviewtag.js +++ b/test/specs/tags/overviewtag.js @@ -1,9 +1,10 @@ /*global describe: true, env: true, expect: true, it: true */ describe("@overview tag", function() { - var parser = require('jsdoc/src/parser'), - srcParser = new parser.Parser(), - doclets; + var runtime = require('jsdoc/util/runtime'); + var parser = require( runtime.getModulePath('jsdoc/src/parser') ); + var srcParser = new parser.Parser(); + var doclets; require('jsdoc/src/handlers').attachTo(srcParser); doclets = srcParser.parse(__dirname + '/test/fixtures/file.js');