diff --git a/changes.md b/changes.md index 1a1d9c78..ce003292 100644 --- a/changes.md +++ b/changes.md @@ -11,7 +11,7 @@ This file describes notable changes in each version of JSDoc 3. To download a sp ### Enhancements + The parser now fires a `parseBegin` event before it starts parsing files, as well as a `parseComplete` event after all files have been parsed. Plugins can define event handlers for these events, and `parseBegin` handlers can modify the list of files to parse. (#299) -+ Event handlers for `jsdocCommentFount` events can now modify the JSDoc comment. (#228) ++ Event handlers for `jsdocCommentFound` events can now modify the JSDoc comment. (#228) + You can now exclude tags from Markdown processing using the new option `markdown.excludeTags` in the configuration file. (#337) + You can now use the [marked](https://github.com/chjj/marked) Markdown parser by setting the configuration property `markdown.parser` to `marked`. In addition, if `markdown.parser` is set to `gfm`, JSDoc will now use the "marked" parser instead. (#385) + The `@typedef` tag no longer requires a name when used with a Closure Compiler-style type definition. For example, the following type definition will automatically get the name `Foo.Bar`: diff --git a/jsdoc.js b/jsdoc.js index 0204ff61..b1f27885 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -170,7 +170,7 @@ function main() { env.opts = jsdoc.opts.args.parse(env.args); confPath = env.opts.configure || path.join(__dirname, 'conf.json'); - if ( !fs.statSync(confPath).isFile() ) { + if ( !fs.statSync(confPath).isFile() && !env.opts.configure ) { confPath = path.join(__dirname, 'conf.json.EXAMPLE'); } @@ -237,6 +237,8 @@ function main() { jsdoc.augment.addInherited(docs); jsdoc.borrow.resolveBorrows(docs); + app.jsdoc.parser.fireProcessingComplete(docs); + if (env.opts.explain) { dump(docs); process.exit(0); diff --git a/lib/jsdoc/augment.js b/lib/jsdoc/augment.js index eaa00a48..9ac7bbbb 100644 --- a/lib/jsdoc/augment.js +++ b/lib/jsdoc/augment.js @@ -90,7 +90,10 @@ function getAdditions(doclets, docs, longnames) { for (var k = 0, kk = members.length; k < kk; k++) { member = doop(members[k]); - member.inherits = member.longname; + if(!member.inherited) + { + member.inherits = member.longname; + } member.inherited = true; member.memberof = doc.longname; diff --git a/lib/jsdoc/src/parser.js b/lib/jsdoc/src/parser.js index d7abd462..01e958f6 100644 --- a/lib/jsdoc/src/parser.js +++ b/lib/jsdoc/src/parser.js @@ -128,7 +128,12 @@ Parser.prototype.parse = function(sourceFiles, encoding) { return this._resultBuffer; }; -// TODO: update docs +// TODO: docs +Parser.prototype.fireProcessingComplete = function(doclets) { + this.emit('processingComplete', { doclets: doclets }); +}; + +// TODO: docs /** * @returns {Array} The accumulated results of any calls to parse. */ diff --git a/lib/jsdoc/tag/dictionary/definitions.js b/lib/jsdoc/tag/dictionary/definitions.js index 25241c02..1adb045f 100644 --- a/lib/jsdoc/tag/dictionary/definitions.js +++ b/lib/jsdoc/tag/dictionary/definitions.js @@ -246,6 +246,10 @@ exports.defineTags = function(dictionary) { if (type === Syntax.Literal) { doclet.defaultvalue = String(value); } + else if (doclet.meta.code.type === 'OBJECTLIT') { + doclet.defaultvalue = String(doclet.meta.code.node.toSource()); + doclet.defaultvaluetype = 'object'; + } } } }) diff --git a/lib/jsdoc/tag/inline.js b/lib/jsdoc/tag/inline.js index b425ef87..8be46e26 100644 --- a/lib/jsdoc/tag/inline.js +++ b/lib/jsdoc/tag/inline.js @@ -61,7 +61,7 @@ exports.replaceInlineTags = function(string, replacers) { string = string || ''; Object.keys(replacers).forEach(function(replacer) { - var tagRegExp = new RegExp('\\{@' + replacer + '\\s+(.+?)\\}', 'gi'); + var tagRegExp = new RegExp('\\{@' + replacer + '\\s+((?:.|\n)+?)\\}', 'gi'); var matches; // call the replacer once for each match while ( (matches = tagRegExp.exec(string)) !== null ) { diff --git a/lib/jsdoc/util/templateHelper.js b/lib/jsdoc/util/templateHelper.js index bc3a59f6..ebdc6387 100644 --- a/lib/jsdoc/util/templateHelper.js +++ b/lib/jsdoc/util/templateHelper.js @@ -256,11 +256,13 @@ function splitLinkText(text) { // if a pipe is not present, we split on the first space splitIndex = text.indexOf('|'); if (splitIndex === -1) { - splitIndex = text.indexOf(' '); + splitIndex = text.search(/\s/); } if (splitIndex !== -1) { linkText = text.substr(splitIndex + 1); + // Normalize subsequent newlines to a single space. + linkText = linkText.replace(/\n+/, ' '); target = text.substr(0, splitIndex); } diff --git a/plugins/eventDumper.js b/plugins/eventDumper.js index 70eff028..27857455 100644 --- a/plugins/eventDumper.js +++ b/plugins/eventDumper.js @@ -19,7 +19,8 @@ var events = conf.include || [ 'symbolFound', 'newDoclet', 'fileComplete', - 'parseComplete' + 'parseComplete', + 'processingComplete' ]; // Don't dump the excluded parser events if (conf.exclude) { diff --git a/templates/default/static/styles/jsdoc-default.css b/templates/default/static/styles/jsdoc-default.css index ea49f607..cbcff70f 100644 --- a/templates/default/static/styles/jsdoc-default.css +++ b/templates/default/static/styles/jsdoc-default.css @@ -195,12 +195,14 @@ h6 font-family: Consolas, "Lucida Console", Monaco, monospace; } -.details { margin-top: 14px; } -.details dt { width:100px; float:left; border-left: 2px solid #DDD; padding-left: 10px; padding-top: 6px; } +.details { margin-top: 14px; border-left: 2px solid #DDD; } +.details dt { width:100px; float:left; padding-left: 10px; padding-top: 6px; } .details dd { margin-left: 50px; } .details ul { margin: 0; } .details ul { list-style-type: none; } .details li { margin-left: 30px; padding-top: 6px; } +.details pre.prettyprint { margin: 0 } +.details .object-value { padding-top: 0; } .description { margin-bottom: 1em; diff --git a/templates/default/tmpl/details.tmpl b/templates/default/tmpl/details.tmpl index d3b35522..a18dfdc0 100644 --- a/templates/default/tmpl/details.tmpl +++ b/templates/default/tmpl/details.tmpl @@ -1,6 +1,13 @@ " + data.defaultvalue + ""; + defaultObjectClass = ' class="object-value"'; +} ?>
Default Value:
-
+
    + > +
