Merge branch 'master' into parsimony

Conflicts:
	lib/jsdoc/name.js
	lib/jsdoc/src/handlers.js
	lib/jsdoc/src/parser.js
	lib/jsdoc/tag/dictionary/definitions.js
	lib/jsdoc/util/templateHelper.js
	package.json
	test/specs/documentation/alias.js
	test/specs/documentation/modules.js
	test/specs/tags/augmentstag.js
	test/specs/tags/overviewtag.js
This commit is contained in:
Jeff Williams 2013-10-21 08:42:29 -07:00
commit e7752cde18
49 changed files with 818 additions and 248 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ jsdoc.jar
test/tutorials/out
conf.json
out/
.tern-port

View File

@ -10,15 +10,13 @@
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
],
"repositories": [
{
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
}
],
"repository": {
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
},
"dependencies": {
"async": "0.1.22",
"catharsis": "0.5.6",
"catharsis": "0.7.0",
"crypto-browserify": "git+https://github.com/dominictarr/crypto-browserify.git#95c5d505",
"esprima": "1.0.4",
"js2xmlparser": "0.1.0",

View File

@ -15,7 +15,7 @@ task('default', [], function(params) {
var metadata = {
appname : 'jsdoc',
appversion : '3.2.0-dev',
appversion : '3.3.0-dev',
timestamp : '' + new Date().getTime()
};

View File

@ -13,7 +13,7 @@ Installation
Use git to clone the [official JSDoc repository](https://github.com/jsdoc3/jsdoc):
git clone git@github.com:jsdoc3/jsdoc.git
git clone https://github.com/jsdoc3/jsdoc.git
Alternatively, you can download a .zip file for the
[latest development version](https://github.com/jsdoc3/jsdoc/archive/master.zip)

View File

@ -3,6 +3,38 @@
This file describes notable changes in each version of JSDoc 3. To download a specific version of JSDoc 3, see [GitHub's tags page](https://github.com/jsdoc3/jsdoc/tags).
## 3.2.1 (October 2013)
### Enhancements
+ JSDoc's parser now fires a `processingComplete` event after JSDoc has completed all post-processing of the parse results. This event has a `doclets` property containing an array of doclets. (#421)
+ When JSDoc's parser fires a `parseComplete` event, the event now includes a `doclets` property containing an array of doclets. (#431)
+ You can now use relative paths in the JSDoc configuration file's `source.exclude` option. Relative paths will be resolved relative to the current working directory. (#405)
+ If a symbol uses the `@default` tag, and its default value is an object literal, this value is now stored as a string, and the doclet will have a `defaultvaluetype` property containing the string `object`. This change enables templates to show the default value with appropriate syntax highlighting. (#419)
+ Inline `{@link}` tags can now contain newlines. (#441)
### Bug fixes
+ Inherited symbols now indicate that they were inherited from the ancestor that defined the symbol, rather than the direct parent. (#422)
+ If the first line of a JavaScript file contains a hashbang (for example, `#!/usr/bin/env node`), the hashbang is now ignored when the file is parsed. (#499)
+ Resolved a crash when a JavaScript file contains a [JavaScript 1.8](https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/1.8) keyword, such as `let`. (#477)
+ The type expression `function[]` is now parsed correctly. (#493)
+ If a module is tagged incorrectly, the module's output file now has a valid filename. (#440, #458)
+ For tags that accept names, such as `@module` and `@param`, if a hyphen is used to separate the name and description, the hyphen must appear on the same line as the name. This change prevents a Markdown bullet on the followng line from being interpreted as a separator. (#459)
+ When lenient mode is enabled, a `@param` tag with an invalid type expression no longer causes a crash. (#448)
+ The `@requires` tag can now contain an inline tag in its tag text. (#486)
+ The `@returns` tag can now contain inline tags even if a type is not specified. (#444)
+ When lenient mode is enabled, a `@returns` tag with no value no longer causes a crash. (#451)
+ The `@type` tag now works correctly with type expressions that span multiple lines. (#427)
+ If a string contains inline `{@link}` tags preceded by bracketed link text (for example, `[test]{@link Test#test}`), HTML links are now generated correctly even if the string contains other bracketed text. (#470)
+ On POSIX systems, if you run JSDoc using a symlink to the startup script, JSDoc now works correctly. (#492)
### Default template
+ Pretty-printed source files are now generated by default. To disable this feature, add the property `templates.default.outputSourceFiles: false` to your `conf.json` file. (#454)
+ Links to a specific line in a source file now work correctly. (#475)
+ Pretty-printed source files are now generated using the encoding specified in the `-e/--encoding` option. (#496)
+ If a `@default` tag is added to a symbol whose default value is an object, the value is now displayed in the output file. (#419)
+ Output files now identify symbols as "abstract" rather than "virtual." (#432)
## 3.2.0 (May 2013)
### Major changes

12
jsdoc
View File

@ -2,7 +2,15 @@
# rhino discards the path to the current script file, so we must add it back
SOURCE="$0"
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
while [ -h "$SOURCE" ] ; do
NEXTSOURCE="$(readlink "$SOURCE")"
echo $NEXTSOURCE | grep -q -e "^/"
if [ $? = 0 ]; then
SOURCE="$NEXTSOURCE"
else
SOURCE="$(dirname $SOURCE)/$NEXTSOURCE"
fi
done
# Get a Windows path under MinGW or Cygwin
BASEPATH="$( cd -P "$( dirname "$SOURCE" )" && (pwd -W 2>/dev/null || cygpath -w $(pwd) 2>/dev/null || pwd))"
if [ "${BASEPATH%${BASEPATH#?}}" != "/" ] ; then
@ -21,7 +29,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"
CMD="org.mozilla.javascript.tools.debugger.Main -debug -opt -1"
# strip --debug argument
shift
else

View File

@ -20,7 +20,7 @@ IF NOT "%_URLPATH%"=="%_URLPATH: =%" GOTO ESCAPE_SPACE
IF [%1]==[--debug] (
ECHO Running Debug
SET CMD=org.mozilla.javascript.tools.debugger.Main -debug
SET CMD=org.mozilla.javascript.tools.debugger.Main -debug -opt -1
REM `SHIFT` doesn't affect %*
:COLLECT_ARGS

View File

@ -61,6 +61,13 @@ require('lib/jsdoc/util/global').env = {
*/
opts: {},
/**
* The source files that JSDoc will parse.
* @type Array
* @memberof env
*/
sourceFiles: [],
/**
* The JSDoc version number and revision date.
*
@ -222,7 +229,8 @@ function main() {
if (env.conf.source && env.opts._.length > 0) { // are there any files to scan and parse?
filter = new jsdoc.src.filter.Filter(env.conf.source);
sourceFiles = app.jsdoc.scanner.scan(env.opts._, (env.opts.recurse? 10 : undefined), filter);
env.sourceFiles = sourceFiles = app.jsdoc.scanner.scan(env.opts._,
(env.opts.recurse? 10 : undefined), filter);
jsdoc.src.handlers.attachTo(app.jsdoc.parser);

View File

@ -120,7 +120,8 @@ exports.addInherited = function(docs) {
// only build the list of longnames if we'll actually need it
if (sorted.length) {
longnames = docs.map(function(doc) {
if (doc.longname) {
// keep the ancestor's docs for a symbol if a local override is not documented
if (doc.longname && !doc.undocumented) {
return doc.longname;
}
});

View File

@ -33,7 +33,7 @@ const defaults = {
},
"source": {
"includePattern": ".+\\.js(doc)?$",
"excludePattern": "(^|\\/)_"
"excludePattern": "(^|\\/|\\\\)_"
},
"plugins": []
};

View File

@ -32,7 +32,7 @@ var DEFAULT_SCOPE = 'static';
@param {module:jsdoc/doclet.Doclet} doclet
*/
exports.resolve = function(doclet) {
var name = doclet.name,
var name = doclet.name ? String(doclet.name) : '',
memberof = doclet.memberof || '',
about = {},
scopePunc = '([' + INNER + INSTANCE + STATIC + '])',
@ -40,8 +40,13 @@ exports.resolve = function(doclet) {
trailingScope = new RegExp(scopePunc + '$'),
parentDoc;
doclet.name = name = name? ('' + name).replace(/(^|\.)prototype\.?/g, INSTANCE) : '';
// change MyClass.prototype.instanceMethod to MyClass#instanceMethod
// (but not in function params, which lack doclet.kind)
if (name && doclet.kind) {
name = name.replace(/(?:^|\.)prototype\.?/g, INSTANCE);
}
doclet.name = name;
// member of a var in an outer scope?
if (name && !memberof && doclet.meta.code && doclet.meta.code.funcscope) {
name = doclet.longname = doclet.meta.code.funcscope + INNER + name;
@ -138,6 +143,7 @@ function quoteUnsafe(name, kind) { // docspaced names may have unsafe characters
return name;
}
// TODO: make this a private method, or remove it if possible
RegExp.escape = RegExp.escape || function(str) {
var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); // .*+?|()[]{}\
return str.replace(specials, "\\$&");
@ -213,7 +219,8 @@ exports.shorten = function(longname, forcedMemberof) {
// like /** @name foo.bar(2) */
if ( /(.+)\(([^)]+)\)$/.test(name) ) {
name = RegExp.$1, variation = RegExp.$2;
name = RegExp.$1;
variation = RegExp.$2;
}
//// restore quoted strings back again
@ -230,44 +237,17 @@ exports.shorten = function(longname, forcedMemberof) {
};
/**
Split a string that starts with a name and ends with a description, into its parts.
Split a string that starts with a name and ends with a description into its parts.
@param {string} nameDesc
@returns {object} Hash with "name" and "description" properties.
*/
exports.splitName = function(nameDesc) {
var name = '',
desc = '',
thisChar = '',
inQuote = false;
for (var i = 0, len = nameDesc.length; i < len; i++) {
thisChar = nameDesc.charAt(i);
if (thisChar === '\\') {
name += thisChar + nameDesc.charAt(++i);
continue;
}
if (thisChar === '"') {
inQuote = !inQuote;
}
if (inQuote) {
name += thisChar;
continue;
}
if (!inQuote) {
if ( /\s/.test(thisChar) ) {
desc = nameDesc.substr(i);
desc = desc.replace(/^[\s\-\s]+/, '').trim();
break;
}
else {
name += thisChar;
}
}
}
return { name: name, description: desc };
// like: name, [name], name text, [name] text, name - text, or [name] - text
// the hyphen must be on the same line as the name; this prevents us from treating a Markdown
// dash as a separator
nameDesc.match(/^(\[[^\]]+\]|\S+)((?:[ \t]*\-\s*|\s+)(\S[\s\S]*))?$/);
return {
name: RegExp.$1,
description: RegExp.$3
};
};

View File

@ -63,8 +63,8 @@ function parseQuery(str) {
return result;
}
argParser.addOption('t', 'template', true, 'The name of the template to use. Default: the "default" template');
argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: jsdoc __dirname + /conf.json');
argParser.addOption('t', 'template', true, 'The path to the template to use. Default: path/to/jsdoc/templates/default');
argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: path/to/jsdoc/conf.json');
argParser.addOption('e', 'encoding', true, 'Assume this encoding when reading all source files. Default: utf8');
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/');

View File

@ -51,7 +51,10 @@ function prefixReducer(previousPath, current) {
* @return {string} The common prefix, or an empty string if there is no common prefix.
*/
exports.commonPrefix = function(paths) {
var common = paths.reduce(prefixReducer, undefined);
var common;
paths = paths || [];
common = paths.reduce(prefixReducer, undefined) || [];
// if there's anything left (other than a placeholder for a leading slash), add a placeholder
// for a trailing slash

View File

@ -19,7 +19,7 @@ function getNewDoclet(comment, e) {
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);
doclet = new Doclet('', {});
doclet = new Doclet('', e);
}
return doclet;

View File

@ -151,7 +151,8 @@ Parser.prototype.parse = function(sourceFiles, encoding) {
}
this.emit('parseComplete', {
sourcefiles: parsedFiles
sourcefiles: parsedFiles,
doclets: this._resultBuffer
});
return this._resultBuffer;
@ -188,6 +189,9 @@ Parser.prototype.getAstNodeVisitors = function() {
// TODO: docs
function pretreat(code) {
return code
// comment out hashbang at the top of the file, like: #!/usr/bin/env node
.replace(/^(\#\![\S \t]+\n)/, '// $1')
// to support code minifiers that preserve /*! comments, treat /*!* as equivalent to /**
.replace(/\/\*\!\*/g, '/**')
// merge adjacent doclets

View File

@ -10,6 +10,18 @@
var path = require('jsdoc/path');
var Syntax = require('jsdoc/src/syntax').Syntax;
function filepathMinusPrefix(filepath) {
var sourceFiles = env.sourceFiles || [];
var commonPrefix = path.commonPrefix( sourceFiles.concat(env.opts._ || []) );
var result = (filepath + '/').replace(commonPrefix, '');
if (result.length > 0 && result[result.length - 1] !== '/') {
result += '/';
}
return result;
}
/** @private */
function setDocletKindToTitle(doclet, tag) {
doclet.addTag( 'kind', tag.title );
@ -35,12 +47,11 @@ function setDocletDescriptionToValue(doclet, tag) {
}
function setNameToFile(doclet, tag) {
var name = '';
var name;
if (doclet.meta.filename) {
// TODO: find the shortest path shared by all input files, and remove that from
// doclet.meta.path
name += path.basename(doclet.meta.path) + '/';
doclet.addTag( 'name', name + doclet.meta.filename );
name = filepathMinusPrefix(doclet.meta.path) + doclet.meta.filename;
doclet.addTag('name', name);
}
}
@ -65,17 +76,13 @@ function applyNamespace(docletOrNs, tag) {
}
function setDocletNameToFilename(doclet, tag) {
// TODO: find the shortest path shared by all input files, and remove that from doclet.meta.path
var name = doclet.meta.path ? path.basename(doclet.meta.path) + '/' : '';
name += doclet.meta.filename;
name = name.replace(/\.js$/i, '');
for (var i = 0, len = env.opts._.length; i < len; i++) {
if (name.indexOf(env.opts._[i]) === 0) {
name = name.replace(env.opts._[0], '');
break;
}
var name = '';
if (doclet.meta.path) {
name = filepathMinusPrefix(doclet.meta.path);
}
name += doclet.meta.filename.replace(/\.js$/i, '');
doclet.name = name;
}
@ -541,15 +548,24 @@ exports.defineTags = function(dictionary) {
dictionary.defineTag('requires', {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var MODULE_PREFIX = require('jsdoc/name').MODULE_PREFIX;
var modName = firstWordOf(tag.value);
var requiresName;
if (modName.indexOf(MODULE_PREFIX) !== 0) {
modName = MODULE_PREFIX + modName;
var MODULE_PREFIX = require('jsdoc/name').MODULE_PREFIX;
// inline link tags are passed through as-is so that `@requires {@link foo}` works
if ( require('jsdoc/tag/inline').isInlineTag(tag.value, 'link\\S*') ) {
requiresName = tag.value;
}
// otherwise, assume it's a module
else {
requiresName = firstWordOf(tag.value);
if (requiresName.indexOf(MODULE_PREFIX) !== 0) {
requiresName = MODULE_PREFIX + requiresName;
}
}
if (!doclet.requires) { doclet.requires = []; }
doclet.requires.push(modName);
doclet.requires.push(requiresName);
}
});
@ -619,17 +635,21 @@ exports.defineTags = function(dictionary) {
mustHaveValue: true,
canHaveType: true,
onTagText: function(text) {
// remove line breaks so we can parse the type expression correctly
text = text.replace(/[\n\r]/g, '');
// any text must be formatted as a type, but for back compat braces are optional
if ( ! /^\{.+\}$/.test(text) ) {
text = '{ '+text+' }';
if ( !/^\{[\s\S]+\}$/.test(text) ) {
text = '{' + text + '}';
}
return text;
},
onTagged: function(doclet, tag) {
if (tag.value && tag.value.type) {
doclet.type = tag.value.type;
// for backwards compatibility, we allow @type for functions to imply return type
if (doclet.kind === 'function') {
doclet.addTag('returns', tag.text); // for backwards compatibility we allow @type for functions to imply return type
doclet.addTag('returns', tag.text);
}
}
}

View File

@ -35,6 +35,44 @@
* @return {string} An updated version of the complete string.
*/
/**
* Create a regexp that matches a specific inline tag, or all inline tags.
*
* @private
* @memberof module:jsdoc/tag/inline
* @param {?string} tagName - The inline tag that the regexp will match. May contain regexp
* characters. If omitted, matches any string.
* @param {?string} prefix - A prefix for the regexp. Defaults to an empty string.
* @param {?string} suffix - A suffix for the regexp. Defaults to an empty string.
* @returns {RegExp} A regular expression that matches the requested inline tag.
*/
function regExpFactory(tagName, prefix, suffix) {
tagName = tagName || '\\S+';
prefix = prefix || '';
suffix = suffix || '';
return new RegExp(prefix + '\\{@' + tagName + '\\s+((?:.|\n)+?)\\}' + suffix, 'gi');
}
/**
* Check whether a string is an inline tag. You can check for a specific inline tag or for any valid
* inline tag.
*
* @param {string} string - The string to check.
* @param {?string} tagName - The inline tag to match. May contain regexp characters. If this
* parameter is omitted, this method returns `true` for any valid inline tag.
* @returns {boolean} Set to `true` if the string is a valid inline tag or `false` in all other
* cases.
*/
exports.isInlineTag = function(string, tagName) {
try {
return regExpFactory(tagName, '^', '$').test(string);
}
catch(e) {
return false;
}
};
/**
* Replace all instances of multiple inline tags with other text.
*
@ -61,7 +99,7 @@ exports.replaceInlineTags = function(string, replacers) {
string = string || '';
Object.keys(replacers).forEach(function(replacer) {
var tagRegExp = new RegExp('\\{@' + replacer + '\\s+((?:.|\n)+?)\\}', 'gi');
var tagRegExp = regExpFactory(replacer);
var matches;
// call the replacer once for each match
while ( (matches = tagRegExp.exec(string)) !== null ) {
@ -69,7 +107,7 @@ exports.replaceInlineTags = function(string, replacers) {
}
});
return {
return {
tags: tagInfo,
newString: string.trim()
};

View File

@ -6,6 +6,13 @@
* @license Apache License 2.0 - See file 'LICENSE.md' in this project.
*/
var jsdoc = {
name: require('jsdoc/name'),
tag: {
inline: require('jsdoc/tag/inline')
}
};
/**
* Information about a type expression extracted from tag text.
*
@ -35,7 +42,7 @@ function unescapeBraces(text) {
var count = 0;
var position = 0;
var expression = '';
var startIndex = string.indexOf('{');
var startIndex = string.search(/\{[^@]/);
var textStartIndex;
if (startIndex !== -1) {
@ -83,6 +90,7 @@ function getTagInfo(tagValue, canHaveName, canHaveType) {
var typeExpression = '';
var text = tagValue;
var expressionAndText;
var nameAndDescription;
var typeOverride;
if (canHaveType) {
@ -92,15 +100,14 @@ function getTagInfo(tagValue, canHaveName, canHaveType) {
}
if (canHaveName) {
// like: name, [name], name text, [name] text, name - text, or [name] - text
text.match(/^(\[[^\]]+\]|\S+)((?:\s*\-\s*|\s+)(\S[\s\S]*))?$/);
name = RegExp.$1;
text = RegExp.$3;
nameAndDescription = jsdoc.name.splitName(text);
name = nameAndDescription.name;
text = nameAndDescription.description;
}
// an inline @type tag, like {@type Foo}, overrides the type expression
if (canHaveType) {
typeOverride = require('jsdoc/tag/inline').extractInlineTag(text, 'type');
typeOverride = jsdoc.tag.inline.extractInlineTag(text, 'type');
if (typeOverride.tags && typeOverride.tags[0]) {
typeExpression = typeOverride.tags[0].text || typeExpression;
}
@ -134,6 +141,7 @@ function getTagInfo(tagValue, canHaveName, canHaveType) {
* can vary (for example, in a function that accepts any number of parameters).
*/
// TODO: move to module:jsdoc/name?
/**
* Extract JSDoc-style type information from the name specified in the tag info, including the
* member name; whether the member is optional; and the default value of the member.

View File

@ -51,8 +51,19 @@ function makeFilenameUnique(filename, str) {
return filename;
}
// compute it here just once
var nsprefix = /^(event|module|external):/;
function cleanseFilename(str) {
str = str || '';
// allow for namespace prefix
// TODO: use prefixes in jsdoc/doclet
return str.replace(/^(event|module|external|package):/, '$1-')
// use - instead of ~ to denote 'inner'
.replace(/~/g, '-')
// use _ instead of # to denote 'instance'
.replace(/\#/g, '_')
// remove the variation, if any
.replace(/\([\s\S]*\)$/, '');
}
var htmlsafe = exports.htmlsafe = function(str) {
return str.replace(/</g, '&lt;');
@ -71,11 +82,7 @@ var htmlsafe = exports.htmlsafe = function(str) {
*/
var getUniqueFilename = exports.getUniqueFilename = function(str) {
// allow for namespace prefix
var basename = str.replace(nsprefix, '$1-')
// use - instead of ~ to denote 'inner'
.replace(/~/g, '-')
// remove the variation, if any
.replace(/\([\s\S]*\)$/, '');
var basename = cleanseFilename(str);
// if the basename includes characters that we can't use in a filepath, remove everything up to
// and including the last bad character
@ -342,13 +349,18 @@ exports.resolveLinks = function(str) {
function extractLeadingText(string, completeTag) {
var tagIndex = string.indexOf(completeTag);
var leadingText = null;
var leadingTextInfo = /\[(.+?)\]/.exec(string);
var leadingTextRegExp = /\[(.+?)\]/g;
var leadingTextInfo = leadingTextRegExp.exec(string);
// did we find leading text, and if so, does it immediately precede the tag?
if ( leadingTextInfo && leadingTextInfo.index &&
(leadingTextInfo.index + leadingTextInfo[0].length === tagIndex) ) {
string = string.replace(leadingTextInfo[0], '');
leadingText = leadingTextInfo[1];
while (leadingTextInfo && leadingTextInfo.length) {
if (leadingTextInfo.index + leadingTextInfo[0].length === tagIndex) {
string = string.replace(leadingTextInfo[0], '');
leadingText = leadingTextInfo[1];
break;
}
leadingTextInfo = leadingTextRegExp.exec(string);
}
return {
@ -466,11 +478,7 @@ exports.getMembers = function(data) {
// functions that are also modules (as in "module.exports = function() {};") are not globals
members.globals = members.globals.filter(function(doclet) {
if ( isModuleFunction(doclet) ) {
return false;
}
return true;
return !isModuleFunction(doclet);
});
return members;
@ -576,7 +584,7 @@ exports.getSignatureReturns = function(d, cssClass) {
if (d.returns) {
d.returns.forEach(function(r) {
if (r.type && r.type.names) {
if (r && r.type && r.type.names) {
if (!returnTypes.length) {
returnTypes = r.type.names;
}
@ -699,18 +707,47 @@ function getFilename(longname) {
/** Turn a doclet into a URL. */
exports.createLink = function(doclet) {
var filename;
var fragment;
var match;
var fakeContainer;
var url = '';
var INSTANCE = exports.scopeToPunc.instance;
var longname = doclet.longname;
var filename;
if ( containers.indexOf(doclet.kind) !== -1 || isModuleFunction(doclet) ) {
url = getFilename(longname);
// handle doclets in which doclet.longname implies that the doclet gets its own HTML file, but
// doclet.kind says otherwise. this happens due to mistagged JSDoc (for example, a module that
// somehow has doclet.kind set to `member`).
// TODO: generate a warning (ideally during parsing!)
if (containers.indexOf(doclet.kind) === -1) {
match = /(\S+):/.exec(longname);
if (match && containers.indexOf(match[1]) !== -1) {
fakeContainer = match[1];
}
}
// the doclet gets its own HTML file
if ( containers.indexOf(doclet.kind) !== -1 || isModuleFunction(doclet) ) {
filename = getFilename(longname);
}
// mistagged version of a doclet that gets its own HTML file
else if ( containers.indexOf(doclet.kind) === -1 && fakeContainer ) {
filename = getFilename(doclet.memberof || longname);
if (doclet.name === doclet.longname) {
fragment = '';
}
else {
fragment = doclet.name || '';
}
}
// the doclet is within another HTML file
else {
filename = getFilename(doclet.memberof || exports.globalName);
url = filename + INSTANCE + getNamespace(doclet.kind) + doclet.name;
fragment = getNamespace(doclet.kind) + (doclet.name || '');
}
url = fragment ? (filename + INSTANCE + fragment) : filename;
return url;
};

View File

@ -56,7 +56,7 @@ function cachedParse(expr, options) {
var cache = getTypeExpressionCache(options);
var parsedType;
if (cache && cache[expr]) {
if (cache && Object.prototype.hasOwnProperty.call(cache, expr)) {
return cache[expr];
} else {
parsedType = parse(expr, options);

File diff suppressed because one or more lines are too long

View File

@ -66,7 +66,6 @@ Stringifier.prototype.nullable = function(nullable) {
};
Stringifier.prototype.optional = function(optional) {
/*jshint boss: true */ // TODO: remove after JSHint releases the fix for jshint/jshint#878
if (optional === true) {
return '=';
} else {
@ -111,43 +110,46 @@ Stringifier.prototype['this'] = function(funcThis) {
return funcThis ? 'this:' + this.type(funcThis) : '';
};
// TODO: refactor for clarity
Stringifier.prototype.type = function(type) {
var result = '';
if (!type) {
return '';
return result;
}
// nullable comes first
var result = this.nullable(type.nullable);
// next portion varies by type
switch(type.type) {
case Types.AllLiteral:
result += this._formatNameAndType(type, '*');
result += this._formatRepeatableAndNullable(type, '',
this._formatNameAndType(type, '*'));
break;
case Types.FunctionType:
result += this._signature(type);
break;
case Types.NullLiteral:
result += this._formatNameAndType(type, 'null');
result += this._formatRepeatableAndNullable(type, '',
this._formatNameAndType(type, 'null'));
break;
case Types.RecordType:
result += this._record(type);
result += this._formatRepeatableAndNullable(type, '', this._record(type));
break;
case Types.TypeApplication:
result += this.type(type.expression);
result += this._formatRepeatableAndNullable(type, '', this.type(type.expression));
result += this.applications(type.applications);
break;
case Types.UndefinedLiteral:
result += this._formatNameAndType(type, 'undefined');
result += this._formatRepeatableAndNullable(type, '',
this._formatNameAndType(type, 'undefined'));
break;
case Types.TypeUnion:
result += this.elements(type.elements);
result += this._formatRepeatableAndNullable(type, '', this.elements(type.elements));
break;
case Types.UnknownLiteral:
result += this._formatNameAndType(type, '?');
result += this._formatRepeatableAndNullable(type, '',
this._formatNameAndType(type, '?'));
break;
default:
result += this._formatNameAndType(type);
result += this._formatRepeatableAndNullable(type, '', this._formatNameAndType(type));
}
// finally, optionality
@ -190,14 +192,23 @@ Stringifier.prototype._recordFields = function(fields) {
function combineNameAndType(nameString, typeString) {
var separator = (nameString && typeString) ? ':' : '';
return nameString + separator + typeString;
}
Stringifier.prototype._formatRepeatable = function(nameString, typeString) {
var open = this._inFunctionSignatureParams ? '...[' : '...';
var close = this._inFunctionSignatureParams ? ']' : '';
Stringifier.prototype._formatRepeatableAndNullable = function(type, nameString, typeString) {
var open = '';
var close = '';
var combined;
return open + combineNameAndType(nameString, typeString) + close;
if (type.repeatable) {
open = this._inFunctionSignatureParams ? '...[' : '...';
close = this._inFunctionSignatureParams ? ']' : '';
}
combined = this.nullable(type.nullable) + combineNameAndType(nameString, typeString);
return open + combined + close;
};
Stringifier.prototype._formatNameAndType = function(type, literal) {
@ -215,17 +226,14 @@ Stringifier.prototype._formatNameAndType = function(type, literal) {
nameString = openTag + nameString + '</a>';
}
if (type.repeatable === true) {
return this._formatRepeatable(nameString, typeString);
} else {
return combineNameAndType(nameString, typeString);
}
return combineNameAndType(nameString, typeString);
};
Stringifier.prototype._signature = function(type) {
var params = [];
var param;
var result;
var signatureBase;
// these go within the signature's parens, in this order
var props = [
@ -245,7 +253,8 @@ Stringifier.prototype._signature = function(type) {
}
this._inFunctionSignatureParams = false;
result = 'function(' + params.join(', ') + ')';
signatureBase = 'function(' + params.join(', ') + ')';
result = this._formatRepeatableAndNullable(type, '', signatureBase);
result += this.result(type.result);
return result;

29
node_modules/catharsis/package.json generated vendored

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,12 @@
{
"name": "jsdoc",
<<<<<<< HEAD
"version": "3.2.0-dev",
"revision": "1382363021327",
=======
"version": "3.3.0-dev",
"revision": "1381768141793",
>>>>>>> master
"description": "An API documentation generator for JavaScript.",
"keywords": [ "documentation", "javascript" ],
"licenses": [
@ -10,15 +15,13 @@
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
],
"repositories": [
{
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
}
],
"repository": {
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
},
"dependencies": {
"async": "0.1.22",
"catharsis": "0.5.6",
"catharsis": "0.7.0",
"crypto-browserify": "git+https://github.com/dominictarr/crypto-browserify.git#95c5d505",
"esprima": "1.0.4",
"js2xmlparser": "0.1.0",

View File

@ -4,6 +4,9 @@
* to get JSDoc to run.
*/
// Set the JS version that the Rhino interpreter will use.
version(180);
/**
* Emulate DOM timeout/interval functions.
* @see https://developer.mozilla.org/en-US/docs/DOM/window#Methods

View File

@ -1,20 +1,27 @@
To create or use your own template, create a folder, and give it the name of your template, for example "mycooltemplate". Within this folder create a file named "publish.js". That file must define a global method named "publish". For example:
To create or use your own template:
1. Create a folder with the same name as your template (for example, `mycooltemplate`).
2. Within the template folder, create a file named `publish.js`. This file must be a CommonJS module that exports a method named `publish`.
For example:
````javascript
/** @module publish */
/**
* Turn the data about your docs into file output.
* @global
* Generate documentation output.
*
* @param {TAFFY} data - A TaffyDB collection representing
* all the symbols documented in your code.
* @param {object} opts - An object with options information.
*/
function publish(data, opts) {
exports.publish = function(data, opts) {
// do stuff here to generate your output files
}
};
````
To invoke JSDoc 3 with your own template, use the `-t` command line option, giving it the path to your template folder.
To invoke JSDoc 3 with your own template, use the `-t` command line option, and specify the path to your template folder:
````
./jsdoc mycode.js -t /path/to/mycooltemplate
````
````

View File

@ -66,7 +66,10 @@ function addSignatureParams(f) {
function addSignatureReturns(f) {
var returnTypes = helper.getSignatureReturns(f);
f.signature = '<span class="signature">'+(f.signature || '') + '</span>' + '<span class="type-signature">'+(returnTypes.length? ' &rarr; {'+returnTypes.join('|')+'}' : '')+'</span>';
f.signature = '<span class="signature">' + (f.signature || '') + '</span>' +
'<span class="type-signature">' +
(returnTypes && returnTypes.length ? ' &rarr; {' + returnTypes.join('|') + '}' : '') +
'</span>';
}
function addSignatureTypes(f) {
@ -78,7 +81,9 @@ function addSignatureTypes(f) {
function addAttribs(f) {
var attribs = helper.getAttribs(f);
f.attribs = '<span class="type-signature">'+htmlsafe(attribs.length? '<'+attribs.join(', ')+'> ' : '')+'</span>';
f.attribs = '<span class="type-signature">' + htmlsafe(attribs.length ?
// we want the template output to say 'abstract', not 'virtual'
'<' + attribs.join(', ').replace('virtual', 'abstract') + '> ' : '') + '</span>';
}
function shortenPaths(files, commonPrefix) {
@ -127,7 +132,8 @@ function generate(title, docs, filename, resolveLinks) {
fs.writeFileSync(outpath, html, 'utf8');
}
function generateSourceFiles(sourceFiles) {
function generateSourceFiles(sourceFiles, encoding) {
encoding = encoding || 'utf8';
Object.keys(sourceFiles).forEach(function(file) {
var source;
// links are keyed to the shortened path in each doclet's `meta.filename` property
@ -137,7 +143,7 @@ function generateSourceFiles(sourceFiles) {
try {
source = {
kind: 'source',
code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, 'utf8') )
code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) )
};
}
catch(e) {
@ -478,10 +484,10 @@ exports.publish = function(taffyData, opts, tutorials) {
attachModuleSymbols( find({ kind: ['class', 'function'], longname: {left: 'module:'} }),
members.modules );
// only output pretty-printed source files if requested; do this before generating any other
// pages, so the other pages can link to the source files
if (conf['default'].outputSourceFiles) {
generateSourceFiles(sourceFiles);
// output pretty-printed source files by default; do this before generating any other pages, so
// that the other pages can link to the source files
if (!conf['default'] || conf['default'].outputSourceFiles !== false) {
generateSourceFiles(sourceFiles, opts.encoding);
}
if (members.globals.length) { generate('Global', [{kind: 'globalobj'}], globalUrl); }

View File

@ -9,7 +9,7 @@
numbered = source.innerHTML.split('\n');
numbered = numbered.map(function(item) {
counter++;
return '<span id="line' + counter + '"></span>' + item;
return '<span id="line' + counter + '" class="line"></span>' + item;
});
source.innerHTML = numbered.join('\n');

View File

@ -242,6 +242,11 @@ h6
border-left: 3px #ddd solid;
}
.prettyprint code span.line
{
display: inline-block;
}
.params, .props
{
border-spacing: 0;

View File

@ -1,5 +1,5 @@
<?js
var data = obj;
var data = obj || {};
if (data.description) {
?>
<div class="param-desc">

21
test/fixtures/augmentstag4.js vendored Normal file
View File

@ -0,0 +1,21 @@
// used to test jsdoc/augments module directly
/**
* @constructor
* @classdesc Base class
*/
var Base = function() {
/** member */
this.test1 = "base";
/** another member */
this.test2 = null;
};
/**
* @constructor
* @extends Base
* @classdesc Extension of Base
*/
var Derived = function() {
this.test1 = "derived";
};

17
test/fixtures/letkeyword.js vendored Normal file
View File

@ -0,0 +1,17 @@
/*global define: true */
define( [], function() {
"use strict";
/**
* My example module.
* @exports exampleModule
*/
let myModule = {
/**
* My example method.
*/
exampleMethod: function() {}
};
return myModule;
} );

9
test/fixtures/paramtaginvalidtype.js vendored Normal file
View File

@ -0,0 +1,9 @@
/**
* @constructor
*/
var Test = function () {};
/**
* @param {string, number} a
*/
Test.prototype.test = function (a) {};

View File

@ -1,12 +1,20 @@
/**
* @requires module:foo/helper
*/
* @requires module:foo/helper
*/
function foo() {
}
/**
* @requires foo
* @requires Pez#blat this text is ignored
*/
* @requires foo
* @requires Pez#blat this text is ignored
*/
function bar() {
}
/**
* @requires {@link module:zest}
* @requires {@linkplain module:zing}
* @requires {@linkstupid module:pizzazz}
*/
function baz() {
}

View File

@ -9,3 +9,11 @@ function find(targetName) {
*/
function bind(callback) {
}
// This test exists because there used to be a bug in jsdoc which
// would cause it to fail parsing.
/**
* @return An object to be passed to {@link find}.
*/
function convert(name) {
}

14
test/fixtures/typetagwithnewline.js vendored Normal file
View File

@ -0,0 +1,14 @@
/** @class Matryoshka */
function Matryoshka() {}
/**
* @type {(!Array.<number>|
* !Array.<!Array.<number>>)}
*/
Matryoshka.mini;
/**
* @type (!Array.<number>|!Array.<!Array.<number>>|
* !Array.<!Array.<!Array.<number>>>)
*/
Matryoshka.mega;

View File

@ -27,7 +27,7 @@ describe("aliases", function() {
expect(foundMember[0].scope).toEqual('instance');
});
it('When a symbol is a member of an aliased class, a this-variables is documented as if it were a member that class.', function() {
it('When a symbol is a member of an aliased class, a this-variable is documented as if it were a member that class.', function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/alias3.js');
var tcm = docSet.getByLongname('trackr.CookieManager')[0];
var tcmValue = docSet.getByLongname('trackr.CookieManager#value')[0];

View File

@ -0,0 +1,28 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe('let keyword', function() {
var docSet;
var exampleModule;
var exampleMethod;
function getDocSet() {
docSet = jasmine.getDocSetFromFile('test/fixtures/letkeyword.js');
exampleModule = docSet.getByLongname('module:exampleModule');
exampleMethod = docSet.getByLongname('module:exampleModule.exampleMethod');
}
it('should be able to compile JS files that contain the "let" keyword', function() {
expect(getDocSet).not.toThrow();
});
it('should correctly recognize a module defined with the "let" keyword', function() {
expect(exampleModule).toBeDefined();
expect( Array.isArray(exampleModule) ).toBe(true);
expect(exampleModule.length).toBe(1);
});
it('should correctly recognize members of a module defined with the "let" keyword', function() {
expect(exampleMethod).toBeDefined();
expect( Array.isArray(exampleMethod) ).toBe(true);
expect(exampleMethod.length).toBe(1);
});
});

View File

@ -1,16 +1,23 @@
/*global beforeEach: true, describe: true, env: true, expect: true, it: true */
/*global afterEach: true, beforeEach: true, describe: true, env: true, expect: true, it: true */
describe("module names", function() {
var runtime = require('jsdoc/util/runtime');
var parser = require( runtime.getModulePath('jsdoc/src/parser') );
var srcParser = null;
var doclets;
var parser = require( runtime.getModulePath('jsdoc/src/parser') );
var srcParser = null;
var sourcePaths = env.opts._.slice(0);
beforeEach(function() {
env.opts._ = [__dirname + '/test/fixtures/modules/'];
env.opts._ = [__dirname + '/test/fixtures/modules/data/'];
srcParser = new parser.Parser();
require('jsdoc/src/handlers').attachTo(srcParser);
});
afterEach(function() {
env.opts._ = sourcePaths;
});
it("should create a name from the file path when no documented module name exists", function() {
doclets = srcParser.parse(__dirname + '/test/fixtures/modules/data/mod-1.js');
expect(doclets.length).toBeGreaterThan(1);

View File

@ -0,0 +1,28 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe('@type tag containing a newline character', function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/typetagwithnewline.js');
var mini = docSet.getByLongname('Matryoshka.mini')[0];
var mega = docSet.getByLongname('Matryoshka.mega')[0];
it('When the type expression for a @type tag contains a newline character and is not ' +
'enclosed in braces, the type expression is parsed correctly.', function() {
expect(mini).toBeDefined();
expect(mini.type).toBeDefined();
expect(mini.type.names).toBeDefined();
expect(mini.type.names.length).toBe(2);
expect(mini.type.names[0]).toBe('!Array.<number>');
expect(mini.type.names[1]).toBe('!Array.<!Array.<number>>');
});
it('When the type expression for a @type tag contains a newline character and is enclosed ' +
'in braces, the type expression is parsed correctly.', function() {
expect(mega).toBeDefined();
expect(mega.type).toBeDefined();
expect(mega.type.names).toBeDefined();
expect(mega.type.names.length).toBe(3);
expect(mega.type.names[0]).toBe('!Array.<number>');
expect(mega.type.names[1]).toBe('!Array.<!Array.<number>>');
expect(mega.type.names[2]).toBe('!Array.<!Array.<!Array.<number>>>');
});
});

View File

@ -85,7 +85,7 @@ describe("jsdoc/name", function() {
expect(parts.memberof).toEqual('channels."#ops"');
expect(parts.scope).toEqual('#');
startName = 'channels["#bots"]["log.max"]',
startName = 'channels["#bots"]["log.max"]';
parts = jsdoc.name.shorten(startName);
expect(parts.name).toEqual('"log.max"');
@ -174,20 +174,37 @@ describe("jsdoc/name", function() {
expect(parts.name, 'ns.Page#"last \\"sentence\\"".words~sort(2)');
expect(parts.description, 'This is a description.');
});
it('should strip the separator when the separator starts on the same line as the name', function() {
var startName = 'socket - The networking kind, not the wrench.';
var parts = jsdoc.name.splitName(startName);
expect(parts.name).toBe('socket');
expect(parts.description).toBe('The networking kind, not the wrench.');
});
it('should not strip a separator that is preceded by a line break', function() {
var startName = 'socket\n - The networking kind, not the wrench.';
var parts = jsdoc.name.splitName(startName);
expect(parts.name).toBe('socket');
expect(parts.description).toBe('- The networking kind, not the wrench.');
});
});
describe("resolve", function() {
// TODO: further tests (namespaces, modules, ...)
// @event testing.
var event = '@event';
var memberOf = '@memberof MyClass';
var name = '@name A';
function makeDoclet(bits) {
var comment = '/**\n' + bits.join('\n') + '\n*/';
return new jsdoc.doclet.Doclet(comment, {});
}
// @event testing.
var event = '@event';
var memberOf = '@memberof MyClass';
var name = '@name A';
// Test the basic @event that is not nested.
it('unnested @event gets resolved correctly', function() {
var doclet = makeDoclet([event, name]),
@ -274,12 +291,23 @@ describe("jsdoc/name", function() {
});
// a double-nested one just in case
it('@event @name MyClass.EventName @memberof somethingelse workse', function() {
it('@event @name MyClass.EventName @memberof somethingelse works', function() {
var doclet = makeDoclet([event, '@name MyClass.A', '@memberof MyNamespace']),
out = jsdoc.name.resolve(doclet);
expect(doclet.name).toEqual('A');
expect(doclet.memberof).toEqual('MyNamespace.MyClass');
expect(doclet.longname).toEqual('MyNamespace.MyClass.event:A');
});
// other cases
it('correctly handles a function parameter named "prototype"', function() {
var doclet = makeDoclet(['@name Bar.prototype.baz', '@function', '@memberof module:foo',
'@param {string} qux']);
var out = jsdoc.name.resolve(doclet);
expect(doclet.name).toBe('baz');
expect(doclet.memberof).toBe('module:foo.Bar');
expect(doclet.longname).toBe('module:foo.Bar#baz');
});
});
});

View File

@ -191,11 +191,25 @@ describe("jsdoc/src/parser", function() {
});
it("should fire 'parseComplete' events after it finishes parsing files", function() {
var eventObject;
var spy = jasmine.createSpy(),
sourceCode = ['javascript:var bar = false;'];
sourceCode = ['javascript:/** @class */function Foo() {}'];
require('jsdoc/src/handlers').attachTo(parser);
parser.on('parseComplete', spy).parse(sourceCode);
expect(spy).toHaveBeenCalled();
expect(spy.mostRecentCall.args[0].sourcefiles).toEqual(["[[string0]]"]);
eventObject = spy.mostRecentCall.args[0];
expect(eventObject).toBeDefined();
expect( Array.isArray(eventObject.sourcefiles) ).toBe(true);
expect(eventObject.sourcefiles.length).toBe(1);
expect(eventObject.sourcefiles[0]).toBe('[[string0]]');
expect( Array.isArray(eventObject.doclets) ).toBe(true);
expect(eventObject.doclets.length).toBe(1);
expect(eventObject.doclets[0].kind).toBe('class');
expect(eventObject.doclets[0].longname).toBe('Foo');
});
it("should fire processingComplete when fireProcessingComplete is called", function() {
@ -219,6 +233,16 @@ describe("jsdoc/src/parser", function() {
expect(parse).not.toThrow();
});
it("should comment out a POSIX hashbang at the start of the file", function() {
function parse() {
parser.parse(parserSrc);
}
var parserSrc = 'javascript:#!/usr/bin/env node\n/** class */function Foo() {}';
expect(parse).not.toThrow();
});
});
describe('results', function() {

View File

@ -12,6 +12,11 @@ describe('jsdoc/tag/inline', function() {
expect(typeof jsdoc.tag.inline).toBe('object');
});
it('should export an isInlineTag function', function() {
expect(jsdoc.tag.inline.isInlineTag).toBeDefined();
expect(typeof jsdoc.tag.inline.isInlineTag).toBe('function');
});
it('should export a replaceInlineTag function', function() {
expect(jsdoc.tag.inline.replaceInlineTag).toBeDefined();
expect(typeof jsdoc.tag.inline.replaceInlineTag).toBe('function');
@ -22,6 +27,43 @@ describe('jsdoc/tag/inline', function() {
expect(typeof jsdoc.tag.inline.replaceInlineTag).toBe('function');
});
describe('isInlineTag', function() {
var isInlineTag = jsdoc.tag.inline.isInlineTag;
it('should correctly identify an inline tag', function() {
expect( isInlineTag('{@mytag hooray}', 'mytag') ).toBe(true);
});
it('should correctly identify a non-inline tag', function() {
expect( isInlineTag('mytag hooray', 'mytag') ).toBe(false);
});
it('should report that a string containing an inline tag is not an inline tag', function() {
expect( isInlineTag('this is {@mytag hooray}', 'mytag') ).toBe(false);
});
it('should default to allowing any inline tag', function() {
expect( isInlineTag('{@anyoldtag will do}') ).toBe(true);
});
it('should still identify non-inline tags when a tag name is not provided', function() {
expect( isInlineTag('mytag hooray') ).toBe(false);
});
it('should allow regexp characters in the tag name', function() {
expect( isInlineTag('{@mytags hooray}', 'mytag\\S') ).toBe(true);
});
it('should return false (rather than throwing) with invalid input', function() {
function badInput() {
return isInlineTag({});
}
expect(badInput).not.toThrow();
expect( badInput() ).toBe(false);
});
});
describe('replaceInlineTag', function() {
it('should throw if the tag is matched and the replacer is invalid', function() {
function badReplacerUndefined() {

View File

@ -737,6 +737,23 @@ 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() {
@ -1094,6 +1111,20 @@ describe("jsdoc/util/templateHelper", function() {
expect(output).toBe('This is a <a href="path/to/test.html">hello there</a>.');
});
it('should translate [dummy text] and [hello there]{@link test} into an HTML link with the custom content.', function() {
var input = 'This is [dummy text] and [hello there]{@link test}.',
output = helper.resolveLinks(input);
expect(output).toBe('This is [dummy text] and <a href="path/to/test.html">hello there</a>.');
});
it('should translate [dummy text] and [more] and [hello there]{@link test} into an HTML link with the custom content.', function() {
var input = 'This is [dummy text] and [more] and [hello there]{@link test}.',
output = helper.resolveLinks(input);
expect(output).toBe('This is [dummy text] and [more] and <a href="path/to/test.html">hello there</a>.');
});
it('should ignore [hello there].', function() {
var input = 'This is a [hello there].',
output = helper.resolveLinks(input);
@ -1342,6 +1373,58 @@ describe("jsdoc/util/templateHelper", function() {
expect(url).toEqual('module-bar.html');
});
it('should create a url for a doclet with the wrong kind (caused by incorrect JSDoc tags', function() {
var moduleDoclet = {
kind: 'module',
longname: 'module:baz',
name: 'module:baz'
};
var badDoclet = {
kind: 'member',
longname: 'module:baz',
name: 'module:baz'
};
var moduleDocletUrl = helper.createLink(moduleDoclet);
var badDocletUrl = helper.createLink(badDoclet);
expect(moduleDocletUrl).toBe('module-baz.html');
expect(badDocletUrl).toBe('module-baz.html');
});
it('should create a url for a function that is a member of a doclet with the wrong kind', function() {
var badModuleDoclet = {
kind: 'member',
longname: 'module:qux',
name: 'module:qux'
};
var memberDoclet = {
kind: 'function',
name: 'frozzle',
memberof: 'module:qux',
scope: 'instance',
longname: 'module:qux#frozzle'
};
var badModuleDocletUrl = helper.createLink(badModuleDoclet);
var memberDocletUrl = helper.createLink(memberDoclet);
expect(badModuleDocletUrl).toBe('module-qux.html');
expect(memberDocletUrl).toBe('module-qux.html#frozzle');
});
it('should create a url for an empty package definition', function() {
var packageDoclet = {
kind: 'package',
name: undefined,
longname: 'package:undefined'
};
var packageDocletUrl = helper.createLink(packageDoclet);
expect(packageDocletUrl).toBe('global.html');
});
});
describe("resolveAuthorLinks", function() {

View File

@ -1,65 +1,54 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe("@augments tag", function() {
/*jshint unused: false */
var docSet = jasmine.getDocSetFromFile('test/fixtures/augmentstag.js');
var foo = docSet.getByLongname('Foo')[0];
var fooProp1 = docSet.getByLongname('Foo#prop1')[0];
var fooProp2 = docSet.getByLongname('Foo#prop2')[0];
var fooProp3 = docSet.getByLongname('Foo#prop3')[0];
var fooMethod1 = docSet.getByLongname('Foo#method1')[0];
var fooMethod2 = docSet.getByLongname('Foo#method2')[0];
var bar = docSet.getByLongname('Bar')[0];
var barProp1 = docSet.getByLongname('Bar#prop1')[0];
var barProp2 = docSet.getByLongname('Bar#prop2')[0];
var barProp3 = docSet.getByLongname('Bar#prop3')[0];
var barMethod1 = docSet.getByLongname('Bar#method1')[0];
var barMethod2 = docSet.getByLongname('Bar#method2')[0];
var barMethod2All = docSet.getByLongname('Bar#method2');
var bazProp1 = docSet.getByLongname('Baz#prop1')[0];
var bazProp1All = docSet.getByLongname('Baz#prop1');
var bazProp2 = docSet.getByLongname('Baz#prop2')[0];
var bazProp3 = docSet.getByLongname('Baz#prop3')[0];
var bazMethod1 = docSet.getByLongname('Baz#method1')[0];
var bazMethod2 = docSet.getByLongname('Baz#method2')[0];
var bazMethod3 = docSet.getByLongname('Baz#method3')[0];
var docSet2 = jasmine.getDocSetFromFile('test/fixtures/augmentstag2.js');
var qux = docSet2.getByLongname('Qux')[0];
var docSet3 = jasmine.getDocSetFromFile('test/fixtures/augmentstag3.js');
var FooMethod1 = docSet3.getByLongname('Foo#method1')[0];
var BarMethod2 = docSet3.getByLongname('Bar#method2')[0];
var FooBarMethod1 = docSet3.getByLongname('FooBar#method1')[0];
var FooBarMethod2 = docSet3.getByLongname('FooBar#method2')[0];
var docSet4 = jasmine.getDocSetFromFile('test/fixtures/augmentstag4.js');
it('When a symbol has an @augments tag, the doclet has a augments property that includes that value.', function() {
var bar = docSet.getByLongname('Bar')[0];
expect(typeof bar.augments).toBe('object');
expect(bar.augments[0]).toBe('Foo');
});
it('When an object is extended, the original is not modified', function() {
var fooProp3 = docSet.getByLongname('Foo#prop3')[0];
expect(fooProp3).toBeUndefined();
});
it('When an object is extended, it inherits properties set in parent constructor', function() {
var fooProp1 = docSet.getByLongname('Foo#prop1')[0];
var barProp1 = docSet.getByLongname('Bar#prop1')[0];
expect(fooProp1.memberof).toBe("Foo");
expect(barProp1.memberof).toBe("Bar");
expect(barProp1.description).toBe(fooProp1.description);
});
it('When an object is extended, it inherits properties set on parent prototype', function() {
var fooProp2 = docSet.getByLongname('Foo#prop2')[0];
var barProp2 = docSet.getByLongname('Bar#prop2')[0];
expect(fooProp2.memberof).toBe("Foo");
expect(barProp2.memberof).toBe("Bar");
expect(barProp2.description).toBe(fooProp2.description);
});
it('When an object is extended, it inherits methods set on parent prototype', function() {
var fooMethod1 = docSet.getByLongname('Foo#method1')[0];
var barMethod1 = docSet.getByLongname('Bar#method1')[0];
expect(fooMethod1.memberof).toBe("Foo");
expect(barMethod1.memberof).toBe("Bar");
expect(barMethod1.description).toBe(fooMethod1.description);
});
it('When an object is extended, it may override methods set on parent prototype', function() {
var fooMethod2 = docSet.getByLongname('Foo#method2')[0];
var barMethod2 = docSet.getByLongname('Bar#method2')[0];
expect(fooMethod2.memberof).toBe("Foo");
expect(fooMethod2.description).toBe("Second parent method.");
expect(barMethod2.memberof).toBe("Bar");
@ -67,10 +56,19 @@
});
it('When an object is extended, and it overrides an ancestor method, the child does not include docs for the ancestor method.', function() {
var barMethod2All = docSet.getByLongname('Bar#method2');
expect(barMethod2All.length).toBe(1);
});
it('When an object is extended, it inherits properties set on grandparent prototype', function() {
var fooProp1 = docSet.getByLongname('Foo#prop1')[0];
var barProp1 = docSet.getByLongname('Bar#prop1')[0];
var bazProp1 = docSet.getByLongname('Baz#prop1')[0];
var bazMethod1 = docSet.getByLongname('Baz#method1')[0];
var bazMethod2 = docSet.getByLongname('Baz#method2')[0];
var bazMethod3 = docSet.getByLongname('Baz#method3')[0];
expect(fooProp1.memberof).toBe("Foo");
expect(barProp1.memberof).toBe("Bar");
expect(bazProp1.memberof).toBe("Baz");
@ -81,28 +79,54 @@
});
it('(Grand)children correctly identify the original source of inherited members', function(){
expect(fooProp1.inherits).not.toBeDefined();
expect(barProp3.inherits).not.toBeDefined();
expect(barProp1.inherits).toBe("Foo#prop1");
expect(bazProp2.inherits).toBe("Foo#prop2");
expect(bazProp3.inherits).toBe("Bar#prop3");
expect(bazMethod1.inherits).toBe("Foo#method1");
var fooProp1 = docSet.getByLongname('Foo#prop1')[0];
var barProp1 = docSet.getByLongname('Bar#prop1')[0];
var barProp3 = docSet.getByLongname('Bar#prop3')[0];
var bazProp2 = docSet.getByLongname('Baz#prop2')[0];
var bazProp3 = docSet.getByLongname('Baz#prop3')[0];
var bazMethod1 = docSet.getByLongname('Baz#method1')[0];
expect(fooProp1.inherits).not.toBeDefined();
expect(barProp3.inherits).not.toBeDefined();
expect(barProp1.inherits).toBe("Foo#prop1");
expect(bazProp2.inherits).toBe("Foo#prop2");
expect(bazProp3.inherits).toBe("Bar#prop3");
expect(bazMethod1.inherits).toBe("Foo#method1");
});
it('When an object is extended, and it overrides an ancestor property, the child does not include docs for the ancestor property.', function() {
var bazProp1All = docSet.getByLongname('Baz#prop1');
expect(bazProp1All.length).toBe(1);
});
it('When a symbol has an @augments tag, and the parent is not documented, the doclet still has an augments property', function() {
var qux = docSet2.getByLongname('Qux')[0];
expect(typeof qux.augments).toBe('object');
expect(qux.augments[0]).toBe('UndocumentedThing');
});
it('When a symbol @augments multiple parents, it inherits methods from all parents', function() {
expect(FooBarMethod1).toBeDefined();
expect(FooBarMethod2).toBeDefined();
expect(FooBarMethod1.description).toBe(FooMethod1.description);
expect(FooBarMethod2.description).toBe(BarMethod2.description);
var fooMethod1 = docSet3.getByLongname('Foo#method1')[0];
var barMethod2 = docSet3.getByLongname('Bar#method2')[0];
var fooBarMethod1 = docSet3.getByLongname('FooBar#method1')[0];
var fooBarMethod2 = docSet3.getByLongname('FooBar#method2')[0];
expect(fooBarMethod1).toBeDefined();
expect(fooBarMethod2).toBeDefined();
expect(fooBarMethod1.description).toBe(fooMethod1.description);
expect(fooBarMethod2.description).toBe(barMethod2.description);
});
it('When a symbol overrides an inherited method without documenting the method, it uses the parent\'s docs', function() {
var baseMethod1 = docSet4.getByLongname('Base#test1')[0];
var derivedMethod1All = docSet4.getByLongname('Derived#test1');
var derivedMethod1 = derivedMethod1All[1];
expect(derivedMethod1All.length).toBe(2);
expect(derivedMethod1.undocumented).not.toBe(true);
expect(derivedMethod1.description).toBe(baseMethod1.description);
});
});

View File

@ -1,19 +1,32 @@
/*global describe: true, env: true, expect: true, it: true */
/*global beforeEach: true, afterEach: true, describe: true, env: true, expect: true, it: true */
describe("@overview tag", function() {
var parser = require('jsdoc/src/parser');
var runtime = require('jsdoc/util/runtime');
var parser = require( runtime.getModulePath('jsdoc/src/parser') );
var srcParser = new parser.Parser();
var doclets;
require('jsdoc/src/handlers').attachTo(srcParser);
doclets = srcParser.parse(__dirname + '/test/fixtures/file.js');
var parser = require( runtime.getModulePath('jsdoc/src/parser') );
var srcParser = null;
var sourcePaths = env.opts._.slice(0);
beforeEach(function() {
env.opts._ = [__dirname + '/test/fixtures/'];
srcParser = new parser.Parser();
require('jsdoc/src/handlers').attachTo(srcParser);
});
afterEach(function() {
env.opts._ = sourcePaths;
});
it('When a file overview tag appears in a doclet, the name of the doclet should contain the path to the file.', function() {
doclets = srcParser.parse(__dirname + '/test/fixtures/file.js');
expect(doclets[0].name).toMatch(/^(fixtures[\/\\]file\.js)$/);
});
it("The name and longname should be equal", function() {
doclets = srcParser.parse(__dirname + '/test/fixtures/file.js');
expect(doclets[0].name).toBe(doclets[0].longname);
});
});

View File

@ -1,3 +1,4 @@
/*global describe: true, env: true, expect: true, it: true, jasmine: true, spyOn: true */
describe("@param tag", function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/paramtag.js'),
find = docSet.getByLongname('find')[0],
@ -77,4 +78,28 @@ 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() {
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];
expect(test).toBeDefined();
expect(typeof test).toBe('object');
expect(test.meta).toBeDefined();
expect(typeof test.meta).toBe('object');
expect(test.meta.filename).toBeDefined();
expect(test.meta.filename).toBe('[[string0]]');
expect(test.description).not.toBeDefined();
env.opts.lenient = lenient;
});
});

View File

@ -1,14 +1,24 @@
/*global describe: true, expect: true, it: true, jasmine: true */
describe("@requires tag", function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/requirestag.js'),
foo = docSet.getByLongname('foo')[0],
bar = docSet.getByLongname('bar')[0];
var docSet = jasmine.getDocSetFromFile('test/fixtures/requirestag.js');
var foo = docSet.getByLongname('foo')[0];
var bar = docSet.getByLongname('bar')[0];
var baz = docSet.getByLongname('baz')[0];
it('When a symbol has an @requires tag, the doclet has a requires property that includes that value, with the "module:" namespace added.', function() {
expect(typeof foo.requires).toBe('object');
it('When a symbol has a @requires tag, the doclet has a requires property that includes that value, with the "module:" namespace added.', function() {
expect( Array.isArray(foo.requires) ).toBe(true);
expect(foo.requires[0]).toBe('module:foo/helper');
expect(typeof bar.requires).toBe('object');
expect( Array.isArray(bar.requires) ).toBe(true);
expect(bar.requires[0]).toBe('module:foo');
expect(bar.requires[1]).toBe('module:Pez#blat');
});
});
it('When a symbol has a @requires tag whose value is an inline {@link} tag, the doclet has a requires property that includes that tag without modification.', function() {
expect( Array.isArray(baz.requires) ).toBe(true);
expect(baz.requires[0]).toBe('{@link module:zest}');
expect(baz.requires[1]).toBe('{@linkplain module:zing}');
// by design, we don't validate the tag name, as long as it starts with @link
expect(baz.requires[2]).toBe('{@linkstupid module:pizzazz}');
});
});

View File

@ -1,7 +1,8 @@
describe("@returns tag", function() {
var docSet = jasmine.getDocSetFromFile('test/fixtures/returnstag.js'),
find = docSet.getByLongname('find')[0],
bind = docSet.getByLongname('bind')[0];
bind = docSet.getByLongname('bind')[0],
convert = docSet.getByLongname('convert')[0];
it('When a symbol has an @returns tag with a type and description, the doclet has a returns array that includes that return.', function() {
expect(typeof find.returns).toBe('object');
@ -15,4 +16,10 @@ describe("@returns tag", function() {
expect(bind.returns.length).toBe(1);
expect(bind.returns[0].description).toBe('The binding id.');
});
it('When a symbol has an @returns tag without a type but with an inline tag, the doclet does not confuse the inline tag for a type.', function() {
expect(typeof convert.returns).toBe('object');
expect(convert.returns.length).toBe(1);
expect(convert.returns[0].description).toBe('An object to be passed to {@link find}.');
});
});