checking in jsdoc dir so people can generate docs themselves

This commit is contained in:
Eric Rowell
2014-01-12 00:39:08 -08:00
parent 98b282b819
commit 8dd503c7bd
376 changed files with 24397 additions and 2 deletions

View File

@@ -0,0 +1,389 @@
Creating and Enabling a Plugin
----
There are two steps required to create and enable a new JSDoc plugin:
1. Create a JavaScript module to contain your plugin code.
2. Include that module in the "plugins" array of `conf.json`. You can specify
an absolute or relative path. If you use a relative path, JSDoc searches for
the plugin in the current working directory and the JSDoc directory, in that
order.
For example, if your plugin source code was saved in the "plugins/shout.js"
file in the current working directory, you would include it by adding a
reference to it in conf.json like so:
...
"plugins": [
"plugins/shout"
]
...
Authoring JSDoc 3 Plugins
----
The plugin system for JSDoc 3 is pretty powerful and provides plugin authors
multiple methods, from high-level to low-level, of affecting document generation:
- Defining event handlers
- Defining tags
- Defining a parse tree node processor
### Event Handlers
At the highest level, a plugin may register handlers for specific named-events
that occur in the documentation generation process. JSDoc will pass the handler
an event object containing pertinent information. Your plugin module should
export a _handlers_ object that contains your handler, like so:
exports.handlers = {
newDoclet: function(e) {
//Do something when we see a new doclet
}
}
#### Event: fileBegin
This is triggered when the parser has started on a new file. You might use this
to do any per-file initialization your plugin needs to do.
The event object will contain the following properties:
- filename: the name of the file
#### Event: beforeParse
This is triggered before parsing has begun. You can use this method to modify
the source code that will be parsed. For instance, you might add some virtual
doclets so they get added to the documentation.
The event object will contain the following properties:
- filename: the name of the file
- source: the contents of the file
Below is an example that adds a virtual doclet for a function to the source so
that it will get parsed and added to the documentation. This might be done to
document methods that will be present for end-user use, but might not be in the
source code being documented, like methods provided by a third-party superclass:
exports.handlers = {
beforeParse: function(e) {
var extraDoc = ["",
"/**",
"Here's a description of this function",
"@name superFunc",
"@memberof ui.mywidget",
"@function",
"*/", ""];
e.source += extraDoc.join("\n");
}
}
#### Event: jsdocCommentFound
This is fired whenever a jsdoc comment is found. It may or may not be associated
with any code. You might use this to modify the contents of a comment before it
is processed.
The event object will contain the following properties:
- filename: the name of the file
- comment: the text of the comment
- lineno: the line number the comment was found on
#### Event: symbolFound
This is fired when the parser comes across a symbol in the code it thinks is
important. This usually means things that one might want to document --
variables, functions, object literals, object property definitions,
assignments, etc., but the symbols the parser finds can be modified by a plugin
(see "Node Visitors" below).
The event object will contain the following properties:
- filename: the name of the file
- comment: the comment associated with the symbol, if any
- id: the unique id of the symbol
- lineno: the line number the symbols was found on
- range: an array containing the first and last characters of the code
associated with the symbol
- astnode: the node of the parse tree
- code: information about the code. This usually contains "name", "type", and
"node" properties and might also have "value", "paramnames", or "funcscope"
properties depending on the symbol.
#### Event: newDoclet
This is the highest level event and is fired when a new doclet has been created.
This means that a jsdoc or a symbol has been processed and the actual doclet
that will be passed to the template has been created.
The event object will contain the following properties:
- doclet: the new doclet that was created
The properties of the doclet can vary depending on the comment or symbol used to
create it. Additionally, tag definitions (See "Tag Definitions" below) can
modify the doclet. Some common properties you're likely to see include:
- comment: the text of the comment (may be empty if symbol is undocumented)
- meta: some information about the doclet, like filename, line number, etc.
- description
- kind
- name
- longname: the fully qualified name, including memberof info
- memberof: the function/class/namespace that this is a member of
- scope: (global|static|instance|inner)
- undocumented: true if the symbol didn't have a jsdoc comment
- defaultvalue: the specified default value for a property/variable
- type: the specified type of parameter/property/function return (e.g. Boolean)
- params: an object containing the list of parameters to a function
- tags: an object containing the set of tags not handled by the parser (note:
this is only available if ```allowUnknownTags``` is set to true in the conf.json
file for JSDoc3)
Below is an example of a newDoclet handler that shouts the descriptions:
exports.handlers = {
newDoclet: function(e) {
// e.doclet will refer to the newly created doclet
// you can read and modify properties of that doclet if you wish
if (typeof e.doclet.description === 'string') {
e.doclet.description = e.doclet.description.toUpperCase();
}
}
};
#### Event: fileComplete
This is fired when the parser is done with a file. You might use this to
perform some cleanup for your plugin.
The event object will contain the following properties:
- filename: the name of the file
- source: the contents of the file
### Tag Definitions
Adding tags to the tag dictionary is a mid-level way to affect documentation
generation. Before a newDoclet event is triggered, jsdoc comment blocks are
parsed to determine the description and any jsdoc tags that may be present. When
a tag is found, if it has been defined in the tag dictionary, it is given a
chance to modify the doclet.
Plugins can define tags by exporting a _defineTags_ function. That function will
be passed a dictionary that can be used to define tags, like so:
exports.defineTags = function(dictionary) {
//define tags here
}
#### The Dictionary
The dictionary provides the following methods:
1. defineTag(title, opts)
Used to define tags.
The first parameter is the name of the tag (e.g. "param" or "overview"). the
second is an object containing options for the tag. The options can be the
following:
- mustHaveValue (Boolean): whether or not the tag must have a value
(e.g "@name TheName")
- mustNotHaveValue (Boolean): whether or not the tag must not have a value
- canHaveType (Boolean): Whether or not the tag can have a type
(e.g. "@param **{String}** name the description of name")
- canHaveName (Boolean): Whether or not the tag can have a name
(e.g. "@param {String} **name** the description of name")
- isNamespace (Boolean): Whether or not the tag marks a doclet as representing
a namespace. The "@module" tag, for instance, sets this to true.
- onTagged (Function): A callback function executed when the tag is found. The
function is passed two parameters: the doclet and the tag. Here's an example:
dictionary.defineTag('instance', {
onTagged: function(doclet, tag) {
doclet.scope = "instance";
}
});
The defineTag method returns a Tag. The Tag object has a method "synonym"
that can be used to declare synonyms to the tag. For example:
dictionary.defineTag('exception', {
<options for exception tag>
})
.synonym('throws');
2. lookUp(title)
Used to lookup a tag. Returns either the tag or false if it's not defined
3. isNamespace(kind)
Used to determine if a particular doclet type represents a namespace
4. normalise(title)
Used to find the canonical name of a tag. The name passed in might be that
name or a synonym
### Node Visitors
At the lowest level, plugin authors can process each node in the parse tree by
defining a node visitor that will visit each node, creating an opportunity to
do things like modify comments and trigger parser events for any arbitrary piece
of code.
Plugins can define a node visitor by exporting a ```nodeVisitor``` object that
contains a ```visitNode``` function, like so:
exports.nodeVisitor = {
visitNode: function(node, e, parser, currentSourceName) {
//do all sorts of crazy things here
}
}
The function is called on each node with the following parameters:
- node: the node of the parse tree
- e: the event. If the node is one that the parser handles, this will already
be populated with the same things described in the _symbolFound_ event above.
Otherwise, it will be an empty object on which to set various properties.
- parser: the parser
- currentSourceName: the name of the file being parsed
#### Making things happen
The primary reasons to implement a node visitor are to be able to document
things that aren't normally documented (like function calls that create classes)
or to auto generate documentation for code that isn't documented. For instance,
a plugin might look for calls to a "_trigger" method since it knows that means
an event is fired and then generate documentation for the event.
To make things happen, the ```visitNode``` function should modify properties
of the event parameter. In general the goal is to construct a comment and then
get an event to fire. After the parser lets all of the node visitors have a
look at the node, it looks to see if the event object has a ```comment```
property and an ```event``` property. If it has both, the event named in the event
property is fired. The event is usually "symbolFound" or "jsdocCommentFound",
but theoretically, a plugin could define its own events and handle them.
#### Example
Below is an example of what a plugin for documenting jQuery UI widgets might do.
jQuery UI uses a factory function call to create widget classes. The plugin
looks for that function call and creates a symbol with documentation. It also
looks for any "this._trigger" function calls and automatically creates
documentation for the events that are triggered:
exports.nodeVisitor = {
visitNode: function(node, e, parser, currentSourceName) {
if (node.type === Token.OBJECTLIT && node.parent && node.parent.type === Token.CALL && isInWidgetFactory(node, 1)) {
var widgetName = node.parent.arguments.get(0).toSource();
e.id = 'astnode' + node.hashCode(); // the id of the object literal node
e.comment = String(node.parent.jsDoc||'');
e.lineno = node.parent.getLineno();
e.filename = currentSourceName;
e.astnode = node;
e.code = {
name: "" + widgetName.substring(1, widgetName.length() - 1),
type: "class",
node: node
};
e.event = "symbolFound";
e.finishers = [parser.addDocletRef];
addCommentTag(e, "param", "{Object=} options A set of configuration options");
}
else if(isTriggerCall(node)) {
var nameNode = node.arguments.get(0);
eventName = String((nameNode.type == Token.STRING) ? nameNode.value : nameNode.toSource()),
func = {},
comment = "@event\n",
eventKey = "";
if (node.enclosingFunction) {
func.id = 'astnode'+node.enclosingFunction.hashCode();
func.doclet = parser.refs[func.id];
}
if(func.doclet) {
func.doclet.addTag("fires", eventName);
if (func.doclet.memberof) {
eventKey = func.doclet.memberof + "#event:" + eventName;
comment += "@name " + func.doclet.memberof + "#" + eventName;
}
}
e.comment = comment;
e.lineno = node.getLineno();
e.filename = currentSourceName;
e.event = "jsdocCommentFound";
}
}
};
function isTriggerCall(node) {
if(node.type != Token.CALL) { return false; }
var target = node.getTarget(),
left = target && target.left && String(target.left.toSource()),
right = target && target.right && String(target.right.toSource());
return (left === "this" && right === "_trigger");
}
function isInWidgetFactory(node, depth) {
var parent = node.parent,
d = 0;
while(parent && (!depth || d < depth)) {
if (parent.type === Token.CALL) {
var target = parent.getTarget(),
left = target && target.left && String(target.left.toSource()),
right = target && target.right && String(target.right.toSource());
return ((left === "$" || left === "jQuery") && right === "widget");
} else {
parent = parent.parent;
d++;
}
}
return false;
}
You'll notice a "finishers" property set. The finishers property should contain
an array of functions to be called after the event is fired and all the handlers
have processed it. The parser provides an ```addDocletRef``` function that adds the
doclet to the map (keyed off of the id property) of doclets it knows about.
Lastly, the visitors are executed in the order the plugins are listed in the
conf.json file. A plugin can stop later plugins from visiting a node by
setting a ```stopPropagation``` property on the event object (e.stopPropagation = true).
A plugin can stop the event from firing setting a ```preventDefault``` property.
### Throwing Errors
If you wish your plugin to throw an error, do it using the `handle` function in
the `jsdoc/util/error` module:
require('jsdoc/util/error').handle( new Error('I do not like green eggs and ham!') );
By default, this will throw the error, halting the execution of JSDoc. However,
if the user enabled JSDoc's `--lenient` switch, JSDoc will simply log the error
to the console and continue.
Packaging JSDoc 3 Plugins
----
The JSDoc 3 Jakefile has an ```install``` task that can be used to install a
plugin into the JSDoc directory. So running the following will install the
plugin:
$>jake install[path/to/YourPluginFolder]
**Note**: On some operating systems, including OS X, you may need to quote the
target name and parameters:
$>jake 'install[path/to/YourPluginFolder]'
The task is passed a directory that should look something like the following:
YourPluginFolder
|- plugins
| |- YourPlugin.js
| \- test
| |- fixtures
| | \- YourFixtures.js
| \- specs
| \- YourTests.js
\- templates
\- YourTemplate
\- publish.js

