diff --git a/lib/jsdoc/tag.js b/lib/jsdoc/tag.js index abcfece7..90893717 100644 --- a/lib/jsdoc/tag.js +++ b/lib/jsdoc/tag.js @@ -25,15 +25,25 @@ var jsdoc = { } }; var path = require('jsdoc/path'); +var util = require('util'); -function trim(text, opts) { +// Check whether the text is the same as a symbol name with leading or trailing whitespace. If so, +// the text cannot be trimmed. +function textIsUntrimmable(text, meta) { + return meta && meta.code && meta.code.name === text && text.match(/(?:^\s+)|(?:\s+$)/); +} + +function trim(text, opts, meta) { var indentMatcher; var match; opts = opts || {}; text = text || ''; - if (opts.keepsWhitespace) { + if ( textIsUntrimmable(text, meta) ) { + text = util.format('"%s"', text); + } + else if (opts.keepsWhitespace) { text = text.replace(/^[\n\r\f]+|[\n\r\f]+$/g, ''); if (opts.removesIndent) { match = text.match(/^([ \t]+)/); @@ -131,7 +141,7 @@ var Tag = exports.Tag = function(tagTitle, tagBody, meta) { this.originalTitle = trim(tagTitle); - /** The title part of the tag: @title text */ + /** The title of the tag (for example, `title` in `@title text`). */ this.title = jsdoc.tag.dictionary.normalise(this.originalTitle); tagDef = jsdoc.tag.dictionary.lookUp(this.title); @@ -140,8 +150,25 @@ var Tag = exports.Tag = function(tagTitle, tagBody, meta) { removesIndent: tagDef.removesIndent }; - /** The text part of the tag: @title text */ - this.text = trim(tagBody, trimOpts); + /** + * The text following the tag (for example, `text` in `@title text`). + * + * Whitespace is trimmed from the tag text as follows: + * + * + If the tag's `keepsWhitespace` option is falsy, all leading and trailing whitespace are + * removed. + * + If the tag's `keepsWhitespace` option is set to `true`, leading and trailing whitespace are + * not trimmed, unless the `removesIndent` option is also enabled. + * + If the tag's `removesIndent` option is set to `true`, any indentation that is shared by + * every line in the string is removed. This option is ignored unless `keepsWhitespace` is set + * to `true`. + * + * **Note**: If the tag text is the name of a symbol, and the symbol's name includes leading or + * trailing whitespace (for example, the property names in `{ ' ': true, ' foo ': false }`), + * the tag text is not trimmed. Instead, the tag text is wrapped in double quotes to prevent the + * whitespace from being trimmed. + */ + this.text = trim(tagBody, trimOpts, meta); if (this.text) { try { diff --git a/test/specs/jsdoc/tag.js b/test/specs/jsdoc/tag.js index 5eb27ff9..028e6f6f 100644 --- a/test/specs/jsdoc/tag.js +++ b/test/specs/jsdoc/tag.js @@ -112,6 +112,26 @@ describe('jsdoc/tag', function() { expect(tagType.text).toBeDefined(); expect(tagType.text).toBe(def.onTagText('MyType')); }); + + it('should be enclosed in quotes, with no whitespace trimming, if it is a symbol name with leading or trailing whitespace', function() { + var wsBoth; + var wsLeading; + var wsOnly; + var wsTrailing; + + spyOn(logger, 'error'); + + wsOnly = new jsdoc.tag.Tag('name', ' ', { code: { name: ' ' } }); + wsLeading = new jsdoc.tag.Tag('name', ' foo', { code: { name: ' foo' } }); + wsTrailing = new jsdoc.tag.Tag('name', 'foo ', { code: { name: 'foo ' } }); + wsBoth = new jsdoc.tag.Tag('name', ' foo ', { code: { name: ' foo ' } }); + + expect(logger.error).not.toHaveBeenCalled(); + expect(wsOnly.text).toBe('" "'); + expect(wsLeading.text).toBe('" foo"'); + expect(wsTrailing.text).toBe('"foo "'); + expect(wsBoth.text).toBe('" foo "'); + }); }); describe("'value' property", function() {