diff --git a/test/fixtures/defaulttag.js b/test/fixtures/defaulttag.js index bd8461aa..996033a7 100644 --- a/test/fixtures/defaulttag.js +++ b/test/fixtures/defaulttag.js @@ -31,4 +31,17 @@ var win = getParentWindow(); /** @default */ -var header = getHeaders(request); \ No newline at end of file +var header = getHeaders(request); + +/** + @default + */ +var obj = { value_a : 'a', value_b : 'b' }; + +/** + * @default + */ +var multilineObject = { + value_a : 'a', + value_b : 'b' +}; diff --git a/test/specs/jsdoc/src/filter.js b/test/specs/jsdoc/src/filter.js index c34d0aae..98593378 100644 --- a/test/specs/jsdoc/src/filter.js +++ b/test/specs/jsdoc/src/filter.js @@ -2,7 +2,7 @@ describe("jsdoc/src/filter", function() { var filter = new (require('jsdoc/src/filter').Filter)({ includePattern: new RegExp(".+\\.js(doc)?$"), - excludePattern: new RegExp("(^|\\/)_"), + excludePattern: new RegExp("(^|\\/|\\\\)_"), exclude: ['.ignore', 'scratch/conf.js'] }); diff --git a/test/specs/jsdoc/src/parser.js b/test/specs/jsdoc/src/parser.js index d1794dee..d75f554a 100644 --- a/test/specs/jsdoc/src/parser.js +++ b/test/specs/jsdoc/src/parser.js @@ -77,6 +77,16 @@ describe("jsdoc/src/parser", function() { expect(spy).toHaveBeenCalled(); expect(spy.mostRecentCall.args[0].sourcefiles).toEqual(["[[string0]]"]); }); + + it("should fire processingComplete when fireProcessingComplete is called", function() { + var spy = jasmine.createSpy(), + doclets = ['a','b']; + parser.on('processingComplete', spy).fireProcessingComplete(doclets); + expect(spy).toHaveBeenCalled(); + expect(typeof spy.mostRecentCall.args[0]).toBe('object'); + expect(spy.mostRecentCall.args[0].doclets).toBeDefined(); + expect(spy.mostRecentCall.args[0].doclets).toBe(doclets); + }); it("should be able to parse its own source file", function() { var fs = require('jsdoc/fs'), diff --git a/test/specs/jsdoc/util/templateHelper.js b/test/specs/jsdoc/util/templateHelper.js index c8439132..8fb1f19f 100644 --- a/test/specs/jsdoc/util/templateHelper.js +++ b/test/specs/jsdoc/util/templateHelper.js @@ -1168,6 +1168,22 @@ describe("jsdoc/util/templateHelper", function() { expect(output).toBe('This is a test.'); }); + it('should allow linebreaks to separate url from link text', function() { + var input = 'This is a {@link\ntest\ntest}.', + output = helper.resolveLinks(input); + + expect(output).toBe('This is a test.'); + }); + + + it('should normalize additional newlines to spaces', function() { + var input = 'This is a {@link\ntest\ntest\n\ntest}.', + output = helper.resolveLinks(input); + + expect(output).toBe('This is a test test.'); + }); + + it('should allow tabs between link tag and content', function() { var input = 'This is a {@link\ttest}.', output = helper.resolveLinks(input); diff --git a/test/specs/tags/augmentstag.js b/test/specs/tags/augmentstag.js index 953e9945..5baad4d0 100644 --- a/test/specs/tags/augmentstag.js +++ b/test/specs/tags/augmentstag.js @@ -80,6 +80,15 @@ expect(bazMethod3.memberof).toBe("Baz"); }); + 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"); + }); + it('When an object is extended, and it overrides an ancestor property, the child does not include docs for the ancestor property.', function() { expect(bazProp1All.length).toBe(1); }); diff --git a/test/specs/tags/defaulttag.js b/test/specs/tags/defaulttag.js index f4c131e8..42a38271 100644 --- a/test/specs/tags/defaulttag.js +++ b/test/specs/tags/defaulttag.js @@ -8,6 +8,8 @@ describe("@default tag", function() { var rerrored = (docSet.getByLongname('rerrored') || [])[0]; var win = (docSet.getByLongname('win') || [])[0]; var header = (docSet.getByLongname('header') || [])[0]; + var obj = docSet.getByLongname('obj')[0]; + var multilineObject = docSet.getByLongname('multilineObject')[0]; it('When symbol set to null has a @default tag with no text, the doclet\'s defaultValue property should be: null', function() { expect(request.defaultvalue).toBe('null'); @@ -37,4 +39,14 @@ describe("@default tag", function() { expect(header.defaultvalue).toBeUndefined(); }); + it('When symbol has a @default tag with an object, the doclet\'s defaultValue property should contain the stringified object', function() { + var expected_value = "{value_a: 'a', value_b: 'b'}"; + expect(obj.defaultvalue).toEqual(expected_value); + }); + + it('When symbol has a @default tag with a multiline object, the doclet\'s defaultValue property should contain the properly stringified object', function() { + var expected_value = "{value_a: 'a', value_b: 'b'}"; + expect(obj.defaultvalue).toEqual(expected_value); + }); + }); diff --git a/test/specs/tags/exampletag.js b/test/specs/tags/exampletag.js index 8d1673c6..6e817177 100644 --- a/test/specs/tags/exampletag.js +++ b/test/specs/tags/exampletag.js @@ -2,21 +2,21 @@ describe("@example tag", function() { var docSet = jasmine.getDocSetFromFile('test/fixtures/exampletag.js'), doc = docSet.getByLongname('x')[0], doc2 = docSet.getByLongname('y')[0], - txt = 'console.log("foo");\nconsole.log("bar");', - txt2 = 'Example 2\n1 + 2;'; + txtRegExp = new RegExp('console\\.log\\("foo"\\);[\\r\\n]{1,2}console\\.log\\("bar"\\);'), + txt2RegExp = new RegExp('Example 2[\\r\\n]{1,2}1 \\+ 2;'); it("creates an 'examples' property on the doclet with the example", function() { expect(doc.examples).toBeDefined(); expect(Array.isArray(doc.examples)).toBe(true); expect(doc.examples.length).toBe(1); - expect(doc.examples).toContain(txt); + expect(doc.examples).toMatch(txtRegExp); }); it("can be specified multiple times on one doclet", function() { expect(doc2.examples).toBeDefined(); expect(Array.isArray(doc2.examples)).toBe(true); expect(doc2.examples.length).toBe(2); - expect(doc2.examples).toContain(txt); - expect(doc2.examples).toContain(txt2); + expect(doc2.examples).toMatch(txtRegExp); + expect(doc2.examples).toMatch(txt2RegExp); }); });