View File

@@ -0,0 +1,21 @@
/**
@overview Demonstrate how to modify the source code before the parser sees it.
@module plugins/commentConvert
@author Michael Mathews <micmath@gmail.com>
*/
exports.handlers = {
///
/// Convert ///-style comments into jsdoc comments.
/// @param e
/// @param e.filename
/// @param e.source
///
beforeParse: function(e) {
e.source = e.source.replace(/(\n[ \t]*\/\/\/[^\n]*)+/g, function($) {
var replacement = '\n/**' + $.replace(/^[ \t]*\/\/\//mg, '').replace(/(\n$|$)/, '*/$1');
return replacement;
});
}
};

View File

@@ -0,0 +1,17 @@
/**
* @overview Remove everything in a file except JSDoc-style comments. By enabling this plugin, you
* can document source files that are not valid JavaScript (including source files for other
* languages).
* @module plugins/commentsOnly
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
*/
exports.handlers = {
beforeParse: function(e) {
// a JSDoc comment looks like: /**[one or more chars]*/
var comments = e.source.match(/\/\*\*[\s\S]+?\*\//g);
if (comments) {
e.source = comments.join('\n\n');
}
}
};

View File

@@ -0,0 +1,21 @@
/**
@overview Escape HTML tags in descriptions.
@module plugins/escapeHtml
@author Michael Mathews <micmath@gmail.com>
*/
exports.handlers = {
/**
Translate HTML tags in descriptions into safe entities.
Replaces <, & and newlines
*/
newDoclet: function(e) {
if (e.doclet.description) {
e.doclet.description = e.doclet.description
.replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/\r\n|\n|\r/g, '<br>');
}
}
};

View File

@@ -0,0 +1,69 @@
/*global env: true */
/**
* @overview Dump information about parser events to the console.
* @module plugins/eventDumper
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
*/
var _ = require('underscore');
var util = require('util');
var conf = env.conf.eventDumper || {};
// Dump the included parser events (defaults to all events)
var events = conf.include || [
'parseBegin',
'fileBegin',
'beforeParse',
'jsdocCommentFound',
'symbolFound',
'newDoclet',
'fileComplete',
'parseComplete'
];
// Don't dump the excluded parser events
if (conf.exclude) {
events = _.difference(events, conf.exclude);
}
/**
* Get rid of native Java crud in an event object so that JSON.stringify() works.
* @param {object} e The event object.
* @return {object} The fixed-up object.
*/
function cleanse(e) {
var result = {};
Object.keys(e).forEach(function(prop) {
// by default, don't stringify properties that contain an array of functions
if (!conf.includeFunctions && util.isArray(e[prop]) && e[prop][0] &&
String(typeof e[prop][0]) === 'function') {
result[prop] = 'function[' + e[prop].length + ']';
}
// never include functions that belong to the object
else if (typeof e[prop] === 'function') {
// do nothing
}
// go down an extra level for these
else if (['code', 'doclet', 'meta'].indexOf(prop) !== -1) {
result[prop] = cleanse(e[prop]);
}
else {
result[prop] = String(e[prop]);
}
});
return result;
}
exports.handlers = {};
events.forEach(function(eventType) {
exports.handlers[eventType] = function(e) {
console.log( JSON.stringify({
type: eventType,
content: cleanse(e)
}, null, 4) );
};
});

View File

@@ -0,0 +1,67 @@
/*global env: true */
/**
* @overview Translate doclet descriptions from MarkDown into HTML.
* @module plugins/markdown
* @author Michael Mathews <micmath@gmail.com>
* @author Ben Blank <ben.blank@gmail.com>
*/
var conf = env.conf.markdown;
var defaultTags = [ "classdesc", "description", "params", "properties", "returns", "see"];
var hasOwnProp = Object.prototype.hasOwnProperty;
var parse = require('jsdoc/util/markdown').getParser();
var tags = [];
var excludeTags = [];
/**
* Process the markdown source in a doclet. The properties that should be
* processed are configurable, but always include "classdesc", "description",
* "params", "properties", and "returns". Handled properties can be bare
* strings, objects, or arrays of objects.
*/
function process(doclet) {
tags.forEach(function(tag) {
if ( !hasOwnProp.call(doclet, tag) ) {
return;
}
if (typeof doclet[tag] === "string" &&
(tag != 'see' ||
// treat '@see' specially, since we only want to process @see text that contains links
(tag == 'see' && doclet[tag].indexOf('[') != -1))) {
doclet[tag] = parse(doclet[tag]);
} else if (doclet[tag] instanceof Array) {
doclet[tag].forEach(function(value, index, original){
var inner = {};
inner[tag] = value;
process(inner);
original[index] = inner[tag];
});
} else if (doclet[tag]) {
process(doclet[tag]);
}
});
}
// set up the list of "tags" (properties) to process
if (conf && conf.tags) {
tags = conf.tags.slice();
}
// set up the list of default tags to exclude from processing
if (conf && conf.excludeTags) {
excludeTags = conf.excludeTags.slice();
}
defaultTags.forEach(function(tag) {
if (excludeTags.indexOf(tag) === -1 && tags.indexOf(tag) === -1) {
tags.push(tag);
}
});
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) {
process(e.doclet);
}
};

