diff --git a/.gitignore b/.gitignore index 4c9f446a..929e4afa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ jsdoc.jar test/tutorials/out conf.json out/ +.tern-port diff --git a/Jake/templates/package.json.tmpl b/Jake/templates/package.json.tmpl index 57043d92..89658af8 100644 --- a/Jake/templates/package.json.tmpl +++ b/Jake/templates/package.json.tmpl @@ -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", diff --git a/Jakefile.js b/Jakefile.js index 751dd54e..ab5969d7 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -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() }; diff --git a/README.md b/README.md index c4a05467..fb0bff79 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/changes.md b/changes.md index ce003292..4fc58726 100644 --- a/changes.md +++ b/changes.md @@ -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 diff --git a/jsdoc b/jsdoc index 5e6db50f..79714e15 100755 --- a/jsdoc +++ b/jsdoc @@ -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 diff --git a/jsdoc.cmd b/jsdoc.cmd index a2ef8f11..9bd2a562 100644 --- a/jsdoc.cmd +++ b/jsdoc.cmd @@ -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 diff --git a/jsdoc.js b/jsdoc.js index 69077861..d7e8609b 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -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); diff --git a/lib/jsdoc/augment.js b/lib/jsdoc/augment.js index 9ac7bbbb..614fd314 100644 --- a/lib/jsdoc/augment.js +++ b/lib/jsdoc/augment.js @@ -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; } }); diff --git a/lib/jsdoc/config.js b/lib/jsdoc/config.js index 9b6a52c4..01edc2c2 100644 --- a/lib/jsdoc/config.js +++ b/lib/jsdoc/config.js @@ -33,7 +33,7 @@ const defaults = { }, "source": { "includePattern": ".+\\.js(doc)?$", - "excludePattern": "(^|\\/)_" + "excludePattern": "(^|\\/|\\\\)_" }, "plugins": [] }; diff --git a/lib/jsdoc/name.js b/lib/jsdoc/name.js index 2ab440ef..e18b57cc 100644 --- a/lib/jsdoc/name.js +++ b/lib/jsdoc/name.js @@ -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 + }; }; diff --git a/lib/jsdoc/opts/args.js b/lib/jsdoc/opts/args.js index ddc3e620..994502fa 100644 --- a/lib/jsdoc/opts/args.js +++ b/lib/jsdoc/opts/args.js @@ -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/'); diff --git a/lib/jsdoc/path.js b/lib/jsdoc/path.js index 0be64476..29b4cccb 100644 --- a/lib/jsdoc/path.js +++ b/lib/jsdoc/path.js @@ -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 diff --git a/lib/jsdoc/src/handlers.js b/lib/jsdoc/src/handlers.js index c0fdf1f2..826e3130 100644 --- a/lib/jsdoc/src/handlers.js +++ b/lib/jsdoc/src/handlers.js @@ -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; diff --git a/lib/jsdoc/src/parser.js b/lib/jsdoc/src/parser.js index d4036b7d..2d4d54ea 100644 --- a/lib/jsdoc/src/parser.js +++ b/lib/jsdoc/src/parser.js @@ -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 diff --git a/lib/jsdoc/tag/dictionary/definitions.js b/lib/jsdoc/tag/dictionary/definitions.js index ad02345b..752f8211 100644 --- a/lib/jsdoc/tag/dictionary/definitions.js +++ b/lib/jsdoc/tag/dictionary/definitions.js @@ -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); } } } diff --git a/lib/jsdoc/tag/inline.js b/lib/jsdoc/tag/inline.js index 8be46e26..c25ab4d1 100644 --- a/lib/jsdoc/tag/inline.js +++ b/lib/jsdoc/tag/inline.js @@ -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() }; diff --git a/lib/jsdoc/tag/type.js b/lib/jsdoc/tag/type.js index a14df5be..e2d384a2 100644 --- a/lib/jsdoc/tag/type.js +++ b/lib/jsdoc/tag/type.js @@ -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. diff --git a/lib/jsdoc/util/templateHelper.js b/lib/jsdoc/util/templateHelper.js index ebdc6387..b5b58d57 100644 --- a/lib/jsdoc/util/templateHelper.js +++ b/lib/jsdoc/util/templateHelper.js @@ -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(/1?arguments[1]:{},peg$startRuleFunctions={TypeExpression:peg$parseTypeExpression},peg$startRuleFunction=peg$parseTypeExpression,peg$c0=null,peg$c1="",peg$c2=function(unk){return unk},peg$c3="?",peg$c4='"?"',peg$c5="!",peg$c6='"!"',peg$c7=function(prefix,expr,postfix){if(prefix!==""&&postfix!==""){return null}[prefix,postfix].forEach(function(modifier){if(modifier!==""){expr.nullable=modifier==="?"?true:false}});return expr},peg$c8=function(repeat,lit,opt){var result=lit;if(opt.optional){result.optional=true}if(repeat.repeatable){result.repeatable=true}return result},peg$c9="*",peg$c10='"*"',peg$c11=function(){return{type:Types.AllLiteral}},peg$c12=function(){return{type:Types.NullLiteral}},peg$c13=function(){return{type:Types.UndefinedLiteral}},peg$c14="...",peg$c15='"..."',peg$c16=function(){return{repeatable:true}},peg$c17="=",peg$c18='"="',peg$c19=function(){return{optional:true}},peg$c20="[]",peg$c21='"[]"',peg$c22=function(name){var result;if(!options.jsdoc){return null}result={type:Types.TypeApplication,expression:{type:Types.NameExpression,name:"Array"},applications:[name]};result.applications[0].type=Types.NameExpression;return result},peg$c23=function(exp,appl,opt){var result={};var nameExp={type:Types.NameExpression,name:exp.name};if(appl.length){result.type=Types.TypeApplication;result.expression=nameExp;result.applications=appl}else{result=nameExp}if(exp.repeatable){result.repeatable=true}if(opt.optional){result.optional=true}return result},peg$c24=function(name){if(!options.jsdoc){return null}return name},peg$c25=function(exp,opt){var result={type:Types.NameExpression,name:exp.name,reservedWord:true};if(exp.repeatable){result.repeatable=true}if(opt.optional){result.optional=true}return result},peg$c26=".",peg$c27='"."',peg$c28="<",peg$c29='"<"',peg$c30=">",peg$c31='">"',peg$c32=function(sep,l){if(sep===""&&!options.jsdoc){return null}return l},peg$c33=[],peg$c34=",",peg$c35='","',peg$c36=function(expr,list){var result=[expr];for(var i=0,l=list.length;ipos){peg$cachedPos=0;peg$cachedPosDetails={line:1,column:1,seenCR:false}}peg$cachedPos=pos;advance(peg$cachedPosDetails,peg$cachedPos)}return peg$cachedPosDetails}function peg$fail(expected){if(peg$currPospeg$maxFailPos){peg$maxFailPos=peg$currPos;peg$maxFailExpected=[]}peg$maxFailExpected.push(expected)}function peg$cleanupExpected(expected){var i=0;expected.sort();while(ipeg$currPos){s0=input.charAt(peg$currPos);peg$currPos++}else{s0=null;if(peg$silentFails===0){peg$fail(peg$c271)}}return s0}function peg$parseHexEscapeSequence(){var s0,s1,s2,s3,s4,s5;s0=peg$currPos;if(input.charCodeAt(peg$currPos)===120){s1=peg$c269;peg$currPos++}else{s1=null;if(peg$silentFails===0){peg$fail(peg$c270)}}if(s1!==null){s2=peg$currPos;s3=peg$currPos;s4=peg$parseHexDigit();if(s4!==null){s5=peg$parseHexDigit();if(s5!==null){s4=[s4,s5];s3=s4}else{peg$currPos=s3;s3=peg$c0}}else{peg$currPos=s3;s3=peg$c0}if(s3!==null){s3=input.substring(s2,peg$currPos)}s2=s3;if(s2!==null){peg$reportedPos=s0;s1=peg$c216(s2);if(s1===null){peg$currPos=s0;s0=s1}else{s0=s1}}else{peg$currPos=s0;s0=peg$c0}}else{peg$currPos=s0;s0=peg$c0}return s0}function peg$parseLineContinuation(){var s0,s1,s2;s0=peg$currPos;if(input.charCodeAt(peg$currPos)===92){s1=peg$c212;peg$currPos++}else{s1=null;if(peg$silentFails===0){peg$fail(peg$c213)}}if(s1!==null){s2=peg$parseLineTerminatorSequence();if(s2!==null){peg$reportedPos=s0;s1=peg$c272(s2);if(s1===null){peg$currPos=s0;s0=s1}else{s0=s1}}else{peg$currPos=s0;s0=peg$c0}}else{peg$currPos=s0;s0=peg$c0}return s0}function peg$parse_(){var s0,s1;peg$silentFails++;s0=[];s1=peg$parseWhitespace();while(s1!==null){s0.push(s1);s1=peg$parseWhitespace()}peg$silentFails--;if(s0===null){s1=null;if(peg$silentFails===0){peg$fail(peg$c273)}}return s0}function peg$parse__(){var s0,s1;peg$silentFails++;s0=peg$c1;peg$silentFails--;if(s0===null){s1=null;if(peg$silentFails===0){peg$fail(peg$c274)}}return s0}function peg$parseWhitespace(){var s0;if(peg$c275.test(input.charAt(peg$currPos))){s0=input.charAt(peg$currPos);peg$currPos++}else{s0=null;if(peg$silentFails===0){peg$fail(peg$c276)}}if(s0===null){s0=peg$parseUnicodeZs()}return s0}var Types=require("./types");peg$result=peg$startRuleFunction();if(peg$result!==null&&peg$currPos===input.length){return peg$result}else{peg$cleanupExpected(peg$maxFailExpected);peg$reportedPos=Math.max(peg$currPos,peg$maxFailPos);throw new SyntaxError(peg$maxFailExpected,peg$reportedPos1?arguments[1]:{},peg$startRuleFunctions={TypeExpression:peg$parseTypeExpression},peg$startRuleFunction=peg$parseTypeExpression,peg$c0=null,peg$c1="",peg$c2=function(r,unk){var result=unk;if(r.repeatable){result=repeatable(result)}return result},peg$c3="?",peg$c4='"?"',peg$c5="!",peg$c6='"!"',peg$c7=function(r,prefix,expr){var result=expr;if(r.repeatable){result=repeatable(result)}return nullable(result,prefix)},peg$c8=function(expr,postfix){return nullable(expr,postfix)},peg$c9=function(lit,opt){var result=lit;if(opt.optional){result.optional=true}return result},peg$c10=function(lit){return repeatable(lit)},peg$c11="*",peg$c12='"*"',peg$c13=function(){return{type:Types.AllLiteral}},peg$c14=function(){return{type:Types.NullLiteral}},peg$c15=function(){return{type:Types.UndefinedLiteral}},peg$c16="...",peg$c17='"..."',peg$c18=function(){return{repeatable:true}},peg$c19="=",peg$c20='"="',peg$c21=function(){return{optional:true}},peg$c22="[]",peg$c23='"[]"',peg$c24=function(name){var result;if(!options.jsdoc){return null}result={type:Types.TypeApplication,expression:{type:Types.NameExpression,name:"Array"},applications:[name]};result.applications[0].type=Types.NameExpression;return result},peg$c25=function(exp,appl,opt){var result={};var nameExp={type:Types.NameExpression,name:exp.name};if(appl.length){result.type=Types.TypeApplication;result.expression=nameExp;result.applications=appl}else{result=nameExp}if(opt.optional){result.optional=true}return result},peg$c26=function(name){if(!options.jsdoc){return null}return name},peg$c27=function(t){return repeatable(t)},peg$c28=function(exp,opt){var result={type:Types.NameExpression,name:exp.name,reservedWord:true};if(opt.optional){result.optional=true}return result},peg$c29=".",peg$c30='"."',peg$c31="<",peg$c32='"<"',peg$c33=">",peg$c34='">"',peg$c35=function(sep,l){if(sep===""&&!options.jsdoc){return null}return l},peg$c36=[],peg$c37=",",peg$c38='","',peg$c39=function(expr,list){var result=[expr];for(var i=0,l=list.length;ipos){peg$cachedPos=0;peg$cachedPosDetails={line:1,column:1,seenCR:false}}peg$cachedPos=pos;advance(peg$cachedPosDetails,peg$cachedPos)}return peg$cachedPosDetails}function peg$fail(expected){if(peg$currPospeg$maxFailPos){peg$maxFailPos=peg$currPos;peg$maxFailExpected=[]}peg$maxFailExpected.push(expected)}function peg$cleanupExpected(expected){var i=0;expected.sort();while(ipeg$currPos){s0=input.charAt(peg$currPos);peg$currPos++}else{s0=null;if(peg$silentFails===0){peg$fail(peg$c277)}}return s0}function peg$parseHexEscapeSequence(){var s0,s1,s2,s3,s4,s5;s0=peg$currPos;if(input.charCodeAt(peg$currPos)===120){s1=peg$c275;peg$currPos++}else{s1=null;if(peg$silentFails===0){peg$fail(peg$c276)}}if(s1!==null){s2=peg$currPos;s3=peg$currPos;s4=peg$parseHexDigit();if(s4!==null){s5=peg$parseHexDigit();if(s5!==null){s4=[s4,s5];s3=s4}else{peg$currPos=s3;s3=peg$c0}}else{peg$currPos=s3;s3=peg$c0}if(s3!==null){s3=input.substring(s2,peg$currPos)}s2=s3;if(s2!==null){peg$reportedPos=s0;s1=peg$c223(s2);if(s1===null){peg$currPos=s0;s0=s1}else{s0=s1}}else{peg$currPos=s0;s0=peg$c0}}else{peg$currPos=s0;s0=peg$c0}return s0}function peg$parseLineContinuation(){var s0,s1,s2;s0=peg$currPos;if(input.charCodeAt(peg$currPos)===92){s1=peg$c219;peg$currPos++}else{s1=null;if(peg$silentFails===0){peg$fail(peg$c220)}}if(s1!==null){s2=peg$parseLineTerminatorSequence();if(s2!==null){peg$reportedPos=s0;s1=peg$c261(s2);if(s1===null){peg$currPos=s0;s0=s1}else{s0=s1}}else{peg$currPos=s0;s0=peg$c0}}else{peg$currPos=s0;s0=peg$c0}return s0}function peg$parse_(){var s0,s1;peg$silentFails++;s0=[];s1=peg$parseWhitespace();while(s1!==null){s0.push(s1);s1=peg$parseWhitespace()}peg$silentFails--;if(s0===null){s1=null;if(peg$silentFails===0){peg$fail(peg$c278)}}return s0}function peg$parse__(){var s0,s1;peg$silentFails++;s0=peg$c1;peg$silentFails--;if(s0===null){s1=null;if(peg$silentFails===0){peg$fail(peg$c279)}}return s0}function peg$parseWhitespace(){var s0;if(peg$c280.test(input.charAt(peg$currPos))){s0=input.charAt(peg$currPos);peg$currPos++}else{s0=null;if(peg$silentFails===0){peg$fail(peg$c281)}}if(s0===null){s0=peg$parseUnicodeZs()}return s0}var Types=require("./types");function repeatable(obj){obj.repeatable=true;return obj}function nullable(obj,modifier){if(modifier){obj.nullable=modifier==="?"?true:false}return obj}peg$result=peg$startRuleFunction();if(peg$result!==null&&peg$currPos===input.length){return peg$result}else{peg$cleanupExpected(peg$maxFailExpected);peg$reportedPos=Math.max(peg$currPos,peg$maxFailPos);throw new SyntaxError(peg$maxFailExpected,peg$reportedPos'; } - 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; diff --git a/node_modules/catharsis/package.json b/node_modules/catharsis/package.json index 49046e01..a4d04923 100644 --- a/node_modules/catharsis/package.json +++ b/node_modules/catharsis/package.json @@ -1,5 +1,5 @@ { - "version": "0.5.6", + "version": "0.7.0", "name": "catharsis", "description": "A JavaScript parser for Google Closure Compiler and JSDoc type expressions.", "author": { @@ -10,21 +10,23 @@ "type": "git", "url": "https://github.com/hegemonic/catharsis" }, - "bugs": "https://github.com/hegemonic/catharsis/issues", + "bugs": { + "url": "https://github.com/hegemonic/catharsis/issues" + }, "main": "catharsis.js", "devDependencies": { - "mocha": "1.6.0", + "mocha": "1.13.0", "pegjs": "git+ssh://git@github.com:dmajda/pegjs.git#76cc5d55", - "should": "1.2.2", - "uglify-js": "2.2.5", - "underscore": "1.4.4" + "should": "1.3.0", + "uglify-js": "2.4.0", + "underscore": "1.5.2" }, "engines": { "node": ">= 0.6" }, "scripts": { - "build": "pegjs ./lib/parser.pegjs", - "prepublish": "pegjs ./lib/parser.pegjs; uglifyjs ./lib/parser.js -o ./lib/parser.js", + "build": "./node_modules/.bin/pegjs ./lib/parser.pegjs", + "prepublish": "./node_modules/.bin/pegjs ./lib/parser.pegjs; ./node_modules/.bin/uglifyjs ./lib/parser.js -o ./lib/parser.js", "test": "mocha" }, "licenses": [ @@ -33,12 +35,13 @@ "url": "http://github.com/hegemonic/catharsis/raw/master/LICENSE" } ], - "readme": "# Catharsis #\n\nA JavaScript parser for\n[Google Closure Compiler](https://developers.google.com/closure/compiler/docs/js-for-compiler#types)\nand [JSDoc](https://github.com/jsdoc3/jsdoc) type expressions.\n\nCatharsis is designed to be:\n\n+ **Accurate**. Catharsis is based on a [PEG.js](http://pegjs.majda.cz/) grammar that's designed to\nhandle any valid type expression. It uses a [Mocha](http://visionmedia.github.com/mocha/) test suite\nto verify the parser's accuracy.\n+ **Fast**. Parse results are cached, so the parser is invoked only when necessary.\n+ **Flexible**. Catharsis can convert parse results back into type expressions. In addition, it can\nparse [JSDoc](https://github.com/jsdoc3/jsdoc)-style type expressions.\n\n\n## Example ##\n\n\tvar catharsis = require('catharsis');\n\n var type;\n var jsdocType;\n var parsedType;\n var parsedJsdocType;\n\n // Google Closure Compiler parsing\n try {\n type = '!Object';\n parsedType = catharsis.parse(type);\n console.log('%j', parsedType); // {\"type\":\"NameExpression,\"name\":\"Object\",\"nullable\":false}\n }\n catch(e) {\n console.error('unable to parse %s: %s', type, e);\n }\n\n // JSDoc-style type expressions enabled\n try {\n jsdocType = 'string[]'; // Closure Compiler expects Array.\n parsedJsdocType = catharsis.parse(jsdocType, {jsdoc: true});\n }\n catch (e) {\n console.error('unable to parse %s: %s', jsdocType, e);\n }\n\n console.log(catharsis.stringify(parsedType)); // !Object\n console.log(catharsis.stringify(parsedJsdocType)); // string[]\n console.log(catharsis.stringify(parsedJsdocType, // Array.\n {restringify: true}));\n\n\nSee the `test/specs/` directory for more examples of Catharsis' parse results.\n\n\n## Methods ##\n\n### parse(typeExpression, options) ###\nParse `typeExpression`, and return the parse results. Throws an error if the type expression cannot\nbe parsed.\n\nWhen called without options, Catharsis attempts to parse type expressions in the same way as\nClosure Compiler. When the `jsdoc` option is enabled, Catharsis can also parse several kinds of\ntype expressions that are permitted in [JSDoc](https://github.com/jsdoc3/jsdoc):\n\n+ The string `function` is treated as a function type with no parameters.\n+ In a function type with repeatable parameters, the names of repeatable parameters are not required\nto be enclosed in square brackets (for example, `function(...foo)` is allowed).\n+ The period may be omitted from type applications. For example, `Array.` and\n`Array` will be parsed in the same way.\n+ You may append `[]` to a name expression (for example, `string[]`) to interpret it as a type\napplication with the expression `Array` (for example, `Array.`).\n+ Name expressions may contain the characters `#`, `~`, `:`, and `/`.\n+ Name expressions may contain a suffix that is similar to a function signature (for example,\n`MyClass(foo, bar)`).\n+ Name expressions may contain a reserved word.\n+ Record types may use types other than name expressions for keys.\n\n#### Parameters ####\n+ `type`: A string containing a Closure Compiler type expression.\n+ `options`: Options for parsing the type expression.\n + `options.jsdoc`: Specifies whether to enable parsing of JSDoc-style type expressions. Defaults\n to `false`.\n + `options.useCache`: Specifies whether to use the cache of parsed types. Defaults to `true`.\n\n#### Returns ####\nAn object containing the parse results. See the `test/specs/` directory for examples of the parse\nresults for different type expressions.\n\nThe object also includes two non-enumerable properties:\n\n+ `jsdoc`: A boolean indicating whether the type expression was parsed with JSDoc support enabled.\n+ `typeExpression`: A string containing the type expression that was parsed.\n\n### stringify(parsedType, options) ###\nStringify `parsedType`, and return the type expression. If validation is enabled, throws an error if\nthe stringified type expression cannot be parsed.\n\n#### Parameters ####\n+ `parsedType`: An object containing a parsed Closure Compiler type expression.\n+ `options`: Options for stringifying the parse results.\n + `options.cssClass`: A CSS class to add to HTML links. Used only if `options.links` is\n provided. By default, no CSS class is added.\n + `options.htmlSafe`: Specifies whether to return an HTML-safe string that replaces left angle\n brackets (`<`) with the corresponding entity (`<`). **Note**: Characters in name expressions\n are not escaped.\n + `options.links`: An object whose keys are name expressions and whose values are URIs. If a\n name expression matches a key in `options.links`, the name expression will be wrapped in an\n HTML `` tag that links to the URI. If `options.cssClass` is specified, the `` tag will\n include a `class` attribute. **Note**: When using this option, parsed types are always\n restringified, and the resulting string is not cached.\n + `options.restringify`: Forces Catharsis to restringify the parsed type. If this option is not\n specified, and the parsed type object includes a `typeExpression` property, Catharsis will\n return the `typeExpression` property without modification when possible. Defaults to `false`.\n + `options.useCache`: Specifies whether to use the cache of stringified parse results. Defaults\n to `true`.\n + `options.validate`: Specifies whether to validate the stringified parse results by attempting\n to parse them as a type expression. If the stringified results are not parsable by default, you\n must also provide the appropriate options to pass to the `parse()` method. Defaults to `false`.\n\n#### Returns ####\nA string containing the type expression.\n\n\n## Installation ##\n\nWith [npm](http://npmjs.org):\n\n npm install catharsis\n\nOr without:\n\n git clone git://github.com/hegemonic/catharsis.git\n\n\n## Roadmap and known issues ##\n\nTake a look at the [issue tracker](https://github.com/hegemonic/catharsis/issues) to see what's in\nstore for Catharsis.\n\nBug reports, feature requests, and pull requests are always welcome! If you're working on a large\npull request, please contact me in advance so I can help things go smoothly.\n\n**Note**: The parse tree's format should not be considered final until Catharsis reaches version\n1.0. I'll do my best to provide release notes for any changes.\n\n\n## Changelog ##\n\n+ 0.5.6 (April 2013):\n + For consistency with Google Closure Library, parentheses are no longer required around type\n unions. (In previous versions, the parentheses could be omitted when JSDoc support was enabled.)\n + For consistency with Google Closure Library, you can now use postfix notation for the `?`\n (nullable) and `!` (non-nullable) modifiers. For example, `?string` and `string?` are now\n treated as equivalent.\n + String literals and numeric literals are now allowed as property names within name\n expressions. For example, the name expression `Foo.\"bar\"` is now parsed correctly.\n+ 0.5.5 (April 2013): Corrected a parsing issue with name expressions that end with a value enclosed\nin parentheses.\n+ 0.5.4 (April 2013):\n + Repeatable literals (for example, `...*`) are now parsed correctly.\n + When JSDoc-style type expressions are enabled, a name expression can now contain a value\n enclosed in parentheses at the end of the name expression (for example, `MyClass(2)`).\n+ 0.5.3 (March 2013): The `parse()` method now correctly parses name expressions that contain\nhyphens.\n+ 0.5.2 (March 2013): The `parse()` method now correctly parses function types when JSDoc-style type\nexpressions are enabled.\n+ 0.5.1 (March 2013): Newlines and extra spaces are now removed from type expressions before they\nare parsed.\n+ 0.5.0 (March 2013):\n + The `parse()` method's `lenient` option has been renamed to `jsdoc`. **Note**: This change is\n not backwards-compatible with previous versions.\n + The `stringify()` method now accepts `cssClass` and `links` options, which you can use to\n add HTML links to a type expression.\n+ 0.4.3 (March 2013):\n + The `stringify()` method no longer caches HTML-safe type expressions as if they were normal\n type expressions.\n + The `stringify()` method's options parameter may now include an `options.restringify`\n property, and the behavior of the `options.useCache` property has changed.\n+ 0.4.2 (March 2013):\n + When lenient parsing is enabled, name expressions can now contain the characters `:` and `/`.\n + When lenient parsing is enabled, a name expression followed by `[]` (for example, `string[]`)\n will be interpreted as a type application with the expression `Array` (for example,\n `Array.`).\n+ 0.4.1 (March 2013):\n + The `parse()` and `stringify()` methods now honor all of the specified options.\n + When lenient parsing is enabled, name expressions can now contain a reserved word.\n+ 0.4.0 (March 2013):\n + Catharsis now supports a lenient parsing option that can parse several kinds of malformed type\n expressions. See the documentation for details.\n + The objects containing parse results are now frozen.\n + The objects containing parse results now have two non-enumerable properties:\n + `lenient`: A boolean indicating whether the type expression was parsed in lenient mode.\n + `typeExpression`: A string containing the original type expression.\n + The `stringify()` method now honors the `useCache` option. If a parsed type includes a\n `typeExpression` property, and `useCache` is not set to `false`, the stringified type will be\n identical to the original type expression.\n+ 0.3.1 (March 2013): Type expressions that begin with a reserved word, such as `integer`, are now\nparsed correctly.\n+ 0.3.0 (March 2013):\n + The `parse()` and `stringify()` methods are now synchronous, and the `parseSync()` and\n `stringifySync()` methods have been removed. **Note**: This change is not backwards-compatible\n with previous versions.\n + The parse results now use a significantly different format from previous versions. The new\n format is more expressive and is similar, but not identical, to the format used by the\n [doctrine](https://github.com/Constellation/doctrine) parser. **Note**: This change is not\n backwards-compatible with previous versions.\n + Name expressions that contain a reserved word now include a `reservedWord: true` property.\n + Union types that are optional or nullable, or that can be passed a variable number of times,\n are now parsed and stringified correctly.\n + Optional function types and record types are now parsed and stringified correctly.\n + Function types now longer include `new` or `this` properties unless the properties are defined\n in the type expression. In addition, the `new` and `this` properties can now use any type\n expression.\n + In record types, the key for a field type can now use any type expression.\n + Standalone single-character literals, such as ALL (`*`), are now parsed and stringified\n correctly.\n + `null` and `undefined` literals with additional properties, such as `repeatable`, are now\n stringified correctly.\n+ 0.2.0 (November 2012):\n + Added `stringify()` and `stringifySync()` methods, which convert a parsed type to a type\n expression.\n + Simplified the parse results for function signatures. **Note**: This change is not\n backwards-compatible with previous versions.\n + Corrected minor errors in README.md.\n+ 0.1.1 (November 2012): Added `opts` argument to `parse()` and `parseSync()` methods. **Note**: The\nchange to `parse()` is not backwards-compatible with previous versions.\n+ 0.1.0 (November 2012): Initial release.\n\n## License ##\n\n[MIT license](https://github.com/hegemonic/catharsis/blob/master/LICENSE).\n", + "readme": "# Catharsis #\n\nA JavaScript parser for\n[Google Closure Compiler](https://developers.google.com/closure/compiler/docs/js-for-compiler#types)\nand [JSDoc](https://github.com/jsdoc3/jsdoc) type expressions.\n\nCatharsis is designed to be:\n\n+ **Accurate**. Catharsis is based on a [PEG.js](http://pegjs.majda.cz/) grammar that's designed to\nhandle any valid type expression. It uses a [Mocha](http://visionmedia.github.com/mocha/) test suite\nto verify the parser's accuracy.\n+ **Fast**. Parse results are cached, so the parser is invoked only when necessary.\n+ **Flexible**. Catharsis can convert parse results back into type expressions. In addition, it can\nparse [JSDoc](https://github.com/jsdoc3/jsdoc)-style type expressions.\n\n\n## Example ##\n\n\tvar catharsis = require('catharsis');\n\n var type;\n var jsdocType;\n var parsedType;\n var parsedJsdocType;\n\n // Google Closure Compiler parsing\n try {\n type = '!Object';\n parsedType = catharsis.parse(type);\n console.log('%j', parsedType); // {\"type\":\"NameExpression,\"name\":\"Object\",\"nullable\":false}\n }\n catch(e) {\n console.error('unable to parse %s: %s', type, e);\n }\n\n // JSDoc-style type expressions enabled\n try {\n jsdocType = 'string[]'; // Closure Compiler expects Array.\n parsedJsdocType = catharsis.parse(jsdocType, {jsdoc: true});\n }\n catch (e) {\n console.error('unable to parse %s: %s', jsdocType, e);\n }\n\n console.log(catharsis.stringify(parsedType)); // !Object\n console.log(catharsis.stringify(parsedJsdocType)); // string[]\n console.log(catharsis.stringify(parsedJsdocType, // Array.\n {restringify: true}));\n\n\nSee the `test/specs/` directory for more examples of Catharsis' parse results.\n\n\n## Methods ##\n\n### parse(typeExpression, options) ###\nParse `typeExpression`, and return the parse results. Throws an error if the type expression cannot\nbe parsed.\n\nWhen called without options, Catharsis attempts to parse type expressions in the same way as\nClosure Compiler. When the `jsdoc` option is enabled, Catharsis can also parse several kinds of\ntype expressions that are permitted in [JSDoc](https://github.com/jsdoc3/jsdoc):\n\n+ The string `function` is treated as a function type with no parameters.\n+ In a function type with repeatable parameters, the names of repeatable parameters are not required\nto be enclosed in square brackets (for example, `function(...foo)` is allowed).\n+ The period may be omitted from type applications. For example, `Array.` and\n`Array` will be parsed in the same way.\n+ You may append `[]` to a name expression (for example, `string[]`) to interpret it as a type\napplication with the expression `Array` (for example, `Array.`).\n+ Name expressions may contain the characters `#`, `~`, `:`, and `/`.\n+ Name expressions may contain a suffix that is similar to a function signature (for example,\n`MyClass(foo, bar)`).\n+ Name expressions may contain a reserved word.\n+ Record types may use types other than name expressions for keys.\n\n#### Parameters ####\n+ `type`: A string containing a Closure Compiler type expression.\n+ `options`: Options for parsing the type expression.\n + `options.jsdoc`: Specifies whether to enable parsing of JSDoc-style type expressions. Defaults\n to `false`.\n + `options.useCache`: Specifies whether to use the cache of parsed types. Defaults to `true`.\n\n#### Returns ####\nAn object containing the parse results. See the `test/specs/` directory for examples of the parse\nresults for different type expressions.\n\nThe object also includes two non-enumerable properties:\n\n+ `jsdoc`: A boolean indicating whether the type expression was parsed with JSDoc support enabled.\n+ `typeExpression`: A string containing the type expression that was parsed.\n\n### stringify(parsedType, options) ###\nStringify `parsedType`, and return the type expression. If validation is enabled, throws an error if\nthe stringified type expression cannot be parsed.\n\n#### Parameters ####\n+ `parsedType`: An object containing a parsed Closure Compiler type expression.\n+ `options`: Options for stringifying the parse results.\n + `options.cssClass`: A CSS class to add to HTML links. Used only if `options.links` is\n provided. By default, no CSS class is added.\n + `options.htmlSafe`: Specifies whether to return an HTML-safe string that replaces left angle\n brackets (`<`) with the corresponding entity (`<`). **Note**: Characters in name expressions\n are not escaped.\n + `options.links`: An object whose keys are name expressions and whose values are URIs. If a\n name expression matches a key in `options.links`, the name expression will be wrapped in an\n HTML `` tag that links to the URI. If `options.cssClass` is specified, the `` tag will\n include a `class` attribute. **Note**: When using this option, parsed types are always\n restringified, and the resulting string is not cached.\n + `options.restringify`: Forces Catharsis to restringify the parsed type. If this option is not\n specified, and the parsed type object includes a `typeExpression` property, Catharsis will\n return the `typeExpression` property without modification when possible. Defaults to `false`.\n + `options.useCache`: Specifies whether to use the cache of stringified parse results. Defaults\n to `true`.\n + `options.validate`: Specifies whether to validate the stringified parse results by attempting\n to parse them as a type expression. If the stringified results are not parsable by default, you\n must also provide the appropriate options to pass to the `parse()` method. Defaults to `false`.\n\n#### Returns ####\nA string containing the type expression.\n\n\n## Installation ##\n\nWith [npm](http://npmjs.org):\n\n npm install catharsis\n\nOr without:\n\n git clone git://github.com/hegemonic/catharsis.git\n\n\n## Roadmap and known issues ##\n\nTake a look at the [issue tracker](https://github.com/hegemonic/catharsis/issues) to see what's in\nstore for Catharsis.\n\nBug reports, feature requests, and pull requests are always welcome! If you're working on a large\npull request, please contact me in advance so I can help things go smoothly.\n\n**Note**: The parse tree's format should not be considered final until Catharsis reaches version\n1.0. I'll do my best to provide release notes for any changes.\n\n\n## Changelog ##\n\n+ 0.7.0 (October 2013):\n + Repeatable type expressions other than name expressions (for example, `...function()`) are now\n parsed and stringified correctly.\n + Type expressions that are both repeatable and either nullable or non-nullable (for example,\n `...!number`) are now parsed and stringified correctly.\n + Name expressions are now parsed correctly when they match a property name in an object\n instance (for example, `constructor`).\n+ 0.6.0 (September 2013): Added support for the type expression `function[]` when JSDoc-style type\nexpressions are enabled.\n+ 0.5.6 (April 2013):\n + For consistency with Google Closure Library, parentheses are no longer required around type\n unions. (In previous versions, the parentheses could be omitted when JSDoc support was enabled.)\n + For consistency with Google Closure Library, you can now use postfix notation for the `?`\n (nullable) and `!` (non-nullable) modifiers. For example, `?string` and `string?` are now\n treated as equivalent.\n + String literals and numeric literals are now allowed as property names within name\n expressions. For example, the name expression `Foo.\"bar\"` is now parsed correctly.\n+ 0.5.5 (April 2013): Corrected a parsing issue with name expressions that end with a value enclosed\nin parentheses.\n+ 0.5.4 (April 2013):\n + Repeatable literals (for example, `...*`) are now parsed correctly.\n + When JSDoc-style type expressions are enabled, a name expression can now contain a value\n enclosed in parentheses at the end of the name expression (for example, `MyClass(2)`).\n+ 0.5.3 (March 2013): The `parse()` method now correctly parses name expressions that contain\nhyphens.\n+ 0.5.2 (March 2013): The `parse()` method now correctly parses function types when JSDoc-style type\nexpressions are enabled.\n+ 0.5.1 (March 2013): Newlines and extra spaces are now removed from type expressions before they\nare parsed.\n+ 0.5.0 (March 2013):\n + The `parse()` method's `lenient` option has been renamed to `jsdoc`. **Note**: This change is\n not backwards-compatible with previous versions.\n + The `stringify()` method now accepts `cssClass` and `links` options, which you can use to\n add HTML links to a type expression.\n+ 0.4.3 (March 2013):\n + The `stringify()` method no longer caches HTML-safe type expressions as if they were normal\n type expressions.\n + The `stringify()` method's options parameter may now include an `options.restringify`\n property, and the behavior of the `options.useCache` property has changed.\n+ 0.4.2 (March 2013):\n + When lenient parsing is enabled, name expressions can now contain the characters `:` and `/`.\n + When lenient parsing is enabled, a name expression followed by `[]` (for example, `string[]`)\n will be interpreted as a type application with the expression `Array` (for example,\n `Array.`).\n+ 0.4.1 (March 2013):\n + The `parse()` and `stringify()` methods now honor all of the specified options.\n + When lenient parsing is enabled, name expressions can now contain a reserved word.\n+ 0.4.0 (March 2013):\n + Catharsis now supports a lenient parsing option that can parse several kinds of malformed type\n expressions. See the documentation for details.\n + The objects containing parse results are now frozen.\n + The objects containing parse results now have two non-enumerable properties:\n + `lenient`: A boolean indicating whether the type expression was parsed in lenient mode.\n + `typeExpression`: A string containing the original type expression.\n + The `stringify()` method now honors the `useCache` option. If a parsed type includes a\n `typeExpression` property, and `useCache` is not set to `false`, the stringified type will be\n identical to the original type expression.\n+ 0.3.1 (March 2013): Type expressions that begin with a reserved word, such as `integer`, are now\nparsed correctly.\n+ 0.3.0 (March 2013):\n + The `parse()` and `stringify()` methods are now synchronous, and the `parseSync()` and\n `stringifySync()` methods have been removed. **Note**: This change is not backwards-compatible\n with previous versions.\n + The parse results now use a significantly different format from previous versions. The new\n format is more expressive and is similar, but not identical, to the format used by the\n [doctrine](https://github.com/Constellation/doctrine) parser. **Note**: This change is not\n backwards-compatible with previous versions.\n + Name expressions that contain a reserved word now include a `reservedWord: true` property.\n + Union types that are optional or nullable, or that can be passed a variable number of times,\n are now parsed and stringified correctly.\n + Optional function types and record types are now parsed and stringified correctly.\n + Function types now longer include `new` or `this` properties unless the properties are defined\n in the type expression. In addition, the `new` and `this` properties can now use any type\n expression.\n + In record types, the key for a field type can now use any type expression.\n + Standalone single-character literals, such as ALL (`*`), are now parsed and stringified\n correctly.\n + `null` and `undefined` literals with additional properties, such as `repeatable`, are now\n stringified correctly.\n+ 0.2.0 (November 2012):\n + Added `stringify()` and `stringifySync()` methods, which convert a parsed type to a type\n expression.\n + Simplified the parse results for function signatures. **Note**: This change is not\n backwards-compatible with previous versions.\n + Corrected minor errors in README.md.\n+ 0.1.1 (November 2012): Added `opts` argument to `parse()` and `parseSync()` methods. **Note**: The\nchange to `parse()` is not backwards-compatible with previous versions.\n+ 0.1.0 (November 2012): Initial release.\n\n## License ##\n\n[MIT license](https://github.com/hegemonic/catharsis/blob/master/LICENSE).\n", "readmeFilename": "README.md", - "_id": "catharsis@0.5.6", + "homepage": "https://github.com/hegemonic/catharsis", + "_id": "catharsis@0.7.0", "dist": { - "shasum": "210a0cfa23c5d09fa994d6fafe1a76383c170cbb" + "shasum": "37b3854a9a79b71bd7750279913313b160978c6e" }, - "_from": "catharsis@0.5.6", - "_resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.5.6.tgz" + "_from": "catharsis@0.7.0", + "_resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.7.0.tgz" } diff --git a/package.json b/package.json index 42b8a3c1..bc42ff61 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/rhino/rhino-shim.js b/rhino/rhino-shim.js index 16c126f6..1ffb1e3b 100644 --- a/rhino/rhino-shim.js +++ b/rhino/rhino-shim.js @@ -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 diff --git a/templates/README.md b/templates/README.md index fbdd7dbf..48c04441 100644 --- a/templates/README.md +++ b/templates/README.md @@ -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 -```` \ No newline at end of file +```` diff --git a/templates/default/publish.js b/templates/default/publish.js index b79200a9..63a62f96 100644 --- a/templates/default/publish.js +++ b/templates/default/publish.js @@ -66,7 +66,10 @@ function addSignatureParams(f) { function addSignatureReturns(f) { var returnTypes = helper.getSignatureReturns(f); - f.signature = ''+(f.signature || '') + '' + ''+(returnTypes.length? ' → {'+returnTypes.join('|')+'}' : '')+''; + f.signature = '' + (f.signature || '') + '' + + '' + + (returnTypes && returnTypes.length ? ' → {' + returnTypes.join('|') + '}' : '') + + ''; } function addSignatureTypes(f) { @@ -78,7 +81,9 @@ function addSignatureTypes(f) { function addAttribs(f) { var attribs = helper.getAttribs(f); - f.attribs = ''+htmlsafe(attribs.length? '<'+attribs.join(', ')+'> ' : '')+''; + f.attribs = '' + htmlsafe(attribs.length ? + // we want the template output to say 'abstract', not 'virtual' + '<' + attribs.join(', ').replace('virtual', 'abstract') + '> ' : '') + ''; } 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); } diff --git a/templates/default/static/scripts/linenumber.js b/templates/default/static/scripts/linenumber.js index a0c570d5..613865d0 100644 --- a/templates/default/static/scripts/linenumber.js +++ b/templates/default/static/scripts/linenumber.js @@ -9,7 +9,7 @@ numbered = source.innerHTML.split('\n'); numbered = numbered.map(function(item) { counter++; - return '' + item; + return '' + item; }); source.innerHTML = numbered.join('\n'); diff --git a/templates/default/static/styles/jsdoc-default.css b/templates/default/static/styles/jsdoc-default.css index cbcff70f..7afd6850 100644 --- a/templates/default/static/styles/jsdoc-default.css +++ b/templates/default/static/styles/jsdoc-default.css @@ -242,6 +242,11 @@ h6 border-left: 3px #ddd solid; } +.prettyprint code span.line +{ + display: inline-block; +} + .params, .props { border-spacing: 0; diff --git a/templates/default/tmpl/returns.tmpl b/templates/default/tmpl/returns.tmpl index 32e059ed..35d1ec15 100644 --- a/templates/default/tmpl/returns.tmpl +++ b/templates/default/tmpl/returns.tmpl @@ -1,5 +1,5 @@
diff --git a/test/fixtures/augmentstag4.js b/test/fixtures/augmentstag4.js new file mode 100644 index 00000000..3ead2752 --- /dev/null +++ b/test/fixtures/augmentstag4.js @@ -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"; +}; diff --git a/test/fixtures/letkeyword.js b/test/fixtures/letkeyword.js new file mode 100644 index 00000000..3cbf88fd --- /dev/null +++ b/test/fixtures/letkeyword.js @@ -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; +} ); diff --git a/test/fixtures/paramtaginvalidtype.js b/test/fixtures/paramtaginvalidtype.js new file mode 100644 index 00000000..5e79d4e8 --- /dev/null +++ b/test/fixtures/paramtaginvalidtype.js @@ -0,0 +1,9 @@ +/** + * @constructor + */ +var Test = function () {}; + +/** + * @param {string, number} a + */ +Test.prototype.test = function (a) {}; diff --git a/test/fixtures/requirestag.js b/test/fixtures/requirestag.js index 3fe27229..49b626fe 100644 --- a/test/fixtures/requirestag.js +++ b/test/fixtures/requirestag.js @@ -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() { +} diff --git a/test/fixtures/returnstag.js b/test/fixtures/returnstag.js index d3cf4de4..f8282770 100644 --- a/test/fixtures/returnstag.js +++ b/test/fixtures/returnstag.js @@ -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) { +} diff --git a/test/fixtures/typetagwithnewline.js b/test/fixtures/typetagwithnewline.js new file mode 100644 index 00000000..88a12f18 --- /dev/null +++ b/test/fixtures/typetagwithnewline.js @@ -0,0 +1,14 @@ +/** @class Matryoshka */ +function Matryoshka() {} + +/** + * @type {(!Array.| + * !Array.>)} + */ +Matryoshka.mini; + +/** + * @type (!Array.|!Array.>| + * !Array.>>) + */ +Matryoshka.mega; diff --git a/test/specs/documentation/alias.js b/test/specs/documentation/alias.js index 00c1dc6f..3f64c89e 100644 --- a/test/specs/documentation/alias.js +++ b/test/specs/documentation/alias.js @@ -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]; diff --git a/test/specs/documentation/letkeyword.js b/test/specs/documentation/letkeyword.js new file mode 100644 index 00000000..43d12063 --- /dev/null +++ b/test/specs/documentation/letkeyword.js @@ -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); + }); +}); diff --git a/test/specs/documentation/modules.js b/test/specs/documentation/modules.js index 9d31e402..d32f74a4 100644 --- a/test/specs/documentation/modules.js +++ b/test/specs/documentation/modules.js @@ -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); diff --git a/test/specs/documentation/typetagwithnewline.js b/test/specs/documentation/typetagwithnewline.js new file mode 100644 index 00000000..f1198e97 --- /dev/null +++ b/test/specs/documentation/typetagwithnewline.js @@ -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.'); + expect(mini.type.names[1]).toBe('!Array.>'); + }); + + 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.'); + expect(mega.type.names[1]).toBe('!Array.>'); + expect(mega.type.names[2]).toBe('!Array.>>'); + }); +}); diff --git a/test/specs/jsdoc/name.js b/test/specs/jsdoc/name.js index f8ba382b..80036ef7 100644 --- a/test/specs/jsdoc/name.js +++ b/test/specs/jsdoc/name.js @@ -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'); + }); }); }); diff --git a/test/specs/jsdoc/src/parser.js b/test/specs/jsdoc/src/parser.js index 6babbef8..49ad2cd7 100644 --- a/test/specs/jsdoc/src/parser.js +++ b/test/specs/jsdoc/src/parser.js @@ -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() { diff --git a/test/specs/jsdoc/tag/inline.js b/test/specs/jsdoc/tag/inline.js index de206a76..a89fb47d 100644 --- a/test/specs/jsdoc/tag/inline.js +++ b/test/specs/jsdoc/tag/inline.js @@ -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() { diff --git a/test/specs/jsdoc/util/templateHelper.js b/test/specs/jsdoc/util/templateHelper.js index 8fb1f19f..15c7ebf0 100644 --- a/test/specs/jsdoc/util/templateHelper.js +++ b/test/specs/jsdoc/util/templateHelper.js @@ -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 hello there.'); }); + 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 hello there.'); + }); + + 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 hello there.'); + }); + 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() { diff --git a/test/specs/tags/augmentstag.js b/test/specs/tags/augmentstag.js index 5baad4d0..73ad3b4f 100644 --- a/test/specs/tags/augmentstag.js +++ b/test/specs/tags/augmentstag.js @@ -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); }); }); diff --git a/test/specs/tags/overviewtag.js b/test/specs/tags/overviewtag.js index 925412a9..11f51670 100644 --- a/test/specs/tags/overviewtag.js +++ b/test/specs/tags/overviewtag.js @@ -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); }); }); diff --git a/test/specs/tags/paramtag.js b/test/specs/tags/paramtag.js index 7145c83f..e4dd33ed 100644 --- a/test/specs/tags/paramtag.js +++ b/test/specs/tags/paramtag.js @@ -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; + }); }); diff --git a/test/specs/tags/requirestag.js b/test/specs/tags/requirestag.js index 40eae164..f6bcf7ac 100644 --- a/test/specs/tags/requirestag.js +++ b/test/specs/tags/requirestag.js @@ -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'); }); -}); \ No newline at end of file + + 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}'); + }); +}); diff --git a/test/specs/tags/returnstag.js b/test/specs/tags/returnstag.js index 2fcf2e59..6b23fd5b 100644 --- a/test/specs/tags/returnstag.js +++ b/test/specs/tags/returnstag.js @@ -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}.'); + }); });