use the correct longname for properties documented in instance methods (#1011)

Fixes a regression caused by 65f307322ac64d965694dca8f82bf98f08a1e2e4.
This commit is contained in:
Jeff Williams 2015-08-18 15:34:24 -07:00
parent 6705b964a2
commit 087f07dcd5
5 changed files with 117 additions and 45 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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
View File

@ -0,0 +1,8 @@
/** @class */
function Foo() {}
/** Set the bar. */
Foo.prototype.setBar = function(bar) {
/** The bar. */
this.bar = bar;
};

View 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');
});
});