View File

@@ -0,0 +1,86 @@
# How to use the Markdown plugin
For most users, all you need to do is add the plugin to your JSDoc configuration (`conf.json`) as you would any other, by adding a reference to the plugin in the `"plugins"` entry:
...
"plugins": [ "plugins/markdown" ]
...
This will cause Markdown in `@description` tags (including implicit descriptions without tags), `@classdesc` tags, `@param` tags, `@property` tags, and `@returns` tags to be parsed.
Also, be sure to use leading asterisks in your doc comments! If you omit the leading asterisks, JSDoc's code parser may remove other asterisks that are used for Markdown formatting.
# Configuring the Markdown plugin
The plugin also offers several configuration options for advanced users who want GitHub integration, extended tag support, etc. All configuration for the Markdown plugin should be added to a `"markdown"` property in your JSDoc configuration:
...
"plugins": [ "plugins/markdown" ],
"markdown": {
"opt1": "value",
"opt2": [ "foo", "bar", "baz" ]
}
...
## Choosing a parser
The plugin currently supports two Markdown parsers. You can select which parser to use by adding a `"parser"` property to your Markdown configuration:
...
"plugins": [ "plugins/markdown" ],
"markdown": {
"parser": "gfm"
}
...
### Dominic "evilstreak" Baggott's markdown-js
The default parser is Dominic Baggott's excellent [markdown-js](https://github.com/evilstreak/markdown-js). It can be explicitly selected by setting the `parser` to `evilstreak` and has one additional (and optional) configuration option, `dialect`, which can be used to select which of markdown-js' built-in dialects to use. If omitted, markdown-js' default dialect will be used.
...
"plugins": [ "plugins/markdown" ],
"markdown": {
"parser": "evilstreak",
"dialect": "Maruku"
}
...
### GitHib Flavored Markdown
The alternative parser is the modified Showdown parser supplied by GitHub for their [GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/). GFM provides several enhancements to standard Markdown syntax (see its documentation) intended to be useful to developers. It *also* has the ability to quickly link to GitHub repositories, files, and issues. It can be selected by setting the `parser` to `gfm` and supports three additional (and optional) configuration options.
The `hardwrap` option controls the hard wrapping of line ends. Unlike standard Markdown, GFM considers a single newline to indicate a "hard break" in the paragraph, but this doesn't work well with the line length limitations commonly used with comment documentation, so is disabled by default. If you want to turn hard wrapping back on, set `hardwrap` to `true` (or any non-falsy value).
The `githubRepoName` and `githubRepoOwner` indicate which GitHub repo should be used for GitHub links that do not fully specify a repo. These options have no effect unless used together, and if they are omitted, several of GFM's default link types will be unavailable. Conversely, if you supply both `github*` options but do not explicitly select `gfm` as your parser, it will be automatically selected for you.
...
"plugins": [ "plugins/markdown" ],
"markdown": {
"parser": "gfm",
"hardwrap": true
}
...
### Why two parsers?
The "evilstreak" parser is flexible, extensible, currently maintained, and was the only parser available in earlier versions of the Markdown plugin, but doesn't support the useful GFM extensions. The "gfm" parser is based on the no-longer-maintained Showdown parser, but it provides GFM extensions.
In the future, if GFM support is made available for the "evilstreak" parser, this plugin will drop the "gfm" parser in favor of that support.
## Extended tag support
While the Markdown plugin already supports JSDoc's default tags, if you're using other plugins, you may well have extra tags available. You can tell the Markdown plugin to handle those extra tags as well using the `tags` property, which is an array of the tags* it should check in addition to the default set.
...
"plugins": [ "plugins/markdown" ],
"markdown": {
"tags": [ "foo", "bars", "bazzes" ]
}
...
\* Because the Markdown plugin works with JSDoc's internal representation rather than with the source comments, the names you need to enter in the `tags` property aren't necessarily the same as the actual tag names. For example, in the default set of tags, `@param` is stored under `params`. If you are having trouble getting the Markdown plugin to work with your extra tags, either take a peek at the output of JSDoc's `--explain` command-line parameter (which displays the internal state that plugins work with) or ask the plugin author which "doclet properties" their plugin uses. The Markdown plugin supports strings, arrays, and objects/subdoclets.

