Added events, added parse handlers, added plugins and markdown plugin support.

This commit is contained in:
Michael Mathews 2010-12-27 15:18:44 +00:00
parent 7b55f59263
commit db62d9e7b4
23 changed files with 2205 additions and 769 deletions

View File

@ -25,7 +25,7 @@ with JSDoc 3. Each is provided under its own license and has source
available from other locations.
Rhino
-----
----
Rhino is open source and licensed by Mozilla under the MPL 1.1 or
later/GPL 2.0 or later licenses.
@ -36,7 +36,7 @@ You can obtain the source code for Rhino from the Mozilla web site at
http://www.mozilla.org/rhino/download.html
json2xml (modules/goessner/json2xml)
--------
----
json2xml is copyright (c) Stefan Goessner 2006
@ -47,7 +47,7 @@ http://goessner.net/
http://goessner.net/download/prj/jsonxml/
Node (modules/common/assert, modules/common/util)
-------
----
Node is Copyright 2009, 2010 Ryan Lienhart Dahl. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
@ -71,7 +71,7 @@ IN THE SOFTWARE.
https://github.com/ry/node
JSONSchema Validator (modules/sitepen/jsonschema)
--------------------
----
JSONSchema is copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com)
@ -80,8 +80,16 @@ http://www.sitepen.com/blog/2010/03/02/commonjs-utilities/
Licensed under the MIT license.
markdown-js (modules/evilstreak/markdown)
----
markdown-js is released under the MIT license.
http://www.opensource.org/licenses/mit-license.php
http://github.com/evilstreak/markdown-js
Mustache (templates/lib/janl/mustache.js)
-------------------
----
Mustache is
Copyright (c) 2009 Chris Wanstrath (Ruby)

View File

@ -17,7 +17,7 @@ directory.
To test that the installed app is working, execute the following:
$ java -jar jsdoc.jar -T
$ java -jar jsdoc.jar --test
Usage
-----
@ -25,11 +25,11 @@ Usage
This example assumes your working directory is the JSDoc project
base directory...
$ java -jar jsdoc.jar -d stdout yourSourceCode.js
$ java -jar jsdoc.jar yourSourceCodeFile.js
For help regarding the supported commandline options use -h.
$ java -jar jsdoc.jar -h
$ java -jar jsdoc.jar --help
See
---

View File

@ -1,2 +1,2 @@
app.name=jsdoc-3
app.version=0.0.0
app.name=jsdoc
app.version=3.0.0alpha1

View File

