Greatly simplify and document the usage of JSDoc
This commit simplifies the exports.js plugin so it only relies on the stability notes to generate the documentation, which completely decouples it from the exportable API. As a rule of thumb, whenever something has an 'api' annotation, it should also have a 'stability' annotation. A more verbose documentation of ol3 specific annotation usage is available in the new 'apidoc/readme.md' file. This commit also modifies all source files to implement these usage suggestions.
This commit is contained in:
committed by
Tim Schaub
parent
aaf6101d0f
commit
c17ac0cae3
@@ -22,7 +22,8 @@
|
||||
"node_modules/jsdoc/plugins/markdown",
|
||||
"apidoc/plugins/inheritdoc",
|
||||
"apidoc/plugins/interface",
|
||||
"apidoc/plugins/exports",
|
||||
"apidoc/plugins/api",
|
||||
"apidoc/plugins/olx-typedefs",
|
||||
"apidoc/plugins/todo",
|
||||
"apidoc/plugins/observable",
|
||||
"apidoc/plugins/stability"
|
||||
|
||||
51
apidoc/plugins/api.js
Normal file
51
apidoc/plugins/api.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Based on @stability annotations, and assuming that items with no @stability
|
||||
* annotation should not be documented, this plugin removes undocumented symbols
|
||||
* from the documentation. Undocumented classes with documented members get a
|
||||
* 'hideConstructur' property, which is read by the template so it can hide the
|
||||
* constructor.
|
||||
*/
|
||||
|
||||
function hasApiMembers(doclet) {
|
||||
return doclet.longname.split('#')[0] == this.longname;
|
||||
}
|
||||
|
||||
var api = [];
|
||||
|
||||
exports.handlers = {
|
||||
|
||||
newDoclet: function(e) {
|
||||
var doclet = e.doclet;
|
||||
// Keep track of api items - needed in parseComplete to determine classes
|
||||
// with api members.
|
||||
if (doclet.stability) {
|
||||
api.push(doclet);
|
||||
}
|
||||
// Mark explicity defined namespaces - needed in parseComplete to keep
|
||||
// namespaces that we need as containers for api items.
|
||||
if (/.*\.jsdoc$/.test(doclet.meta.filename) && doclet.kind == 'namespace') {
|
||||
doclet.namespace_ = true;
|
||||
}
|
||||
},
|
||||
|
||||
parseComplete: function(e) {
|
||||
var doclets = e.doclets;
|
||||
for (var i = doclets.length - 1; i >= 0; --i) {
|
||||
var doclet = doclets[i];
|
||||
// Always document namespaces and items with stability annotation
|
||||
if (doclet.stability || doclet.namespace_) {
|
||||
continue;
|
||||
}
|
||||
if (doclet.kind == 'class' && api.some(hasApiMembers, doclet)) {
|
||||
// Mark undocumented classes with documented members as unexported.
|
||||
// This is used in ../template/tmpl/container.tmpl to hide the
|
||||
// constructor from the docs.
|
||||
doclet.hideConstructor = true;
|
||||
} else {
|
||||
// Remove all other undocumented symbols
|
||||
doclets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* This plugin removes unexported symbols from the documentation.
|
||||
* Unexported modules linked from @param or @fires will be marked unexported,
|
||||
* and the documentation will not contain the constructor. Everything else is
|
||||
* marked undocumented, which will remove it from the docs.
|
||||
*/
|
||||
|
||||
var api = [];
|
||||
var unexported = [];
|
||||
var observablesByClass = {};
|
||||
|
||||
function collectExports(source) {
|
||||
var symbols = JSON.parse(source).symbols;
|
||||
for (var i = 0, ii = symbols.length; i < ii; ++i) {
|
||||
api.push(symbols[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
var encoding = env.conf.encoding || 'utf8';
|
||||
var fs = require('jsdoc/fs');
|
||||
collectExports(fs.readFileSync('build/symbols.json', encoding));
|
||||
|
||||
|
||||
exports.handlers = {
|
||||
|
||||
newDoclet: function(e) {
|
||||
var i, ii, j, jj;
|
||||
if (e.doclet.meta.filename == "olx.js" && e.doclet.longname != 'olx') {
|
||||
api.push(e.doclet.longname);
|
||||
}
|
||||
if (e.doclet.longname.indexOf('oli.') === 0) {
|
||||
unexported.push(e.doclet.longname.replace(/^oli\./, 'ol.'));
|
||||
}
|
||||
if (api.indexOf(e.doclet.longname) > -1) {
|
||||
var names, name;
|
||||
var params = e.doclet.params;
|
||||
if (params) {
|
||||
for (i = 0, ii = params.length; i < ii; ++i) {
|
||||
names = params[i].type.names;
|
||||
if (names) {
|
||||
for (j = 0, jj=names.length; j < jj; ++j) {
|
||||
name = names[j];
|
||||
if (unexported.indexOf(name) === -1) {
|
||||
unexported.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var links = e.doclet.comment.match(/\{@link ([^\}]*)\}/g);
|
||||
if (links) {
|
||||
for (i=0, ii=links.length; i < ii; ++i) {
|
||||
var link = links[i].match(/\{@link (.*)\}/)[1];
|
||||
if (unexported.indexOf(link) === -1) {
|
||||
unexported.push(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.doclet.observables) {
|
||||
var observables = observablesByClass[e.doclet.longname] = [];
|
||||
for (i = e.doclet.observables.length - 1; i >= 0; --i) {
|
||||
observables.push(e.doclet.observables[i].name);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
parseComplete: function(e) {
|
||||
for (var j = e.doclets.length - 1; j >= 0; --j) {
|
||||
var doclet = e.doclets[j];
|
||||
if (doclet.meta.filename == 'olx.js' && doclet.kind == 'typedef') {
|
||||
for (var i = e.doclets.length - 1; i >= 0; --i) {
|
||||
var propertyDoclet = e.doclets[i];
|
||||
if (propertyDoclet.memberof == doclet.longname) {
|
||||
if (!doclet.properties) {
|
||||
doclet.properties = [];
|
||||
}
|
||||
doclet.properties.unshift(propertyDoclet);
|
||||
e.doclets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (doclet.kind == 'namespace' || doclet.kind == 'event' || doclet.fires) {
|
||||
continue;
|
||||
}
|
||||
var fqn = doclet.longname;
|
||||
if (fqn) {
|
||||
var getterOrSetter = fqn.match(/([^#]*)#[gs]et(.*)/);
|
||||
if (getterOrSetter) {
|
||||
var observables = observablesByClass[getterOrSetter[1]];
|
||||
if (observables && observables.indexOf(getterOrSetter[2].toLowerCase()) > -1) {
|
||||
// Always document getters/setters of observables
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (doclet.memberof && doclet.memberof.indexOf('oli.') === 0 &&
|
||||
unexported.indexOf(doclet.memberof) > -1) {
|
||||
// Always document members of referenced oli interfaces
|
||||
continue;
|
||||
}
|
||||
doclet.unexported = (api.indexOf(fqn) === -1 && unexported.indexOf(fqn) !== -1);
|
||||
if (api.indexOf(fqn) === -1 && unexported.indexOf(fqn) === -1) {
|
||||
e.doclets.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -10,13 +10,16 @@ exports.defineTags = function(dictionary) {
|
||||
}
|
||||
});
|
||||
|
||||
var augmentsTag = dictionary.lookUp('augments');
|
||||
dictionary.defineTag('implements', {
|
||||
mustHaveValue: true,
|
||||
onTagged: function(doclet, tag) {
|
||||
tag.value = tag.value.match(/^\{?([^\}]*)\}?$/)[1];
|
||||
augmentsTag.onTagged.apply(this, arguments);
|
||||
if (!doclet.implements) {
|
||||
doclet.implements = [];
|
||||
}
|
||||
doclet.implements.push(tag.value.match(/^{(.*)}$/)[1]);
|
||||
doclet.implements.push(tag.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
23
apidoc/plugins/olx-typedefs.js
Normal file
23
apidoc/plugins/olx-typedefs.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Converts olx.js @type annotations into properties of the previous @typedef.
|
||||
*/
|
||||
|
||||
var olxTypedef = null;
|
||||
|
||||
exports.handlers = {
|
||||
|
||||
newDoclet: function(e) {
|
||||
var doclet = e.doclet;
|
||||
if (doclet.meta.filename == 'olx.js') {
|
||||
if (doclet.kind == 'typedef') {
|
||||
olxTypedef = doclet;
|
||||
doclet.properties = [];
|
||||
} else if (olxTypedef && doclet.memberof == olxTypedef.longname) {
|
||||
olxTypedef.properties.push(doclet);
|
||||
} else {
|
||||
olxTypedef = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
136
apidoc/readme.md
Normal file
136
apidoc/readme.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# API Documentation
|
||||
|
||||
This directory contains configuration (`conf.json`), static content (`index.md`), template (`template/`) and plugins (`plugins/`) for the [JSDoc3](http://usejsdoc.org/) API generator.
|
||||
|
||||
## Documenting the source code
|
||||
|
||||
JSDoc annotations are used for metadata used by the compiler, for defining the user facing API, and for user documentation.
|
||||
|
||||
In the simplest case, a JSDoc block can look like this:
|
||||
```js
|
||||
/**
|
||||
* Add the given control to the map.
|
||||
* @param {ol.control.Control} control Control.
|
||||
* @todo stability experimental
|
||||
* @todo api
|
||||
*/
|
||||
ol.Map.prototype.addControl = function(control) {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
The first line is text for the user documentation. This can be long, and it can
|
||||
contain Markdown.
|
||||
|
||||
The second line tells the Closure compiler the type of the argument.
|
||||
|
||||
The third line marks the API stability. Once the documentation story is fully settled, we will remove the `todo ` and just write `@stability experimental`. Without such a stability note, the method will not be documented in the generated API documentation.
|
||||
|
||||
The last line marks the method as exportable so it can be made available to the user facing API. This will also change to just `@api` eventually.
|
||||
|
||||
### Observable properties
|
||||
|
||||
For classes that inherit from `ol.Object`, there is a special documentation case for getters and setters:
|
||||
```js
|
||||
/**
|
||||
* Get the size of this map.
|
||||
* @return {ol.Size|undefined} Size.
|
||||
* @todo stability experimental
|
||||
*/
|
||||
ol.Map.prototype.getSize = function() {
|
||||
// ...
|
||||
};
|
||||
goog.exportProperty(
|
||||
ol.Map.prototype,
|
||||
'getSize',
|
||||
ol.Map.prototype.getSize);
|
||||
```
|
||||
Because `ol.Object` needs to rely on these getter and setter names, these methods are not marked `@api` as exportable. Instead, `goog.exportProperty()` is used after the method definition to make sure that this method is always part of the API and not renamed in build configurations that do not need it.
|
||||
|
||||
To document observable properties with the `ol.ObjectEvent` types they are associated with, the `@observable` property is used (currently still `@todo observable`):
|
||||
```js
|
||||
* @constructor
|
||||
* @todo observable layergroup {ol.layer.Group} a layer group containing the
|
||||
* layers in this map.
|
||||
* @todo observable size {ol.Size} the size in pixels of the map in the DOM
|
||||
* @todo observable target {string|Element} the Element or id of the Element
|
||||
* that the map is rendered in.
|
||||
* @todo observable view {ol.IView} the view that controls this map
|
||||
*/
|
||||
ol.Map = function(options) {
|
||||
```
|
||||
The first argument to that annotation is the name of the property, then the type(s) in curly braces, and then a description. NOTE/TODO: The `apidoc/plugins/observable.js` plugin does currently not handle inherited observable properties.
|
||||
|
||||
### Events
|
||||
|
||||
Events are documented using `@fires` and `@event` annotations:
|
||||
```js
|
||||
/**
|
||||
* Constants for event names.
|
||||
* @enum {string}
|
||||
*/
|
||||
ol.MapBrowserEvent.EventType = {
|
||||
/**
|
||||
* A true single click with no dragging and no double click. Note that this
|
||||
* event is delayed by 250 ms to ensure that it is not a double click.
|
||||
* @event ol.MapBrowserEvent#singleclick
|
||||
* @todo stability experimental
|
||||
*/
|
||||
SINGLECLICK: 'singleclick',
|
||||
// ...
|
||||
};
|
||||
```
|
||||
Note the value of the `@event` annotation. The text before the hash refers to the event class that the event belongs to, and the text after the hash is the type of the event. To export these properties, they need to be defined in `externs/oli.js` (also see `readme.md` in `externs/`). In addition, a stability note is required in the source code (`src/ol/MapBrowserEvent.js`) to make sure that documentation gets generated:
|
||||
```js
|
||||
ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {
|
||||
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @type {ol.Coordinate}
|
||||
* @todo stability experimental
|
||||
*/
|
||||
this.coordinate = map.getEventCoordinate(this.originalEvent);
|
||||
|
||||
// ...
|
||||
|
||||
};
|
||||
```
|
||||
To document which events are fired by a class or method, the `@fires` annotation is used:
|
||||
```js
|
||||
* @fires {@link ol.MapBrowserEvent} ol.MapBrowserEvent
|
||||
* @fires {@link ol.MapEvent} ol.MapEvent
|
||||
* @fires {@link ol.render.Event} ol.render.Event
|
||||
*/
|
||||
ol.Map = function(options) {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
Again, note the syntax of the `@fires` annotation. The link is necessary to provide a link to the documentation of the event, and the name of the event class is necessary for JSDoc3 to know which event we are talking about.
|
||||
|
||||
### Special cases with inheritance
|
||||
|
||||
When an item is marked `@api` in a subclass and not the base class, the documentation needs to be provided in the class where the item is exported. If the item is a (member) function, the `@function` annotation needs to be used:
|
||||
```js
|
||||
/**
|
||||
* Read a feature from a GeoJSON Feature source. This method will throw
|
||||
* an error if used with a FeatureCollection source.
|
||||
* @function
|
||||
* @param {ArrayBuffer|Document|Node|Object|string} source Source.
|
||||
* @return {ol.Feature} Feature.
|
||||
* @todo stability experimental
|
||||
* @todo api
|
||||
*/
|
||||
ol.format.GeoJSON.prototype.readFeature;
|
||||
```
|
||||
The `@function` annotation is also needed when the function assignment is a
|
||||
constant function from a `goog` namespace (e.g. `goog.AbstractMethod`).
|
||||
|
||||
For an abstract method, if it exported by every subclass, the documentation can be provided in the abstract class, with a `@stability` note. Implementing classes can use `@inheritDoc` and export the item:
|
||||
```js
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @todo api
|
||||
*/
|
||||
```
|
||||
When only a subset of the subclasses exports the item, @inheritDoc cannot
|
||||
be used, and every exporting class needs to provide the documentation.
|
||||
@@ -27,7 +27,7 @@
|
||||
<?js if (doc.kind === 'module' && doc.module) { ?>
|
||||
<?js= self.partial('method.tmpl', doc.module) ?>
|
||||
<?js } ?>
|
||||
<?js if (!doc.unexported && doc.kind === 'class') { ?>
|
||||
<?js if (doc.kind === 'class' && !doc.hideConstructor && !doc.interface) { ?>
|
||||
<?js= self.partial('method.tmpl', doc) ?>
|
||||
<?js } else { ?>
|
||||
<?js if (doc.description) { ?>
|
||||
@@ -47,7 +47,8 @@
|
||||
<h3 class="subsection-title">Extends</h3>
|
||||
|
||||
<ul><?js doc.augments.forEach(function(a) { ?>
|
||||
<li><?js= self.linkto(a, a) ?></li>
|
||||
<li><?js= self.linkto(a, a) ?>
|
||||
<?js= (doc.implements&&doc.implements.indexOf(a)>-1?'(Interface)':'') ?></li>
|
||||
<?js }); ?></ul>
|
||||
<?js } ?>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user