View File

@@ -0,0 +1,184 @@
/**
* The Overload Helper plugin automatically adds a signature-like string to the longnames of
* overloaded functions and methods. In JSDoc, this string is known as a _variation_. (The longnames
* of overloaded constructor functions are _not_ updated, so that JSDoc can identify the class'
* members correctly.)
*
* Using this plugin allows you to link to overloaded functions without manually adding `@variation`
* tags to your documentation.
*
* For example, suppose your code includes a function named `foo` that you can call in the
* following ways:
*
* + `foo()`
* + `foo(bar)`
* + `foo(bar, baz)` (where `baz` is repeatable)
*
* This plugin assigns the following variations and longnames to each version of `foo`:
*
* + `foo()` gets the variation `()` and the longname `foo()`.
* + `foo(bar)` gets the variation `(bar)` and the longname `foo(bar)`.
* + `foo(bar, baz)` (where `baz` is repeatable) gets the variation `(bar, ...baz)` and the longname
* `foo(bar, ...baz)`.
*
* You can then link to these functions with `{@link foo()}`, `{@link foo(bar)}`, and
* `{@link foo(bar, ...baz)`. Note that the variation is based on the names of the function
* parameters, _not_ their types.
*
* If you prefer to manually assign variations to certain functions, you can still do so with the
* `@variation` tag. This plugin will not change these variations or add more variations for that
* function, as long as the variations you've defined result in unique longnames.
*
* If an overloaded function includes multiple signatures with the same parameter names, the plugin
* will assign numeric variations instead, starting at `(1)` and counting upwards.
*
* @module plugins/overloadHelper
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
* @license Apache License 2.0
*/
// lookup table of function doclets by longname
var functionDoclets;
function hasUniqueValues(obj) {
var isUnique = true;
var seen = [];
Object.keys(obj).forEach(function(key) {
if (seen.indexOf(obj[key]) !== -1) {
isUnique = false;
}
seen.push(obj[key]);
});
return isUnique;
}
function getParamNames(params) {
var names = [];
params.forEach(function(param) {
var name = param.name || '';
if (param.variable) {
name = '...' + name;
}
if (name !== '') {
names.push(name);
}
});
return names.length ? names.join(', ') : '';
}
function getParamVariation(doclet) {
return getParamNames(doclet.params || []);
}
function getUniqueVariations(doclets) {
var counter = 0;
var variations = {};
var docletKeys = Object.keys(doclets);
function getUniqueNumbers() {
var format = require('util').format;
docletKeys.forEach(function(doclet) {
var newLongname;
while (true) {
counter++;
variations[doclet] = String(counter);
// is this longname + variation unique?
newLongname = format('%s(%s)', doclets[doclet].longname, variations[doclet]);
if ( !functionDoclets[newLongname] ) {
break;
}
}
});
}
function getUniqueNames() {
// start by trying to preserve existing variations
docletKeys.forEach(function(doclet) {
variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
});
// if they're identical, try again, without preserving existing variations
if ( !hasUniqueValues(variations) ) {
docletKeys.forEach(function(doclet) {
variations[doclet] = getParamVariation(doclets[doclet]);
});
// if they're STILL identical, switch to numeric variations
if ( !hasUniqueValues(variations) ) {
getUniqueNumbers();
}
}
}
// are we already using numeric variations? if so, keep doing that
if (functionDoclets[doclets.newDoclet.longname + '(1)']) {
getUniqueNumbers();
}
else {
getUniqueNames();
}
return variations;
}
function ensureUniqueLongname(newDoclet) {
var doclets = {
oldDoclet: functionDoclets[newDoclet.longname],
newDoclet: newDoclet
};
var docletKeys = Object.keys(doclets);
var oldDocletLongname;
var variations = {};
if (doclets.oldDoclet) {
oldDocletLongname = doclets.oldDoclet.longname;
// if the shared longname has a variation, like MyClass#myLongname(variation),
// remove the variation
if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
docletKeys.forEach(function(doclet) {
doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
doclets[doclet].variation = null;
});
}
variations = getUniqueVariations(doclets);
// update the longnames/variations
docletKeys.forEach(function(doclet) {
doclets[doclet].longname += '(' + variations[doclet] + ')';
doclets[doclet].variation = variations[doclet];
});
// update the old doclet in the lookup table
functionDoclets[oldDocletLongname] = null;
functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
}
// always store the new doclet in the lookup table
functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
return doclets.newDoclet;
}
exports.handlers = {
parseBegin: function() {
functionDoclets = {};
},
newDoclet: function(e) {
if (e.doclet.kind === 'function') {
e.doclet = ensureUniqueLongname(e.doclet);
}
},
parseComplete: function() {
functionDoclets = null;
}
};

