overhaul logging and error handling (#416)

This commit is contained in:
Jeff Williams 2013-12-23 15:25:28 -08:00
parent 2babc5b1c3
commit c9b0237c12
39 changed files with 1533 additions and 1140 deletions

View File

@ -261,6 +261,21 @@ The source code for Node.js is available at:
https://github.com/joyent/node
## node-browser-builtins ##
Portions of the node-browser-builtins source code are incorporated into the
following files:
- `rhino/assert.js`
- `rhino/rhino-shim.js`
node-browser-builtins is distributed under the MIT license, which is reproduced
above.
The source code for node-browser-builtins is available at:
https://github.com/alexgorbatchev/node-browser-builtins
## node-browserify ##
Portions of the node-browserify source code are incorporated into the following

195
cli.js
View File

@ -3,32 +3,39 @@
*
* A few critical notes for anyone who works on this module:
*
* + The module should really export an instance of `JSDoc`, and `props` should be properties of a
* `JSDoc` instance. However, Rhino interpreted `this` as a reference to `global` within the
* + The module should really export an instance of `cli`, and `props` should be properties of a
* `cli` instance. However, Rhino interpreted `this` as a reference to `global` within the
* prototype's methods, so we couldn't do that.
* + Use the `fs` and `path` modules rather than `jsdoc/fs` and `jsdoc/path`, which are not
* initialized correctly when they're loaded this early.
* + On Rhino, for unknown reasons, the `jsdoc/fs` and `jsdoc/path` modules can fail in some cases
* when they are required by this module. You may need to use `fs` and `path` instead.
*
* @private
*/
module.exports = (function() {
'use strict';
var logger = require('jsdoc/util/logger');
var props = {
docs: [],
shouldExitWithError: false,
packageJson: null
};
var app = global.app;
var env = global.env;
var JSDoc = {};
var fatalErrorMessage = 'Exiting JSDoc because an error occurred. See the previous log ' +
'messages for details.';
var cli = {};
// TODO: docs
JSDoc.setVersionInfo = function() {
cli.setVersionInfo = function() {
var fs = require('fs');
var path = require('path');
// allow this to throw--something is really wrong if we can't read our own package file
var info = JSON.parse( fs.readFileSync(path.join(env.dirname, 'package.json'), 'utf8') );
env.version = {
@ -36,11 +43,11 @@ JSDoc.setVersionInfo = function() {
revision: new Date( parseInt(info.revision, 10) ).toUTCString()
};
return JSDoc;
return cli;
};
// TODO: docs
JSDoc.loadConfig = function() {
cli.loadConfig = function() {
var _ = require('underscore');
var args = require('jsdoc/opts/args');
var Config = require('jsdoc/config');
@ -55,7 +62,12 @@ JSDoc.loadConfig = function() {
encoding: 'utf8'
};
env.opts = args.parse(env.args);
try {
env.opts = args.parse(env.args);
}
catch (e) {
cli.exit(1, e.message + '\n' + fatalErrorMessage);
}
confPath = env.opts.configure || path.join(env.dirname, 'conf.json');
try {
@ -74,48 +86,109 @@ JSDoc.loadConfig = function() {
.get();
}
catch (e) {
throw new Error('Cannot parse the config file ' + confPath + ': ' + e);
cli.exit(1, 'Cannot parse the config file ' + confPath + ': ' + e + '\n' +
fatalErrorMessage);
}
// look for options on the command line, in the config file, and in the defaults, in that order
env.opts = _.defaults(env.opts, env.conf.opts, defaultOpts);
return JSDoc;
return cli;
};
// TODO: docs
JSDoc.runCommand = function(cb) {
cli.configureLogger = function() {
function recoverableError() {
props.shouldExitWithError = true;
}
function fatalError() {
cli.exit(1, fatalErrorMessage);
}
if (env.opts.debug) {
logger.setLevel(logger.LEVELS.DEBUG);
}
else if (env.opts.verbose) {
logger.setLevel(logger.LEVELS.INFO);
}
if (env.opts.pedantic) {
logger.once('logger:warn', recoverableError);
logger.once('logger:error', fatalError);
}
else {
logger.once('logger:error', recoverableError);
}
logger.once('logger:fatal', fatalError);
return cli;
};
// TODO: docs
cli.logStart = function() {
var loggerFunc = env.opts.help ? console.log : logger.info;
cli.printVersion(loggerFunc);
logger.debug('Environment info: {"env":{"conf":%j,"opts":%j}}', env.conf, env.opts);
};
// TODO: docs
cli.logFinish = function() {
var delta;
var deltaSeconds;
if (env.run.finish && env.run.start) {
delta = env.run.finish.getTime() - env.run.start.getTime();
}
if (delta !== undefined) {
deltaSeconds = (delta / 1000).toFixed(2);
logger.info('Finished running in %s seconds.', deltaSeconds);
}
};
// TODO: docs
cli.runCommand = function(cb) {
var cmd;
var opts = env.opts;
function done(errorCode) {
if (!errorCode && props.shouldExitWithError) {
cb(1);
}
else {
cb(errorCode);
}
}
if (opts.help) {
cmd = JSDoc.printHelp;
cmd = cli.printHelp;
}
else if (opts.test) {
cmd = JSDoc.runTests;
cmd = cli.runTests;
}
else if (opts.version) {
cmd = JSDoc.printVersion;
cmd = function(callback) { callback(); };
}
else {
cmd = JSDoc.main;
cmd = cli.main;
}
cmd(cb);
cmd(done);
};
// TODO: docs
JSDoc.printHelp = function(cb) {
JSDoc.printVersion(function() {
console.log( '\n' + require('jsdoc/opts/args').help() );
console.log('\n' + 'Visit http://usejsdoc.org for more information.');
cb(0);
});
cli.printHelp = function(cb) {
console.log( '\n' + require('jsdoc/opts/args').help() + '\n' );
console.log('Visit http://usejsdoc.org for more information.');
cb(0);
};
// TODO: docs
JSDoc.runTests = function(cb) {
cli.runTests = function(cb) {
var path = require('jsdoc/path');
var runner = require( path.join(env.dirname, 'test/runner') );
@ -127,17 +200,26 @@ JSDoc.runTests = function(cb) {
};
// TODO: docs
JSDoc.printVersion = function(cb) {
console.log('JSDoc %s (%s)', env.version.number, env.version.revision);
cb(0);
cli.getVersion = function() {
return 'JSDoc ' + env.version.number + ' (' + env.version.revision + ')';
};
// TODO: docs
JSDoc.main = function(cb) {
JSDoc.scanFiles();
cli.printVersion = function(loggerFunc, cb) {
loggerFunc = loggerFunc || logger.info;
loggerFunc.call( null, cli.getVersion() );
if (cb) {
cb(0);
}
};
// TODO: docs
cli.main = function(cb) {
cli.scanFiles();
if (env.sourceFiles.length) {
JSDoc.createParser()
cli.createParser()
.parseFiles()
.processParseResults();
}
@ -146,7 +228,8 @@ JSDoc.main = function(cb) {
cb(0);
};
JSDoc.scanFiles = function() {
// TODO: docs
cli.scanFiles = function() {
var Filter = require('jsdoc/src/filter').Filter;
var fs = require('jsdoc/fs');
var Readme = require('jsdoc/readme');
@ -181,10 +264,10 @@ JSDoc.scanFiles = function() {
filter);
}
return JSDoc;
return cli;
};
JSDoc.createParser = function() {
cli.createParser = function() {
var handlers = require('jsdoc/src/handlers');
var parser = require('jsdoc/src/parser');
var plugins = require('jsdoc/plugins');
@ -197,10 +280,10 @@ JSDoc.createParser = function() {
handlers.attachTo(app.jsdoc.parser);
return JSDoc;
return cli;
};
JSDoc.parseFiles = function() {
cli.parseFiles = function() {
var augment = require('jsdoc/augment');
var borrow = require('jsdoc/borrow');
var Package = require('jsdoc/package').Package;
@ -216,40 +299,46 @@ JSDoc.parseFiles = function() {
packageDocs.files = env.sourceFiles || [];
docs.push(packageDocs);
logger.debug('Adding inherited symbols...');
borrow.indexAll(docs);
augment.addInherited(docs);
borrow.resolveBorrows(docs);
app.jsdoc.parser.fireProcessingComplete(docs);
return JSDoc;
return cli;
};
JSDoc.processParseResults = function() {
cli.processParseResults = function() {
if (env.opts.explain) {
JSDoc.dumpParseResults();
cli.dumpParseResults();
}
else {
JSDoc.resolveTutorials();
JSDoc.generateDocs();
cli.resolveTutorials();
cli.generateDocs();
}
return cli;
};
JSDoc.dumpParseResults = function() {
cli.dumpParseResults = function() {
global.dump(props.docs);
return cli;
};
JSDoc.resolveTutorials = function() {
cli.resolveTutorials = function() {
var resolver = require('jsdoc/tutorial/resolver');
if (env.opts.tutorials) {
resolver.load(env.opts.tutorials);
resolver.resolve();
}
return cli;
};
JSDoc.generateDocs = function() {
cli.generateDocs = function() {
var path = require('jsdoc/path');
var resolver = require('jsdoc/tutorial/resolver');
var taffy = require('taffydb').taffy;
@ -266,26 +355,38 @@ JSDoc.generateDocs = function() {
template = require(env.opts.template + '/publish');
}
catch(e) {
throw new Error('Unable to load template: ' + e.message || e);
logger.fatal('Unable to load template: ' + e.message || e);
}
// templates should include a publish.js file that exports a "publish" function
if (template.publish && typeof template.publish === 'function') {
// convert this from a URI back to a path if necessary
env.opts.template = path._uriToPath(env.opts.template);
logger.printInfo('Generating output files...');
template.publish(
taffy(props.docs),
env.opts,
resolver.root
);
logger.info('complete.');
}
else {
throw new Error(env.opts.template + ' does not export a "publish" function. Global ' +
logger.fatal(env.opts.template + ' does not export a "publish" function. Global ' +
'"publish" functions are no longer supported.');
}
return cli;
};
return JSDoc;
// TODO: docs
cli.exit = function(exitCode, errorMessage) {
if (errorMessage) {
logger.fatal(errorMessage);
}
process.exit(exitCode || 0);
};
return cli;
})();

3
jsdoc
View File

@ -28,10 +28,7 @@ ENCODEDBASEPATH=`echo "$BASEPATH" | sed -e 's/ /%20/g'`
if test "$1" = "--debug"
then
echo "Running Debug"
CMD="org.mozilla.javascript.tools.debugger.Main -debug -opt -1"
# strip --debug argument
shift
else
CMD="org.mozilla.javascript.tools.shell.Main"
fi

View File

@ -25,18 +25,10 @@ IF NOT "%_URLPATH%"=="%_URLPATH: =%" GOTO ESCAPE_SPACE
IF [%1]==[--debug] (
ECHO Running Debug
SET CMD=org.mozilla.javascript.tools.debugger.Main -debug -opt -1
REM `SHIFT` doesn't affect %*
:COLLECT_ARGS
IF [%2]==[] GOTO LAST_ARG
SET ARGS=%ARGS% %2
SHIFT
GOTO COLLECT_ARGS
) ELSE (
SET CMD=org.mozilla.javascript.tools.shell.Main
SET ARGS=%*
)
:LAST_ARG
SET ARGS=%*
IF [%1]==[-T] (
java -classpath "%_BASEPATH%/rhino/js.jar" %CMD% -opt -1 -modules "%_URLPATH%/lib" -modules "%_URLPATH%/node_modules" -modules "%_URLPATH%/rhino" -modules "%_URLPATH%" "%_BASEPATH%/jsdoc.js" %ARGS% --nocolor --dirname="%_BASEPATH%/

View File

@ -126,10 +126,7 @@ global.dump = function() {
(function() {
'use strict';
function cb(errorCode) {
process.exit(errorCode || 0);
}
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
var runtime = require('jsdoc/util/runtime');
@ -138,6 +135,17 @@ global.dump = function() {
cli.setVersionInfo()
.loadConfig();
if (!global.env.opts.test) {
cli.configureLogger();
}
cli.logStart();
function cb(errorCode) {
cli.logFinish();
cli.exit(errorCode || 0);
}
// On Rhino, we use a try/catch block so we can log the Java exception (if available)
if ( runtime.isRhino() ) {
try {
@ -145,11 +153,10 @@ global.dump = function() {
}
catch(e) {
if (e.rhinoException) {
e.rhinoException.printStackTrace();
process.exit(1);
logger.fatal( e.rhinoException.printStackTrace() );
} else {
console.trace(e);
process.exit(1);
cli.exit(1);
}
}
}

View File

@ -6,7 +6,8 @@
*/
'use strict';
var doop = require("jsdoc/util/doop").doop;
var doop = require('jsdoc/util/doop');
var logger = require('jsdoc/util/logger');
var hasOwnProp = Object.prototype.hasOwnProperty;
@ -31,7 +32,8 @@ exports.indexAll = function(docs) {
*/
exports.resolveBorrows = function(docs) {
if (!docs.index) {
throw 'Docs has not been indexed: docs.index must be defined here.';
logger.error('Unable to resolve borrowed symbols, because the docs have not been indexed.');
return;
}
docs.forEach(function(doc) {

View File

@ -1,8 +1,8 @@
/**
Parse the command line arguments.
@module jsdoc/opts/argparser
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
* Parse the command line arguments.
* @module jsdoc/opts/argparser
* @author Michael Mathews <micmath@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
@ -11,29 +11,45 @@ var _ = require('underscore');
var hasOwnProp = Object.prototype.hasOwnProperty;
/**
Create an instance of the parser.
@classdesc A parser to interpret the key-value pairs entered on the command
line.
@constructor
* Create an instance of the parser.
* @classdesc A parser to interpret the key-value pairs entered on the command line.
* @constructor
*/
var ArgParser = function() {
this._options = [];
this._shortNameIndex = {};
this._longNameIndex = {};
this._options = [];
this._shortNameIndex = {};
this._longNameIndex = {};
};
ArgParser.prototype._getOptionByShortName = function(name) {
if (hasOwnProp.call(this._shortNameIndex, name)) {
return this._options[this._shortNameIndex[name]];
}
return null;
if (hasOwnProp.call(this._shortNameIndex, name)) {
return this._options[this._shortNameIndex[name]];
}
return null;
};
ArgParser.prototype._getOptionByLongName = function(name) {
if (hasOwnProp.call(this._longNameIndex, name)) {
return this._options[this._longNameIndex[name]];
}
return null;
if (hasOwnProp.call(this._longNameIndex, name)) {
return this._options[this._longNameIndex[name]];
}
return null;
};
ArgParser.prototype._addOption = function(option) {
var currentIndex;
var longName = option.longName;
var shortName = option.shortName;
this._options.push(option);
currentIndex = this._options.length - 1;
if (shortName) {
this._shortNameIndex[shortName] = currentIndex;
}
if (longName) {
this._longNameIndex[longName] = currentIndex;
}
};
/**
@ -49,209 +65,234 @@ ArgParser.prototype._getOptionByLongName = function(name) {
* myParser.addOption('h', 'help', false, 'Show the help message.');
*/
ArgParser.prototype.addOption = function(shortName, longName, hasValue, helpText, canHaveMultiple, coercer) {
this._options.push({
shortName: shortName,
longName: longName,
hasValue: hasValue,
helpText: helpText,
canHaveMultiple: (canHaveMultiple || false),
coercer: coercer
});
if (shortName) {
this._shortNameIndex[shortName] = this._options.length - 1;
}
if (longName) {
this._longNameIndex[longName] = this._options.length - 1;
}
var option = {
shortName: shortName,
longName: longName,
hasValue: hasValue,
helpText: helpText,
canHaveMultiple: (canHaveMultiple || false),
coercer: coercer
};
this._addOption(option);
};
// TODO: refactor addOption to accept objects, then get rid of this method
/**
* Provide information about an option that should not cause an error if present, but that is always
* ignored (for example, an option that was used in previous versions but is no longer supported).
*
* @private
* @param {string} shortName - The short name of the option with a leading hyphen (for example,
* `-v`).
* @param {string} longName - The long name of the option with two leading hyphens (for example,
* `--version`).
*/
ArgParser.prototype.addIgnoredOption = function(shortName, longName) {
var option = {
shortName: shortName,
longName: longName,
ignore: true
};
this._addOption(option);
};
function padding(length) {
return new Array(length + 1).join(' ');
return new Array(length + 1).join(' ');
}
function padLeft(str, length) {
return padding(length) + str;
return padding(length) + str;
}
function padRight(str, length) {
return str + padding(length);
return str + padding(length);
}
function findMaxLength(arr) {
var max = 0;
var max = 0;
arr.forEach(function(item) {
if (item.length > max) {
max = item.length;
}
});
arr.forEach(function(item) {
if (item.length > max) {
max = item.length;
}
});
return max;
return max;
}
function concatWithMaxLength(items, maxLength) {
var result = '';
// to prevent endless loops, always use the first item, regardless of length
result += items.shift();
var result = '';
// to prevent endless loops, always use the first item, regardless of length
result += items.shift();
while ( items.length && (result.length + items[0].length < maxLength) ) {
result += ' ' + items.shift();
}
while ( items.length && (result.length + items[0].length < maxLength) ) {
result += ' ' + items.shift();
}
return result;
return result;
}
// we want to format names and descriptions like this:
// | -f, --foo Very long description very long description very long |
// | description very long description. |
function formatHelpInfo(options) {
var MARGIN_LENGTH = 4;
var results = [];
var MARGIN_LENGTH = 4;
var results = [];
var maxLength = process.stdout.columns;
var maxNameLength = findMaxLength(options.names);
var maxDescriptionLength = findMaxLength(options.descriptions);
var maxLength = process.stdout.columns;
var maxNameLength = findMaxLength(options.names);
var maxDescriptionLength = findMaxLength(options.descriptions);
var wrapDescriptionAt = maxLength - (MARGIN_LENGTH * 3) - maxNameLength;
// build the string for each option
options.names.forEach(function(name, i) {
var result;
var partialDescription;
var words;
var wrapDescriptionAt = maxLength - (MARGIN_LENGTH * 3) - maxNameLength;
// build the string for each option
options.names.forEach(function(name, i) {
var result;
var partialDescription;
var words;
// add a left margin to the name
result = padLeft(options.names[i], MARGIN_LENGTH);
// and a right margin, with extra padding so the descriptions line up with one another
result = padRight(result, maxNameLength - options.names[i].length + MARGIN_LENGTH);
// add a left margin to the name
result = padLeft(options.names[i], MARGIN_LENGTH);
// and a right margin, with extra padding so the descriptions line up with one another
result = padRight(result, maxNameLength - options.names[i].length + MARGIN_LENGTH);
// split the description on spaces
words = options.descriptions[i].split(' ');
// add as much of the description as we can fit on the first line
result += concatWithMaxLength(words, wrapDescriptionAt);
// if there's anything left, keep going until we've consumed the description
while (words.length) {
partialDescription = padding( maxNameLength + (MARGIN_LENGTH * 2) );
partialDescription += concatWithMaxLength(words, wrapDescriptionAt);
result += '\n' + partialDescription;
}
// split the description on spaces
words = options.descriptions[i].split(' ');
// add as much of the description as we can fit on the first line
result += concatWithMaxLength(words, wrapDescriptionAt);
// if there's anything left, keep going until we've consumed the description
while (words.length) {
partialDescription = padding( maxNameLength + (MARGIN_LENGTH * 2) );
partialDescription += concatWithMaxLength(words, wrapDescriptionAt);
result += '\n' + partialDescription;
}
results.push(result);
});
results.push(result);
});
return results;
return results;
}
/**
Generate a summary of all the options with corresponding help text.
@returns {string}
* Generate a summary of all the options with corresponding help text.
* @returns {string}
*/
ArgParser.prototype.help = function() {
var options = {
names: [],
descriptions: []
};
var options = {
names: [],
descriptions: []
};
this._options.forEach(function(option) {
var name = '';
this._options.forEach(function(option) {
var name = '';
if (option.shortName) {
name += '-' + option.shortName + (option.longName ? ', ' : '');
}
// don't show ignored options
if (option.ignore) {
return;
}
if (option.longName) {
name += '--' + option.longName;
}
if (option.shortName) {
name += '-' + option.shortName + (option.longName ? ', ' : '');
}
if (option.hasValue) {
name += ' <value>';
}
if (option.longName) {
name += '--' + option.longName;
}
options.names.push(name);
options.descriptions.push(option.helpText);
});
if (option.hasValue) {
name += ' <value>';
}
return 'Options:\n' + formatHelpInfo(options).join('\n');
options.names.push(name);
options.descriptions.push(option.helpText);
});
return 'Options:\n' + formatHelpInfo(options).join('\n');
};
/**
Get the options.
@param {Array.<string>} args An array, like ['-x', 'hello']
@param {Object} [defaults={}] An optional collection of default values.
@returns {Object} The keys will be the longNames, or the shortName if
no longName is defined for that option. The values will be the values
provided, or `true` if the option accepts no value.
* Get the options.
* @param {Array.<string>} args An array, like ['-x', 'hello']
* @param {Object} [defaults={}] An optional collection of default values.
* @returns {Object} The keys will be the longNames, or the shortName if no longName is defined for
* that option. The values will be the values provided, or `true` if the option accepts no value.
*/
ArgParser.prototype.parse = function(args, defaults) {
var result = defaults && _.defaults({}, defaults) || {};
var result = defaults && _.defaults({}, defaults) || {};
result._ = [];
for (var i = 0, leni = args.length; i < leni; i++) {
var arg = '' + args[i],
next = (i < leni-1)? '' + args[i+1] : null,
option,
shortName = null,
longName,
name,
value = null;
result._ = [];
for (var i = 0, leni = args.length; i < leni; i++) {
var arg = '' + args[i],
next = (i < leni-1)? '' + args[i+1] : null,
option,
shortName = null,
longName,
name,
value = null;
// like -t
if (arg.charAt(0) === '-') {
// like -t
if (arg.charAt(0) === '-') {
// like --template
if (arg.charAt(1) === '-') {
name = longName = arg.slice(2);
option = this._getOptionByLongName(longName);
}
else {
name = shortName = arg.slice(1);
option = this._getOptionByShortName(shortName);
}
// like --template
if (arg.charAt(1) === '-') {
name = longName = arg.slice(2);
option = this._getOptionByLongName(longName);
}
else {
name = shortName = arg.slice(1);
option = this._getOptionByShortName(shortName);
}
if (option === null) {
throw new Error( 'Unknown command line option found: ' + name );
}
if (option === null) {
throw new Error( 'Unknown command line option found: ' + name );
}
if (option.hasValue) {
value = next;
i++;
if (option.hasValue) {
value = next;
i++;
if (value === null || value.charAt(0) === '-') {
throw new Error( 'Command line option requires a value: ' + name );
}
}
else {
value = true;
}
if (value === null || value.charAt(0) === '-') {
throw new Error( 'Command line option requires a value: ' + name );
}
}
else {
value = true;
}
if (option.longName && shortName) {
name = option.longName;
}
// skip ignored options now that we've consumed the option text
if (option.ignore) {
continue;
}
if (typeof option.coercer === 'function') {
value = option.coercer(value);
}
// Allow for multiple options of the same type to be present
if (option.canHaveMultiple && hasOwnProp.call(result, name)) {
var val = result[name];
if (val instanceof Array) {
val.push(value);
} else {
result[name] = [val, value];
}
}
else {
result[name] = value;
}
}
else {
result._.push(arg);
}
}
if (option.longName && shortName) {
name = option.longName;
}
return result;
if (typeof option.coercer === 'function') {
value = option.coercer(value);
}
// Allow for multiple options of the same type to be present
if (option.canHaveMultiple && hasOwnProp.call(result, name)) {
var val = result[name];
if (val instanceof Array) {
val.push(value);
} else {
result[name] = [val, value];
}
}
else {
result[name] = value;
}
}
else {
result._.push(arg);
}
}
return result;
};
module.exports = ArgParser;

View File

@ -1,67 +1,76 @@
/**
@module jsdoc/opts/args
@requires jsdoc/opts/argparser
@author Michael Mathews <micmath@gmail.com>
@license Apache License 2.0 - See file 'LICENSE.md' in this project.
* @module jsdoc/opts/args
* @requires jsdoc/opts/argparser
* @author Michael Mathews <micmath@gmail.com>
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
'use strict';
var ArgParser = require('jsdoc/opts/argparser'),
argParser = new ArgParser(),
hasOwnProp = Object.prototype.hasOwnProperty,
ourOptions,
querystring = require('querystring'),
util = require('util');
var ArgParser = require('jsdoc/opts/argparser');
var querystring = require('querystring');
var util = require('util');
var ourOptions;
var argParser = new ArgParser();
var hasOwnProp = Object.prototype.hasOwnProperty;
// cast strings to booleans or integers where appropriate
function castTypes(item) {
var result = item;
var integer;
switch (result) {
case 'true':
return true;
case 'false':
return false;
default:
// might be an integer
var integer = parseInt(result, 10);
if (String(integer) === result && integer !== 'NaN') {
return integer;
} else {
return result;
}
}
var result = item;
switch (result) {
case 'true':
result = true;
break;
case 'false':
result = false;
break;
default:
// might be an integer
integer = parseInt(result, 10);
if (String(integer) === result && integer !== 'NaN') {
result = integer;
}
}
return result;
}
// check for strings that we need to cast to other types
function fixTypes(item) {
var result = item;
var result = item;
// recursively process arrays and objects
if ( util.isArray(result) ) {
for (var i = 0, l = result.length; i < l; i++) {
result[i] = fixTypes(result[i]);
}
} else if (typeof result === 'object') {
Object.keys(result).forEach(function(prop) {
result[prop] = fixTypes(result[prop]);
});
} else {
result = castTypes(result);
}
// recursively process arrays and objects
if ( util.isArray(result) ) {
for (var i = 0, l = result.length; i < l; i++) {
result[i] = fixTypes(result[i]);
}
}
else if (typeof result === 'object') {
Object.keys(result).forEach(function(prop) {
result[prop] = fixTypes(result[prop]);
});
}
else {
result = castTypes(result);
}
return result;
return result;
}
function parseQuery(str) {
var result = querystring.parse(str);
var result = querystring.parse(str);
Object.keys(result).forEach(function(prop) {
result[prop] = fixTypes(result[prop]);
});
Object.keys(result).forEach(function(prop) {
result[prop] = fixTypes(result[prop]);
});
return result;
return result;
}
argParser.addOption('t', 'template', true, 'The path to the template to use. Default: path/to/jsdoc/templates/default');
@ -71,58 +80,59 @@ argParser.addOption('T', 'test', false, 'Run all tests and quit.');
argParser.addOption('d', 'destination', true, 'The path to the output folder. Use "console" to dump data to the console. Default: ./out/');
argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Default: false');
argParser.addOption('r', 'recurse', false, 'Recurse into subdirectories when scanning for source code files.');
argParser.addOption('l', 'lenient', false, 'Continue to generate output if a doclet is incomplete or contains errors. Default: false');
argParser.addOption('h', 'help', false, 'Print this message and quit.');
argParser.addOption('X', 'explain', false, 'Dump all found doclet internals to console and quit.');
argParser.addOption('q', 'query', true, 'A query string to parse and store in env.opts.query. Example: foo=bar&baz=true', false, parseQuery);
argParser.addOption('u', 'tutorials', true, 'Directory in which JSDoc should search for tutorials.');
argParser.addOption('v', 'version', false, 'Display the version number and quit.');
argParser.addOption('', 'debug', false, 'Log information for debugging JSDoc. On Rhino, launches the debugger when passed as the first option.');
argParser.addOption('', 'verbose', false, 'Log detailed information to the console as JSDoc runs.');
argParser.addOption('', 'pedantic', false, 'Treat errors as fatal errors, and treat warnings as errors. Default: false');
//TODO [-R, recurseonly] = a number representing the depth to recurse
//TODO [-f, filter] = a regex to filter on <-- this can be better defined in the configs?
// Options specific to tests
argParser.addOption(null, 'match', true, 'Only run tests containing <value>.', true);
argParser.addOption(null, 'nocolor', false, 'Do not use color in console output from tests.');
//Here are options specific to tests
argParser.addOption(null, 'verbose', false, 'Display verbose output for tests');
argParser.addOption(null, 'match', true, 'Only run tests containing <value>', true);
argParser.addOption(null, 'nocolor', false, 'Do not use color in console output from tests');
// Options that are no longer supported and should be ignored
argParser.addIgnoredOption('l', 'lenient'); // removed in JSDoc 3.3.0
/**
Set the options for this app.
@throws {Error} Illegal arguments will throw errors.
@param {string|String[]} args The command line arguments for this app.
* Set the options for this app.
* @throws {Error} Illegal arguments will throw errors.
* @param {string|String[]} args The command line arguments for this app.
*/
exports.parse = function(args) {
args = args || [];
args = args || [];
if (typeof args === 'string' || args.constructor === String) {
args = (''+args).split(/\s+/g);
}
if (typeof args === 'string' || args.constructor === String) {
args = (''+args).split(/\s+/g);
}
ourOptions = argParser.parse(args);
ourOptions = argParser.parse(args);
return ourOptions;
return ourOptions;
};
/**
Display help message for options.
* Retrieve help message for options.
*/
exports.help = function() {
return argParser.help();
};
/**
Get a named option.
@param {string} name The name of the option.
@return {string} The value associated with the given name.
* Get a named option.
* @param {string} name The name of the option.
* @return {string} The value associated with the given name.
*//**
Get all the options for this app.
@return {Object} A collection of key/values representing all the options.
* Get all the options for this app.
* @return {Object} A collection of key/values representing all the options.
*/
exports.get = function(name) {
if (typeof name === 'undefined') {
return ourOptions;
}
else {
return ourOptions[name];
}
if (typeof name === 'undefined') {
return ourOptions;
}
else if ( hasOwnProp.call(ourOptions, name) ) {
return ourOptions[name];
}
};

View File

@ -5,7 +5,7 @@
*/
'use strict';
var error = require('jsdoc/util/error');
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
exports.installPlugins = function(plugins, parser) {
@ -19,8 +19,7 @@ exports.installPlugins = function(plugins, parser) {
pluginPath = path.getResourcePath(path.dirname(plugins[i]), path.basename(plugins[i]));
if (!pluginPath) {
error.handle(new Error('Unable to find the plugin "' + plugins[i] + '"'));
continue;
logger.error('Unable to find the plugin "%s"', plugins[i]);
}
plugin = require(pluginPath);
@ -41,8 +40,8 @@ exports.installPlugins = function(plugins, parser) {
//...add a Rhino node visitor (deprecated in JSDoc 3.3)
if (plugin.nodeVisitor) {
if ( !parser.addNodeVisitor ) {
error.handle( new Error('Unable to add the Rhino node visitor from ' + plugins[i] +
', because JSDoc is not using the Rhino JavaScript parser.') );
logger.error('Unable to add the Rhino node visitor from %s, because JSDoc ' +
'is not using the Rhino JavaScript parser.', plugins[i]);
}
else {
parser.addNodeVisitor(plugin.nodeVisitor);

View File

@ -143,6 +143,7 @@ var nodeToString = exports.nodeToString = function(node) {
str = node.operator + str;
}
else {
// this shouldn't happen
throw new Error('Found a UnaryExpression with a postfix operator: %j', node);
}
break;

View File

@ -18,7 +18,7 @@ function getNewDoclet(comment, e) {
catch (error) {
err = new Error( util.format('cannot create a doclet for the comment "%s": %s',
comment.replace(/[\r\n]/g, ''), error.message) );
require('jsdoc/util/error').handle(err);
require('jsdoc/util/logger').error(err);
doclet = new Doclet('', e);
}

View File

@ -12,6 +12,7 @@ var jsdoc = {
syntax: require('jsdoc/src/syntax')
}
};
var logger = require('jsdoc/util/logger');
var util = require('util');
var hasOwnProp = Object.prototype.hasOwnProperty;
@ -63,7 +64,7 @@ exports.createParser = function(type) {
return new ( require(modulePath) ).Parser();
}
catch (e) {
throw new Error('Unable to create the parser type "' + type + '": ' + e);
logger.fatal('Unable to create the parser type "' + type + '": ' + e);
}
};
@ -142,6 +143,7 @@ Parser.prototype.parse = function(sourceFiles, encoding) {
}
e.sourcefiles = sourceFiles;
logger.debug('Parsing source files: %j', sourceFiles);
this.emit('parseBegin', e);
@ -158,10 +160,7 @@ Parser.prototype.parse = function(sourceFiles, encoding) {
sourceCode = require('jsdoc/fs').readFileSync(filename, encoding);
}
catch(e) {
// TODO: shouldn't this be fatal if we're not in lenient mode?
console.error('FILE READ ERROR: in module:jsdoc/src/parser.Parser#parse: "' +
filename + '" ' + e);
continue;
logger.error('Unable to read and parse the source file %s: %s', filename, e);
}
}
@ -175,6 +174,7 @@ Parser.prototype.parse = function(sourceFiles, encoding) {
sourcefiles: parsedFiles,
doclets: this._resultBuffer
});
logger.debug('Finished parsing source files.');
return this._resultBuffer;
};
@ -235,6 +235,7 @@ Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
};
this.emit('fileBegin', e);
logger.printInfo('Parsing %s ...', sourceName);
if (!e.defaultPrevented) {
e = {
@ -252,6 +253,7 @@ Parser.prototype._parseSourceCode = function(sourceCode, sourceName) {
}
this.emit('fileComplete', e);
logger.info('complete.');
};
// TODO: docs

View File

@ -9,6 +9,7 @@
'use strict';
var fs = require('jsdoc/fs');
var logger = require('jsdoc/util/logger');
var path = require('jsdoc/path');
/**
@ -25,6 +26,7 @@ exports.Scanner.prototype = Object.create( require('events').EventEmitter.protot
@fires sourceFileFound
*/
exports.Scanner.prototype.scan = function(searchPaths, depth, filter) {
var currentFile;
var isFile;
var filePaths = [];
@ -38,13 +40,14 @@ exports.Scanner.prototype.scan = function(searchPaths, depth, filter) {
var filepath = path.resolve( pwd, decodeURIComponent($) );
try {
isFile = fs.statSync(filepath).isFile();
currentFile = fs.statSync(filepath);
}
catch(e) {
isFile = false;
catch (e) {
logger.error('Unable to find the source file or directory %s', filepath);
return;
}
if (isFile) {
if ( currentFile.isFile() ) {
filePaths.push(filepath);
}
else {

View File

@ -19,8 +19,12 @@ var jsdoc = {
dictionary: require('jsdoc/tag/dictionary'),
validator: require('jsdoc/tag/validator'),
type: require('jsdoc/tag/type')
},
util: {
logger: require('jsdoc/util/logger')
}
};
var path = require('jsdoc/path');
function trim(text, opts) {
var indentMatcher;
@ -117,8 +121,19 @@ var Tag = exports.Tag = function(tagTitle, tagBody, meta) {
this.text = trim(tagBody, trimOpts);
if (this.text) {
processTagText(this, tagDef);
try {
processTagText(this, tagDef);
jsdoc.tag.validator.validate(this, tagDef, meta);
}
catch (e) {
// probably a type-parsing error
jsdoc.util.logger.error(
'Unable to create a Tag object%s with title "%s" and body "%s": %s',
meta.filename ? ( ' for source file ' + path.join(meta.path, meta.filename) ) : '',
tagTitle,
tagBody,
e.message
);
}
}
jsdoc.tag.validator.validate(this, tagDef, meta);
};

View File

@ -239,8 +239,8 @@ function parseTypeExpression(tagInfo) {
}
catch (e) {
// always re-throw so the caller has a chance to report which file was bad
throw new Error( util.format('unable to parse the type expression "%s": %s',
tagInfo.typeExpression, e.message) );
throw new Error( util.format('Invalid type expression "%s": %s', tagInfo.typeExpression,
e.message) );
}
if (parsedType) {

View File

@ -10,6 +10,7 @@
var dictionary = require('jsdoc/tag/dictionary');
var format = require('util').format;
var logger = require('jsdoc/util/logger');
function buildMessage(tagName, meta, desc) {
var result = format('The @%s tag %s. File: %s, line: %s', tagName, desc, meta.filename,
@ -20,43 +21,21 @@ function buildMessage(tagName, meta, desc) {
return result;
}
function UnknownTagError(tagName, meta) {
this.name = 'UnknownTagError';
this.message = buildMessage(tagName, meta, 'is not a known tag');
}
UnknownTagError.prototype = new Error();
UnknownTagError.prototype.constructor = UnknownTagError;
function TagValueRequiredError(tagName, meta) {
this.name = 'TagValueRequiredError';
this.message = buildMessage(tagName, meta, 'requires a value');
}
TagValueRequiredError.prototype = new Error();
TagValueRequiredError.prototype.constructor = TagValueRequiredError;
function TagValueNotPermittedError(tagName, meta) {
this.name = 'TagValueNotPermittedError';
this.message = buildMessage(tagName, meta, 'does not permit a value');
}
TagValueNotPermittedError.prototype = new Error();
TagValueNotPermittedError.prototype.constructor = TagValueNotPermittedError;
/**
* Validate the given tag.
*/
exports.validate = function(tag, tagDef, meta) {
if (!tagDef && !env.conf.tags.allowUnknownTags) {
require('jsdoc/util/error').handle( new UnknownTagError(tag.title, meta) );
logger.error( buildMessage(tag.title, meta, 'is not a known tag') );
}
if (!tag.text) {
else if (!tag.text) {
if (tagDef.mustHaveValue) {
require('jsdoc/util/error').handle( new TagValueRequiredError(tag.title, meta) );
logger.error( buildMessage(tag.title, meta, 'requires a value') );
}
}
else {
if (tagDef.mustNotHaveValue) {
require('jsdoc/util/error').handle( new TagValueNotPermittedError(tag.title, meta) );
logger.error( buildMessage(tag.title, meta, 'does not permit a value') );
}
}
};

View File

@ -12,7 +12,7 @@
var tutorial = require('jsdoc/tutorial'),
fs = require('jsdoc/fs'),
error = require('jsdoc/util/error'),
logger = require('jsdoc/util/logger'),
path = require('path'),
hasOwnProp = Object.prototype.hasOwnProperty,
conf = {},
@ -61,7 +61,7 @@ function addTutorialConf(name, meta) {
}
// check if the tutorial has already been defined...
if (hasOwnProp.call(conf, name)) {
error.handle(new Error("Tutorial " + name + "'s metadata is defined multiple times, only the first will be used."));
logger.warn('Metadata for the tutorial %s is defined more than once. Only the first definition will be used.', name );
} else {
conf[name] = meta;
}
@ -79,7 +79,7 @@ function addTutorialConf(name, meta) {
*/
exports.addTutorial = function(current) {
if (hasOwnProp.call(tutorials, current.name)) {
error.handle(new Error("Tutorial with name " + current.name + " exists more than once, not adding (same name, different file extensions?)"));
logger.warn('The tutorial %s is defined more than once. Only the first definition will be used.', current.name);
} else {
tutorials[current.name] = current;
@ -179,7 +179,7 @@ exports.resolve = function() {
if (item.children) {
item.children.forEach(function(child) {
if (!hasOwnProp.call(tutorials, child)) {
error.handle( new Error("Missing child tutorial: " + child) );
logger.error('Missing child tutorial: %s', child);
}
else {
tutorials[child].setParent(current);

View File

@ -1,33 +1,35 @@
/*global env: true */
/**
Helper functions for handling errors.
@module jsdoc/util/error
* Helper functions for handling errors.
*
* @deprecated As of JSDoc 3.3.0. This module may be removed in a future release. Use the module
* {@link module:jsdoc/util/logger} to log warnings and errors.
* @module jsdoc/util/error
*/
'use strict';
/**
Handle an exception appropriately based on whether lenient mode is enabled:
+ If lenient mode is enabled, log the exception to the console.
+ If lenient mode is not enabled, re-throw the exception.
@param {Error} e - The exception to handle.
@exception {Error} Re-throws the original exception unless lenient mode is enabled.
@memberof module:jsdoc/util/error
* Log an exception as an error.
*
* Prior to JSDoc 3.3.0, this method would either log the exception (if lenient mode was enabled) or
* re-throw the exception (default).
*
* In JSDoc 3.3.0 and later, lenient mode has been replaced with strict mode, which is disabled by
* default. If strict mode is enabled, calling the `handle` method causes JSDoc to exit immediately,
* just as if the exception had been re-thrown.
*
* @deprecated As of JSDoc 3.3.0. This module may be removed in a future release.
* @param {Error} e - The exception to log.
* @memberof module:jsdoc/util/error
*/
exports.handle = function(e) {
var msg;
var logger = require('jsdoc/util/logger');
var msg = e ? ( e.message || JSON.stringify(e) ) : '';
if (env.opts.lenient) {
msg = e.message || JSON.stringify(e);
// include the error type if it's an Error object
if (e instanceof Error) {
msg = e.name + ': ' + msg;
}
// include the error type if it's an Error object
if (e instanceof Error) {
msg = e.name + ': ' + msg;
}
console.log(msg);
}
else {
throw e;
}
logger.error(msg);
};

232
lib/jsdoc/util/logger.js Normal file
View File

@ -0,0 +1,232 @@
/**
* Logging tools for JSDoc.
*
* Log messages are printed to the console based on the current logging level. By default, messages
* at level `{@link module:jsdoc/util/logger.LEVELS.ERROR}` or above are logged; all other messages
* are ignored.
*
* In addition, the module object emits an event whenever a logger method is called, regardless of
* the current logging level. The event's name is the string `logger:` followed by the logger's name
* (for example, `logger:error`). The event handler receives an array of arguments that were passed
* to the logger method.
*
* Each logger method accepts a `message` parameter that may contain zero or more placeholders. Each
* placeholder is replaced by the corresponding argument following the message. If the placeholder
* does not have a corresponding argument, the placeholder is not replaced.
*
* The following placeholders are supported:
*
* + `%s`: String.
* + `%d`: Number.
* + `%j`: JSON.
*
* @module jsdoc/util/logger
* @extends module:events.EventEmitter
* @example
* var logger = require('jsdoc/util/logger');
*
* var data = {
* foo: 'bar'
* };
* var name = 'baz';
*
* logger.warn('%j %s', data, name); // prints '{"foo":"bar"} baz'
* @see http://nodejs.org/api/util.html#util_util_format_format
*/
'use strict';
var runtime = require('jsdoc/util/runtime');
var util = require('util');
function Logger() {}
util.inherits(Logger, require('events').EventEmitter);
var logger = module.exports = new Logger();
/**
* Logging levels for the JSDoc logger. The default logging level is
* {@link module:jsdoc/util/logger.LEVELS.ERROR}.
*
* @enum
* @type {number}
*/
var LEVELS = logger.LEVELS = {
/** Do not log any messages. */
SILENT: 0,
/** Log fatal errors that prevent JSDoc from running. */
FATAL: 10,
/** Log all errors, including errors from which JSDoc can recover. */
ERROR: 20,
/**
* Log the following messages:
*
* + Warnings
* + Errors
*/
WARN: 30,
/**
* Log the following messages:
*
* + Informational messages
* + Warnings
* + Errors
*/
INFO: 40,
/**
* Log the following messages:
*
* + Debugging messages
* + Informational messages
* + Warnings
* + Errors
*/
DEBUG: 50,
/** Log all messages. */
VERBOSE: 1000
};
var DEFAULT_LEVEL = LEVELS.ERROR;
var logLevel = DEFAULT_LEVEL;
var PREFIXES = {
DEBUG: 'DEBUG: ',
ERROR: 'ERROR: ',
FATAL: 'FATAL: ',
WARN: 'WARNING: '
};
// Add a prefix to a log message if necessary.
function addPrefix(args, prefix) {
var updatedArgs;
if (prefix && typeof args[0] === 'string') {
updatedArgs = args.slice(0);
updatedArgs[0] = prefix + updatedArgs[0];
}
return updatedArgs || args;
}
// TODO: document events
function wrapLogFunction(name, func) {
var eventName = 'logger:' + name;
var upperCaseName = name.toUpperCase();
var level = LEVELS[upperCaseName];
var prefix = PREFIXES[upperCaseName];
return function() {
var loggerArgs;
var args = Array.prototype.slice.call(arguments, 0);
if (logLevel >= level) {
loggerArgs = addPrefix(args, prefix);
func.apply(null, loggerArgs);
}
args.unshift(eventName);
logger.emit.apply(logger, args);
};
}
// Print a message to STDOUT without a terminating newline.
function printToStdout() {
var args = Array.prototype.slice.call(arguments, 0);
util.print( util.format.apply(util, args) );
}
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.DEBUG}.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.debug = wrapLogFunction('debug', console.info);
/**
* Print a string at log level {@link module:jsdoc/util/logger.LEVELS.DEBUG}. The string is not
* terminated by a newline.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.printDebug = wrapLogFunction('debug', printToStdout);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.ERROR}.
*
* @name module:jsdoc/util/logger.error
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.error = wrapLogFunction('error', console.error);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.FATAL}.
*
* @name module:jsdoc/util/logger.error
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.fatal = wrapLogFunction('fatal', console.error);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.INFO}.
*
* @name module:jsdoc/util/logger.info
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.info = wrapLogFunction('info', console.info);
/**
* Print a string at log level {@link module:jsdoc/util/logger.LEVELS.INFO}. The string is not
* terminated by a newline.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.printInfo = wrapLogFunction('info', printToStdout);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.VERBOSE}.
*
* @name module:jsdoc/util/logger.verbose
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.verbose = wrapLogFunction('verbose', console.info);
/**
* Print a string at log level {@link module:jsdoc/util/logger.LEVELS.VERBOSE}. The string is not
* terminated by a newline.
*
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.printVerbose = wrapLogFunction('verbose', printToStdout);
/**
* Log a message at log level {@link module:jsdoc/util/logger.LEVELS.WARN}.
*
* @name module:jsdoc/util/logger.warn
* @function
* @param {string} message - The message to log.
* @param {...*=} values - The values that will replace the message's placeholders.
*/
logger.warn = wrapLogFunction('warn', console.warn);
/**
* Set the log level.
*
* @param {module:jsdoc/util/logger.LEVELS} level - The log level to use.
*/
logger.setLevel = function setLevel(level) {
logLevel = (level !== undefined) ? level : DEFAULT_LEVEL;
};
/**
* Get the current log level.
*
* @return {module:jsdoc/util/logger.LEVELS} The current log level.
*/
logger.getLevel = function getLevel() {
return logLevel;
};

View File

@ -73,9 +73,9 @@ function unescapeUrls(source) {
* @param {Object} [conf] Configuration for the selected parser, if any.
* @returns {Function} A function that accepts Markdown source, feeds it to the selected parser, and
* returns the resulting HTML.
* @throws {Error} If the name does not correspond to a known parser.
*/
function getParseFunction(parserName, conf) {
var logger = require('jsdoc/util/logger');
var marked = require('marked');
var parserFunction;
@ -99,7 +99,8 @@ function getParseFunction(parserName, conf) {
return parserFunction;
}
else {
throw new Error("unknown Markdown parser: '" + parserName + "'");
logger.error('Unrecognized Markdown parser "%s". Markdown support is disabled.',
parserName);
}
}
@ -108,9 +109,9 @@ function getParseFunction(parserName, conf) {
* `env.conf.markdown` property. The parsing function accepts a single parameter containing Markdown
* source. The function uses the parser specified in `conf.json` to transform the Markdown source to
* HTML, then returns the HTML as a string.
* @returns {Function} A function that accepts Markdown source, feeds it to the selected parser, and
*
* @returns {function} A function that accepts Markdown source, feeds it to the selected parser, and
* returns the resulting HTML.
* @throws {Error} If the value of `env.conf.markdown.parser` does not correspond to a known parser.
*/
exports.getParser = function() {
var conf = env.conf.markdown;

View File

@ -107,7 +107,7 @@ exports.initialize = function(args) {
initializeNode(args);
break;
default:
throw new Error('Unable to initialize the JavaScript runtime!');
throw new Error('Cannot initialize the unknown JavaScript runtime "' + runtime + '"!');
}
};

View File

@ -124,7 +124,7 @@ function parseType(longname) {
}
catch (e) {
err = new Error('unable to parse ' + longname + ': ' + e.message);
require('jsdoc/util/error').handle(err);
require('jsdoc/util/logger').error(err);
return longname;
}
}
@ -285,7 +285,7 @@ var tutorialToUrl = exports.tutorialToUrl = function(tutorial) {
var node = tutorials.getByName(tutorial);
// no such tutorial
if (!node) {
require('jsdoc/util/error').handle( new Error('No such tutorial: '+tutorial) );
require('jsdoc/util/logger').error( new Error('No such tutorial: ' + tutorial) );
return;
}
@ -317,7 +317,7 @@ var tutorialToUrl = exports.tutorialToUrl = function(tutorial) {
*/
var toTutorial = exports.toTutorial = function(tutorial, content, missingOpts) {
if (!tutorial) {
require('jsdoc/util/error').handle( new Error('Missing required parameter: tutorial') );
require('jsdoc/util/logger').error( new Error('Missing required parameter: tutorial') );
return;
}

View File

@ -1,7 +1,7 @@
{
"name": "jsdoc",
"version": "3.3.0-dev",
"revision": "1386539957698",
"revision": "1387841089759",
"description": "An API documentation generator for JavaScript.",
"keywords": [ "documentation", "javascript" ],
"licenses": [

View File

@ -1,389 +0,0 @@
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

@ -4,6 +4,8 @@
*/
'use strict';
var logger = require('jsdoc/util/logger');
exports.handlers = {
/**
Support @source tag. Expected value like:
@ -32,7 +34,8 @@ exports.handlers = {
value = JSON.parse(tag.value);
}
catch(e) {
throw new Error('@source tag expects a valid JSON value, like { "filename": "myfile.js", "lineno": 123 }.');
logger.error('@source tag expects a valid JSON value, like { "filename": "myfile.js", "lineno": 123 }.');
return;
}
e.doclet.meta = e.doclet.meta || {};
@ -41,4 +44,4 @@ exports.handlers = {
}
}
}
};
};

View File

@ -1,24 +0,0 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true, xit: true */
/**
* @author Rob Taylor [manix84@gmail.com]
*/
var path = require('jsdoc/path');
describe("verbose output plugin", function () {
var parser = jasmine.createParser();
var path = require('jsdoc/path');
var docSet;
var pluginPath = 'plugins/verboseOutput';
var plugin = require( path.resolve(env.dirname, pluginPath) );
//require('jsdoc/plugins').installPlugins(['plugins/verboseOutput'], parser);
docSet = jasmine.getDocSetFromFile(pluginPath + '.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

@ -1,16 +0,0 @@
/**
* 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 :)
*/
'use strict';
exports.handlers = {
/**
* Logging the file name to the console.
*/
fileBegin: function (data) {
console.log(data.filename);
}
};

315
rhino/assert.js Normal file
View File

@ -0,0 +1,315 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// UTILITY
var util = require('util');
var pSlice = Array.prototype.slice;
// 1. The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.
var assert = module.exports = ok;
// 2. The AssertionError is defined in assert.
// new assert.AssertionError({ message: message,
// actual: actual,
// expected: expected })
assert.AssertionError = function AssertionError(options) {
this.name = 'AssertionError';
this.actual = options.actual;
this.expected = options.expected;
this.operator = options.operator;
this.message = options.message || getMessage(this);
};
// assert.AssertionError instanceof Error
util.inherits(assert.AssertionError, Error);
function replacer(key, value) {
if (util.isUndefined(value)) {
return '' + value;
}
if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) {
return value.toString();
}
if (util.isFunction(value) || util.isRegExp(value)) {
return value.toString();
}
return value;
}
function truncate(s, n) {
if (util.isString(s)) {
return s.length < n ? s : s.slice(0, n);
} else {
return s;
}
}
function getMessage(self) {
return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' +
self.operator + ' ' +
truncate(JSON.stringify(self.expected, replacer), 128);
}
// At present only the three keys mentioned above are used and
// understood by the spec. Implementations or sub modules can pass
// other keys to the AssertionError's constructor - they will be
// ignored.
// 3. All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.
function fail(actual, expected, message, operator, stackStartFunction) {
throw new assert.AssertionError({
message: message,
actual: actual,
expected: expected,
operator: operator,
stackStartFunction: stackStartFunction
});
}
// EXTENSION! allows for well behaved errors defined elsewhere.
assert.fail = fail;
// 4. Pure assertion tests whether a value is truthy, as determined
// by !!guard.
// assert.ok(guard, message_opt);
// This statement is equivalent to assert.equal(true, !!guard,
// message_opt);. To test strictly for the value true, use
// assert.strictEqual(true, guard, message_opt);.
function ok(value, message) {
if (!value) fail(value, true, message, '==', assert.ok);
}
assert.ok = ok;
// 5. The equality assertion tests shallow, coercive equality with
// ==.
// assert.equal(actual, expected, message_opt);
assert.equal = function equal(actual, expected, message) {
if (actual != expected) fail(actual, expected, message, '==', assert.equal);
};
// 6. The non-equality assertion tests for whether two objects are not equal
// with != assert.notEqual(actual, expected, message_opt);
assert.notEqual = function notEqual(actual, expected, message) {
if (actual == expected) {
fail(actual, expected, message, '!=', assert.notEqual);
}
};
// 7. The equivalence assertion tests a deep equality relation.
// assert.deepEqual(actual, expected, message_opt);
assert.deepEqual = function deepEqual(actual, expected, message) {
if (!_deepEqual(actual, expected)) {
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
}
};
function _deepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
} else if (util.isBuffer(actual) && util.isBuffer(expected)) {
if (actual.length != expected.length) return false;
for (var i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) return false;
}
return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (util.isDate(actual) && util.isDate(expected)) {
return actual.getTime() === expected.getTime();
// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (util.isRegExp(actual) && util.isRegExp(expected)) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if (!util.isObject(actual) && !util.isObject(expected)) {
return actual == expected;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
}
}
function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}
function objEquiv(a, b) {
if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b))
return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
//~~~I've managed to break Object.keys through screwy arguments passing.
// Converting to array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
try {
var ka = Object.keys(a),
kb = Object.keys(b),
key, i;
} catch (e) {//happens when one is a string literal and the other isn't
return false;
}
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] != kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key])) return false;
}
return true;
}
// 8. The non-equivalence assertion tests for any deep inequality.
// assert.notDeepEqual(actual, expected, message_opt);
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (_deepEqual(actual, expected)) {
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
}
};
// 9. The strict equality assertion tests strict equality, as determined by ===.
// assert.strictEqual(actual, expected, message_opt);
assert.strictEqual = function strictEqual(actual, expected, message) {
if (actual !== expected) {
fail(actual, expected, message, '===', assert.strictEqual);
}
};
// 10. The strict non-equality assertion tests for strict inequality, as
// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (actual === expected) {
fail(actual, expected, message, '!==', assert.notStrictEqual);
}
};
function expectedException(actual, expected) {
if (!actual || !expected) {
return false;
}
if (Object.prototype.toString.call(expected) == '[object RegExp]') {
return expected.test(actual);
} else if (actual instanceof expected) {
return true;
} else if (expected.call({}, actual) === true) {
return true;
}
return false;
}
function _throws(shouldThrow, block, expected, message) {
var actual;
if (util.isString(expected)) {
message = expected;
expected = null;
}
try {
block();
} catch (e) {
actual = e;
}
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
(message ? ' ' + message : '.');
if (shouldThrow && !actual) {
fail(actual, expected, 'Missing expected exception' + message);
}
if (!shouldThrow && expectedException(actual, expected)) {
fail(actual, expected, 'Got unwanted exception' + message);
}
if ((shouldThrow && actual && expected &&
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
}
// 11. Expected to throw an error:
// assert.throws(block, Error_opt, message_opt);
assert.throws = function(block, /*optional*/error, /*optional*/message) {
_throws.apply(this, [true].concat(pSlice.call(arguments)));
};
// EXTENSION! This is annoying to write outside this module.
assert.doesNotThrow = function(block, /*optional*/message) {
_throws.apply(this, [false].concat(pSlice.call(arguments)));
};
assert.ifError = function(err) { if (err) {throw err;}};

View File

@ -16,15 +16,18 @@ global.setTimeout = null;
global.clearTimeout = null;
global.setInterval = null;
global.clearInterval = null;
global.setImmediate = null;
global.clearImmediate = null;
(function() {
'use strict';
// TODO: tune number of threads if necessary
var timerPool = new java.util.concurrent.ScheduledThreadPoolExecutor(10);
var timerPool = new java.util.concurrent.ScheduledThreadPoolExecutor(1);
var timers = {};
var timerCount = 1;
var timerUnits = java.util.concurrent.TimeUnit.MILLISECONDS;
var queue = {};
var queueActive = false;
function getCallback(fn) {
return new java.lang.Runnable({
@ -54,6 +57,41 @@ global.clearInterval = null;
};
global.clearInterval = global.clearTimeout;
// adapted from https://github.com/alexgorbatchev/node-browser-builtins
// MIT license
global.setImmediate = (function() {
function drain() {
var key;
var keys = Object.keys(queue);
queueActive = false;
for (var i = 0, l = keys.length; i < l; i++) {
key = keys[i];
var fn = queue[key];
delete queue[key];
fn();
}
}
return function(fn) {
var timerId = timerCount++;
queue[timerId] = fn;
if (!queueActive) {
queueActive = true;
global.setTimeout(drain, 0);
}
return timerId;
};
})();
global.clearImmediate = function(id) {
delete queue[id];
};
})();
/**

View File

@ -5,7 +5,7 @@ var template = require('jsdoc/template'),
fs = require('jsdoc/fs'),
path = require('jsdoc/path'),
taffy = require('taffydb').taffy,
handle = require('jsdoc/util/error').handle,
logger = require('jsdoc/util/logger'),
helper = require('jsdoc/util/templateHelper'),
htmlsafe = helper.htmlsafe,
linkto = helper.linkto,
@ -141,7 +141,7 @@ function generateSourceFiles(sourceFiles, encoding) {
};
}
catch(e) {
handle(e);
logger.error('Error while generating source file %s: %s', file, e.message);
}
generate('Source: ' + sourceFiles[file].shortened, [source], sourceOutfile,

View File

@ -7,6 +7,7 @@
* 4. Run Jasmine on each directory
*/
var fs = require('jsdoc/fs');
var logger = require('jsdoc/util/logger');
var path = require('path');
fs.existsSync = fs.existsSync || path.existsSync;
@ -47,6 +48,9 @@ function testedAllParsers() {
var runNextFolder = module.exports = function(callback) {
testsCompleteCallback = testsCompleteCallback || callback;
// silence the logger while we run the tests
logger.setLevel(logger.LEVELS.SILENT);
if (index < specFolders.length) {
// we need to run the test specs once for each parser
// TODO: We currently support testing one parser per runtime

View File

@ -1,184 +1,184 @@
/*global describe: true, expect: true, it: true */
describe("jsdoc/opts/args", function() {
describe('jsdoc/opts/args', function() {
var args = require('jsdoc/opts/args');
var querystring = require('querystring');
it("should exist", function() {
it('should exist', function() {
expect(args).toBeDefined();
expect(typeof args).toEqual("object");
expect(typeof args).toBe('object');
});
it("should export a 'parse' function", function() {
it('should export a "parse" function', function() {
expect(args.parse).toBeDefined();
expect(typeof args.parse).toEqual("function");
expect(typeof args.parse).toBe('function');
});
it("should export a 'help' function", function() {
it('should export a "help" function', function() {
expect(args.help).toBeDefined();
expect(typeof args.help).toEqual("function");
expect(typeof args.help).toBe('function');
});
it("should export a 'get' function", function() {
it('should export a "get" function', function() {
expect(args.get).toBeDefined();
expect(typeof args.get).toEqual("function");
expect(typeof args.get).toBe('function');
});
describe("parse", function() {
it("should accept a '-t' option and return an object with a 'template' property", function() {
describe('parse', function() {
it('should accept a "-t" option and return an object with a "template" property', function() {
args.parse(['-t', 'mytemplate']);
var r = args.get();
expect(r.template).toEqual('mytemplate');
expect(r.template).toBe('mytemplate');
});
it("should accept a '--template' option and return an object with a 'template' property", function() {
it('should accept a "--template" option and return an object with a "template" property', function() {
args.parse(['--template', 'mytemplate']);
var r = args.get();
expect(r.template).toEqual('mytemplate');
expect(r.template).toBe('mytemplate');
});
it("should accept a '-c' option and return an object with a 'configure' property", function() {
it('should accept a "-c" option and return an object with a "configure" property', function() {
args.parse(['-c', 'myconf.json']);
var r = args.get();
expect(r.configure).toEqual('myconf.json');
expect(r.configure).toBe('myconf.json');
});
it("should accept a '--configure' option and return an object with a 'configure' property", function() {
it('should accept a "--configure" option and return an object with a "configure" property', function() {
args.parse(['--configure', 'myconf.json']);
var r = args.get();
expect(r.configure).toEqual('myconf.json');
expect(r.configure).toBe('myconf.json');
});
it("should accept a '-e' option and return an object with a 'encoding' property", function() {
it('should accept an "-e" option and return an object with a "encoding" property', function() {
args.parse(['-e', 'ascii']);
var r = args.get();
expect(r.encoding).toEqual('ascii');
expect(r.encoding).toBe('ascii');
});
it("should accept a '--encoding' option and return an object with a 'encoding' property", function() {
it('should accept an "--encoding" option and return an object with an "encoding" property', function() {
args.parse(['--encoding', 'ascii']);
var r = args.get();
expect(r.encoding).toEqual('ascii');
expect(r.encoding).toBe('ascii');
});
it("should accept a '-T' option and return an object with a 'test' property", function() {
it('should accept a "-T" option and return an object with a "test" property', function() {
args.parse(['-T']);
var r = args.get();
expect(r.test).toEqual(true);
expect(r.test).toBe(true);
});
it("should accept a '--test' option and return an object with a 'test' property", function() {
it('should accept a "--test" option and return an object with a "test" property', function() {
args.parse(['--test']);
var r = args.get();
expect(r.test).toEqual(true);
expect(r.test).toBe(true);
});
it("should accept a '-d' option and return an object with a 'destination' property", function() {
it('should accept a "-d" option and return an object with a "destination" property', function() {
args.parse(['-d', 'mydestination']);
var r = args.get();
expect(r.destination).toEqual('mydestination');
expect(r.destination).toBe('mydestination');
});
it("should accept a '--destination' option and return an object with a 'destination' property", function() {
it('should accept a "--destination" option and return an object with a "destination" property', function() {
args.parse(['--destination', 'mydestination']);
var r = args.get();
expect(r.destination).toEqual('mydestination');
expect(r.destination).toBe('mydestination');
});
it("should accept a '-p' option and return an object with a 'private' property", function() {
it('should accept a "-p" option and return an object with a "private" property', function() {
args.parse(['-p']);
var r = args.get();
expect(r['private']).toEqual(true);
expect(r.private).toBe(true);
});
it("should accept a '--private' option and return an object with a 'private' property", function() {
it('should accept a "--private" option and return an object with a "private" property', function() {
args.parse(['--private']);
var r = args.get();
expect(r['private']).toEqual(true);
expect(r.private).toBe(true);
});
it("should accept a '-r' option and return an object with a 'recurse' property", function() {
it('should accept a "-r" option and return an object with a "recurse" property', function() {
args.parse(['-r']);
var r = args.get();
expect(r.recurse).toEqual(true);
expect(r.recurse).toBe(true);
});
it("should accept a '--recurse' option and return an object with a 'recurse' property", function() {
it('should accept a "--recurse" option and return an object with a "recurse" property', function() {
args.parse(['--recurse']);
var r = args.get();
expect(r.recurse).toEqual(true);
expect(r.recurse).toBe(true);
});
it("should accept a '-l' option and return an object with a 'lenient' property", function() {
it('should accept a "-l" option and ignore it', function() {
args.parse(['-l']);
var r = args.get();
expect(r.lenient).toEqual(true);
expect(r.lenient).not.toBeDefined();
});
it("should accept a '--lenient' option and return an object with a 'lenient' property", function() {
it('should accept a "--lenient" option and ignore it', function() {
args.parse(['--lenient']);
var r = args.get();
expect(r.lenient).toEqual(true);
expect(r.lenient).not.toBeDefined();
});
it("should accept a '-h' option and return an object with a 'help' property", function() {
it('should accept a "-h" option and return an object with a "help" property', function() {
args.parse(['-h']);
var r = args.get();
expect(r.help).toEqual(true);
expect(r.help).toBe(true);
});
it("should accept a '--help' option and return an object with a 'help' property", function() {
it('should accept a "--help" option and return an object with a "help" property', function() {
args.parse(['--help']);
var r = args.get();
expect(r.help).toEqual(true);
expect(r.help).toBe(true);
});
it("should accept a '-X' option and return an object with a 'explain' property", function() {
it('should accept an "-X" option and return an object with an "explain" property', function() {
args.parse(['-X']);
var r = args.get();
expect(r.explain).toEqual(true);
expect(r.explain).toBe(true);
});
it("should accept a '--explain' option and return an object with a 'explain' property", function() {
it('should accept an "--explain" option and return an object with an "explain" property', function() {
args.parse(['--explain']);
var r = args.get();
expect(r.explain).toEqual(true);
expect(r.explain).toBe(true);
});
it("should accept a '-q' option and return an object with a 'query' property", function() {
it('should accept a "-q" option and return an object with a "query" property', function() {
args.parse(['-q', 'foo=bar&fab=baz']);
var r = args.get();
expect(r.query).toEqual({ foo: 'bar', fab: 'baz' });
});
it("should accept a '--query' option and return an object with a 'query' property", function() {
it('should accept a "--query" option and return an object with a "query" property', function() {
args.parse(['--query', 'foo=bar&fab=baz']);
var r = args.get();
expect(r.query).toEqual({ foo: 'bar', fab: 'baz' });
});
it("should use type coercion on the 'query' property so it has real booleans and numbers", function() {
it('should use type coercion on the "query" property so it has real booleans and numbers', function() {
var obj = {
foo: 'fab',
bar: true,
@ -191,63 +191,77 @@ describe("jsdoc/opts/args", function() {
expect(r.query).toEqual(obj);
});
it("should accept a '-t' option and return an object with a 'tutorials' property", function() {
args.parse(['-d', 'mytutorials']);
it('should accept a "-u" option and return an object with a "tutorials" property', function() {
args.parse(['-u', 'mytutorials']);
var r = args.get();
expect(r.destination).toEqual('mytutorials');
expect(r.tutorials).toBe('mytutorials');
});
it("should accept a '--tutorials' option and return an object with a 'tutorials' property", function() {
it('should accept a "--tutorials" option and return an object with a "tutorials" property', function() {
args.parse(['--tutorials', 'mytutorials']);
var r = args.get();
expect(r.tutorials).toEqual('mytutorials');
expect(r.tutorials).toBe('mytutorials');
});
it("should accept a '--verbose' option and return an object with a 'verbose' property", function() {
it('should accept a "--debug" option and return an object with a "debug" property', function() {
args.parse(['--debug']);
var r = args.get();
expect(r.debug).toBe(true);
});
it('should accept a "--verbose" option and return an object with a "verbose" property', function() {
args.parse(['--verbose']);
var r = args.get();
expect(r.verbose).toEqual(true);
expect(r.verbose).toBe(true);
});
it("should accept a '--match' option and return an object with a 'match' property", function() {
it('should accept a "--pedantic" option and return an object with a "pedantic" property', function() {
args.parse(['--pedantic']);
var r = args.get();
expect(r.pedantic).toBe(true);
});
it('should accept a "--match" option and return an object with a "match" property', function() {
args.parse(['--match', '.*tag']);
var r = args.get();
expect(r.match).toEqual('.*tag');
expect(r.match).toBe('.*tag');
});
it("should accept multiple '--match' options and return an object with a 'match' property", function() {
it('should accept multiple "--match" options and return an object with a "match" property', function() {
args.parse(['--match', '.*tag', '--match', 'parser']);
var r = args.get();
expect(r.match).toEqual(['.*tag', 'parser']);
});
it("should accept a '--nocolor' option and return an object with a 'nocolor' property", function() {
it('should accept a "--nocolor" option and return an object with a "nocolor" property', function() {
args.parse(['--nocolor']);
var r = args.get();
expect(r.nocolor).toEqual(true);
expect(r.nocolor).toBe(true);
});
it("should accept a '-v' option and return an object with a 'version' property", function() {
it('should accept a "-v" option and return an object with a "version" property', function() {
args.parse(['-v']);
var r = args.get();
expect(r.version).toEqual(true);
expect(r.version).toBe(true);
});
it("should accept a '--version' option and return an object with a 'version' property", function() {
it('should accept a "--version" option and return an object with a "version" property', function() {
args.parse(['--version']);
var r = args.get();
expect(r.version).toEqual(true);
expect(r.version).toBe(true);
});
it("should accept a naked option (i.e. no '-') and return an object with a '_' property", function() {
it('should accept a naked option (with no "-") and return an object with a "_" property', function() {
args.parse(['myfile1', 'myfile2']);
var r = args.get();

View File

@ -1,10 +1,11 @@
/*global afterEach: true, describe: true, env: true, expect: true, it: true, spyOn: true */
/*global afterEach, beforeEach, describe, env, expect, it, spyOn */
describe("jsdoc/tag", function() {
var jsdoc = {
tag: require('jsdoc/tag'),
dictionary: require('jsdoc/tag/dictionary'),
type: require('jsdoc/tag/type')
};
var logger = require('jsdoc/util/logger');
it('should exist', function() {
expect(jsdoc.tag).toBeDefined();
@ -151,28 +152,12 @@ describe("jsdoc/tag", function() {
// further tests for this sort of thing are in jsdoc/tag/validator.js tests.
describe("tag validating", function() {
var lenient = !!env.opts.lenient;
function badTag() {
var tag = new jsdoc.tag.Tag("name");
return tag;
}
it("logs an error for bad tags", function() {
spyOn(logger, 'error');
afterEach(function() {
env.opts.lenient = lenient;
});
var tag = new jsdoc.tag.Tag('param', '{!*!*!*!} foo');
it("throws an exception for bad tags if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(badTag).toThrow();
});
it("doesn't throw an exception for bad tags if the lenient option is enabled", function() {
spyOn(console, 'log');
env.opts.lenient = true;
expect(badTag).not.toThrow();
expect(logger.error).toHaveBeenCalled();
});
});
});

View File

@ -1,6 +1,8 @@
/*global afterEach: true, beforeEach: true, describe: true, env: true, expect: true, it: true, spyOn: true */
/*global afterEach, beforeEach, describe, env, expect, it, spyOn */
describe('jsdoc/tag/validator', function() {
var validator = require('jsdoc/tag/validator'),
doop = require('jsdoc/util/doop'),
logger = require('jsdoc/util/logger'),
tag = require('jsdoc/tag');
it('should exist', function() {
@ -13,11 +15,8 @@ describe('jsdoc/tag/validator', function() {
expect(typeof validator.validate).toBe('function');
});
// Note: various Error classes are private so we just test whether *any*
// error was thrown, not against particular types (e.g. UnknownTagError).
describe('validate', function() {
var dictionary = require('jsdoc/tag/dictionary'),
lenient = !!env.opts.lenient,
allowUnknown = !!env.conf.tags.allowUnknownTags,
badTag = {title: 'lkjasdlkjfb'},
meta = {filename: 'asdf.js', lineno: 1},
@ -25,72 +24,53 @@ describe('jsdoc/tag/validator', function() {
goodTag2 = new tag.Tag('ignore', '', meta); // mustNotHaveValue
function validateTag(tag) {
return function() { validator.validate(tag, dictionary.lookUp(tag.title), meta); };
}
validator.validate(tag, dictionary.lookUp(tag.title), meta);
}
beforeEach(function() {
spyOn(console, 'log');
spyOn(logger, 'error');
});
afterEach(function() {
env.opts.lenient = lenient;
env.conf.tags.allowUnknownTags = allowUnknown;
});
it("throws an error if the tag is not in the dictionary, conf.tags.allowUnknownTags is false and lenient is false", function() {
env.opts.lenient = false;
it("logs an error if the tag is not in the dictionary and conf.tags.allowUnknownTags is false", function() {
env.conf.tags.allowUnknownTags = false;
expect(validateTag(badTag)).toThrow();
validateTag(badTag);
expect(logger.error).toHaveBeenCalled();
});
it("throws NO error if the tag is not in the dictionary, conf.tags.allowUnknownTags is false and lenient is true", function() {
env.opts.lenient = true;
env.conf.tags.allowUnknownTags = false;
expect(validateTag(badTag)).not.toThrow();
});
it("doesn't throw an error if the tag is not in the dictionary and conf.tags.allowUnknownTags is true, regardless of lenience", function() {
// if it doesn't throw an error with lenient false, then it won't throw it with lenient true (we have
// tested lenient already in util/error.js)
env.opts.lenient = false;
it("doesn't log an error if the tag is not in the dictionary and conf.tags.allowUnknownTags is true", function() {
env.conf.tags.allowUnknownTags = true;
expect(validateTag(badTag)).not.toThrow();
validateTag(badTag);
expect(logger.error).not.toHaveBeenCalled();
});
it("throws no error for valid tags", function() {
env.opts.lenient = false;
expect(validateTag(goodTag)).not.toThrow();
expect(validateTag(goodTag2)).not.toThrow();
it("logs no error for valid tags", function() {
validateTag(goodTag);
validateTag(goodTag2);
expect(logger.error).not.toHaveBeenCalled();
});
it("throws an error if the tag has no text but .mustHaveValue is true and lenient is false, or none if it's true", function() {
// the name tag has .mustHaveValue.
var oldText = goodTag.text;
delete goodTag.text;
it("logs an error if the tag has no text but .mustHaveValue is true", function() {
var missingName = doop(goodTag);
missingName.text = null;
validateTag(missingName);
env.opts.lenient = false;
expect(validateTag(goodTag)).toThrow();
env.opts.lenient = true;
expect(validateTag(goodTag)).not.toThrow();
goodTag.text = oldText;
expect(logger.error).toHaveBeenCalled();
});
it("throws an error if the tag has text but .mustNotHaveValue is true and lenient is false, or none if it's true", function() {
var oldVal = goodTag2.mustNotHaveValue,
text = goodTag2.text;
goodTag2.mustNotHaveValue = true;
goodTag2.text = goodTag2.text || 'asdf';
it("logs an error if the tag has text but .mustNotHaveValue is true", function() {
var missingText = doop(goodTag2);
missingText.mustNotHaveValue = true;
missingText.text = missingText.text || 'asdf';
validateTag(missingText);
env.opts.lenient = false;
expect(validateTag(goodTag2)).toThrow();
env.opts.lenient = true;
expect(validateTag(goodTag2)).not.toThrow();
goodTag2.mustNotHaveValue = oldVal;
goodTag2.text = oldVal;
expect(logger.error).toHaveBeenCalled();
});
});
});

View File

@ -1,9 +1,8 @@
/*global afterEach: true, beforeEach: true, describe: true, env: true, expect: true, it: true,
spyOn: true */
/*global beforeEach, describe, env, expect, it, spyOn */
describe("jsdoc/tutorial/resolver", function() {
var logger = require('jsdoc/util/logger');
var resolver = require('jsdoc/tutorial/resolver');
var tutorial = require('jsdoc/tutorial');
var lenient = !!env.opts.lenient;
it("should exist", function() {
expect(resolver).toBeDefined();
@ -171,59 +170,30 @@ describe("jsdoc/tutorial/resolver", function() {
// error reporting.
describe("Error reporting", function() {
// Tests for error reporting.
function missingTutorial() {
beforeEach(function() {
spyOn(logger, 'error');
spyOn(logger, 'warn');
});
it("logs an error for missing tutorials", function() {
resolver.load(env.dirname + "/test/tutorials/incomplete");
resolver.resolve();
}
function duplicateNamedTutorials() {
// can't add a tutorial if another with its name has already been added
expect(logger.error).toHaveBeenCalled();
});
it("logs a warning for duplicate-named tutorials (e.g. test.md, test.html)", function() {
resolver.addTutorial(tute);
}
function duplicateDefinedTutorials() {
expect(logger.warn).toHaveBeenCalled();
});
it("logs an error for tutorials defined twice in .json files", function() {
// can't have a tutorial's metadata defined twice in .json files
resolver.load(env.dirname + "/test/tutorials/duplicateDefined");
resolver.resolve();
}
beforeEach(function() {
spyOn(console, 'log');
});
afterEach(function() {
env.opts.lenient = lenient;
});
it("throws an exception for missing tutorials if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(missingTutorial).toThrow();
});
it("doesn't throw an exception for missing tutorials if the lenient option is enabled", function() {
env.opts.lenient = true;
expect(missingTutorial).not.toThrow();
});
it("throws an exception for duplicate-named tutorials (e.g. test.md, test.html) if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(duplicateNamedTutorials).toThrow();
});
it("doesn't throw an exception for duplicate-named tutorials (e.g. test.md, test.html) if the lenient option is not enabled", function() {
env.opts.lenient = true;
expect(duplicateNamedTutorials).not.toThrow();
});
it("throws an exception for tutorials defined twice in .jsons if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(duplicateDefinedTutorials).toThrow();
});
it("doesn't throw an exception for tutorials defined twice in .jsons if the lenient option is not enabled", function() {
env.opts.lenient = true;
expect(duplicateDefinedTutorials).not.toThrow();
expect(logger.error).toHaveBeenCalled();
});
});

View File

@ -1,53 +1,29 @@
/*global describe: true, env: true, it: true */
/*global beforeEach, describe, expect, it, spyOn */
describe("jsdoc/util/error", function() {
var error = require('jsdoc/util/error'),
handle = error.handle;
var error = require('jsdoc/util/error');
var handle = error.handle;
var logger = require('jsdoc/util/logger');
it("should exist", function() {
expect(error).toBeDefined();
expect(typeof error).toEqual("object");
});
it("should exist", function() {
expect(error).toBeDefined();
expect(typeof error).toEqual("object");
});
it("should export a 'handle' function", function() {
expect(handle).toBeDefined();
expect(typeof handle).toEqual("function");
});
it("should export a 'handle' function", function() {
expect(handle).toBeDefined();
expect(typeof handle).toEqual("function");
});
describe("handle", function() {
/*jshint evil: true */
var lenient = !!env.opts.lenient;
describe("handle", function() {
it('should not throw', function() {
expect(handle).not.toThrow();
});
function handleError() {
handle( new Error("foo") );
}
it('should log messages with logger.error()', function() {
spyOn(logger, 'error');
handle('test');
function handleObject() {
handle( { foo: "bar", baz: "qux"} );
}
afterEach(function() {
env.opts.lenient = lenient;
});
it("should re-throw errors by default", function() {
expect(handleError).toThrow();
});
it("should re-throw errors if lenient mode is not enabled", function() {
env.opts.lenient = false;
expect(handleError).toThrow();
});
it("should not re-throw errors if lenient mode is enabled", function() {
env.opts.lenient = true;
spyOn(console, 'log');
expect(handleError).not.toThrow();
});
it("should still work if the 'e' param is not an instanceof Error", function() {
expect(handleObject).toThrow();
});
});
expect(logger.error).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,200 @@
/*global afterEach, describe, expect, it, jasmine */
describe('jsdoc/util/logger', function() {
var logger = require('jsdoc/util/logger');
var loggerArgs = ['foo bar %s', 'hello'];
it('should exist', function() {
expect(logger).toBeDefined();
expect(typeof logger).toBe('object');
});
it('should inherit from EventEmitter', function() {
var EventEmitter = require('events').EventEmitter;
expect(logger instanceof EventEmitter).toBe(true);
});
it('should export a "debug" method', function() {
expect(logger.debug).toBeDefined();
expect(typeof logger.debug).toBe('function');
});
it('should export an "error" method', function() {
expect(logger.error).toBeDefined();
expect(typeof logger.error).toBe('function');
});
it('should export a "fatal" method', function() {
expect(logger.fatal).toBeDefined();
expect(typeof logger.fatal).toBe('function');
});
it('should export a "getLevel" method', function() {
expect(logger.getLevel).toBeDefined();
expect(typeof logger.getLevel).toBe('function');
});
it('should export an "info" method', function() {
expect(logger.info).toBeDefined();
expect(typeof logger.info).toBe('function');
});
it('should export a "LEVELS" object', function() {
expect(logger.LEVELS).toBeDefined();
expect(typeof logger.LEVELS).toBe('object');
});
it('should export a "setLevel" method', function() {
expect(logger.setLevel).toBeDefined();
expect(typeof logger.setLevel).toBe('function');
});
it('should export a "verbose" method', function() {
expect(logger.verbose).toBeDefined();
expect(typeof logger.verbose).toBe('function');
});
it('should export a "warn" method', function() {
expect(logger.warn).toBeDefined();
expect(typeof logger.warn).toBe('function');
});
// helpers for testing logger methods
function eventIsEmitted(name) {
var called = false;
logger.once('logger:' + name, function() {
called = true;
});
logger[name]();
expect(called).toBe(true);
}
function eventGetsArguments(name) {
var args;
logger.once('logger:' + name, function() {
args = Array.prototype.slice.call(arguments, 0);
});
logger[name](loggerArgs[0], loggerArgs[1]);
expect(args).toBeDefined();
expect( Array.isArray(args) ).toBe(true);
expect(args[0]).toBe(loggerArgs[0]);
expect(args[1]).toBe(loggerArgs[1]);
}
describe('debug', function() {
var methodName = 'debug';
it('should cause the logger to emit the correct event', function() {
eventIsEmitted(methodName);
});
it('should pass its arguments to listeners', function() {
eventGetsArguments(methodName);
});
});
describe('error', function() {
var methodName = 'error';
it('should cause the logger to emit the correct event', function() {
eventIsEmitted(methodName);
});
it('should pass its arguments to listeners', function() {
eventGetsArguments(methodName);
});
});
describe('fatal', function() {
var methodName = 'fatal';
it('should cause the logger to emit the correct event', function() {
eventIsEmitted(methodName);
});
it('should pass its arguments to listeners', function() {
eventGetsArguments(methodName);
});
});
describe('getLevel', function() {
it('should return LEVELS.SILENT when we are running tests', function() {
expect( logger.getLevel() ).toBe(logger.LEVELS.SILENT);
});
});
describe('info', function() {
var methodName = 'info';
it('should cause the logger to emit the correct event', function() {
eventIsEmitted(methodName);
});
it('should pass its arguments to listeners', function() {
eventGetsArguments(methodName);
});
});
describe('LEVELS', function() {
var LEVELS = logger.LEVELS;
it('should include the correct properties', function() {
expect(LEVELS.VERBOSE).toBeDefined();
expect(LEVELS.DEBUG).toBeDefined();
expect(LEVELS.INFO).toBeDefined();
expect(LEVELS.WARN).toBeDefined();
expect(LEVELS.ERROR).toBeDefined();
expect(LEVELS.SILENT).toBeDefined();
});
it('should weight the logging levels correctly relative to one another', function() {
expect(LEVELS.VERBOSE).toBeGreaterThan(LEVELS.DEBUG);
expect(LEVELS.DEBUG).toBeGreaterThan(LEVELS.INFO);
expect(LEVELS.INFO).toBeGreaterThan(LEVELS.WARN);
expect(LEVELS.WARN).toBeGreaterThan(LEVELS.ERROR);
expect(LEVELS.ERROR).toBeGreaterThan(LEVELS.SILENT);
});
});
describe('setLevel', function() {
var oldLevel = logger.getLevel();
afterEach(function() {
logger.setLevel(oldLevel);
});
it('should update the log level', function() {
logger.setLevel(logger.LEVELS.VERBOSE);
expect( logger.getLevel() ).toBe(logger.LEVELS.VERBOSE);
});
});
describe('verbose', function() {
var methodName = 'verbose';
it('should cause the logger to emit the correct event', function() {
eventIsEmitted(methodName);
});
it('should pass its arguments to listeners', function() {
eventGetsArguments(methodName);
});
});
describe('warn', function() {
var methodName = 'warn';
it('should cause the logger to emit the correct event', function() {
eventIsEmitted(methodName);
});
it('should pass its arguments to listeners', function() {
eventGetsArguments(methodName);
});
});
});

View File

@ -1,11 +1,11 @@
/*global afterEach: true, beforeEach: true, describe: true, expect: true, env: true, it: true,
jasmine: true, spyOn: true, xdescribe: true */
/*global afterEach, beforeEach, describe, expect, env, it, jasmine, spyOn */
var hasOwnProp = Object.prototype.hasOwnProperty;
describe("jsdoc/util/templateHelper", function() {
var helper = require('jsdoc/util/templateHelper'),
doclet = require('jsdoc/doclet'),
doop = require('jsdoc/util/doop'),
logger = require('jsdoc/util/logger'),
resolver = require('jsdoc/tutorial/resolver'),
taffy = require('taffydb').taffy;
helper.registerLink('test', 'path/to/test.html');
@ -141,18 +141,11 @@ describe("jsdoc/util/templateHelper", function() {
});
it("setting tutorials to the root tutorial object lets lookups work", function() {
var lenient = !!env.opts.lenient;
spyOn(console, 'log');
// tutorial doesn't exist, we want to muffle that error
env.opts.lenient = true;
helper.setTutorials(resolver.root);
spyOn(resolver.root, 'getByName');
helper.tutorialToUrl('asdf');
expect(resolver.root.getByName).toHaveBeenCalled();
env.opts.lenient = lenient;
expect(resolver.root.getByName).toHaveBeenCalled();
});
});
@ -747,23 +740,6 @@ describe("jsdoc/util/templateHelper", function() {
delete helper.longnameToUrl.MyClass;
});
it("doesn't throw an error in lenient mode if a 'returns' item has no value", function() {
function getReturns() {
return helper.getSignatureReturns(doc);
}
var doc;
var lenient = !!env.opts.lenient;
env.opts.lenient = true;
spyOn(console, 'log');
doc = new doclet.Doclet('/** @function myFunction\n@returns */', {});
expect(getReturns).not.toThrow();
env.opts.lenient = lenient;
});
});
describe("getAncestorLinks", function() {
@ -926,44 +902,32 @@ describe("jsdoc/util/templateHelper", function() {
});
describe("tutorialToUrl", function() {
var lenient = !!env.opts.lenient;
function missingTutorial() {
var url = helper.tutorialToUrl("be-a-perfect-person-in-just-three-days");
}
beforeEach(function() {
spyOn(console, 'log');
spyOn(logger, 'error');
helper.setTutorials(resolver.root);
});
afterEach(function() {
helper.setTutorials(null);
env.opts.lenient = lenient;
});
it('throws an exception if the tutorial is missing and the lenient option is not enabled', function() {
env.opts.lenient = false;
expect(missingTutorial).toThrow();
it('logs an error if the tutorial is missing', function() {
helper.tutorialToUrl('be-a-perfect-person-in-just-three-days');
expect(logger.error).toHaveBeenCalled();
});
it('does not throw an exception if the tutorial is missing and the lenient option is enabled', function() {
env.opts.lenient = true;
it("logs an error if the tutorial's name is a reserved JS keyword and it doesn't exist", function() {
helper.tutorialToUrl('prototype');
expect(missingTutorial).not.toThrow();
});
it("does not return a tutorial if its name is a reserved JS keyword and it doesn't exist", function() {
env.opts.lenient = false;
expect(function () { helper.tutorialToUrl('prototype'); }).toThrow();
expect(logger.error).toHaveBeenCalled();
});
it("creates links to tutorials if they exist", function() {
// NOTE: we have to set lenient = true here because otherwise JSDoc will
// cry when trying to resolve the same set of tutorials twice (once
// for the tutorials tests, and once here).
env.opts.lenient = true;
// load the tutorials we already have for the tutorials tests
resolver.load(env.dirname + "/test/tutorials/tutorials");
resolver.resolve();
@ -985,34 +949,21 @@ describe("jsdoc/util/templateHelper", function() {
});
describe("toTutorial", function() {
var lenient = !!env.opts.lenient;
function missingParam() {
helper.toTutorial();
}
afterEach(function() {
env.opts.lenient = lenient;
helper.setTutorials(null);
});
beforeEach(function () {
spyOn(logger, 'error');
helper.setTutorials(resolver.root);
});
it('throws an exception if the first param is missing and the lenient option is not enabled', function() {
env.opts.lenient = false;
afterEach(function() {
helper.setTutorials(null);
});
expect(missingParam).toThrow();
it('logs an error if the first param is missing', function() {
helper.toTutorial();
expect(logger.error).toHaveBeenCalled();
});
it('does not throw an exception if the first param is missing and the lenient option is enabled', function() {
spyOn(console, 'log');
env.opts.lenient = true;
expect(missingParam).not.toThrow();
});
// missing tutorials
it("returns the tutorial name if it's missing and no missingOpts is provided", function() {
helper.setTutorials(resolver.root);
@ -1046,12 +997,6 @@ describe("jsdoc/util/templateHelper", function() {
// now we do non-missing tutorials.
it("returns a link to the tutorial if not missing", function() {
// NOTE: we have to set lenient = true here because otherwise JSDoc will
// cry when trying to resolve the same set of tutorials twice (once
// for the tutorials tests, and once here).
env.opts.lenient = true;
spyOn(console, 'log');
// load the tutorials we already have for the tutorials tests
resolver.load(env.dirname + "/test/tutorials/tutorials");
resolver.resolve();

View File

@ -78,14 +78,9 @@ describe("@param tag", function() {
expect(commit.params[0].name).toBe('atomic');
});
it('When a symbol has a @param tag with an invalid type expression, the doclet is generated in lenient mode, and the JSDoc comment is ignored.', function() {
it('When a symbol has a @param tag with an invalid type expression, the JSDoc comment is ignored.', function() {
var badDocSet;
var test;
var lenient = !!env.opts.lenient;
env.opts.lenient = true;
spyOn(console, 'log');
badDocSet = jasmine.getDocSetFromFile('test/fixtures/paramtaginvalidtype.js');
test = badDocSet.getByLongname('Test#test')[0];
@ -99,7 +94,5 @@ describe("@param tag", function() {
expect(test.meta.filename).toBe('[[string0]]');
expect(test.description).not.toBeDefined();
env.opts.lenient = lenient;
});
});