@ -2,7 +2,7 @@
"name": "@app.name@",
"version": "@app.version@",
"revision": "@timestamp@",
"description": "An automatic documentation generator for javascript",
"description": "An automatic documentation generator for javascript.",
"keywords": [ "documentation", "javascript" ],
"licenses": [
{
@ -16,7 +16,13 @@
"url": "http://github.com/micmath/JSDoc"
}
],
"bugs": "http://jsdoc.lighthouseapp.com/",
"contributors" : [
{
"name": "Michael Mathews",
"email": "micmath@gmail.com",
"web": "http://micmath.ws"
}
],
"maintainers": [
{
"name": "Michael Mathews",

View File

@ -1,3 +1,11 @@
{
"permitUnknownTags": false
"tags": {
"allowUnknownTags": false
},
"source": {
"includePattern": ".+\\.js(doc)?$"
},
"plugins": [
"plugins/markdown.js"
]
}

309
main.js
View File

@ -1,156 +1,171 @@
/**
* @project JSDoc
* @copyright 2010 (c) Michael Mathews <micmath@gmail.com>
* @license See LICENSE.md file included in this distribution.
* @overview JSDoc/main.js
* @copyright 2010, 2011 (c) Michael Mathews <micmath@gmail.com>
* @license See LICENSE.md file included in this distribution.
*/
//// bootstrap
/** @global */
const BASEDIR = arguments[0].replace(/([\/\\])main\.js$/, '$1'); // jsdoc.jar sets argument[0] to the abspath to main.js
/** @global */
function require(id) { // like commonjs
var path = require.base + id + '.js',
fileContent = '';
try {
var file = new java.io.File(path),
scanner = new java.util.Scanner(file).useDelimiter("\\Z"),
fileContent = String( scanner.next() );
}
catch(e) {
print(e);
}
try {
var f = new Function('require', 'exports', 'module', fileContent),
exports = require.cache[path] || {},
module = { id: id, uri: path };
require.cache[id] = exports;
f.call({}, require, exports, module);
}
catch(e) {
print('Unable to require source code from "' + path + '": ' + e);
}
return exports;
}
require.base = BASEDIR + '/modules/'; // assume all module paths are relative to here
require.cache = {}; // cache module exports. Like: {id: exported}
////
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//// main
/** @global */
const BASEDIR = arguments[0].replace(/([\/\\])main\.js$/, '$1'); // jsdoc.jar sets argument[0] to the abspath to main.js
/** @global */
function require(id) { // like commonjs
var path = require.base + id + '.js',
fileContent = '';
try {
var file = new java.io.File(path),
scanner = new java.util.Scanner(file).useDelimiter("\\Z"),
fileContent = String( scanner.next() );
}
catch(e) {
print('Unable to read source code from "' + path + '": ' + e);
}
/** @global */
env = {
run: {
start: new Date(),
finish: null
},
args: arguments.slice(1), // jsdoc.jar sets argument[0] to the abspath to main.js, user args follow
conf: {}, // TODO: populate from file BASEDIR+'/conf.json'
opts: {}
};
try { main(); }
finally { env.run.finish = new Date(); }
/** @global */
function print(/*...*/) {
for (var i = 0, leni = arguments.length; i < leni; i++) {
java.lang.System.out.println('' + arguments[i]);
}
}
/** @global */
function dump(/*...*/) {
for (var i = 0, leni = arguments.length; i < leni; i++) {
print( require('common/dumper').dump(arguments[i]) );
}
}
try {
var f = new Function('require', 'exports', 'module', fileContent),
exports = require.cache[path] || {},
module = { id: id, uri: path };
require.cache[id] = exports;
f.call({}, require, exports, module);
}
catch(e) {
print('Unable to require source code from "' + path + '": ' + e.toSource());
}
return exports;
}
require.base = BASEDIR + '/modules/'; // assume all module paths are relative to here
require.cache = {}; // cache module exports. Like: {id: exported}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/** @global */
function include(filepath) {
try {
load(BASEDIR + filepath);
}
catch (e) {
print('Cannot include "' + BASEDIR + filepath + '": '+e);
}
}
/** @global */
function exit(v) {
java.lang.System.exit(v);
}
function main() {
var sourceFiles,
docs,
jsdoc = {
opts: {
parser: require('jsdoc/opts/parser')
},
src: {
scanner: require('jsdoc/src/scanner'),
parser: require('jsdoc/src/parser')
}
};
try {
env.conf = JSON.parse( require('common/fs').read(BASEDIR+'conf.json') );
}
catch (e) {
throw('Configuration file cannot be evaluated. '+e);
}
env.opts = jsdoc.opts.parser.parse(env.args);
if (env.opts.help) {
print( jsdoc.optParser.help() );
exit(0);
}
else if (env.opts.test) {
include('test/runall.js');
exit(0);
}
if (env.opts._.length > 0) { // are there any source files to scan?
sourceFiles = jsdoc.src.scanner.scan(env.opts._, (env.opts.recurse? 10 : undefined));
//dump('sourceFiles...', sourceFiles);
docs = jsdoc.src.parser.parse(sourceFiles, env.opts.encoding);
//dump('jsdoc.docs...', docs);
if (env.opts.template) {
include('templates/'+env.opts.template+'/publish.js');
if (typeof publish === 'function') {
publish(docs, {});
}
/** @global */
env = {
run: {
start: new Date(),
finish: null
},
args: arguments.slice(1), // jsdoc.jar sets argument[0] to the abspath to main.js, user args follow
conf: {}, // TODO: populate from file BASEDIR+'/conf.json'
opts: {}
};
/** @global */
app = {
jsdoc: {
scanner: new (require('jsdoc/src/scanner').Scanner)(),
parser: new (require('jsdoc/src/parser').Parser)()
}
}
try { main(); }
finally { env.run.finish = new Date(); }
/** @global */
function print(/*...*/) {
for (var i = 0, leni = arguments.length; i < leni; i++) {
java.lang.System.out.println('' + arguments[i]);
}
}
/** @global */
function dump(/*...*/) {
for (var i = 0, leni = arguments.length; i < leni; i++) {
print( require('common/dumper').dump(arguments[i]) );
}
}
/** @global */
function include(filepath) {
try {
load(BASEDIR + filepath);
}
catch (e) {
print('Cannot include "' + BASEDIR + filepath + '": '+e);
}
}
/** @global */
function exit(v) {
java.lang.System.exit(v);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/** @global */
function main() {
var sourceFiles,
docs,
jsdoc = {
opts: {
parser: require('jsdoc/opts/parser')
}
//
// if (env.opts.validate) {
// var jsonSchema = require('sitepen/jsonSchema');
// var jsdocSchema = require('jsdoc/schema').jsdocSchema;
// var validation = jsonSchema.validate(jsdoc.srcParser.result.toObject(), jsdocSchema);
// print('Validation: ' + validation.toSource());
// }
//
// if (!env.opts.destination || env.opts.destination.indexOf('stdout') === 0) {
// print( jsdoc.srcParser.result.toString(env.opts.destination) );
// }
// else if (env.opts.template) {
// try {
// load(BASEDIR+'/templates/'+env.opts.template+'/publish.js');
// }
// catch (e) {
// print('Cannot load the specified template: templates/'+env.opts.template+'/publish.js: '+e);
// }
//
// publish(jsdoc.srcParser.result.toObject(), {});
// }
}
}
////
};
try {
env.conf = JSON.parse( require('common/fs').read(BASEDIR+'conf.json') );
}
catch (e) {
throw('Configuration file cannot be evaluated. '+e);
}
env.opts = jsdoc.opts.parser.parse(env.args);
if (env.opts.help) {
print( jsdoc.opts.parser.help() );
exit(0);
}
else if (env.opts.test) {
include('test/runner.js');
exit(0);
}
// allow user-defined plugins to register listeners
if (env.conf.plugins) {
for (var i = 0, leni = env.conf.plugins.length; i < leni; i++) {
include(env.conf.plugins[i]);
}
}
if (env.opts._.length > 0) { // are there any files to scan and parse?
// allow filtering of found source files
if (env.conf.source && env.conf.source.includePattern) {
var includeRegexp = new RegExp(env.conf.source.includePattern);
app.jsdoc.scanner.on('sourceFileFound', function(e) {
if ( !includeRegexp.test(e.fileName) ) {
return false;
}
});
}
sourceFiles = app.jsdoc.scanner.scan(env.opts._, (env.opts.recurse? 10 : undefined));
require('jsdoc/src/handlers');
docs = app.jsdoc.parser.parse(sourceFiles, env.opts.encoding);
//dump(docs);
//exit(0);
env.opts.template = env.opts.template || 'default';
// should define a global "publish" function
include('templates/' + env.opts.template + '/publish.js');
if (typeof publish === 'function') {
publish(
docs,
{ destination: env.opts.destination }
);
}
}
}

88
modules/common/events.js Normal file
View File

@ -0,0 +1,88 @@
/**
@module common/events
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
(function() {
/**
@function module:common/events.mixin
@param {*} o The object to recieve the EventEmitter members.
*/
exports.mixin = function(o) {
for ( var p in EventEmitter ) {
o[p] = EventEmitter[p];
}
}
/**
@mixin module:common/events.EventEmitter
*/
var EventEmitter = {
/**
@function module:common/events.EventEmitter.on
@param {string} type
@param {function} handler
@returns this
*/
on: function(type, handler, context) {
if ( !type || !handler ) {
return;
}
if ( !context ) { context = this; }
if ( !this.__bindings ) { this.__bindings = {}; }
// TODO check here for hasOwnProperty?
if ( !this.__bindings[type] ) { this.__bindings[type] = []; }
var call = function(e) {
return handler.call(context, e);
};
this.__bindings[type].push( {handler: handler, call: call} );
return this; // chainable
},
/**
@function module:common/events.EventEmitter.fire
@param {string} type
@param {object} [eventData]
@returns this
*/
fire: function(eventType, eventData) {
if ( !eventType || !this.__bindings || !this.__bindings[eventType] ) {
return;
}
// run handlers in first-in-first-run order
for (var i = 0, leni = this.__bindings[eventType].length; i < leni; i++) {
if ( false === this.__bindings[eventType][i].call(eventData) ) {
if (eventData) { eventData.defaultPrevented = true; }
}
}
return this; // chainable
},
/**
@function module:common/events.EventEmitter.removeListener
@param {string} type
@param {function} handler
*/
removeListener: function(type, handler) {
if ( !type || !handler || !this.__bindings || !this.__bindings[type] ) {
return;
}
var i = this.__bindings[type].length;
while ( i-- ) {
if ( this.__bindings[type][i].handler === handler ) {
this.__bindings[type].splice(i, 1);
}
}
if (this.__bindings[type].length === 0) {
delete this.__bindings[type];
}
}
};
})();

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,12 @@
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
(function() {
var Tag = require('jsdoc/tag').Tag,
tagDictionary = require('jsdoc/tag/dictionary');
var jsdoc = {
tag: {
Tag: require('jsdoc/tag').Tag,
dictionary: require('jsdoc/tag/dictionary')
}
};
/**
@constructor
@ -27,13 +31,11 @@
for (var i = 0, leni = newTags.length; i < leni; i++) {
this.addTag(newTags[i].title, newTags[i].text);
}
this.applyTags(this.tags);
}
exports.Doclet.prototype.addTag = function(title, text) {
var tagDef = tagDictionary.lookUp(title),
newTag = new Tag(title, text, {});
var tagDef = jsdoc.tag.dictionary.lookUp(title),
newTag = new jsdoc.tag.Tag(title, text, {});
if (tagDef.onTagged) {
if (tagDef.onTagged(this, newTag) !== false) { // onTagged handler prevents tag being added bt returning false
@ -43,26 +45,22 @@
else {
this.tags.push(newTag);
}
applyTag.call(this, newTag);
}
exports.Doclet.prototype.applyTags = function(tags) {
var tag;
for (var i = 0, leni = tags.length; i < leni; i++) {
tag = tags[i];
if (tag.title === 'name') {
this.name = tag.value;
}
if (tag.title === 'kind') {
this.kind = tag.value;
}
if (tag.title === 'description') {
this.description = tag.value;
}
}
function applyTag(tag) {
if (tag.title === 'name') {
this.name = tag.value;
}
if (tag.title === 'kind') {
this.kind = tag.value;
}
if (tag.title === 'description') {
this.description = tag.value;
}
}
/**
@ -75,7 +73,6 @@
docletSrc = unwrap(docletSrc);
tagSrcs = split(docletSrc);
//dump('tagSrcs', tagSrcs);
for each(tagSrc in tagSrcs) {
tags.push( {title: tagSrc.title, text: tagSrc.text} );

View File

@ -13,7 +13,7 @@
ourOptions,
defaults = {
template: 'default',
destination: 'jsdoc.xml'
destination: 'console'
};
argsParser.addOption('t', 'template', true, 'The name of the template to use. Default: the "default" template');

View File

@ -0,0 +1,17 @@
(function() {
var Doclet = require('jsdoc/doclet').Doclet;
app.jsdoc.parser.on('jsdocCommentFound', function(e) {
var newDoclet = new Doclet(e.comment, e.node, e.filename);
if (newDoclet) {
e = { doclet: newDoclet };
this.fire('newDoclet', e);
if (!e.defaultPrevented) {
this.addResult(newDoclet);
}
}
});
})();

View File

@ -1,307 +1,356 @@
/**
* @module jsdoc/src/parser
@module jsdoc/src/parser
@requires module:common/events
@requires module:jsdoc/doclet
*/
(function() {
var Token = Packages.org.mozilla.javascript.Token,
Doclet = require('jsdoc/doclet').Doclet,
_parseResult = [];
exports.result = function() {
return _parseResult;
}
/**
*/
function visitNode(node) {
var commentSrc = '',
thisDoclet = null,
thisDocletName = '',
thisDocletPath = '';
// look for all comments that have names provided
if (node.type === Token.SCRIPT && node.comments) {
for each(var comment in node.comments.toArray()) {
if (comment.commentType === Token.CommentType.JSDOC) {
commentSrc = '' + comment.toSource();
if (commentSrc) {
thisDoclet = new Doclet(commentSrc, node, currentSourceName);
if (thisDoclet) {
_parseResult.push(thisDoclet);
}
// if ( thisDoclet.hasTag('name') && thisDoclet.hasTag('kind') ) {
// jsdoc.doclets.addDoclet(thisDoclet);
// if (thisDoclet.tagValue('kind') === 'module') {
// jsdoc.name.setCurrentModule( thisDoclet.tagValue('path') );
// }
// }
}
}
}
}
// use the nocode option to shortcut all the following blah blah
if (env.opts.nocode) { return true; }
//
// // like function foo() {}
// if (node.type == Token.FUNCTION && String(node.name) !== '') {
// commentSrc = (node.jsDoc)? String(node.jsDoc) : '';
//
// if (commentSrc) {
// thisDoclet = jsdoc.doclet.makeDoclet(commentSrc, node, currentSourceName);
// thisDocletName = thisDoclet.tagValue('name');
//
// if (!thisDoclet.hasTag('kind')) { // guess kind from the source code
// thisDoclet.addTag('kind', 'method')
// }
//
// if (!thisDocletName) { // guess name from the source code
// nodeName = jsdoc.name.resolveInner(node.name, node, thisDoclet);
// thisDoclet.setName(nodeName);
// jsdoc.doclets.addDoclet(thisDoclet);
// }
// jsdoc.name.refs.push([node, thisDoclet]);
// }
// else { // an uncommented function?
// // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
// thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', node, currentSourceName);
//
// nodeName = jsdoc.name.resolvePath(node.name, node, thisDoclet);
// thisDoclet.setName(nodeName);
// jsdoc.name.refs.push([
// node,
// thisDoclet
// ]);
// }
//
// return true;
// }
//
// // like foo = function(){} or foo: function(){}
// if (node.type === Token.ASSIGN || node.type === Token.COLON) {
//
// var nodeName = nodeToString(node.left),
// nodeKind = '';
// commentSrc = node.jsDoc || node.left.jsDoc;
//
// if (commentSrc) {
// commentSrc = '' + commentSrc;
//
// thisDoclet = jsdoc.doclet.makeDoclet(commentSrc, node, currentSourceName);
// thisDocletName = thisDoclet.tagValue('name');
// nodeKind = thisDoclet.tagValue('kind');
//
// if (!thisDoclet.hasTag('kind')) { // guess kind from the source code
// if (node.right.type == Token.FUNCTION) { // assume it's a method
// thisDoclet.addTag('kind', 'method');
// }
// else {
// thisDoclet.addTag('kind', 'property');
// }
// }
//
// if (!thisDocletName) { // guess name from the source code
// nodeName = jsdoc.name.resolvePath(nodeName, node, thisDoclet);
//
// thisDoclet.setName(nodeName);
// jsdoc.doclets.addDoclet(thisDoclet);
// }
// jsdoc.name.refs.push([node.right, thisDoclet]);
// }
// else { // an uncommented objlit or anonymous function?
//
// // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
//
// thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', node, currentSourceName);
// nodeName = jsdoc.name.resolvePath(nodeName, node, thisDoclet);
//
// thisDoclet.setName(nodeName);
// jsdoc.name.refs.push([
// node.right,
// thisDoclet
// ]);
// }
// return true;
// }
//
// // like var foo = function(){} or var bar = {}
// if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
// var counter = 0,
// nodeKind;
//
// if (node.variables) for each (var n in node.variables.toArray()) {
//
// if (n.target.type === Token.NAME) {
// var val = n.initializer;
//
// commentSrc = (counter++ === 0 && !n.jsDoc)? node.jsDoc : n.jsDoc;
// if (commentSrc) {
// thisDoclet = jsdoc.doclet.makeDoclet('' + commentSrc, node, currentSourceName);
// thisDocletPath = thisDoclet.tagValue('path');
// thisDocletName = thisDoclet.tagValue('name');
//
// if (!thisDoclet.hasTag('kind') && val) { // guess kind from the source code
// if (val.type == Token.FUNCTION) {
// thisDoclet.addTag('kind', 'method');
// }
// else {
// thisDoclet.addTag('kind', 'property');
// }
// }
//
// if (!thisDocletName) {
// thisDocletName = n.target.string;
// if (!thisDocletPath) { // guess path from the source code
// thisDocletPath = jsdoc.name.resolveInner(thisDocletName, node, thisDoclet);
// thisDoclet.setName(thisDocletPath);
// }
// else {
// thisDoclet.setName(thisDocletName);
// }
// jsdoc.doclets.addDoclet(thisDoclet);
// }
//
// if (val) { jsdoc.name.refs.push([val, thisDoclet]); }
// }
// else { // an uncommented objlit or anonymous function?
// var nodeName = nodeToString(n.target);
// // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
// thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', n.target, currentSourceName);
//
// nodeName = jsdoc.name.resolveInner(nodeName, n.target, thisDoclet);
// thisDoclet.setName(nodeName);
//
// if (val) jsdoc.name.refs.push([val, thisDoclet]);
// }
// }
//
// }
// return true;
// }
//
return true;
}
var currentSourceName = '';
/**
*/
exports.parseSource = function(source, sourceName) {
currentSourceName = sourceName;
var ast = getParser().parse(source, sourceName, 1);
ast.visit(
new Packages.org.mozilla.javascript.ast.NodeVisitor({
visit: visitNode
})
);
currentSourceName = '';
return _parseResult;
}
/**
*/
exports.clear = function() {
_parseResult = [];
}
/**
*/
exports.parse = function(sourceFiles, encoding) {
var ast = getParser(),
fs = require('common/fs'),
sourceCode = '',
filename = '',
jsUriScheme = 'javascript:';
if (arguments.length === 0) {
throw 'module:jsdoc/parser.parseFiles requires argument sourceFiles(none provided).';
}
if (typeof sourceFiles === 'string') { sourceFiles = [sourceFiles]; }
for (i = 0, leni = sourceFiles.length; i < leni; i++) {
if (sourceFiles[i].indexOf(jsUriScheme) === 0) {
sourceCode = sourceFiles[i].substr(jsUriScheme.length);
filename = '[[string' + i + ']]';
}
else {
filename = sourceFiles[i];
var Token = Packages.org.mozilla.javascript.Token,
Doclet = require('jsdoc/doclet').Doclet,
currentParser = null,
currentSourceName = '';
/**
@constructor module:jsdoc/src/parser.Parser
@mixesIn module:common/events.EventEmitter
*/
var Parser = exports.Parser = function() {
this._resultBuffer = [];
}
require('common/events').mixin(exports.Parser.prototype);
/**
@event jsdocCommentFound
@param e
@param {string} e.comment The raw text of the JSDoc comment that will be parsed.
This value may be modified in place by your event handler.
@param {string} e.file The name of the file containing the comment.
@defaultAction The comment text will be used to create a new Doclet.
Returning false from your handler will prevent this.
*/
/**
@event newDoclet
@param e
@param {string} e.doclet The new doclet that will be added to the results.
The properties of this value may be modified in place by your event handler.
@defaultAction The new doclet will be added to the parsers result set.
Returning false from your handler will prevent this.
*/
/**
@method module:jsdoc/src/parser.Parser#parse
@param {Array<string>} sourceFiles
@param {string} [encoding=utf8]
@fires jsdocCommentFound
@fires newDoclet
*/
Parser.prototype.parse = function(sourceFiles, encoding) {
var sourceCode = '',
filename = '',
jsUriScheme = 'javascript:';
if (arguments.length === 0) {
throw 'module:jsdoc/parser.parseFiles requires argument sourceFiles(none provided).';
}
if (typeof sourceFiles === 'string') { sourceFiles = [sourceFiles]; }
for (i = 0, leni = sourceFiles.length; i < leni; i++) {
if (sourceFiles[i].indexOf(jsUriScheme) === 0) {
sourceCode = sourceFiles[i].substr(jsUriScheme.length);
filename = '[[string' + i + ']]';
}
else {
filename = sourceFiles[i];
try {
sourceCode = fs.read(filename, encoding);
sourceCode = require('common/fs').read(filename, encoding);
}
catch(e) {
print('FILE READ ERROR: in module:jsdoc/parser.parseFiles: "' + filename + '" ' + e);
continue;
}
}
exports.parseSource(sourceCode, filename);
}
return _parseResult;
}
/**
@private
@function getParser
*/
function getParser() {
var cx = Packages.org.mozilla.javascript.Context.getCurrentContext();
var ce = new Packages.org.mozilla.javascript.CompilerEnvirons();
ce.setRecordingComments(true);
ce.setRecordingLocalJsDocComments(true);
ce.initFromContext(cx);
return new Packages.org.mozilla.javascript.Parser(ce, ce.getErrorReporter());
}
/**
@private
@function nodeToString
@param {org.mozilla.javascript.ast.AstNode} node
@returns {string}
*/
// credit: ringojs ninjas
function nodeToString(node) {
var str;
if (node.type === Token.GETPROP) {
str = [nodeToString(node.target), node.property.string].join('.');
}
else if (node.type === Token.NAME) {
str = node.string;
}
else if (node.type === Token.STRING) {
str = node.value;
}
else if (node.type === Token.THIS) {
str = 'this';
}
else if (node.type === Token.GETELEM) {
str = node.toSource(); // like: Foo['Bar']
}
else {
str = getTypeName(node);
}
return '' + str;
};
/**
@private
@function getTypeName
@param {org.mozilla.javascript.ast.AstNode} node
@returns {string}
*/
// credit: ringojs ninjas
function getTypeName(node) {
return node ? ''+Packages.org.mozilla.javascript.Token.typeToName(node.getType()) : '' ;
}
}
currentParser = this;
parseSource(sourceCode, filename);
currentParser = null;
}
return this._resultBuffer;
}
/**
@method module:jsdoc/src/parser.Parser#results
@returns {Array<Doclet>} The accumulated results of any calls to parse.
*/
Parser.prototype.results = function() {
return this._resultBuffer;
}
/**
@method module:jsdoc/src/parser.Parser#addResult
*/
Parser.prototype.addResult = function(o) {
this._resultBuffer.push(o);
}
/**
@method module:jsdoc/src/parser.Parser#clearResults
@desc Empty any accumulated results of calls to parse.
*/
Parser.prototype.clearResults = function() {
this._resultBuffer = [];
}
/**
@private
@function parseSource
*/
function parseSource(source, sourceName) {
currentSourceName = sourceName;
var ast = parserFactory().parse(source, sourceName, 1);
ast.visit(
new Packages.org.mozilla.javascript.ast.NodeVisitor({
visit: visitNode
})
);
currentSourceName = '';
}
/**
@private
@function visitNode
*/
function visitNode(node) {
var commentSrc = '',
thisDoclet = null;//,
//thisDocletName = '',
//thisDocletPath = '';
// look for all comments that have names provided
if (node.type === Token.SCRIPT && node.comments) {
for each(var comment in node.comments.toArray()) {
if (comment.commentType === Token.CommentType.JSDOC) {
if (commentSrc = '' + comment.toSource()) {
var e = {
comment: commentSrc,
filename: currentSourceName,
node: node
};
currentParser.fire('jsdocCommentFound', e, currentParser);
// if ( thisDoclet.hasTag('name') && thisDoclet.hasTag('kind') ) {
// jsdoc.doclets.addDoclet(thisDoclet);
// if (thisDoclet.tagValue('kind') === 'module') {
// jsdoc.name.setCurrentModule( thisDoclet.tagValue('path') );
// }
// }
}
}
}
}
// use the nocode option to shortcut all the following blah blah
if (env.opts.nocode) { return true; }
//
// // like function foo() {}
// if (node.type == Token.FUNCTION && String(node.name) !== '') {
// commentSrc = (node.jsDoc)? String(node.jsDoc) : '';
//
// if (commentSrc) {
// thisDoclet = jsdoc.doclet.makeDoclet(commentSrc, node, currentSourceName);
// thisDocletName = thisDoclet.tagValue('name');
//
// if (!thisDoclet.hasTag('kind')) { // guess kind from the source code
// thisDoclet.addTag('kind', 'method')
// }
//
// if (!thisDocletName) { // guess name from the source code
// nodeName = jsdoc.name.resolveInner(node.name, node, thisDoclet);
// thisDoclet.setName(nodeName);
// jsdoc.doclets.addDoclet(thisDoclet);
// }
// jsdoc.name.refs.push([node, thisDoclet]);
// }
// else { // an uncommented function?
// // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
// thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', node, currentSourceName);
//
// nodeName = jsdoc.name.resolvePath(node.name, node, thisDoclet);
// thisDoclet.setName(nodeName);
// jsdoc.name.refs.push([
// node,
// thisDoclet
// ]);
// }
//
// return true;
// }
//
// // like foo = function(){} or foo: function(){}
// if (node.type === Token.ASSIGN || node.type === Token.COLON) {
//
// var nodeName = nodeToString(node.left),
// nodeKind = '';
// commentSrc = node.jsDoc || node.left.jsDoc;
//
// if (commentSrc) {
// commentSrc = '' + commentSrc;
//
// thisDoclet = jsdoc.doclet.makeDoclet(commentSrc, node, currentSourceName);
// thisDocletName = thisDoclet.tagValue('name');
// nodeKind = thisDoclet.tagValue('kind');
//
// if (!thisDoclet.hasTag('kind')) { // guess kind from the source code
// if (node.right.type == Token.FUNCTION) { // assume it's a method
// thisDoclet.addTag('kind', 'method');
// }
// else {
// thisDoclet.addTag('kind', 'property');
// }
// }
//
// if (!thisDocletName) { // guess name from the source code
// nodeName = jsdoc.name.resolvePath(nodeName, node, thisDoclet);
//
// thisDoclet.setName(nodeName);
// jsdoc.doclets.addDoclet(thisDoclet);
// }
// jsdoc.name.refs.push([node.right, thisDoclet]);
// }
// else { // an uncommented objlit or anonymous function?
//
// // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
//
// thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', node, currentSourceName);
// nodeName = jsdoc.name.resolvePath(nodeName, node, thisDoclet);
//
// thisDoclet.setName(nodeName);
// jsdoc.name.refs.push([
// node.right,
// thisDoclet
// ]);
// }
// return true;
// }
//
// // like var foo = function(){} or var bar = {}
// if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
// var counter = 0,
// nodeKind;
//
// if (node.variables) for each (var n in node.variables.toArray()) {
//
// if (n.target.type === Token.NAME) {
// var val = n.initializer;
//
// commentSrc = (counter++ === 0 && !n.jsDoc)? node.jsDoc : n.jsDoc;
// if (commentSrc) {
// thisDoclet = jsdoc.doclet.makeDoclet('' + commentSrc, node, currentSourceName);
// thisDocletPath = thisDoclet.tagValue('path');
// thisDocletName = thisDoclet.tagValue('name');
//
// if (!thisDoclet.hasTag('kind') && val) { // guess kind from the source code
// if (val.type == Token.FUNCTION) {
// thisDoclet.addTag('kind', 'method');
// }
// else {
// thisDoclet.addTag('kind', 'property');
// }
// }
//
// if (!thisDocletName) {
// thisDocletName = n.target.string;
// if (!thisDocletPath) { // guess path from the source code
// thisDocletPath = jsdoc.name.resolveInner(thisDocletName, node, thisDoclet);
// thisDoclet.setName(thisDocletPath);
// }
// else {
// thisDoclet.setName(thisDocletName);
// }
// jsdoc.doclets.addDoclet(thisDoclet);
// }
//
// if (val) { jsdoc.name.refs.push([val, thisDoclet]); }
// }
// else { // an uncommented objlit or anonymous function?
// var nodeName = nodeToString(n.target);
// // this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
// thisDoclet = jsdoc.doclet.makeDoclet('[[undocumented]]', n.target, currentSourceName);
//
// nodeName = jsdoc.name.resolveInner(nodeName, n.target, thisDoclet);
// thisDoclet.setName(nodeName);
//
// if (val) jsdoc.name.refs.push([val, thisDoclet]);
// }
// }
//
// }
// return true;
// }
//
return true;
}
/**
@private
@function parserFactory
*/
function parserFactory() {
var cx = Packages.org.mozilla.javascript.Context.getCurrentContext();
var ce = new Packages.org.mozilla.javascript.CompilerEnvirons();
ce.setRecordingComments(true);
ce.setRecordingLocalJsDocComments(true);
ce.initFromContext(cx);
return new Packages.org.mozilla.javascript.Parser(ce, ce.getErrorReporter());
}
/**
@private
@function nodeToString
@param {org.mozilla.javascript.ast.AstNode} node
@returns {string}
*/
// credit: ringojs ninjas
function nodeToString(node) {
var str;
if (node.type === Token.GETPROP) {
str = [nodeToString(node.target), node.property.string].join('.');
}
else if (node.type === Token.NAME) {
str = node.string;
}
else if (node.type === Token.STRING) {
str = node.value;
}
else if (node.type === Token.THIS) {
str = 'this';
}
else if (node.type === Token.GETELEM) {
str = node.toSource(); // like: Foo['Bar']
}
else {
str = getTypeName(node);
}
return '' + str;
};
/**
@private
@function getTypeName
@param {org.mozilla.javascript.ast.AstNode} node
@returns {string}
*/
// credit: ringojs ninjas
function getTypeName(node) {
return node ? ''+Packages.org.mozilla.javascript.Token.typeToName(node.getType()) : '' ;
}
})();

View File

@ -11,13 +11,22 @@
fs: require('common/fs')
};
/**
@constructor
*/
exports.Scanner = function() {
}
require('common/events').mixin(exports.Scanner.prototype);
/**
Recursively searches the given searchPaths for js files.
@param {Array.<string>} searchPaths
@param {number} [depth=1]
@fires sourceFileFound
*/
exports.scan = function(searchPaths, depth) {
var filePaths = [];
exports.Scanner.prototype.scan = function(searchPaths, depth) {
var filePaths = [],
that = this;
searchPaths = searchPaths || [];
depth = depth || 1;
@ -26,9 +35,11 @@
filePaths = filePaths.concat(common.fs.ls($, depth));
});
// TODO: allow user-defined filtering of files
filePaths = filePaths.filter(function($) {
return /.+\.js(doc)?$/i.test($);
var e = { fileName: $ };
that.fire('sourceFileFound', e);
return !e.defaultPrevented;
});
return filePaths;

View File

@ -9,19 +9,23 @@
*/
(function() {
var dictionary = require('jsdoc/tag/dictionary'),
validator = require('jsdoc/tag/validator'),
tagType = require('jsdoc/tag/type');
var jsdoc = {
tag: {
dictionary: require('jsdoc/tag/dictionary'),
validator: require('jsdoc/tag/validator'),
type: require('jsdoc/tag/type')
}
};
/**
@constructor Tag
*/
exports.Tag = function(tagTitle, tagBody, meta) {
var tagDef = dictionary.lookUp(tagTitle),
var tagDef = jsdoc.tag.dictionary.lookUp(tagTitle),
meta = meta || {};
this.title = dictionary.normalise( trim(tagTitle) );
this.text = trim(tagBody, tagDef.preservesWhitespace);
this.title = jsdoc.tag.dictionary.normalise( trim(tagTitle) );
this.text = trim(tagBody, tagDef.keepsWhitespace);
if (this.text) {
if (tagDef.canHaveType) {
@ -33,9 +37,7 @@
/*?boolean*/ optional,
/*?boolean*/ nullable,
/*?boolean*/ variable
] = tagType.parse(this.text);
] = jsdoc.tag.type.parse(this.text);
if (typeNames.length) {
this.value.type = {
@ -45,6 +47,7 @@
variable: variable
};
}
if (remainingText) {
if (tagDef.canHaveName) {
var [tagName, tagDesc, tagOptional, tagDefault] = parseTagText(remainingText);
@ -64,7 +67,7 @@
}
}
validator.validate(this, meta);
jsdoc.tag.validator.validate(this, meta);
}
function trim(text, newlines) {

View File

@ -38,7 +38,7 @@
});
dictionary.defineTag('example', {
preservesWhitespace: true,
keepsWhitespace: true,
mustHaveValue: true
});

View File

@ -15,7 +15,7 @@
exports.validate = function(tag, meta) {
var tagDef = dictionary.lookUp(tag.title);
if (!tagDef && !env.conf.permitUnknownTags) {
if (!tagDef && !env.conf.tags.allowUnknownTags) {
throw new UnknownTagError(tag.title, meta);
}

View File

@ -1,199 +0,0 @@
/**
* Normal Template
*/
var TOKEN_RE = new RegExp("(\{[\=\:\#\/].+?\})"),
COMMAND_RE = new RegExp("^\{[\:\/\=]");
var xpath = function (path) {
if (path === '$last') {
return "$last";
}
if (/\||;|\$|~/.test(path)) {
throw new Error("Invalid characters in path '" + path + "'");
}
path = path.replace(/\//g, ".").replace(/'|"/, "");
if (path == ".") {
return "d";
} else if (/^\./.test(path)) {
return "data" + path;
} else {
return "d." + path;
}
}
/**
* Template filters. Add your own to this dictionary.
*/
exports.filters = {
str: function (val) { // used to override default filtering.
return val.toString();
},
strip: function (val) {
return val.toString().replace(/<([^>]+)>/g, "");
},
html: function (val) {
return val.toString().replace(/&/g, "&amp;").replace(/>/g, "&gt;").
replace(/</g, "&lt;");
},
attr: function (val) {
return val.toString().replace(/&/g, "&amp;").replace(/>/g, "&gt;").
replace(/</g, "&lt;").replace(/"/g, "&quot;");
},
uri: encodeURI
}
/**
* Compile the template source into the template function.
*/
exports.compile = function (src, options) {
// v = curent value, d = cursor, a = reduced array, df = default filter, res = result
var code = ['var v,a,d = data,res = [];'],
stack = ["data"],
nesting = [],
tokens = src.split(TOKEN_RE);
var filters, tag;
if (options && options.filters) {
filters = {};
for (var i in exports.filters) filters[i] = exports.filters[i];
for (var i in options.filters) filters[i] = options.filters[i];
} else {
filters = exports.filters;
}
if (filters.defaultfilter) {
code.push('var df = filters.defaultfilter;');
}
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token == "") continue;
if (token.match(COMMAND_RE)) {
if (token[1] == ":") { // open tag
var parts = token.substring(2, token.length-1).split(" "),
cmd = parts[0],
arg = parts[1],
val;
switch (cmd) {
case "if": // checks for undefined and boolean.
nesting.push("if");
val = xpath(arg);
code.push('if (' + val + ') {');
continue;
case "select":
case "s":
nesting.push("select");
val = xpath(arg);
code.push('d = ' + val + ';if (d != undefined) {');
stack.unshift(val.replace(/^d\./, stack[0] + "."));
continue;
case "reduce":
case "r":
nesting.push("reduce");
val = xpath(arg);
var depth = stack.length;
code.push('var a' + depth + ' = ' + val + ';if ((a' + depth + ' != undefined) && (a' + depth + '.length > 0)) ');
stack.unshift("a" + depth + "[i" + depth + "]");
code.push('for (var i' + depth + ' = 0,l' + depth + ' = a' + depth + '.length; i' + depth + ' < l' + depth + '; i' + depth + '++) {$last = (i' + depth + ' == l' + depth + '-1); d = a' + depth + '[i' + depth + '];');
continue;
case "else":
case "e":
tag = nesting.pop();
if (tag) {
code.push('} else {');
nesting.push(tag);
} else {
throw new Error("Unbalanced 'else' tag");
}
continue;
case "lb": // output left curly bracket '{'
code.push('res.push("{");');
continue;
case "rb": // output right curly bracket '}'
code.push('res.push("}");');
continue;
case "!": // comment
continue;
}
} else if (token[1] == "/") { // close tag
if (token[2] == ":") {
var cmd = token.substring(3, token.length-1).split(" ")[0];
switch (cmd) {
case "if":
tag = nesting.pop();
if (tag == "if") {
code.push('};');
} else {
throw new Error("Unbalanced 'if' close tag" + (tag ? ", expecting '" + tag + "' close tag" : ""));
}
continue;
case "select":
case "s":
tag = nesting.pop();
if (tag == "select") {
stack.shift();
code.push('};d = ' + stack[0] + ';');
} else {
throw new Error("Unbalanced 'select' close tag" + (tag ? ", expecting '" + tag + "' close tag" : ""));
}
continue;
case "reduce":
case "r":
tag = nesting.pop();
if (tag == "reduce") {
stack.shift();
code.push('}; $last = false; d = ' + stack[0] + ';');
} else {
throw new Error("Unbalanced 'reduce' close tag" + (tag ? ", expecting '" + tag + "' close tag" : ""));
}
continue;
}
}
} else if (token[1] == "=") { // interpolation
var parts = token.substring(2, token.length-1).split(" "),
pre = "", post = "";
for (var j = 0; j < parts.length-1; j++) {
pre += "filters." + parts[j] + "("; post += ")";
}
if (pre == "") {
if (filters.defaultfilter) {
pre = "df("; post = ")";
}
}
code.push('v = ' + xpath(parts[j]) + ';if (v != undefined) res.push(' + pre + 'v' + post +');');
continue;
}
}
// plain text
code.push('res.push("' + token.replace(/\\/g, "\\\\").replace(/\r/g, "").replace(/\n/g, "\\n").replace(/"/g, '\\"') + '");');
}
tag = nesting.pop();
if (tag) {
throw new Error("Unbalanced '" + tag + "' tag, is not closed");
}
code.push('return res.join("");');
var func = new Function("data", "filters", code.join(""));
return function (data) { return func(data, filters) };
}

View File

@ -1,7 +1,7 @@
{
"name": "jsdoc-3",
"version": "0.0.0",
"revision": "2010-07-28-0024",
"name": "jsdoc",
"version": "3.0.0alpha1",
"revision": "2010-12-27-1223",
"description": "An automatic documentation generator for javascript",
"keywords": [ "documentation", "javascript" ],
"licenses": [
@ -16,7 +16,6 @@
"url": "http://github.com/micmath/JSDoc"
}
],
"bugs": "http://jsdoc.lighthouseapp.com/",
"maintainers": [
{
"name": "Michael Mathews",

15
plugins/markdown.js Normal file
View File

@ -0,0 +1,15 @@
/**
@overview Translate doclet descriptions from MarkDown into HTML.
*/
(function() {
var markdown = require('evilstreak/markdown');
app.jsdoc.parser.on('newDoclet', function(e) {
if (e.doclet.description) {
e.doclet.description = markdown.toHTML(e.doclet.description);
}
});
})();

View File

@ -4,25 +4,40 @@
publish = function(docs, opts) {
var out = '',
template = readFile(BASEDIR + 'templates/default/tmpl/index.html');
templates = {
index: readFile(BASEDIR + 'templates/default/tmpl/index.html')
};
var summarize = function () {
function summarize () {
return function(text, render) {
text = render(text);
/^(.*?(\.|\n|\r|$))/.test(text);
return RegExp.$1;
var summary = trim(text);
summary = render(text);
summary = summary.replace(/<\/?p>/gi, ''); // text may be HTML
/^(.*?(\.$|\.\s|\n|\r|$|<br>))/.test(summary);
return RegExp.$1? RegExp.$1 : summary;
}
};
function trim(text) {
return text.replace(/^\s+|\s+$/g, '');
}
out = Mustache.to_html(
template,
templates.index,
{
docs: docs,
summarize: summarize
}
);
print(out);
if (opts.destination === 'console') {
print(out);
}
else {
print('The only -d destination option currently supported is "console"!');
}
}
})();

View File

@ -12,7 +12,7 @@
<ul>
{{#docs}}
<li class="symbol">
<i>{{kind}}</i> {{name}}{{#description}} - {{#summarize}}{{description}}{{/summarize}}{{/description}}
<i>{{kind}}</i> {{name}}{{#description}} - {{#summarize}}{{{description}}}{{/summarize}}{{/description}}
</li>
{{/docs}}
</ul>

View File

@ -3,73 +3,31 @@
var src = { parser: require('jsdoc/src/parser')};
test('There is a src/parser module.', function() {
assert.notEqual(typeof src, 'undefined', 'The src/parser module should be defined.');
assert.equal(typeof src, 'object', 'The src/parser module should be an object.');
});
test('The src/parser module has a "parse" function.', function() {
assert.notEqual(typeof src.parser.parse, 'undefined', 'The src.parser.parse method should be defined.');
assert.equal(typeof src.parser.parse, 'function', 'The src.parser.parse method should be a function.');
test('The src/parser module has a "Parser" constructor.', function() {
assert.equal(typeof src.parser.Parser, 'function', 'The src.parser.Parser member should be a function.');
});
test('The src/parser module has a "parseSource" function.', function() {
assert.notEqual(typeof src.parser.parseSource, 'undefined', 'The src.parser.parseSource method should be defined.');
assert.equal(typeof src.parser.parseSource, 'function', 'The src.parser.parseSource method should be a function.');
test('The src/parser module has a "Parser#parse" function.', function() {
assert.equal(typeof src.parser.Parser.prototype.parse, 'function', 'The src.parser.Parser#parse member should be a function.');
});
test('The src/parser module has a "result" function.', function() {
assert.notEqual(typeof src.parser.result, 'undefined', 'The src.parser.result method should be defined.');
assert.equal(typeof src.parser.result, 'function', 'The src.parser.result method should be a function.');
test('The src/parser module has a "results" function.', function() {
assert.equal(typeof src.parser.Parser.prototype.results, 'function', 'The src.parser.Parser#results member should be a function.');
});
test('The src/parser module has a "clear" function.', function() {
assert.notEqual(typeof src.parser.clear, 'undefined', 'The src.parser.clear method should be defined.');
assert.equal(typeof src.parser.clear, 'function', 'The src.parser.clear method should be a function.');
});
test('The src/parser.result function can return the result array.', function() {
var docs = src.parser.result;
assert.notEqual(typeof docs, 'undefined', 'The src.parser.result method should return a result.');
assert.equal(docs.length, 0, 'The src.parser.result method should return an array of 0 results if there has been nothing parsed yet.');
});
test('The src/parser.parseSource function can parse js source code containing a jsdoc comment.', function() {
var sourceCode = '/** @name foo */';
test('The src/parser.Parser#parse function fires jsdocCommentFound events when parsing source code containing a jsdoc comment.', function() {
var sourceCode = 'javascript:/** @name bar */',
jsdocCounter = 0;
(new src.parser.Parser())
.on('jsdocCommentFound', function(e) {
jsdocCounter++;
})
.parse(sourceCode);
var docs = src.parser.parseSource(sourceCode, '/bar/foo.js');
assert.notEqual(typeof docs, 'undefined', 'The src.parser.parseSource method should return a result.');
assert.equal(docs.length, 1, 'The src.parser.parseSource method should return an array of 1 result if there is 1 jsdoc comment.');
assert.notEqual(typeof docs[0].tags, 'undefined', 'The result array returned by src.parser.parseSource should be a collection of Doclets with Tags.');
});
test('The src/parser.clear function can empty the result array.', function() {
src.parser.clear();
var docs = src.parser.result;
assert.equal(docs.length, 0, 'The src.parser.result method should return an array of 0 results if clear was called.');
});
test('The src/parser.parse function can parse js source code containing a doc comment.', function() {
var sourceCode = 'javascript:/** @name bar */';
src.parser.clear();
var docs = src.parser.parse([sourceCode]);
assert.notEqual(typeof docs, 'undefined', 'The src.parser.parse method should return a result.');
assert.equal(docs.length, 1, 'The src.parser.parse method should return an array of 1 result if there is 1 jsdoc comment.');
assert.notEqual(typeof docs[0].tags, 'undefined', 'The result array returned by src.parser.parse should be a collection of Doclets with Tags.');
});
test('The src/parser.parse function can cope with source code containing no jsdoc comments.', function() {
var sourceCode = 'javascript:var blah;';
src.parser.clear();
var docs = src.parser.parse([sourceCode]);
assert.notEqual(typeof docs, 'undefined', 'The src.parser.parse method should return a result.');
assert.equal(docs.length, 0, 'The src.parser.parse method should return an array of 0 Doclets if there are no jsdocc omments.');
});
assert.equal(jsdocCounter, 1, 'The Parser#parse method should fire jsdocCommentFound once if there is 1 jsdoc comment.');
});
})();