123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- /**
- * Provides methods for augmenting the parse results based on their content.
- * @module jsdoc/augment
- */
- const doop = require('jsdoc/util/doop');
- const jsdoc = {
- doclet: require('jsdoc/doclet')
- };
- const name = require('jsdoc/name');
- const hasOwnProp = Object.prototype.hasOwnProperty;
- function mapDependencies(index, propertyName) {
- const dependencies = {};
- let doc;
- let doclets;
- const kinds = ['class', 'external', 'interface', 'mixin'];
- let len = 0;
- Object.keys(index).forEach(indexName => {
- doclets = index[indexName];
- for (let i = 0, ii = doclets.length; i < ii; i++) {
- doc = doclets[i];
- if (kinds.includes(doc.kind)) {
- dependencies[indexName] = {};
- if (hasOwnProp.call(doc, propertyName)) {
- len = doc[propertyName].length;
- for (let j = 0; j < len; j++) {
- dependencies[indexName][doc[propertyName][j]] = true;
- }
- }
- }
- }
- });
- return dependencies;
- }
- class Sorter {
- constructor(dependencies) {
- this.dependencies = dependencies;
- this.visited = {};
- this.sorted = [];
- }
- visit(key) {
- if (!(key in this.visited)) {
- this.visited[key] = true;
- if (this.dependencies[key]) {
- Object.keys(this.dependencies[key]).forEach(path => {
- this.visit(path);
- });
- }
- this.sorted.push(key);
- }
- }
- sort() {
- Object.keys(this.dependencies).forEach(key => {
- this.visit(key);
- });
- return this.sorted;
- }
- }
- function sort(dependencies) {
- const sorter = new Sorter(dependencies);
- return sorter.sort();
- }
- function getMembers(longname, {index}, scopes) {
- const memberof = index.memberof[longname] || [];
- const members = [];
- memberof.forEach(candidate => {
- if (scopes.includes(candidate.scope)) {
- members.push(candidate);
- }
- });
- return members;
- }
- function getDocumentedLongname(longname, {index}) {
- const doclets = index.documented[longname] || [];
- return doclets[doclets.length - 1];
- }
- function addDocletProperty(doclets, propName, value) {
- for (let i = 0, l = doclets.length; i < l; i++) {
- doclets[i][propName] = value;
- }
- }
- function reparentDoclet({longname}, child) {
- const parts = name.shorten(child.longname);
- parts.memberof = longname;
- child.memberof = longname;
- child.longname = name.combine(parts);
- }
- function parentIsClass({kind}) {
- return kind === 'class';
- }
- function staticToInstance(doclet) {
- const parts = name.shorten(doclet.longname);
- parts.scope = name.SCOPE.PUNC.INSTANCE;
- doclet.longname = name.combine(parts);
- doclet.scope = name.SCOPE.NAMES.INSTANCE;
- }
- /**
- * Update the list of doclets to be added to another symbol.
- *
- * We add only one doclet per longname. For example: If `ClassA` inherits from two classes that both
- * use the same method name, `ClassA` gets docs for one method rather than two.
- *
- * Also, the last symbol wins for any given longname. For example: If you write `@extends Class1
- * @extends Class2`, and both classes have an instance method called `myMethod`, you get the docs
- * from `Class2#myMethod`.
- *
- * @private
- * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added.
- * @param {Array.<module:jsdoc/doclet.Doclet>} additions - An array of doclets that will be added to
- * another symbol.
- * @param {Object.<string, number>} indexes - A dictionary of indexes into the `additions` array.
- * Each key is a longname, and each value is the index of the longname's doclet.
- * @return {void}
- */
- function updateAddedDoclets(doclet, additions, indexes) {
- if (typeof indexes[doclet.longname] !== 'undefined') {
- // replace the existing doclet
- additions[indexes[doclet.longname]] = doclet;
- }
- else {
- // add the doclet to the array, and track its index
- additions.push(doclet);
- indexes[doclet.longname] = additions.length - 1;
- }
- }
- /**
- * Update the index of doclets whose `undocumented` property is not `true`.
- *
- * @private
- * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added to the index.
- * @param {Object.<string, Array.<module:jsdoc/doclet.Doclet>>} documented - The index of doclets
- * whose `undocumented` property is not `true`.
- * @return {void}
- */
- function updateDocumentedDoclets(doclet, documented) {
- if ( !hasOwnProp.call(documented, doclet.longname) ) {
- documented[doclet.longname] = [];
- }
- documented[doclet.longname].push(doclet);
- }
- /**
- * Update the index of doclets with a `memberof` value.
- *
- * @private
- * @param {module:jsdoc/doclet.Doclet} doclet - The doclet to be added to the index.
- * @param {Object.<string, Array.<module:jsdoc/doclet.Doclet>>} memberof - The index of doclets
- * with a `memberof` value.
- * @return {void}
- */
- function updateMemberofDoclets(doclet, memberof) {
- if (doclet.memberof) {
- if ( !hasOwnProp.call(memberof, doclet.memberof) ) {
- memberof[doclet.memberof] = [];
- }
- memberof[doclet.memberof].push(doclet);
- }
- }
- function explicitlyInherits(doclets) {
- let doclet;
- let inherits = false;
- for (let i = 0, l = doclets.length; i < l; i++) {
- doclet = doclets[i];
- if (typeof doclet.inheritdoc !== 'undefined' || typeof doclet.override !== 'undefined') {
- inherits = true;
- break;
- }
- }
- return inherits;
- }
- function changeMemberof(longname, newMemberof) {
- const atoms = name.shorten(longname);
- atoms.memberof = newMemberof;
- return name.combine(atoms);
- }
- // TODO: try to reduce overlap with similar methods
- function getInheritedAdditions(doclets, docs, {documented, memberof}) {
- let additionIndexes;
- const additions = [];
- let childDoclet;
- let childLongname;
- let doc;
- let parentDoclet;
- let parentMembers;
- let parents;
- let member;
- let parts;
- // doclets will be undefined if the inherited symbol isn't documented
- doclets = doclets || [];
- for (let i = 0, ii = doclets.length; i < ii; i++) {
- doc = doclets[i];
- parents = doc.augments;
- if ( parents && (doc.kind === 'class' || doc.kind === 'interface') ) {
- // reset the lookup table of added doclet indexes by longname
- additionIndexes = {};
- for (let j = 0, jj = parents.length; j < jj; j++) {
- parentMembers = getMembers(parents[j], docs, ['instance']);
- for (let k = 0, kk = parentMembers.length; k < kk; k++) {
- parentDoclet = parentMembers[k];
- // We only care about symbols that are documented.
- if (parentDoclet.undocumented) {
- continue;
- }
- childLongname = changeMemberof(parentDoclet.longname, doc.longname);
- childDoclet = getDocumentedLongname(childLongname, docs) || {};
- // We don't want to fold in properties from the child doclet if it had an
- // `@inheritdoc` tag.
- if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
- childDoclet = {};
- }
- member = jsdoc.doclet.combine(childDoclet, parentDoclet);
- if (!member.inherited) {
- member.inherits = member.longname;
- }
- member.inherited = true;
- member.memberof = doc.longname;
- parts = name.shorten(member.longname);
- parts.memberof = doc.longname;
- member.longname = name.combine(parts);
- // Indicate what the descendant is overriding. (We only care about the closest
- // ancestor. For classes A > B > C, if B#a overrides A#a, and C#a inherits B#a,
- // we don't want the doclet for C#a to say that it overrides A#a.)
- if ( hasOwnProp.call(docs.index.longname, member.longname) ) {
- member.overrides = parentDoclet.longname;
- }
- else {
- delete member.overrides;
- }
- // Add the ancestor's docs unless the descendant overrides the ancestor AND
- // documents the override.
- if ( !hasOwnProp.call(documented, member.longname) ) {
- updateAddedDoclets(member, additions, additionIndexes);
- updateDocumentedDoclets(member, documented);
- updateMemberofDoclets(member, memberof);
- }
- // If the descendant used an @inheritdoc or @override tag, add the ancestor's
- // docs, and ignore the existing doclets.
- else if ( explicitlyInherits(documented[member.longname]) ) {
- // Ignore any existing doclets. (This is safe because we only get here if
- // `member.longname` is an own property of `documented`.)
- addDocletProperty(documented[member.longname], 'ignore', true);
- updateAddedDoclets(member, additions, additionIndexes);
- updateDocumentedDoclets(member, documented);
- updateMemberofDoclets(member, memberof);
- // Remove property that's no longer accurate.
- if (member.virtual) {
- delete member.virtual;
- }
- // Remove properties that we no longer need.
- if (member.inheritdoc) {
- delete member.inheritdoc;
- }
- if (member.override) {
- delete member.override;
- }
- }
- // If the descendant overrides the ancestor and documents the override,
- // update the doclets to indicate what the descendant is overriding.
- else {
- addDocletProperty(documented[member.longname], 'overrides',
- parentDoclet.longname);
- }
- }
- }
- }
- }
- return additions;
- }
- function updateMixes(mixedDoclet, mixedLongname) {
- let idx;
- let mixedName;
- let names;
- // take the fast path if there's no array of mixed-in longnames
- if (!mixedDoclet.mixes) {
- mixedDoclet.mixes = [mixedLongname];
- }
- else {
- // find the short name of the longname we're mixing in
- mixedName = name.shorten(mixedLongname).name;
- // find the short name of each previously mixed-in symbol
- // TODO: why do we run a map if we always shorten the same value? this looks like a bug...
- names = mixedDoclet.mixes.map(() => name.shorten(mixedDoclet.longname).name);
- // if we're mixing `myMethod` into `MixinC` from `MixinB`, and `MixinB` had the method mixed
- // in from `MixinA`, don't show `MixinA.myMethod` in the `mixes` list
- idx = names.indexOf(mixedName);
- if (idx !== -1) {
- mixedDoclet.mixes.splice(idx, 1);
- }
- mixedDoclet.mixes.push(mixedLongname);
- }
- }
- // TODO: try to reduce overlap with similar methods
- function getMixedInAdditions(mixinDoclets, allDoclets, {documented, memberof}) {
- let additionIndexes;
- const additions = [];
- const commentedDoclets = documented;
- let doclet;
- let mixedDoclet;
- let mixedDoclets;
- let mixes;
- // mixinDoclets will be undefined if the mixed-in symbol isn't documented
- mixinDoclets = mixinDoclets || [];
- for (let i = 0, ii = mixinDoclets.length; i < ii; i++) {
- doclet = mixinDoclets[i];
- mixes = doclet.mixes;
- if (mixes) {
- // reset the lookup table of added doclet indexes by longname
- additionIndexes = {};
- for (let j = 0, jj = mixes.length; j < jj; j++) {
- mixedDoclets = getMembers(mixes[j], allDoclets, ['static']);
- for (let k = 0, kk = mixedDoclets.length; k < kk; k++) {
- // We only care about symbols that are documented.
- if (mixedDoclets[k].undocumented) {
- continue;
- }
- mixedDoclet = doop(mixedDoclets[k]);
- updateMixes(mixedDoclet, mixedDoclet.longname);
- mixedDoclet.mixed = true;
- reparentDoclet(doclet, mixedDoclet);
- // if we're mixing into a class, treat the mixed-in symbol as an instance member
- if (parentIsClass(doclet)) {
- staticToInstance(mixedDoclet);
- }
- updateAddedDoclets(mixedDoclet, additions, additionIndexes);
- updateDocumentedDoclets(mixedDoclet, commentedDoclets);
- updateMemberofDoclets(mixedDoclet, memberof);
- }
- }
- }
- }
- return additions;
- }
- function updateImplements(implDoclets, implementedLongname) {
- if ( !Array.isArray(implDoclets) ) {
- implDoclets = [implDoclets];
- }
- implDoclets.forEach(implDoclet => {
- if ( !hasOwnProp.call(implDoclet, 'implements') ) {
- implDoclet.implements = [];
- }
- if (!implDoclet.implements.includes(implementedLongname)) {
- implDoclet.implements.push(implementedLongname);
- }
- });
- }
- // TODO: try to reduce overlap with similar methods
- function getImplementedAdditions(implDoclets, allDoclets, {documented, memberof}) {
- let additionIndexes;
- const additions = [];
- let childDoclet;
- let childLongname;
- const commentedDoclets = documented;
- let doclet;
- let implementations;
- let implExists;
- let implementationDoclet;
- let interfaceDoclets;
- let parentDoclet;
- // interfaceDoclets will be undefined if the implemented symbol isn't documented
- implDoclets = implDoclets || [];
- for (let i = 0, ii = implDoclets.length; i < ii; i++) {
- doclet = implDoclets[i];
- implementations = doclet.implements;
- if (implementations) {
- // reset the lookup table of added doclet indexes by longname
- additionIndexes = {};
- for (let j = 0, jj = implementations.length; j < jj; j++) {
- interfaceDoclets = getMembers(implementations[j], allDoclets, ['instance']);
- for (let k = 0, kk = interfaceDoclets.length; k < kk; k++) {
- parentDoclet = interfaceDoclets[k];
- // We only care about symbols that are documented.
- if (parentDoclet.undocumented) {
- continue;
- }
- childLongname = changeMemberof(parentDoclet.longname, doclet.longname);
- childDoclet = getDocumentedLongname(childLongname, allDoclets) || {};
- // We don't want to fold in properties from the child doclet if it had an
- // `@inheritdoc` tag.
- if (hasOwnProp.call(childDoclet, 'inheritdoc')) {
- childDoclet = {};
- }
- implementationDoclet = jsdoc.doclet.combine(childDoclet, parentDoclet);
- reparentDoclet(doclet, implementationDoclet);
- updateImplements(implementationDoclet, parentDoclet.longname);
- // If there's no implementation, move along.
- implExists = hasOwnProp.call(allDoclets.index.longname,
- implementationDoclet.longname);
- if (!implExists) {
- continue;
- }
- // Add the interface's docs unless the implementation is already documented.
- if ( !hasOwnProp.call(commentedDoclets, implementationDoclet.longname) ) {
- updateAddedDoclets(implementationDoclet, additions, additionIndexes);
- updateDocumentedDoclets(implementationDoclet, commentedDoclets);
- updateMemberofDoclets(implementationDoclet, memberof);
- }
- // If the implementation used an @inheritdoc or @override tag, add the
- // interface's docs, and ignore the existing doclets.
- else if ( explicitlyInherits(commentedDoclets[implementationDoclet.longname]) ) {
- // Ignore any existing doclets. (This is safe because we only get here if
- // `implementationDoclet.longname` is an own property of
- // `commentedDoclets`.)
- addDocletProperty(commentedDoclets[implementationDoclet.longname], 'ignore',
- true);
- updateAddedDoclets(implementationDoclet, additions, additionIndexes);
- updateDocumentedDoclets(implementationDoclet, commentedDoclets);
- updateMemberofDoclets(implementationDoclet, memberof);
- // Remove property that's no longer accurate.
- if (implementationDoclet.virtual) {
- delete implementationDoclet.virtual;
- }
- // Remove properties that we no longer need.
- if (implementationDoclet.inheritdoc) {
- delete implementationDoclet.inheritdoc;
- }
- if (implementationDoclet.override) {
- delete implementationDoclet.override;
- }
- }
- // If there's an implementation, and it's documented, update the doclets to
- // indicate what the implementation is implementing.
- else {
- updateImplements(commentedDoclets[implementationDoclet.longname],
- parentDoclet.longname);
- }
- }
- }
- }
- }
- return additions;
- }
- function augment(doclets, propertyName, docletFinder) {
- const index = doclets.index.longname;
- const dependencies = sort( mapDependencies(index, propertyName) );
- dependencies.forEach(depName => {
- const additions = docletFinder(index[depName], doclets, doclets.index);
- additions.forEach(addition => {
- const longname = addition.longname;
- if ( !hasOwnProp.call(index, longname) ) {
- index[longname] = [];
- }
- index[longname].push(addition);
- doclets.push(addition);
- });
- });
- }
- /**
- * Add doclets to reflect class inheritance.
- *
- * For example, if `ClassA` has the instance method `myMethod`, and `ClassB` inherits from `ClassA`,
- * calling this method creates a new doclet for `ClassB#myMethod`.
- *
- * @param {!Array.<module:jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
- * @param {!Object} doclets.index - The doclet index.
- * @return {void}
- */
- exports.addInherited = doclets => {
- augment(doclets, 'augments', getInheritedAdditions);
- };
- /**
- * Add doclets to reflect mixins. When a symbol is mixed into a class, the class' version of the
- * mixed-in symbol is treated as an instance member.
- *
- * For example:
- *
- * + If `MixinA` has the static method `myMethod`, and `MixinB` mixes `MixinA`, calling this method
- * creates a new doclet for the static method `MixinB.myMethod`.
- * + If `MixinA` has the static method `myMethod`, and `ClassA` mixes `MixinA`, calling this method
- * creates a new doclet for the instance method `ClassA#myMethod`.
- *
- * @param {!Array.<module:jsdoc/doclet.Doclet>} doclets - The doclets generated by JSDoc.
- * @param {!Object} doclets.index - The doclet index.
- * @return {void}
- */
- exports.addMixedIn = doclets => {
- augment(doclets, 'mixes', getMixedInAdditions);
- };
- /**
- * Add and update doclets to reflect implementations of interfaces.
- *
- * For example, if `InterfaceA` has the instance method `myMethod`, and `ClassA` implements
- * `InterfaceA`, calling this method does the following:
- *
- * + Updates `InterfaceA` to indicate that it is implemented by `ClassA`
- * + Updates `InterfaceA#myMethod` to indicate that it is implemented by `ClassA#myMethod`
- * + Updates `ClassA#myMethod` to indicate that it implements `InterfaceA#myMethod`
- *
- * If `ClassA#myMethod` used the `@override` or `@inheritdoc` tag, calling this method would also
- * generate a new doclet that reflects the interface's documentation for `InterfaceA#myMethod`.
- *
- * @param {!Array.<module:jsdoc/doclet.Doclet>} docs - The doclets generated by JSDoc.
- * @param {!Object} doclets.index - The doclet index.
- * @return {void}
- */
- exports.addImplemented = doclets => {
- augment(doclets, 'implements', getImplementedAdditions);
- };
- /**
- * Add and update doclets to reflect all of the following:
- *
- * + Inherited classes
- * + Mixins
- * + Interface implementations
- *
- * Calling this method is equivalent to calling all other methods exported by this module.
- *
- * @return {void}
- */
- exports.augmentAll = doclets => {
- exports.addMixedIn(doclets);
- exports.addImplemented(doclets);
- exports.addInherited(doclets);
- // look for implemented doclets again, in case we inherited an interface
- exports.addImplemented(doclets);
- };
|