From adb76df18f1c5d9e9a9c22d8c7933a7f81eca32f Mon Sep 17 00:00:00 2001 From: Hage Yaapa Date: Thu, 7 Jan 2016 00:19:49 +0530 Subject: [PATCH] @promise support --- README.md | 107 ++++++++++++++++++++--- lib/annotation.js | 45 +++++++++- package.json | 4 +- public/css/main.css | 3 +- templates/annotation.ejs | 48 ++++++++-- test/docs.test.js | 102 ++++++++++++++------- test/fixtures/{ => js}/complex-attrs.js | 0 test/fixtures/{ => js}/ngdoc.js | 0 test/fixtures/js/promise-callback.js | 15 ++++ test/fixtures/js/promise-custom.js | 15 ++++ test/fixtures/js/promise-standalone.js | 10 +++ test/fixtures/js/promise-unresolvable.js | 15 ++++ 12 files changed, 311 insertions(+), 53 deletions(-) rename test/fixtures/{ => js}/complex-attrs.js (100%) rename test/fixtures/{ => js}/ngdoc.js (100%) create mode 100644 test/fixtures/js/promise-callback.js create mode 100644 test/fixtures/js/promise-custom.js create mode 100644 test/fixtures/js/promise-standalone.js create mode 100644 test/fixtures/js/promise-unresolvable.js diff --git a/README.md b/README.md index 7e61c7f..fb8f8b6 100644 --- a/README.md +++ b/README.md @@ -99,13 +99,13 @@ Strong-docs uses [Github Flavored Markdown](http://github.github.com/github-flav To create a section you only need to provide a markdown header eg. `#` or `###`. The following example creates several sections. # My Section - + This is a paragraph. - + ## Sub Section - + This is a paragraph within a sub section. - + The first section `# My Section` will have a depth of 1 and the second's depth will be 2. See (section depth)[#section-depth] for more info. #### Links / Anchors @@ -133,7 +133,7 @@ Strong-docs supports linking to images absolutely (using regular markdown): ![Alt text](http://foo.com/path/to/img.jpg) ![Alt text](http://foo.com/path/to/img.jpg "Optional title") - + Or you can bundled your images with your site using the [assets setting](#assets). { @@ -173,8 +173,8 @@ exports.escape = function(html){ .replace(//g, '>'); }; -``` - +``` + See the [JSDoc](http://usejsdoc.org/) site for more examples. ##### Ignoring Annotations @@ -191,7 +191,7 @@ To ignore an annotation change the comment block from `/**` to `/*!`. // ... ``` -You can also use the `@private` attribute to prevent your annotation from being rendered in your doc site. +You can also use the `@private` attribute to prevent your annotation from being rendered in your doc site. ```js /** @@ -227,10 +227,10 @@ The following is a list of configuration properties for strong-docs. You may spe - **content** - default: 'content' - specify your [documentation source files](#documentation-source-files) - **codeSectionDepth** - default `4` - specify the depth of [JavaScript sections](#section-depth) - **assets** - path to your assets directory - + ### Content -Documentation will be rendered in the order it is listed in the content array. Below is an example content array with markdown, JavaScript, and an injected section. +Documentation will be rendered in the order it is listed in the content array. Below is an example content array with markdown, JavaScript, and an injected section. [ "docs/overview.md", @@ -261,7 +261,7 @@ Link to these files from your docs like this: ![Alt text](assets/img.jpg) [My File](assets/pkg.zip) - + ## JSDoc Annotations ### Supported annnotations @@ -332,3 +332,88 @@ Link to these files from your docs like this: * typedef * variation * version + +### StrongLoop annnotations + +#### promise + +Syntax: `@promise [{Types}] [resolve object]` + +`Types` and `resolve object` must be specified for a promise-only function. + +``` +/** + * Function to test a standalone promise. + * + * @param {Array} An array parameter. + * @promise {Array} Resolves an Array. + * + */ +function promiseStandalone(arg) { + +} +``` + +`Types` and `resolve object` are optional if the function also accepts a callback. +The promise details are derived from the callback. + +``` +/** +* Function to test promise from a callback. +* +* @param {Array} An array parameter. +* +* @callback {Function} callback This is the callback. +* @param {Error} err Error object. +* @param {Object} instance Model instance. +* +* @promise +* +*/ +function promiseCallback(arg, callback) { + +} +``` + +Specifying `Types` and `resolve object` will overwrite the defaults derived from +the callback. + +``` +/** +* Function to test custom promise details. +* +* @param {Array} An array parameter. +* +* @callback {Function} callback This is the callback. +* @param {Error} err Error object. +* @param {Object} instance Model instance. +* +* @promise {String} Custom resolve object of type String. +* +*/ +function promiseCustom(arg, callback) { + +} +``` + +A warning message will be printed on the console and the documentation page, +if the promise cannot be meaningfully resolve with respect to the callback +implementation. + +``` +/** +* Function to test unresolvable promise from a callback. +* +* @param {Array} An array parameter. +* +* @callback {Function} callback This is the callback. +* @param {Error} err Error object. +* @param {Object} instanceA first Model instance. +* @param {Object} instanceB second Model instance. +* @promise +* +*/ +function promiseUnresolvable(arg, callback) { + +} +``` diff --git a/lib/annotation.js b/lib/annotation.js index 4906531..918422d 100644 --- a/lib/annotation.js +++ b/lib/annotation.js @@ -39,7 +39,7 @@ function Annotation(comment, doc) { if(shouldParse) { var fn = tagParsers[tag.type] || notSupported; - fn.call(this, tag); + fn.call(this, tag, comment.ctx); } if(tag.description) { @@ -68,7 +68,6 @@ function Annotation(comment, doc) { var header; var anchorId; var memberOf = attrs.memberof; - if (args) { args.forEach(function(arg) { if (!arg.types) return; @@ -380,6 +379,48 @@ var tagParsers = { } }, private: setProperty, + promise: function (tag, ctx) { + var resolveObject = { name: 'resolve' }; + if (!tag.types) { + parseTagForType(tag); + if(tag.name) tag.description = tag.name + ' ' + tag.description; + tag.name = undefined; + } + tag.name = tag.name || 'promise'; + mapStarToAny(tag); + this.promise = {}; + this.promise.attrs = []; + // assign the input object types + if (this.args.length) { + this.promise.types = this.args[0].types; + } + // try to fill in the promise details from Callback properties + if ('callbackTag' in this) { + if (this.callbackTag.args.length === 1) { + console.log('Resolve object not found in %s', ctx.string) + resolveObject.types = ['undefined'] + resolveObject.description = 'The resolve handler does not receive any arguments.' + } + else if (this.callbackTag.args.length === 2) { + resolveObject.types = this.callbackTag.args[1].types; + resolveObject.description = this.callbackTag.args[1].description; + } + else { + var warningMessage = 'Promise cannot be resolved in ' + ctx.string; + this.promise.warning = warningMessage; + console.log(warningMessage); + } + } + // custom description takes precedence over properties from Callback + if (tag.description.length > 0) { + resolveObject.types = tag.types; + resolveObject.description = tag.description; + } + this.promise.attrs.push(resolveObject); + if (!('description' in resolveObject)) { + console.log('Description for resolve object not found in %s', ctx.string) + } + }, property: function(tag) { if(!tag.types) { parseTagForType(tag); diff --git a/package.json b/package.json index 083e388..c6bed73 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "strong-docs", "description": "Sleek, intuitive, and powerful documentation site generator", - "version": "1.0.0", + "version": "1.1.0", "repository": { "type": "git", "url": "git://github.com/strongloop/strong-docs.git" @@ -16,7 +16,7 @@ "opener": "~1.3.0", "express": "~3.4.0", "js-yaml": "~2.1.0", - "dox": "iolo/dox", + "dox": "0.8.0", "ejs": "~0.8.4", "strong-task-emitter": "~0.0.5", "markdown": "~0.5.0", diff --git a/public/css/main.css b/public/css/main.css index 8038826..427d6c4 100755 --- a/public/css/main.css +++ b/public/css/main.css @@ -50,7 +50,6 @@ a[name] { /* Annotations */ .code-arguments-hdr { - text-transform: capitalize; font-size: 120%; margin-top: 20px; margin-bottom: 10px; @@ -137,4 +136,4 @@ section + section { section { margin-left: 30px; -} \ No newline at end of file +} diff --git a/templates/annotation.ejs b/templates/annotation.ejs index 36051c6..813ca1c 100644 --- a/templates/annotation.ejs +++ b/templates/annotation.ejs @@ -43,7 +43,17 @@ <% } else if(arg.type === 'callback' && arg.args.length) { %> -
<%= arg.name || 'Callback' %>
+ + <% if (!ann.promise) {%> +
Callback
+ <% } else { %> +
Callback (optional)
+

+ Optional callback. When the callback function is not provided, + a promise is returned instead (see below). +

+ <% } %> + @@ -60,7 +70,7 @@ %> <% } %> - + <% if(Array.isArray(ann.properties) && ann.properties.length) { %>
Class Properties
Name
@@ -90,10 +100,10 @@ arg.properties.forEach(renderArg); %>
- <% } + <% } }) } - %> + %> <% if(ann.attrs.returns) { %>
Returns
@@ -106,13 +116,39 @@ <% renderArg(ann.attrs.returns) %> <% } %> - - <% if(ann.attrs.example) {%> + + <% if(ann.attrs.example) {%>
Example
<%- md(ann.attrs.example) %>
<% } %> + + <% if(ann.promise) {%> +
Promise
+

+ This method supports both callback-based and promise-based invocation. + Call this method with no callback argument to get back a promise instead. + <% if(ann.promise.warning) {%> +

+ WARNING: this promise implementation will not resolve according to + the callback function. +
+ <% } %> +

+ + + + + + + <% + ann.promise.attrs.forEach(renderArg); + %> +
HandlerArgumentDescription
+ + <% } %> + diff --git a/test/docs.test.js b/test/docs.test.js index 7ba3ce4..601979d 100644 --- a/test/docs.test.js +++ b/test/docs.test.js @@ -63,7 +63,7 @@ describe('Docs', function() { done(); }); }); - + it('should have unique anchors', function () { var docs = new Docs(); var samples = [ @@ -75,7 +75,7 @@ describe('Docs', function() { 'foo bar', 'foo bar' ]; - + var expected = [ 'model-validatesnumericalityof', 'model-validatesnumericalityof-1', @@ -85,12 +85,12 @@ describe('Docs', function() { 'foo-bar', 'foo-bar-1' ]; - + samples.forEach(function (s, i) { assert.equal(docs.getUniqueAnchor(s), expected[i]); }); }); - + it('should be able to generate html', function(done) { Docs.toHtml({ content: SAMPLE, @@ -102,7 +102,7 @@ describe('Docs', function() { done(); }); }); - + it('should error when a file does not exist', function (done) { Docs.parse({ content: ['does-not-exist'], @@ -113,7 +113,7 @@ describe('Docs', function() { done(); }); }); - + describe('.readConfig(options, fn)', function(){ it('should read a config file', function(done) { Docs.readConfig({ @@ -126,16 +126,16 @@ describe('Docs', function() { assert.equal(config.assets, 'assets'); assert.equal(config.content[0], 'README.md'); assert.equal(config.package.name, 'strong-docs'); - done(); + done(); } }); }); }); - + describe('@options', function () { it('should define a param of type object with properties following', function (done) { Docs.parse({ - content: ['fixtures/complex-attrs.js'], + content: ['fixtures/js/complex-attrs.js'], root: __dirname }, function (err, docs) { done(); @@ -162,30 +162,72 @@ describe('Docs', function() { }); describe('ngdoc flavour', function() { - var annotation; - - beforeEach(function parseNgDocSourceFile(done) { - Docs.parse( - { - content: ['fixtures/ngdoc.js'], - root: __dirname - }, - function(err, docs) { - if (err) return done(err); - annotation = docs.content[0].sections[0].annotation; - done(); - }); - }); - it('should include @description', function() { - expect(annotation.html).to.contain('Some description'); + it('should include @description', function(done) { + parseNgDocSourceFile('fixtures/js/ngdoc.js', done, function (annotation) { + expect(annotation.html).to.contain('Some description'); + }) }); - it('should handle multi-param function type', function() { - // @param {String|Number} - expect(annotation.args[0].types).to.eql(['String', 'Number']); - // @param {function(Error=,Object=)} - expect(annotation.args[1].types).to.eql(['function(Error=, Object=)']); + it('should handle multi-param function type', function(done) { + parseNgDocSourceFile('fixtures/js/ngdoc.js', done, function(annotation) { + // @param {String|Number} + expect(annotation.args[0].types).to.eql(['String', 'Number']); + // @param {function(Error|undefined, Object|undefined)} + expect(annotation.args[1].types).to.eql(['function(Error|undefined, Object|undefined)']); + }) }); + + describe('@promise', function () { + + it('should be supported', function(done) { + parseNgDocSourceFile('fixtures/js/promise-standalone.js', done, function (annotation) { + assertPromise(annotation, ['Array'], ['Array']); + }) + }); + + it('should be generated from a callback', function(done) { + parseNgDocSourceFile('fixtures/js/promise-callback.js', done, function (annotation) { + assertPromise(annotation, ['Array'], ['Object']); + }) + }); + + it('should be generated from a custom description', function(done) { + parseNgDocSourceFile('fixtures/js/promise-custom.js', done, function (annotation) { + assertPromise(annotation, ['Array'], ['String']); + }) + }); + + it('should include a warning when unresolvable', function(done) { + parseNgDocSourceFile('fixtures/js/promise-unresolvable.js', done, function (annotation) { + expect(annotation.promise.warning).to.not.be.undefined; + expect(annotation.promise.warning).to.contain('Promise cannot be resolved in'); + }) + }); + + }) + }); }); + +function parseNgDocSourceFile(file, done, assertion) { + Docs.parse( + { + content: [file], + root: __dirname + }, + function(err, docs) { + if (err) return done(err); + var annotation = docs.content[0].sections[0].annotation; + done(assertion(annotation)); + }); +} + +function assertPromise(annotation, paramType, resolveType) { + expect(annotation.promise).to.not.be.undefined; + expect(annotation.promise.warning).to.be.undefined; + expect(annotation.promise.types).to.eql(paramType); + expect(annotation.promise.attrs[0]).to.have.property.name; + expect(annotation.promise.attrs[0].name).to.equal('resolve'); + expect(annotation.promise.attrs[0].types).to.eql(resolveType); +} diff --git a/test/fixtures/complex-attrs.js b/test/fixtures/js/complex-attrs.js similarity index 100% rename from test/fixtures/complex-attrs.js rename to test/fixtures/js/complex-attrs.js diff --git a/test/fixtures/ngdoc.js b/test/fixtures/js/ngdoc.js similarity index 100% rename from test/fixtures/ngdoc.js rename to test/fixtures/js/ngdoc.js diff --git a/test/fixtures/js/promise-callback.js b/test/fixtures/js/promise-callback.js new file mode 100644 index 0000000..5e5adfa --- /dev/null +++ b/test/fixtures/js/promise-callback.js @@ -0,0 +1,15 @@ +/** +* Function to test promise from a callback. +* +* @param {Array} An array parameter. +* +* @callback {Function} callback This is the callback. +* @param {Error} err Error object. +* @param {Object} instance Model instance. +* +* @promise +* +*/ +function promiseCallback(arg, callback) { + +} diff --git a/test/fixtures/js/promise-custom.js b/test/fixtures/js/promise-custom.js new file mode 100644 index 0000000..e1114c4 --- /dev/null +++ b/test/fixtures/js/promise-custom.js @@ -0,0 +1,15 @@ +/** +* Function to test custom promise details. +* +* @param {Array} An array parameter. +* +* @callback {Function} callback This is the callback. +* @param {Error} err Error object. +* @param {Object} instance Model instance. +* +* @promise {String} Custom resolve object of type String. +* +*/ +function promiseCustom(arg, callback) { + +} diff --git a/test/fixtures/js/promise-standalone.js b/test/fixtures/js/promise-standalone.js new file mode 100644 index 0000000..a5b4bfb --- /dev/null +++ b/test/fixtures/js/promise-standalone.js @@ -0,0 +1,10 @@ +/** + * Function to test a standalone promise. + * + * @param {Array} An array parameter. + * @promise {Array} Resolves an Array. + * + */ +function promiseStandalone(arg) { + +} diff --git a/test/fixtures/js/promise-unresolvable.js b/test/fixtures/js/promise-unresolvable.js new file mode 100644 index 0000000..1353df2 --- /dev/null +++ b/test/fixtures/js/promise-unresolvable.js @@ -0,0 +1,15 @@ +/** +* Function to test unresolvable promise from a callback. +* +* @param {Array} An array parameter. +* +* @callback {Function} callback This is the callback. +* @param {Error} err Error object. +* @param {Object} instanceA first Model instance. +* @param {Object} instanceB second Model instance. +* @promise +* +*/ +function promiseUnresolvable(arg, callback) { + +}