View File

@@ -0,0 +1,31 @@
/*global env: true */
/**
@overview Adds support for reusable partial jsdoc files.
@module plugins/partial
@author Ludo Antonov <ludo@hulu.com>
*/
var fs = require('jsdoc/fs');
var path = require('path');
exports.handlers = {
///
/// Include a partial jsdoc
/// @param e
/// @param e.filename
/// @param e.source
///
/// @example
/// @partial "partial_doc.jsdoc"
///
beforeParse: function(e) {
e.source = e.source.replace(/(@partial \".*\")+/g, function($) {
var pathArg = $.match(/\".*\"/)[0].replace(/"/g,'');
var fullPath = path.join(e.filename , '..', pathArg);
var partialData = fs.readFileSync(fullPath, env.opts.encoding);
return partialData;
});
}
};

View File

@@ -0,0 +1,20 @@
/**
@overview Strips the rails template tags from a js.erb file
@module plugins/railsTemplate
@author Jannon Frank <jannon@jannon.net>
*/
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, "");
}
}
};

View File

@@ -0,0 +1,16 @@
/**
@overview This is just an example.
@module plugins/shout
@author Michael Mathews <micmath@gmail.com>
*/
exports.handlers = {
/**
Make your descriptions more shoutier.
*/
newDoclet: function(e) {
if (typeof e.doclet.description === 'string') {
e.doclet.description = e.doclet.description.toUpperCase();
}
}
};

