mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
commit
c0e6b63d2d
25
jsdoc.js
25
jsdoc.js
@ -143,7 +143,8 @@ function main() {
|
||||
parser: require('jsdoc/opts/parser'),
|
||||
}
|
||||
},
|
||||
resolver;
|
||||
resolver,
|
||||
dictionary = require('jsdoc/tag/dictionary');
|
||||
|
||||
env.opts = jsdoc.opts.parser.parse(env.args);
|
||||
|
||||
@ -184,16 +185,30 @@ function main() {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// allow user-defined plugins to register listeners
|
||||
// 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]);
|
||||
for (var eventName in plugin) {
|
||||
app.jsdoc.parser.on(eventName, plugin[eventName]);
|
||||
|
||||
//...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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// any source file named package.json is treated special
|
||||
for (var i = 0, l = env.opts._.length; i < l; i++ ) {
|
||||
if (/\bpackage\.json$/i.test(env.opts._[i])) {
|
||||
|
||||
BIN
lib/js.jar
BIN
lib/js.jar
Binary file not shown.
@ -18,19 +18,327 @@ file, you would include it in conf.json like so:
|
||||
Authoring JSDoc 3 Plugins
|
||||
----
|
||||
|
||||
The plugin system for JSDoc 3 is event-based, meaning you register an interest
|
||||
in a specific named-event with a handler function that will be called by JSDoc
|
||||
whenever that event occurs. JSDoc will pass an event object into your handler as
|
||||
the argument: this can be used to access information about the event.
|
||||
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:
|
||||
|
||||
For example, to handle events fired whenever a new doclet is created by JSDoc,
|
||||
you would add a handler for the "newDoclet" event to your plugin's exported
|
||||
functions, like so:
|
||||
- Defining event handlers
|
||||
- Defining tags
|
||||
- Defining a parse tree node processor
|
||||
|
||||
exports.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 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.comment += 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
|
||||
- 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.
|
||||
@ -4,15 +4,18 @@
|
||||
@author Michael Mathews <micmath@gmail.com>
|
||||
*/
|
||||
|
||||
///
|
||||
/// Convert ///-style comments into jsdoc comments.
|
||||
/// @param e
|
||||
/// @param e.filename
|
||||
/// @param e.source
|
||||
///
|
||||
exports.beforeParse = function(e) {
|
||||
e.source = e.source.replace(/(\n[ \t]*\/\/\/[^\n]*)+/g, function($) {
|
||||
var replacement = '\n/**' + $.replace(/^[ \t]*\/\/\//mg, '').replace(/(\n$|$)/, '*/$1');
|
||||
return replacement;
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -4,14 +4,17 @@
|
||||
@author Michael Mathews <micmath@gmail.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
Translate HTML tags in descriptions into safe entities.
|
||||
*/
|
||||
exports.newDoclet = function(e) {
|
||||
if (e.doclet.description) {
|
||||
e.doclet.description = e.doclet.description
|
||||
.replace(/&/g,'&')
|
||||
.replace(/</g,'<')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
exports.handlers = {
|
||||
/**
|
||||
Translate HTML tags in descriptions into safe entities.
|
||||
*/
|
||||
newDoclet: function(e) {
|
||||
if (e.doclet.description) {
|
||||
e.doclet.description = e.doclet.description
|
||||
.replace(/&/g,'&')
|
||||
.replace(/</g,'<')
|
||||
.replace(/\n/g, '<br>');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -6,15 +6,17 @@
|
||||
|
||||
var mdParser = require('evilstreak/markdown');
|
||||
|
||||
/**
|
||||
Translate markdown syntax in a new doclet's description into HTML. Is run
|
||||
by JSDoc 3 whenever a "newDoclet" event fires.
|
||||
*/
|
||||
exports.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, ">" );
|
||||
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, ">" );
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -4,11 +4,13 @@
|
||||
@author Michael Mathews <micmath@gmail.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
Make your descriptions more shoutier.
|
||||
*/
|
||||
exports.newDoclet = function(e) {
|
||||
if (typeof e.doclet.description === 'string') {
|
||||
e.doclet.description = e.doclet.description.toUpperCase();
|
||||
exports.handlers = {
|
||||
/**
|
||||
Make your descriptions more shoutier.
|
||||
*/
|
||||
newDoclet: function(e) {
|
||||
if (typeof e.doclet.description === 'string') {
|
||||
e.doclet.description = e.doclet.description.toUpperCase();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -3,38 +3,40 @@
|
||||
@author Michael Mathews <micmath@gmail.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
Support @source tag. Expected value like:
|
||||
{ "filename": "myfile.js", "lineno": 123 }
|
||||
Modifies the corresponding meta values on the given doclet.
|
||||
*/
|
||||
exports.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];
|
||||
exports.handlers = {
|
||||
/**
|
||||
Support @source tag. Expected value like:
|
||||
{ "filename": "myfile.js", "lineno": 123 }
|
||||
Modifies the corresponding meta values on the given doclet.
|
||||
*/
|
||||
newDoclet: function(e) {
|
||||
var tags = e.doclet.tags,
|
||||
tag,
|
||||
value;
|
||||
|
||||
try {
|
||||
value = JSON.parse(tag.value);
|
||||
}
|
||||
catch(e) {
|
||||
throw new Error('@source tag expects a valid JSON value, like { "filename": "myfile.js", "lineno": 123 }.');
|
||||
}
|
||||
//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';
|
||||
});
|
||||
|
||||
!e.doclet.meta && (e.doclet.meta = {});
|
||||
e.doclet.meta.filename = value.filename || '';
|
||||
e.doclet.meta.lineno = value.lineno || '';
|
||||
}
|
||||
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 || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -19,6 +19,7 @@ var Token = Packages.org.mozilla.javascript.Token,
|
||||
exports.Parser = function() {
|
||||
this._resultBuffer = [];
|
||||
this.refs = {};
|
||||
this._visitors = [];
|
||||
}
|
||||
require('common/util').mixin(exports.Parser.prototype, require('common/events'));
|
||||
|
||||
@ -91,6 +92,20 @@ exports.Parser.prototype.clear = function() {
|
||||
this._resultBuffer = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node visitor to use in parsing
|
||||
*/
|
||||
exports.Parser.prototype.addNodeVisitor = function(visitor) {
|
||||
this._visitors.push(visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node visitors used in parsing
|
||||
*/
|
||||
exports.Parser.prototype.getVisitors = function() {
|
||||
return this._visitors;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
exports.Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
|
||||
var e = {filename: sourceName};
|
||||
@ -103,7 +118,7 @@ exports.Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
|
||||
currentSourceName = sourceName = e.filename;
|
||||
|
||||
sourceCode = pretreat(e.source);
|
||||
|
||||
|
||||
var ast = parserFactory().parse(sourceCode, sourceName, 1);
|
||||
ast.visit(
|
||||
new Packages.org.mozilla.javascript.ast.NodeVisitor({
|
||||
@ -135,6 +150,9 @@ function pretreat(code) {
|
||||
.replace(/»/g, '*/');
|
||||
}
|
||||
|
||||
var tkn = { NAMEDFUNCTIONSTATEMENT: -1001 };
|
||||
exports.Parser.tkn = tkn;
|
||||
|
||||
/**
|
||||
* Given a node, determine what the node is a member of.
|
||||
* @param {astnode} node
|
||||
@ -143,7 +161,7 @@ function pretreat(code) {
|
||||
exports.Parser.prototype.astnodeToMemberof = function(node) {
|
||||
var memberof = {};
|
||||
|
||||
if (node.type === Token.VAR || node.type === Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONTATEMENT) {
|
||||
if (node.type === Token.VAR || node.type === Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) {
|
||||
if (node.enclosingFunction) { // an inner var or func
|
||||
memberof.id = 'astnode'+node.enclosingFunction.hashCode();
|
||||
memberof.doclet = this.refs[memberof.id];
|
||||
@ -247,6 +265,31 @@ exports.Parser.prototype.resolveVar = function(node, basename) {
|
||||
return this.resolveVar(enclosingFunction, basename);
|
||||
}
|
||||
|
||||
exports.Parser.prototype.addDocletRef = function(e) {
|
||||
var node = e.code.node;
|
||||
if (e.doclet) {
|
||||
currentParser.refs['astnode'+node.hashCode()] = e.doclet; // allow lookup from value => doclet
|
||||
}
|
||||
else if ((node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) && !currentParser.refs['astnode'+node.hashCode()]) { // keep references to undocumented anonymous functions too as they might have scoped vars
|
||||
currentParser.refs['astnode'+node.hashCode()] = {
|
||||
longname: '<anonymous>',
|
||||
meta: { code: e.code }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
exports.Parser.prototype.resolveEnum = function(e) {
|
||||
var parent = currentParser.resolvePropertyParent(e.code.node);
|
||||
if (parent && parent.doclet.isEnum) {
|
||||
if (!parent.doclet.properties) { parent.doclet.properties = []; }
|
||||
// members of an enum inherit the enum's type
|
||||
if (parent.doclet.type && !e.doclet.type) { e.doclet.type = parent.doclet.type; }
|
||||
delete e.doclet.undocumented;
|
||||
e.doclet.defaultvalue = e.doclet.meta.code.value;
|
||||
parent.doclet.properties.push(e.doclet);
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
function visitNode(node) {
|
||||
var e,
|
||||
@ -267,12 +310,13 @@ function visitNode(node) {
|
||||
lineno: comment.getLineno(),
|
||||
filename: currentSourceName
|
||||
};
|
||||
|
||||
|
||||
if ( isValidJsdoc(commentSrc) ) {
|
||||
currentParser.fire('jsdocCommentFound', e, currentParser);
|
||||
}
|
||||
}
|
||||
}
|
||||
e = null;
|
||||
}
|
||||
else if (node.type === Token.ASSIGN) {
|
||||
e = {
|
||||
@ -281,20 +325,14 @@ function visitNode(node) {
|
||||
lineno: node.left.getLineno(),
|
||||
filename: currentSourceName,
|
||||
astnode: node,
|
||||
code: aboutNode(node)
|
||||
code: aboutNode(node),
|
||||
event: "symbolFound",
|
||||
finishers: [currentParser.addDocletRef]
|
||||
};
|
||||
|
||||
var basename = e.code.name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
|
||||
|
||||
if (basename !== 'this') e.code.funcscope = currentParser.resolveVar(node, basename);
|
||||
|
||||
if ( isValidJsdoc(e.comment) ) {
|
||||
currentParser.fire('symbolFound', e, currentParser);
|
||||
}
|
||||
|
||||
if (e.doclet) {
|
||||
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
|
||||
}
|
||||
}
|
||||
else if (node.type === Token.COLON) { // assignment within an object literal
|
||||
e = {
|
||||
@ -303,26 +341,10 @@ function visitNode(node) {
|
||||
lineno: node.left.getLineno(),
|
||||
filename: currentSourceName,
|
||||
astnode: node,
|
||||
code: aboutNode(node)
|
||||
code: aboutNode(node),
|
||||
event: "symbolFound",
|
||||
finishers: [currentParser.addDocletRef, currentParser.resolveEnum]
|
||||
};
|
||||
|
||||
if ( isValidJsdoc(e.comment) ) {
|
||||
currentParser.fire('symbolFound', e, currentParser);
|
||||
}
|
||||
|
||||
if (e.doclet) {
|
||||
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
|
||||
}
|
||||
|
||||
var parent = currentParser.resolvePropertyParent(node);
|
||||
if (parent && parent.doclet.isEnum) {
|
||||
if (!parent.doclet.properties) { parent.doclet.properties = []; }
|
||||
// members of an enum inherit the enum's type
|
||||
if (parent.doclet.type && !e.doclet.type) { e.doclet.type = parent.doclet.type; }
|
||||
delete e.doclet.undocumented;
|
||||
e.doclet.defaultvalue = e.doclet.meta.code.value;
|
||||
parent.doclet.properties.push(e.doclet);
|
||||
}
|
||||
}
|
||||
else if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
|
||||
|
||||
@ -341,7 +363,9 @@ function visitNode(node) {
|
||||
lineno: node.getLineno(),
|
||||
filename: currentSourceName,
|
||||
astnode: node,
|
||||
code: aboutNode(node)
|
||||
code: aboutNode(node),
|
||||
event: "symbolFound",
|
||||
finishers: [currentParser.addDocletRef]
|
||||
};
|
||||
|
||||
// keep track of vars in a function scope
|
||||
@ -354,26 +378,20 @@ function visitNode(node) {
|
||||
funcDoc.meta.vars.push(e.code.name);
|
||||
}
|
||||
}
|
||||
|
||||
if ( isValidJsdoc(e.comment) ) {
|
||||
currentParser.fire('symbolFound', e, currentParser);
|
||||
}
|
||||
|
||||
if (e.doclet) {
|
||||
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
|
||||
}
|
||||
}
|
||||
else if (node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONTATEMENT) {
|
||||
else if (node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) {
|
||||
e = {
|
||||
id: 'astnode'+node.hashCode(), // the id of the COLON node
|
||||
comment: String(node.jsDoc||'@undocumented'),
|
||||
lineno: node.getLineno(),
|
||||
filename: currentSourceName,
|
||||
astnode: node,
|
||||
code: aboutNode(node)
|
||||
code: aboutNode(node),
|
||||
event: "symbolFound",
|
||||
finishers: [currentParser.addDocletRef]
|
||||
};
|
||||
|
||||
e.code.name = (node.type == tkn.NAMEDFUNCTIONTATEMENT)? '' : String(node.name) || '';
|
||||
e.code.name = (node.type == tkn.NAMEDFUNCTIONSTATEMENT)? '' : String(node.name) || '';
|
||||
//console.log(':: e.code.name is '+e.code.name);
|
||||
// keep track of vars in a function scope
|
||||
if (node.enclosingFunction) {
|
||||
@ -388,22 +406,22 @@ function visitNode(node) {
|
||||
|
||||
var basename = e.code.name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
|
||||
e.code.funcscope = currentParser.resolveVar(node, basename);
|
||||
|
||||
if ( isValidJsdoc(e.comment) ) {
|
||||
currentParser.fire('symbolFound', e, currentParser);
|
||||
}
|
||||
|
||||
if (e.doclet) {
|
||||
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
|
||||
}
|
||||
else if (!currentParser.refs['astnode'+e.code.node.hashCode()]) { // keep references to undocumented anonymous functions too as they might have scoped vars
|
||||
currentParser.refs['astnode'+e.code.node.hashCode()] = {
|
||||
longname: '<anonymous>',
|
||||
meta: { code: e.code }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!e) { e = {finishers: []}; }
|
||||
for(var i = 0, l = currentParser._visitors.length; i < l; i++) {
|
||||
currentParser._visitors[i].visitNode(node, e, currentParser, currentSourceName);
|
||||
if (e.stopPropagation) { break; }
|
||||
}
|
||||
|
||||
if (!e.preventDefault && isValidJsdoc(e.comment)) {
|
||||
currentParser.fire(e.event, e, currentParser);
|
||||
}
|
||||
|
||||
for (var i = 0, l = e.finishers.length; i < l; i++) {
|
||||
e.finishers[i].call(currentParser, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -419,7 +437,7 @@ function parserFactory() {
|
||||
ce.initFromContext(cx);
|
||||
return new Packages.org.mozilla.javascript.Parser(ce, ce.getErrorReporter());
|
||||
}
|
||||
var tkn = { NAMEDFUNCTIONTATEMENT: -1001 };
|
||||
|
||||
/**
|
||||
* Attempts to find the name and type of the given node.
|
||||
* @private
|
||||
@ -428,8 +446,8 @@ var tkn = { NAMEDFUNCTIONTATEMENT: -1001 };
|
||||
function aboutNode(node) {
|
||||
about = {};
|
||||
|
||||
if (node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONTATEMENT) {
|
||||
about.name = node.type == tkn.NAMEDFUNCTIONTATEMENT? '' : '' + node.name;
|
||||
if (node.type == Token.FUNCTION || node.type == tkn.NAMEDFUNCTIONSTATEMENT) {
|
||||
about.name = node.type == tkn.NAMEDFUNCTIONSTATEMENT? '' : '' + node.name;
|
||||
about.type = 'function';
|
||||
about.node = node;
|
||||
}
|
||||
@ -437,15 +455,15 @@ function aboutNode(node) {
|
||||
about.name = nodeToString(node.target);
|
||||
if (node.initializer) { // like var i = 0;
|
||||
about.node = node.initializer;
|
||||
about.value = nodeToString(about.node);
|
||||
about.value = nodeToString(about.node);
|
||||
about.type = getTypeName(node.initializer);
|
||||
if (about.type === 'FUNCTION' && about.node.name) {
|
||||
about.node.type = tkn.NAMEDFUNCTIONTATEMENT;
|
||||
about.node.type = tkn.NAMEDFUNCTIONSTATEMENT;
|
||||
}
|
||||
}
|
||||
else { // like var i;
|
||||
about.node = node.target;
|
||||
about.value = nodeToString(about.node);
|
||||
about.value = nodeToString(about.node);
|
||||
about.type = 'undefined';
|
||||
}
|
||||
}
|
||||
@ -459,11 +477,11 @@ function aboutNode(node) {
|
||||
}
|
||||
}
|
||||
about.node = node.right;
|
||||
about.value = nodeToString(about.node);
|
||||
about.value = nodeToString(about.node);
|
||||
about.type = getTypeName(node.right);
|
||||
|
||||
if (about.type === 'FUNCTION' && about.node.name) {
|
||||
about.node.type = tkn.NAMEDFUNCTIONTATEMENT;
|
||||
about.node.type = tkn.NAMEDFUNCTIONSTATEMENT;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -510,7 +528,7 @@ function nodeToString(node) {
|
||||
else if (node.type === Token.STRING) {
|
||||
str = node.value;
|
||||
}
|
||||
else if (node.type === Token.NUMBER) {
|
||||
else if (node.type === Token.NUMBER) {
|
||||
str = node.value;
|
||||
}
|
||||
else if (node.type === Token.THIS) {
|
||||
@ -546,7 +564,7 @@ function getTypeName(node) {
|
||||
@memberof module:src/parser.Parser
|
||||
*/
|
||||
function isValidJsdoc(commentSrc) {
|
||||
return commentSrc.indexOf('/***') !== 0; /*** ignore comments that start with many stars ***/
|
||||
return commentSrc && commentSrc.indexOf('/***') !== 0; /*** ignore comments that start with many stars ***/
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user