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).
+
+ <% } %>
+
Name |
@@ -60,7 +70,7 @@
%>
<% } %>
-
+
<% if(Array.isArray(ann.properties) && ann.properties.length) { %>
Class Properties
@@ -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.
+
+ <% } %>
+
+
+
+ Handler |
+ Argument |
+ Description |
+
+ <%
+ ann.promise.attrs.forEach(renderArg);
+ %>
+
+
+ <% } %>
+
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) {
+
+}