View File

@@ -0,0 +1,43 @@
/**
@module plugins/sourcetag
@author Michael Mathews <micmath@gmail.com>
*/
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?
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 || '';
}
}
}
};

View File

@@ -0,0 +1,50 @@
/**
* A bowl of non-spicy soup.
* @class
*//**
* A bowl of spicy soup.
* @class
* @param {number} spiciness - The spiciness of the soup, in Scoville heat units (SHU).
*/
function Soup(spiciness) {}
/**
* Slurp the soup.
*//**
* Slurp the soup loudly.
* @param {number} dBA - The slurping volume, in A-weighted decibels.
*/
Soup.prototype.slurp = function(dBA) {};
/**
* Salt the soup as needed, using a highly optimized soup-salting heuristic.
*//**
* Salt the soup, specifying the amount of salt to add.
* @variation mg
* @param {number} amount - The amount of salt to add, in milligrams.
*/
Soup.prototype.salt = function(amount) {};
/**
* Heat the soup by the specified number of degrees.
* @param {number} degrees - The number of degrees, in Fahrenheit, by which to heat the soup.
*//**
* Heat the soup by the specified number of degrees.
* @variation 1
* @param {string} degrees - The number of degrees, in Fahrenheit, by which to heat the soup, but
* as a string for some reason.
*//**
* Heat the soup by the specified number of degrees.
* @param {boolean} degrees - The number of degrees, as a boolean. Wait, what?
*/
Soup.prototype.heat = function(degrees) {};
/**
* Discard the soup.
* @variation discardSoup
*//**
* Discard the soup by pouring it into the specified container.
* @variation discardSoup
* @param {Object} container - The container in which to discard the soup.
*/
Soup.prototype.discard = function(container) {};

