diff --git a/jsdoc.js b/jsdoc.js index c04e2702..2fa7d654 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -34,7 +34,7 @@ load(__dirname + '/lib/rhino-shim.js'); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// - + /** Data representing the environment in which this app is running. @namespace */ @@ -44,20 +44,20 @@ env = { start: new Date(), finish: null }, - + /** The command line arguments passed into jsdoc. @type Array */ args: Array.prototype.slice.call(args, 0), - - + + /** The parsed JSON data from the configuration file. @type Object */ conf: {}, - + /** The command line arguments, parsed into a key/value hash. @type Object @@ -79,12 +79,13 @@ app = { } try { main(); } -catch(e) { - if (e.rhinoException != null) { +catch(e) { + if (e.rhinoException != null) { e.rhinoException.printStackTrace(); - } - else throw e; -} + } else { + throw e; + } +} finally { env.run.finish = new Date(); } /** Print string/s out to the console. @@ -119,7 +120,7 @@ function include(filepath) { } } -/** +/** Cause the VM running jsdoc to exit running. @param {number} [n = 0] The exit status. */ @@ -128,6 +129,33 @@ function exit(n) { java.lang.System.exit(n); } +function installPlugins(plugins, p) { + var dictionary = require('jsdoc/tag/dictionary'), + parser = p || app.jsdoc.parser; + + // allow user-defined plugins to... + for (var i = 0, leni = plugins.length; i < leni; i++) { + var plugin = require(plugins[i]); + + //...register event handlers + if (plugin.handlers) { + for (var eventName in plugin.handlers) { + parser.on(eventName, plugin.handlers[eventName]); + } + } + + //...define tags + if (plugin.defineTags) { + plugin.defineTags(dictionary); + } + + //...add a node visitor + if (plugin.nodeVisitor) { + parser.addNodeVisitor(plugin.nodeVisitor); + } + } +} + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// @@ -144,11 +172,10 @@ function main() { } }, resolver, - dictionary = require('jsdoc/tag/dictionary'), fs = require('fs'); - + env.opts = jsdoc.opts.parser.parse(env.args); - + try { env.conf = JSON.parse( fs.readFileSync( env.opts.configure || __dirname + '/conf.json' ) @@ -162,10 +189,10 @@ function main() { env.conf = JSON.parse(example); } catch(e) { - throw('Configuration file cannot be evaluated. ' + e); + throw('Configuration file cannot be evaluated. ' + e); } } - + // allow to pass arguments from configuration file if (env.conf.opts) { for (var opt in env.conf.opts) { @@ -179,47 +206,26 @@ function main() { env.opts._ = env.conf.opts._.concat( env.opts._ ); } } - + if (env.opts.query) { env.opts.query = require('common/query').toObject(env.opts.query); } - + // which version of javascript will be supported? (rhino only) if (typeof version === 'function') { version(env.conf.jsVersion || 180); } - + if (env.opts.help) { console.log( jsdoc.opts.parser.help() ); exit(0); - } - else if (env.opts.test) { + } else if (env.opts.test) { include('test/runner.js'); exit(0); } - - // allow user-defined plugins to... + if (env.conf.plugins) { - for (var i = 0, leni = env.conf.plugins.length; i < leni; i++) { - var plugin = require(env.conf.plugins[i]); - - //...register event handlers - if (plugin.handlers) { - for (var eventName in plugin.handlers) { - app.jsdoc.parser.on(eventName, plugin.handlers[eventName]); - } - } - - //...define tags - if (plugin.defineTags) { - plugin.defineTags(dictionary); - } - - //...add a node visitor - if (plugin.nodeVisitor) { - app.jsdoc.parser.addNodeVisitor(plugin.nodeVisitor); - } - } + installPlugins(env.conf.plugins); } // any source file named package.json is treated special @@ -231,25 +237,25 @@ function main() { } if (env.opts._.length > 0) { // are there any files to scan and parse? - + var includeMatch = (env.conf.source && env.conf.source.includePattern)? new RegExp(env.conf.source.includePattern) : null, excludeMatch = (env.conf.source && env.conf.source.excludePattern)? new RegExp(env.conf.source.excludePattern) : null; - + sourceFiles = app.jsdoc.scanner.scan(env.opts._, (env.opts.recurse? 10 : undefined), includeMatch, excludeMatch); require('jsdoc/src/handlers').attachTo(app.jsdoc.parser); - + docs = app.jsdoc.parser.parse(sourceFiles, env.opts.encoding); - + //The files are ALWAYS useful for the templates to have //If there is no package.json, just create an empty package var packageDocs = new (require('jsdoc/package').Package)(packageJson); packageDocs.files = sourceFiles || []; docs.push(packageDocs); - + function indexAll(docs) { var lookupTable = {}; - + docs.forEach(function(doc) { if ( !lookupTable.hasOwnProperty(doc.longname) ) { lookupTable[doc.longname] = []; @@ -258,12 +264,12 @@ function main() { }); docs.index = lookupTable; } - + indexAll(docs); - + require('jsdoc/augment').addInherited(docs); require('jsdoc/borrow').resolveBorrows(docs); - + if (env.opts.explain) { console.log(docs); exit(0); @@ -279,7 +285,7 @@ function main() { } env.opts.template = env.opts.template || 'templates/default'; - + // should define a global "publish" function include(env.opts.template + '/publish.js'); diff --git a/plugins/commentConvert.js b/plugins/commentConvert.js index e97a291a..244c3313 100644 --- a/plugins/commentConvert.js +++ b/plugins/commentConvert.js @@ -1,10 +1,10 @@ /** @overview Demonstrate how to modify the source code before the parser sees it. - @module plugins/comentConvert + @module plugins/commentConvert @author Michael Mathews */ - + exports.handlers = { /// /// Convert ///-style comments into jsdoc comments. diff --git a/plugins/escapeHtml.js b/plugins/escapeHtml.js index 7b561682..925bbede 100644 --- a/plugins/escapeHtml.js +++ b/plugins/escapeHtml.js @@ -8,6 +8,7 @@ exports.handlers = { /** Translate HTML tags in descriptions into safe entities. + Replaces <, & and newlines */ newDoclet: function(e) { if (e.doclet.description) { diff --git a/plugins/railsTemplate.js b/plugins/railsTemplate.js index cb046f99..9ca63098 100644 --- a/plugins/railsTemplate.js +++ b/plugins/railsTemplate.js @@ -6,12 +6,12 @@ exports.handlers = { - /// - /// Remove rails tags from the source input (e.g. <% foo bar %>) - /// @param e - /// @param e.filename - /// @param e.source - /// + /** + * Remove rails tags from the source input (e.g. <% foo bar %>) + * @param e + * @param e.filename + * @param e.source + */ beforeParse: function(e) { if (e.filename.match(/\.erb$/)) { e.source = e.source.replace(/<%.*%>/g, ""); diff --git a/plugins/sourcetag.js b/plugins/sourcetag.js index 7c065d1b..74656c52 100644 --- a/plugins/sourcetag.js +++ b/plugins/sourcetag.js @@ -8,35 +8,36 @@ exports.handlers = { Support @source tag. Expected value like: { "filename": "myfile.js", "lineno": 123 } Modifies the corresponding meta values on the given doclet. + @source { "filename": "sourcetag.js", "lineno": 13 } */ newDoclet: function(e) { var tags = e.doclet.tags, tag, value; - + //console.log(e.doclet); - // any user-defined tags in this doclet? + // any user-defined tags in this doclet? if (typeof tags !== 'undefined') { // only interested in the @source tags tags = tags.filter(function($) { return $.title === 'source'; }); - + if (tags.length) { // take the first one tag = tags[0]; - + try { value = JSON.parse(tag.value); } catch(e) { throw new Error('@source tag expects a valid JSON value, like { "filename": "myfile.js", "lineno": 123 }.'); } - + !e.doclet.meta && (e.doclet.meta = {}); e.doclet.meta.filename = value.filename || ''; e.doclet.meta.lineno = value.lineno || ''; - } + } } } }; \ No newline at end of file diff --git a/plugins/test/fixtures/railsTemplate.js.erb b/plugins/test/fixtures/railsTemplate.js.erb new file mode 100644 index 00000000..446aa409 --- /dev/null +++ b/plugins/test/fixtures/railsTemplate.js.erb @@ -0,0 +1,20 @@ +/** + @overview Strips the rails template tags from a js.erb file + @module plugins/railsTemplate + @author Jannon Frank + */ + + +exports.handlers = { + /** + * Remove rails tags from the source input (e.g. <% foo bar %>) + * @param e + * @param e.filename + * @param e.source + */ + beforeParse: function(e) { + if (e.filename.match(/\.erb$/)) { + e.source = e.source.replace(/<%.*%> /g, ""); + } + } +}; \ No newline at end of file diff --git a/plugins/test/specs/commentConvert.js b/plugins/test/specs/commentConvert.js new file mode 100644 index 00000000..831c4750 --- /dev/null +++ b/plugins/test/specs/commentConvert.js @@ -0,0 +1,13 @@ +describe("commentConvert plugin", function() { + var parser = new (require("jsdoc/src/parser")).Parser(), + plugin = require('plugins/commentConvert'), + docSet; + + installPlugins(['plugins/commentConvert'], parser); + docSet = jasmine.getDocSetFromFile("plugins/commentConvert.js", parser); + + it("should convert '///-style comments into jsdoc comments", function() { + var doclet = docSet.getByLongname("module:plugins/commentConvert.handlers.beforeParse"); + expect(doclet.length).toEqual(1); + }); +}); \ No newline at end of file diff --git a/plugins/test/specs/escapeHtml.js b/plugins/test/specs/escapeHtml.js new file mode 100644 index 00000000..19ef29b8 --- /dev/null +++ b/plugins/test/specs/escapeHtml.js @@ -0,0 +1,13 @@ +describe("escapeHtml plugin", function() { + var parser = new (require("jsdoc/src/parser")).Parser(), + plugin = require('plugins/escapeHtml'), + docSet; + + installPlugins(['plugins/escapeHtml'], parser); + docSet = jasmine.getDocSetFromFile("plugins/escapeHtml.js", parser); + + it("should escape '&', '<' and newlines in doclet descriptions", function() { + var doclet = docSet.getByLongname("module:plugins/escapeHtml.handlers.newDoclet"); + expect(doclet[0].description).toEqual("Translate HTML tags in descriptions into safe entities.
Replaces <, & and newlines"); + }); +}); \ No newline at end of file diff --git a/plugins/test/specs/markdown.js b/plugins/test/specs/markdown.js new file mode 100644 index 00000000..382c0d50 --- /dev/null +++ b/plugins/test/specs/markdown.js @@ -0,0 +1,22 @@ +/** + @overview Translate doclet descriptions from MarkDown into HTML. + @module plugins/markdown + @author Michael Mathews + */ + +var mdParser = require('evilstreak/markdown'); + +exports.handlers = { + /** + Translate markdown syntax in a new doclet's description into HTML. Is run + by JSDoc 3 whenever a "newDoclet" event fires. + */ + newDoclet: function(e) { + if (e.doclet.description) { + e.doclet.description = mdParser.toHTML(e.doclet.description) + .replace( /&/g, "&" ) // because markdown escapes these + .replace( /</g, "<" ) + .replace( />/g, ">" ); + } + } +}; \ No newline at end of file diff --git a/plugins/test/specs/railsTemplate.js b/plugins/test/specs/railsTemplate.js new file mode 100644 index 00000000..7541f95b --- /dev/null +++ b/plugins/test/specs/railsTemplate.js @@ -0,0 +1,15 @@ +describe("railsTemplate plugin", function() { + var parser = new (require("jsdoc/src/parser")).Parser(), + plugin = require('plugins/railsTemplate'); + + + installPlugins(['plugins/railsTemplate'], parser); + require('jsdoc/src/handlers').attachTo(parser); + + it("should remove <% %> rails template tags from the source of *.erb files", function() { + var path = require("path"), + docSet = parser.parse([path.join(__dirname, "plugins/test/fixtures/railsTemplate.js.erb")]); + + expect(docSet[2].description).toEqual("Remove rails tags from the source input (e.g. )"); + }); +}); \ No newline at end of file diff --git a/plugins/test/specs/shout.js b/plugins/test/specs/shout.js new file mode 100644 index 00000000..18e581ef --- /dev/null +++ b/plugins/test/specs/shout.js @@ -0,0 +1,13 @@ +describe("shout plugin", function() { + var parser = new (require("jsdoc/src/parser")).Parser(), + plugin = require('plugins/shout'), + docSet; + + installPlugins(['plugins/shout'], parser); + docSet = jasmine.getDocSetFromFile("plugins/shout.js", parser); + + it("should make the description uppercase", function() { + var doclet = docSet.getByLongname("module:plugins/shout.handlers.newDoclet"); + expect(doclet[0].description).toEqual("MAKE YOUR DESCRIPTIONS MORE SHOUTIER."); + }); +}); \ No newline at end of file diff --git a/plugins/test/specs/sourcetag.js b/plugins/test/specs/sourcetag.js new file mode 100644 index 00000000..d5202cc0 --- /dev/null +++ b/plugins/test/specs/sourcetag.js @@ -0,0 +1,15 @@ +describe("sourcetag plugin", function() { + var parser = new (require("jsdoc/src/parser")).Parser(), + plugin = require('plugins/sourcetag'), + docSet; + + installPlugins(['plugins/sourcetag'], parser); + docSet = jasmine.getDocSetFromFile("plugins/sourcetag.js", parser); + + it("should set the lineno and filename of the doclet's meta property", function() { + var doclet = docSet.getByLongname("module:plugins/sourcetag.handlers.newDoclet"); + expect(doclet[0].meta).toBeDefined(); + expect(doclet[0].meta.filename).toEqual("sourcetag.js"); + expect(doclet[0].meta.lineno).toEqual(13); + }); +}); \ No newline at end of file diff --git a/test/fixtures/plugins.js b/test/fixtures/plugins.js new file mode 100644 index 00000000..d71e6e9d --- /dev/null +++ b/test/fixtures/plugins.js @@ -0,0 +1,10 @@ +/** + * @name virtual + */ + +var foo = "bar"; + +/** + * @foo bar + */ +var test = "tada"; \ No newline at end of file diff --git a/test/fixtures/testPlugin1.js b/test/fixtures/testPlugin1.js new file mode 100644 index 00000000..9436152c --- /dev/null +++ b/test/fixtures/testPlugin1.js @@ -0,0 +1,23 @@ +exports.handlers = { + fileBegin: jasmine.createSpy('fileBegin'), + beforeParse: jasmine.createSpy('beforeParse'), + jsdocCommentFound: jasmine.createSpy('jsdocCommentFound'), + symbolFound: jasmine.createSpy('symbolFound'), + newDoclet: jasmine.createSpy('newDoclet'), + fileComplete: jasmine.createSpy('fileComplete') +}; + +exports.defineTags = function(dictionary) { + dictionary.defineTag("foo", { + canHaveName: true, + onTagged: function(doclet, tag) { + doclet.foo = true; + } + }); +}; + +exports.nodeVisitor = { + visitNode: jasmine.createSpy("plugin 1 visitNode").andCallFake(function(node, e, parser, currentSourceName) { + e.stopPropagation = true; + }) +}; \ No newline at end of file diff --git a/test/fixtures/testPlugin2.js b/test/fixtures/testPlugin2.js new file mode 100644 index 00000000..475999f7 --- /dev/null +++ b/test/fixtures/testPlugin2.js @@ -0,0 +1,12 @@ +exports.handlers = { + fileBegin: jasmine.createSpy('fileBegin'), + beforeParse: jasmine.createSpy('beforeParse'), + jsdocCommentFound: jasmine.createSpy('jsdocCommentFound'), + symbolFound: jasmine.createSpy('symbolFound'), + newDoclet: jasmine.createSpy('newDoclet'), + fileComplete: jasmine.createSpy('fileComplete') +}; + +exports.nodeVisitor = { + visitNode: jasmine.createSpy("plugin 2 visitNode") +}; \ No newline at end of file diff --git a/test/specs/helpers.js b/test/specs/helpers.js index 3f6742ab..6d750756 100644 --- a/test/specs/helpers.js +++ b/test/specs/helpers.js @@ -1,9 +1,8 @@ -exports.getDocSetFromFile = function(filename) { +exports.getDocSetFromFile = function(filename, parser) { var sourceCode = readFile(__dirname + '/' + filename), - testParser, + testParser = parser || new (require('jsdoc/src/parser')).Parser(), doclets; - testParser = new (require('jsdoc/src/parser')).Parser(); require('jsdoc/src/handlers').attachTo(testParser); doclets = testParser.parse('javascript:' + sourceCode); diff --git a/test/specs/jsdoc/plugins.js b/test/specs/jsdoc/plugins.js new file mode 100644 index 00000000..420d47c8 --- /dev/null +++ b/test/specs/jsdoc/plugins.js @@ -0,0 +1,40 @@ +describe("plugins", function() { + installPlugins(['test/fixtures/testPlugin1', 'test/fixtures/testPlugin2']); + + var plugin1 = require('test/fixtures/testPlugin1'), + plugin2 = require('test/fixtures/testPlugin2'), + docSet; + + docSet = jasmine.getDocSetFromFile("test/fixtures/plugins.js", app.jsdoc.parser); + + it("should fire the plugin's event handlers", function() { + expect(plugin1.handlers.fileBegin).toHaveBeenCalled(); + expect(plugin1.handlers.beforeParse).toHaveBeenCalled(); + expect(plugin1.handlers.jsdocCommentFound).toHaveBeenCalled(); + expect(plugin1.handlers.symbolFound).toHaveBeenCalled(); + expect(plugin1.handlers.newDoclet).toHaveBeenCalled(); + expect(plugin1.handlers.fileComplete).toHaveBeenCalled(); + + expect(plugin2.handlers.fileBegin).toHaveBeenCalled(); + expect(plugin2.handlers.beforeParse).toHaveBeenCalled(); + expect(plugin2.handlers.jsdocCommentFound).toHaveBeenCalled(); + expect(plugin2.handlers.symbolFound).toHaveBeenCalled(); + expect(plugin2.handlers.newDoclet).toHaveBeenCalled(); + expect(plugin2.handlers.fileComplete).toHaveBeenCalled(); + }); + + it("should add the plugin's tag definitions to the dictionary", function() { + var test = docSet.getByLongname("test"); + + expect(test[0].longname).toEqual("test"); + expect(test[0].foo).toEqual(true); + }); + + it("should call the plugin's visitNode function", function() { + expect(plugin1.nodeVisitor.visitNode).toHaveBeenCalled(); + }); + + it("should not call a second plugin's visitNode function if the first stopped propagation", function() { + expect(plugin2.nodeVisitor.visitNode).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file