mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
use the correct longname for properties documented in instance methods (#1011)
Fixes a regression caused by 65f307322ac64d965694dca8f82bf98f08a1e2e4.
This commit is contained in:
parent
6705b964a2
commit
087f07dcd5
@ -264,7 +264,6 @@ function removeGlobal(longname) {
|
||||
Doclet.prototype.setMemberof = function(sid) {
|
||||
/**
|
||||
* The longname of the symbol that contains this one, if any.
|
||||
* @alias module:jsdoc/doclet.Doclet#memberof
|
||||
* @type {string}
|
||||
*/
|
||||
this.memberof = removeGlobal(sid)
|
||||
@ -279,7 +278,6 @@ Doclet.prototype.setMemberof = function(sid) {
|
||||
Doclet.prototype.setLongname = function(name) {
|
||||
/**
|
||||
* The fully resolved symbol name.
|
||||
* @alias module:jsdoc/doclet.Doclet#longname
|
||||
* @type {string}
|
||||
*/
|
||||
this.longname = removeGlobal(name);
|
||||
@ -347,7 +345,6 @@ Doclet.prototype.borrow = function(source, target) {
|
||||
if (!this.borrowed) {
|
||||
/**
|
||||
* A list of symbols that are borrowed by this one, if any.
|
||||
* @alias module:jsdoc/doclet.Doclet#borrowed
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
this.borrowed = [];
|
||||
@ -358,7 +355,6 @@ Doclet.prototype.borrow = function(source, target) {
|
||||
Doclet.prototype.mix = function(source) {
|
||||
/**
|
||||
* A list of symbols that are mixed into this one, if any.
|
||||
* @alias module:jsdoc/doclet.Doclet#mixes
|
||||
* @type Array.<string>
|
||||
*/
|
||||
this.mixes = this.mixes || [];
|
||||
@ -373,7 +369,6 @@ Doclet.prototype.mix = function(source) {
|
||||
Doclet.prototype.augment = function(base) {
|
||||
/**
|
||||
* A list of symbols that are augmented by this one, if any.
|
||||
* @alias module:jsdoc/doclet.Doclet#augments
|
||||
* @type Array.<string>
|
||||
*/
|
||||
this.augments = this.augments || [];
|
||||
@ -388,7 +383,6 @@ Doclet.prototype.augment = function(base) {
|
||||
Doclet.prototype.setMeta = function(meta) {
|
||||
/**
|
||||
* Information about the source code associated with this doclet.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta
|
||||
* @namespace
|
||||
*/
|
||||
this.meta = this.meta || {};
|
||||
@ -396,7 +390,6 @@ Doclet.prototype.setMeta = function(meta) {
|
||||
if (meta.range) {
|
||||
/**
|
||||
* The positions of the first and last characters of the code associated with this doclet.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta.range
|
||||
* @type Array.<number>
|
||||
*/
|
||||
this.meta.range = meta.range.slice(0);
|
||||
@ -405,13 +398,11 @@ Doclet.prototype.setMeta = function(meta) {
|
||||
if (meta.lineno) {
|
||||
/**
|
||||
* The name of the file containing the code associated with this doclet.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta.filename
|
||||
* @type string
|
||||
*/
|
||||
this.meta.filename = path.basename(meta.filename);
|
||||
/**
|
||||
* The line number of the code associated with this doclet.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta.lineno
|
||||
* @type number
|
||||
*/
|
||||
this.meta.lineno = meta.lineno;
|
||||
@ -424,7 +415,6 @@ Doclet.prototype.setMeta = function(meta) {
|
||||
|
||||
/**
|
||||
* Information about the code symbol.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta.code
|
||||
* @namespace
|
||||
*/
|
||||
this.meta.code = this.meta.code || {};
|
||||
@ -433,7 +423,6 @@ Doclet.prototype.setMeta = function(meta) {
|
||||
if (meta.code.name) {
|
||||
/**
|
||||
* The name of the symbol in the source code.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta.code.name
|
||||
* @type {string}
|
||||
*/
|
||||
this.meta.code.name = meta.code.name;
|
||||
@ -441,7 +430,6 @@ Doclet.prototype.setMeta = function(meta) {
|
||||
if (meta.code.type) {
|
||||
/**
|
||||
* The type of the symbol in the source code.
|
||||
* @alias module:jsdoc/doclet.Doclet#
|
||||
* @type {string}
|
||||
*/
|
||||
this.meta.code.type = meta.code.type;
|
||||
@ -458,7 +446,6 @@ Doclet.prototype.setMeta = function(meta) {
|
||||
if (typeof meta.code.value !== 'undefined') {
|
||||
/**
|
||||
* The value of the symbol in the source code.
|
||||
* @alias module:jsdoc/doclet.Doclet#meta.code.value
|
||||
* @type {*}
|
||||
*/
|
||||
this.meta.code.value = meta.code.value;
|
||||
|
||||
@ -35,6 +35,27 @@ var PARSERS = exports.PARSERS = {
|
||||
var SCHEMA = 'javascript:';
|
||||
/* eslint-enable no-script-url */
|
||||
|
||||
function DocletCache() {
|
||||
this._doclets = {};
|
||||
}
|
||||
|
||||
DocletCache.prototype.get = function(name) {
|
||||
if ( !hasOwnProp.call(this._doclets, name) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// always return the most recent doclet
|
||||
return this._doclets[name][this._doclets[name].length - 1];
|
||||
};
|
||||
|
||||
DocletCache.prototype.put = function(name, value) {
|
||||
if ( !hasOwnProp.call(this._doclets, name) ) {
|
||||
this._doclets[name] = [];
|
||||
}
|
||||
|
||||
this._doclets[name].push(value);
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
exports.createParser = function(type) {
|
||||
var modulePath;
|
||||
@ -96,9 +117,11 @@ util.inherits(Parser, events.EventEmitter);
|
||||
// TODO: docs
|
||||
Parser.prototype.clear = function() {
|
||||
this._resultBuffer = [];
|
||||
this.refs = {};
|
||||
this.refs[jsdoc.name.LONGNAMES.GLOBAL] = {};
|
||||
this.refs[jsdoc.name.LONGNAMES.GLOBAL].meta = {};
|
||||
this._byNodeId = new DocletCache();
|
||||
this._byLongname = new DocletCache();
|
||||
this._byLongname.put(jsdoc.name.LONGNAMES.GLOBAL, {
|
||||
meta: {}
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: update docs
|
||||
@ -247,35 +270,45 @@ Parser.prototype._walkAst = function(ast, visitor, sourceName) {
|
||||
|
||||
// TODO: docs
|
||||
Parser.prototype.addDocletRef = function(e) {
|
||||
var fakeDoclet;
|
||||
var node;
|
||||
|
||||
if (e && e.code && e.code.node) {
|
||||
node = e.code.node;
|
||||
// allow lookup from value => doclet
|
||||
if (e.doclet) {
|
||||
this.refs[node.nodeId] = e.doclet;
|
||||
// allow lookup from node ID => doclet
|
||||
this._byNodeId.put(node.nodeId, e.doclet);
|
||||
this._byLongname.put(e.doclet.longname, e.doclet);
|
||||
}
|
||||
// keep references to undocumented anonymous functions, too, as they might have scoped vars
|
||||
else if (
|
||||
(node.type === Syntax.FunctionDeclaration || node.type === Syntax.FunctionExpression) &&
|
||||
!this.refs[node.nodeId] ) {
|
||||
this.refs[node.nodeId] = {
|
||||
!this._getDocletById(node.nodeId) ) {
|
||||
fakeDoclet = {
|
||||
longname: jsdoc.name.LONGNAMES.ANONYMOUS,
|
||||
meta: {
|
||||
code: e.code
|
||||
}
|
||||
};
|
||||
this._byNodeId.put(node.nodeId, fakeDoclet);
|
||||
this._byLongname.put(fakeDoclet.longname, fakeDoclet);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
Parser.prototype._getDoclet = function(id) {
|
||||
if ( hasOwnProp.call(this.refs, id) ) {
|
||||
return this.refs[id];
|
||||
}
|
||||
Parser.prototype._getDocletById = function(id) {
|
||||
return this._byNodeId.get(id);
|
||||
};
|
||||
|
||||
return null;
|
||||
/**
|
||||
* Retrieve the most recently seen doclet that has the given longname.
|
||||
*
|
||||
* @param {string} longname - The longname to search for.
|
||||
* @return {module:jsdoc/doclet.Doclet?} The most recent doclet for the longname.
|
||||
*/
|
||||
Parser.prototype._getDocletByLongname = function(longname) {
|
||||
return this._byLongname.get(longname);
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
@ -311,7 +344,7 @@ Parser.prototype.astnodeToMemberof = function(node) {
|
||||
|
||||
if ( (type === Syntax.FunctionDeclaration || type === Syntax.FunctionExpression ||
|
||||
type === Syntax.VariableDeclarator) && node.enclosingScope ) {
|
||||
doclet = this._getDoclet(node.enclosingScope.nodeId);
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
result = jsdoc.name.LONGNAMES.ANONYMOUS + jsdoc.name.SCOPE.PUNC.INNER;
|
||||
@ -327,7 +360,7 @@ Parser.prototype.astnodeToMemberof = function(node) {
|
||||
|
||||
// walk up the scope chain until we find the scope in which the node is defined
|
||||
while (scope.enclosingScope) {
|
||||
doclet = this._getDoclet(scope.enclosingScope.nodeId);
|
||||
doclet = this._getDocletById(scope.enclosingScope.nodeId);
|
||||
if ( doclet && definedInScope(doclet, basename) ) {
|
||||
result = [doclet.meta.vars[basename], basename];
|
||||
break;
|
||||
@ -339,13 +372,12 @@ Parser.prototype.astnodeToMemberof = function(node) {
|
||||
}
|
||||
|
||||
// do we know that it's a global?
|
||||
doclet = this.refs[jsdoc.name.LONGNAMES.GLOBAL];
|
||||
|
||||
doclet = this._getDocletByLongname(jsdoc.name.LONGNAMES.GLOBAL);
|
||||
if ( doclet && definedInScope(doclet, basename) ) {
|
||||
result = [doclet.meta.vars[basename], basename];
|
||||
}
|
||||
else {
|
||||
doclet = this._getDoclet(node.parent.nodeId);
|
||||
doclet = this._getDocletById(node.parent.nodeId);
|
||||
|
||||
// set the result if we found a doclet. (if we didn't, the AST node may describe a
|
||||
// global symbol.)
|
||||
@ -367,13 +399,39 @@ Parser.prototype.astnodeToMemberof = function(node) {
|
||||
*/
|
||||
Parser.prototype._getParentClass = function(node) {
|
||||
var doclet;
|
||||
var nameAtoms;
|
||||
var scope = node.enclosingScope;
|
||||
|
||||
do {
|
||||
doclet = this._getDoclet(node.enclosingScope.nodeId);
|
||||
node = node.enclosingScope;
|
||||
} while (node && node.enclosingScope && doclet && doclet.kind !== 'class');
|
||||
function isClass(d) {
|
||||
return d && d.kind === 'class';
|
||||
}
|
||||
|
||||
return (doclet.kind === 'class' ? doclet : null);
|
||||
while (scope) {
|
||||
// get the doclet, if any, for the parent scope
|
||||
doclet = this._getDocletById(scope.nodeId);
|
||||
|
||||
if (doclet) {
|
||||
// is the doclet for a class? if so, we're done
|
||||
if ( isClass(doclet) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// is the doclet for an instance member of a class? if so, try to get the doclet for the
|
||||
// owning class
|
||||
nameAtoms = jsdoc.name.shorten(doclet.longname);
|
||||
if (nameAtoms.scope === jsdoc.name.SCOPE.PUNC.INSTANCE) {
|
||||
doclet = this._getDocletByLongname(nameAtoms.memberof);
|
||||
if ( isClass(doclet) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move up to the next parent scope
|
||||
scope = scope.enclosingScope;
|
||||
}
|
||||
|
||||
return (isClass(doclet) ? doclet : null);
|
||||
};
|
||||
|
||||
// TODO: docs
|
||||
@ -390,7 +448,7 @@ Parser.prototype.resolveThis = function(node) {
|
||||
// In general, if there's an enclosing scope, we use the enclosing scope to resolve `this`.
|
||||
// For object properties, we use the node's parent (the object) instead.
|
||||
if (node.type !== Syntax.Property && node.enclosingScope) {
|
||||
doclet = this._getDoclet(node.enclosingScope.nodeId);
|
||||
doclet = this._getDocletById(node.enclosingScope.nodeId);
|
||||
|
||||
if (!doclet) {
|
||||
result = jsdoc.name.LONGNAMES.ANONYMOUS; // TODO handle global this?
|
||||
@ -402,6 +460,7 @@ Parser.prototype.resolveThis = function(node) {
|
||||
parentClass = this._getParentClass(node);
|
||||
|
||||
// like: function Foo() { this.bar = function(n) { /** blah */ this.name = n; };
|
||||
// or: Foo.prototype.bar = function(n) { /** blah */ this.name = n; };
|
||||
// or: var Foo = exports.Foo = function(n) { /** blah */ this.name = n; };
|
||||
// or: Foo.constructor = function(n) { /** blah */ this.name = n; }
|
||||
if ( parentClass || /\.constructor$/.test(doclet.longname) ) {
|
||||
@ -425,7 +484,7 @@ Parser.prototype.resolveThis = function(node) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
doclet = this.refs[node.parent.nodeId];
|
||||
doclet = this._getDocletById(node.parent.nodeId);
|
||||
|
||||
// TODO: is this behavior correct? when do we get here?
|
||||
if (!doclet) {
|
||||
@ -450,7 +509,7 @@ Parser.prototype.resolveThis = function(node) {
|
||||
* this method returns multiple doclets (in this case, the doclets for `foo` and `exports.FOO`).
|
||||
*
|
||||
* @param {Object} node - An AST node representing an object property.
|
||||
* @return {Array.<jsdoc/doclet.Doclet>} An array of doclets for the parent object or objects, or
|
||||
* @return {Array.<module:jsdoc/doclet.Doclet>} An array of doclets for the parent object or objects, or
|
||||
* an empty array if no doclets are found.
|
||||
*/
|
||||
Parser.prototype.resolvePropertyParents = function(node) {
|
||||
@ -460,7 +519,7 @@ Parser.prototype.resolvePropertyParents = function(node) {
|
||||
var doclets = [];
|
||||
|
||||
while (currentAncestor) {
|
||||
doclet = this._getDoclet(currentAncestor.nodeId);
|
||||
doclet = this._getDocletById(currentAncestor.nodeId);
|
||||
if (doclet) {
|
||||
doclets.push(doclet);
|
||||
}
|
||||
@ -500,7 +559,7 @@ Parser.prototype.resolveVar = function(node, basename) {
|
||||
result = ''; // global
|
||||
}
|
||||
else {
|
||||
doclet = this._getDoclet(scope.nodeId);
|
||||
doclet = this._getDocletById(scope.nodeId);
|
||||
if ( definedInScope(doclet, basename) ) {
|
||||
result = doclet.longname;
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ function makeVarsFinisher(scopeDoclet) {
|
||||
function getParentDocletFromEvent(parser, e) {
|
||||
if (e.doclet && e.doclet.meta && e.doclet.meta.code && e.doclet.meta.code.node &&
|
||||
e.doclet.meta.code.node.parent) {
|
||||
return parser._getDoclet(e.doclet.meta.code.node.parent.nodeId);
|
||||
return parser._getDocletById(e.doclet.meta.code.node.parent.nodeId);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -269,7 +269,7 @@ function makeDefaultParamFinisher(parser) {
|
||||
function makeConstructorFinisher(parser) {
|
||||
return function(e) {
|
||||
var doclet = e.doclet;
|
||||
var parentDoclet = parser._getDoclet(e.code.node.parent.parent.nodeId);
|
||||
var parentDoclet = parser._getDocletById(e.code.node.parent.parent.nodeId);
|
||||
|
||||
if (!doclet || !parentDoclet || parentDoclet.undocumented) {
|
||||
return;
|
||||
@ -471,9 +471,15 @@ Visitor.prototype.visitNode = function(node, parser, filename) {
|
||||
// TODO: docs
|
||||
// TODO: note that it's essential to call this function before you try to resolve names!
|
||||
function trackVars(parser, node, e) {
|
||||
var enclosingScopeId = node.enclosingScope ? node.enclosingScope.nodeId :
|
||||
jsdoc.name.LONGNAMES.GLOBAL;
|
||||
var doclet = parser.refs[enclosingScopeId];
|
||||
var doclet;
|
||||
var enclosingScopeId = node.enclosingScope ? node.enclosingScope.nodeId : null;
|
||||
|
||||
if (enclosingScopeId) {
|
||||
doclet = parser._getDocletById(enclosingScopeId);
|
||||
}
|
||||
else {
|
||||
doclet = parser._getDocletByLongname(jsdoc.name.LONGNAMES.GLOBAL);
|
||||
}
|
||||
|
||||
if (doclet) {
|
||||
doclet.meta.vars = doclet.meta.vars || {};
|
||||
|
||||
8
test/fixtures/instanceproperty.js
vendored
Normal file
8
test/fixtures/instanceproperty.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/** @class */
|
||||
function Foo() {}
|
||||
|
||||
/** Set the bar. */
|
||||
Foo.prototype.setBar = function(bar) {
|
||||
/** The bar. */
|
||||
this.bar = bar;
|
||||
};
|
||||
12
test/specs/documentation/instanceproperty.js
Normal file
12
test/specs/documentation/instanceproperty.js
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
describe('Properties documented in instance methods', function() {
|
||||
var docSet = jasmine.getDocSetFromFile('test/fixtures/instanceproperty.js');
|
||||
var bar = docSet.getByLongname('Foo#bar')[0];
|
||||
|
||||
it('should set the correct longname when a property is documented in an instance method', function() {
|
||||
expect(bar).toBeDefined();
|
||||
expect(bar.name).toBe('bar');
|
||||
expect(bar.kind).toBe('member');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user