View File

@@ -0,0 +1,20 @@
/**
@overview Strips the rails template tags from a js.erb file
@module plugins/railsTemplate
@author Jannon Frank <jannon@jannon.net>
*/
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, "");
}
}
};

View File

@@ -0,0 +1,12 @@
/**
* @see [Nowhere](http://nowhere.com)
*/
function foo() {
}
/**
* @see AnObject#myProperty
*/
function bar() {
}

View File

@@ -0,0 +1,14 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe("commentConvert plugin", function() {
var parser = new (require("jsdoc/src/parser")).Parser(),
plugin = require('plugins/commentConvert'),
docSet;
require('jsdoc/plugins').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);
});
});

View File

@@ -0,0 +1,14 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe("escapeHtml plugin", function() {
var parser = new (require("jsdoc/src/parser")).Parser(),
plugin = require('plugins/escapeHtml'),
docSet;
require('jsdoc/plugins').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.<br> Replaces &lt;, &amp; and newlines");
});
});

View File

@@ -0,0 +1,22 @@
describe("markdown plugin", function() {
//TODO
});
describe("markdown see tag support", function() {
var plugin = require('plugins/markdown'),
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/seetag-markdown.js'),
foo = docSet.getByLongname('foo')[0],
bar = docSet.getByLongname('bar')[0];
it ('should parse @see tags containing links', function() {
plugin.handlers.newDoclet({doclet:foo});
expect(typeof foo).toEqual('object');
expect(foo.see[0]).toEqual('<p><a href="http://nowhere.com">Nowhere</a></p>');
})
it ('should not parse @see tags that do not contain links', function() {
plugin.handlers.newDoclet({doclet:bar});
expect(typeof bar).toEqual('object');
expect(bar.see[0]).toEqual('AnObject#myProperty');
})
});

View File

