mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
fix closures with multiple lends tags (#569)
There were three separate problems here: 1. The visitor called `trackVars` at the wrong time for `AssignmentExpression` and `VariableDeclarator` nodes, which prevented JSDoc from setting the `funcscope` property correctly. 2. The `funcscope` property was being added to `VariableDeclarator` nodes. It should only be added to `AssignmentExpression` nodes. 3. We were trying to resolve the variable name `____` in `AssignmentExpression` nodes. This is a special value we add to the source code so that the `lends` tag will work, and it should never be resolved against the enclosing scope. The previous, buggy behavior looked reasonable in most cases, but it didn't work for closures that contain multiple `lends` tags.
This commit is contained in:
parent
c6fd086f4b
commit
3b5db81d92
@ -229,6 +229,7 @@ Visitor.prototype.visitNode = function(node, parser, filename) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// TODO: docs
|
// TODO: docs
|
||||||
|
// TODO: note that it's essential to call this function before you try to resolve names!
|
||||||
// TODO: may be able to get rid of this using knownAliases
|
// TODO: may be able to get rid of this using knownAliases
|
||||||
function trackVars(parser, node, e) {
|
function trackVars(parser, node, e) {
|
||||||
var enclosingScopeId = node.enclosingScope ? node.enclosingScope.nodeId :
|
var enclosingScopeId = node.enclosingScope ? node.enclosingScope.nodeId :
|
||||||
@ -256,20 +257,17 @@ Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) {
|
|||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
// like: i = 0;
|
// like: i = 0;
|
||||||
case Syntax.AssignmentExpression:
|
case Syntax.AssignmentExpression:
|
||||||
// falls through
|
|
||||||
|
|
||||||
// like: var i = 0;
|
|
||||||
case Syntax.VariableDeclarator:
|
|
||||||
e = new SymbolFound(node, filename, extras);
|
e = new SymbolFound(node, filename, extras);
|
||||||
|
|
||||||
|
trackVars(parser, node, e);
|
||||||
|
|
||||||
basename = parser.getBasename(e.code.name);
|
basename = parser.getBasename(e.code.name);
|
||||||
// TODO: handle code that does things like 'var self = this';
|
// in addition to `this`, we need to special-case the string `____`, or else our
|
||||||
if (basename !== 'this') {
|
// `@lends` tag hackery causes things to appear in the wrong scope
|
||||||
|
if (basename !== 'this' && basename !== '____') {
|
||||||
e.code.funcscope = parser.resolveVar(node, basename);
|
e.code.funcscope = parser.resolveVar(node, basename);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackVars(parser, node, e);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// like: function foo() {}
|
// like: function foo() {}
|
||||||
@ -308,6 +306,16 @@ Visitor.prototype.makeSymbolFoundEvent = function(node, parser, filename) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// like: var i = 0;
|
||||||
|
case Syntax.VariableDeclarator:
|
||||||
|
e = new SymbolFound(node, filename, extras);
|
||||||
|
|
||||||
|
trackVars(parser, node, e);
|
||||||
|
|
||||||
|
basename = parser.getBasename(e.code.name);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|||||||
29
test/fixtures/lends6.js
vendored
Normal file
29
test/fixtures/lends6.js
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
define([], function() {
|
||||||
|
var Person = makeClass(
|
||||||
|
/** @lends Person.prototype */
|
||||||
|
{
|
||||||
|
/** @constructs */
|
||||||
|
initialize: function(name) {
|
||||||
|
this.name = name;
|
||||||
|
},
|
||||||
|
/** Speak a message. */
|
||||||
|
say: function(message) {
|
||||||
|
return this.name + " says: " + message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var Robot = makeClass(
|
||||||
|
/** @lends Robot.prototype */
|
||||||
|
{
|
||||||
|
/** @constructs */
|
||||||
|
initialize: function(name) {
|
||||||
|
this.name = name;
|
||||||
|
},
|
||||||
|
/** Feign emotion. */
|
||||||
|
emote: function() {
|
||||||
|
return this.name + " loves you!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -1,6 +1,10 @@
|
|||||||
/*global describe, expect, it, jasmine */
|
/*global describe, expect, it, jasmine */
|
||||||
describe("lends", function() {
|
describe("lends", function() {
|
||||||
describe("when a documented member is inside an object literal associated with a @lends tag", function() {
|
describe("when a documented member is inside an object literal associated with a @lends tag", function() {
|
||||||
|
function removeUndocumented($) {
|
||||||
|
return !$.undocumented;
|
||||||
|
}
|
||||||
|
|
||||||
describe("standard case", function() {
|
describe("standard case", function() {
|
||||||
var docSet = jasmine.getDocSetFromFile('test/fixtures/lends.js'),
|
var docSet = jasmine.getDocSetFromFile('test/fixtures/lends.js'),
|
||||||
init = docSet.getByLongname('Person#initialize'),
|
init = docSet.getByLongname('Person#initialize'),
|
||||||
@ -67,12 +71,8 @@ describe("lends", function() {
|
|||||||
|
|
||||||
describe("case that uses @lends within nested function calls", function() {
|
describe("case that uses @lends within nested function calls", function() {
|
||||||
var docSet = jasmine.getDocSetFromFile('test/fixtures/lends5.js');
|
var docSet = jasmine.getDocSetFromFile('test/fixtures/lends5.js');
|
||||||
var person = docSet.getByLongname('Person').filter(function(d) {
|
var person = docSet.getByLongname('Person').filter(removeUndocumented)[0];
|
||||||
return !d.undocumented;
|
var say = docSet.getByLongname('Person#say').filter(removeUndocumented)[0];
|
||||||
})[0];
|
|
||||||
var say = docSet.getByLongname('Person#say').filter(function(d) {
|
|
||||||
return !d.undocumented;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
it("The class constructor should be documented with the name of the lendee", function() {
|
it("The class constructor should be documented with the name of the lendee", function() {
|
||||||
expect(person).toBeDefined();
|
expect(person).toBeDefined();
|
||||||
@ -84,6 +84,36 @@ describe("lends", function() {
|
|||||||
expect(say).toBeDefined();
|
expect(say).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('case that uses @lends twice within a closure', function() {
|
||||||
|
var docSet = jasmine.getDocSetFromFile('test/fixtures/lends6.js');
|
||||||
|
|
||||||
|
it('The first class with a @lends tag should appear in the parse results', function() {
|
||||||
|
var person = docSet.getByLongname('Person').filter(removeUndocumented)[0];
|
||||||
|
var say = docSet.getByLongname('Person#say').filter(removeUndocumented)[0];
|
||||||
|
|
||||||
|
expect(person).toBeDefined();
|
||||||
|
expect(person.name).toBe('Person');
|
||||||
|
expect(person.kind).toBe('class');
|
||||||
|
|
||||||
|
expect(say).toBeDefined();
|
||||||
|
expect(say.name).toBe('say');
|
||||||
|
expect(say.kind).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The second class with a @lends tag should appear in the parse results', function() {
|
||||||
|
var robot = docSet.getByLongname('Robot').filter(removeUndocumented)[0];
|
||||||
|
var emote = docSet.getByLongname('Robot#emote').filter(removeUndocumented)[0];
|
||||||
|
|
||||||
|
expect(robot).toBeDefined();
|
||||||
|
expect(robot.name).toBe('Robot');
|
||||||
|
expect(robot.kind).toBe('class');
|
||||||
|
|
||||||
|
expect(emote).toBeDefined();
|
||||||
|
expect(emote.name).toBe('emote');
|
||||||
|
expect(emote.kind).toBe('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when a documented member is inside an objlit associated with a @lends tag that has no value.", function() {
|
describe("when a documented member is inside an objlit associated with a @lends tag that has no value.", function() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user