@@ -0,0 +1,96 @@
/*global describe: true, expect: true, it: true, jasmine: true, xit: true */
describe('plugins/overloadHelper', function() {
var parser = new (require('jsdoc/src/parser')).Parser();
var plugin = require('plugins/overloadHelper');
var docSet;
require('jsdoc/plugins').installPlugins(['plugins/overloadHelper'], parser);
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/overloadHelper.js', parser);
it('should exist', function() {
expect(plugin).toBeDefined();
expect(typeof plugin).toBe('object');
});
it('should export handlers', function() {
expect(plugin.handlers).toBeDefined();
expect(typeof plugin.handlers).toBe('object');
});
it('should export a "newDoclet" handler', function() {
expect(plugin.handlers.newDoclet).toBeDefined();
expect(typeof plugin.handlers.newDoclet).toBe('function');
});
it('should export a "parseComplete" handler', function() {
expect(plugin.handlers.parseComplete).toBeDefined();
expect(typeof plugin.handlers.parseComplete).toBe('function');
});
describe('newDoclet handler', function() {
it('should not add unique longnames to constructors', function() {
var soup = docSet.getByLongname('Soup');
var soup1 = docSet.getByLongname('Soup()');
var soup2 = docSet.getByLongname('Soup(spiciness)');
expect(soup.length).toBe(2);
expect(soup1.length).toBe(0);
expect(soup2.length).toBe(0);
});
it('should add unique longnames to methods', function() {
var slurp = docSet.getByLongname('Soup#slurp');
var slurp1 = docSet.getByLongname('Soup#slurp()');
var slurp2 = docSet.getByLongname('Soup#slurp(dBA)');
expect(slurp.length).toBe(0);
expect(slurp1.length).toBe(1);
expect(slurp2.length).toBe(1);
});
it('should update the "variation" property of the method', function() {
var slurp1 = docSet.getByLongname('Soup#slurp()')[0];
var slurp2 = docSet.getByLongname('Soup#slurp(dBA)')[0];
expect(slurp1.variation).toBe('');
expect(slurp2.variation).toBe('dBA');
});
it('should not add to or change existing variations that are unique', function() {
var salt1 = docSet.getByLongname('Soup#salt');
var salt2 = docSet.getByLongname('Soup#salt(mg)');
expect(salt1.length).toBe(1);
expect(salt2.length).toBe(1);
});
it('should not duplicate the names of existing numeric variations', function() {
var heat1 = docSet.getByLongname('Soup#heat(1)');
var heat2 = docSet.getByLongname('Soup#heat(2)');
var heat3 = docSet.getByLongname('Soup#heat(3)');
expect(heat1.length).toBe(1);
expect(heat2.length).toBe(1);
expect(heat3.length).toBe(1);
});
it('should replace identical variations with new, unique variations', function() {
var discard1 = docSet.getByLongname('Soup#discard()');
var discard2 = docSet.getByLongname('Soup#discard(container)');
expect(discard1.length).toBe(1);
expect(discard2.length).toBe(1);
});
});
describe('parseComplete handler', function() {
// disabled because on the second run, each comment is being parsed twice; who knows why...
xit('should not retain parse results between parser runs', function() {
parser.clear();
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/overloadHelper.js', parser);
var heat = docSet.getByLongname('Soup#heat(4)');
expect(heat.length).toBe(0);
});
});
});

View File

@@ -0,0 +1,16 @@
/*global describe: true, expect: true, it: true */
describe("railsTemplate plugin", function() {
var parser = new (require("jsdoc/src/parser")).Parser(),
plugin = require('plugins/railsTemplate');
require('jsdoc/plugins').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. )");
});
});

View File

@@ -0,0 +1,14 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe("shout plugin", function() {
var parser = new (require("jsdoc/src/parser")).Parser(),
plugin = require('plugins/shout'),
docSet;
require('jsdoc/plugins').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.");
});
});

View File

@@ -0,0 +1,16 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe("sourcetag plugin", function() {
var parser = new (require("jsdoc/src/parser")).Parser(),
plugin = require('plugins/sourcetag'),
docSet;
require('jsdoc/plugins').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);
});
});

View File

@@ -0,0 +1,19 @@
/*global describe: true, expect: true, it: true, jasmine: true, xit: true */
/**
* @author Rob Taylor [manix84@gmail.com]
*/
describe("verbose output plugin", function () {
var parser = new (require("jsdoc/src/parser")).Parser(),
plugin = require('plugins/verboseOutput'),
docSet;
//require('jsdoc/plugins').installPlugins(['plugins/verboseOutput'], parser);
docSet = jasmine.getDocSetFromFile("plugins/verboseOutput.js", parser);
xit("should log file names to console", function() {
// TODO: this doesn't actually test the plugin...
var fileBegin = docSet.getByLongname("module:plugins/verboseOutput.handlers.fileBegin");
expect(fileBegin[0].description).toEqual("Logging the file name to the console.");
});
});

View File

@@ -0,0 +1,15 @@
/**
* Adds a verbose output to the console, so that you can see what's happening in your process.
* @module plugins/verboseOutput
* @author Rob Taylor <manix84@gmail.com> - The basic idea
* @author Michael Mathews <micmath@gmail.com> - Wrote the first itteration with me :)
*/
exports.handlers = {
/**
* Logging the file name to the console.
*/
fileBegin: function (data) {
console.log(data.filename);
}
};