Improved detection of outer/inner scoped variables.

This commit is contained in:
Michael Mathews 2011-02-10 23:23:12 +00:00
parent 228835ec1f
commit e4852bcb15
11 changed files with 145 additions and 22 deletions

View File

@ -160,6 +160,12 @@
/** The type of the symbol in the source code. */
this.meta.code.type = meta.code.type;
}
if (meta.code.node) {
this.meta.code.node = meta.code.node;
}
if (meta.code.funcscope) {
this.meta.code.funcscope = meta.code.funcscope;
}
if (meta.code.value) {
/** The value of the symbol in the source code. */
this.meta.code.value = meta.code.value;

View File

@ -47,6 +47,15 @@
doclet.name = about.name;
}
if (doclet.name && !memberof) {
if (doclet.meta.code && doclet.meta.code.funcscope) {
about.memberof = doclet.meta.code.funcscope;
doclet.longname = about.memberof + '~' + name;
about.scope = '~';
}
}
if (about.memberof) {
doclet.setMemberof(about.memberof);
}

View File

@ -125,7 +125,7 @@
memberof.id = 'astnode'+astnode.enclosingFunction.hashCode();
memberof.doclet = this.refs[memberof.id];
if (!memberof.doclet) {
return '[[anonymous]]~';
return '<anonymous>~';
}
return (memberof.doclet.longname||memberof.doclet.name) + '~';
}
@ -138,7 +138,6 @@
}
}
/**
Resolve what "this" refers too, relative to a node
*/
@ -150,7 +149,7 @@
memberof.doclet = this.refs[memberof.id];
if (!memberof.doclet) {
return '[[anonymous]]'; // TODO handle global this?
return '<anonymous>'; // TODO handle global this?
}
if (memberof.doclet['this']) {
@ -165,15 +164,13 @@
return memberof.doclet.longname||memberof.doclet.name;
}
else {
if (memberof.doclet.meta.code.val){
return this.resolveThis(memberof.doclet.meta.code.val);
if (astnode.enclosingFunction){
return this.resolveThis(astnode.enclosingFunction/*memberof.doclet.meta.code.val*/);
}
else return ''; // TODO handle global this?
}
}
else if (astnode.parent) {
//print('member of something that isnt a function? '+'astnode'+astnode.parent.parent.hashCode()+' ('+nodeToString(astnode.parent.parent)+')');
var parent = astnode.parent;
if (parent.type === Token.COLON) parent = parent.parent; // go up one more
@ -189,6 +186,37 @@
}
}
/**
Resolve what function a var is limited to.
@param {astnode} node
@param {string} basename The leftmost name in the long name: in foo.bar.zip the basename is foo.
*/
Parser.prototype.resolveVar = function(node, basename) {
var memberof = {};
if (node.enclosingFunction) {
memberof.id = 'astnode'+node.enclosingFunction.hashCode();
memberof.doclet = this.refs[memberof.id];
if (!memberof.doclet) {
return this.resolveVar(node.enclosingFunction, basename);
}
if (memberof.doclet.vars && ~memberof.doclet.vars.indexOf(basename) ) {
return memberof.doclet.longname;
}
else {
if (memberof.doclet.meta.code.node){
return this.resolveVar(memberof.doclet.meta.code.node, basename);
}
else return ''; // TODO handle global this?
}
}
else {
return ''; // global?
}
}
/**
@private
*/
@ -228,12 +256,15 @@
code: aboutNode(node)
};
var basename = e.code.name.replace(/^([$a-z_][$a-z_0-9]*).*?$/i, '$1');
if (basename !== 'this') e.code.funcscope = currentParser.resolveVar(node, basename);
if ( isValidJsdoc(e.comment) ) {
currentParser.fire('symbolFound', e, currentParser);
}
if (e.doclet) {
currentParser.refs['astnode'+e.code.val.hashCode()] = e.doclet; // allow lookup from value => doclet
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
}
}
else if (node.type === Token.COLON) {
@ -251,7 +282,7 @@
}
if (e.doclet) {
currentParser.refs['astnode'+e.code.val.hashCode()] = e.doclet; // allow lookup from value => doclet
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
}
}
else if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
@ -274,12 +305,23 @@
code: aboutNode(node)
};
// keep track of vars in a function scope
if (node.enclosingFunction) {
var func = 'astnode'+node.enclosingFunction.hashCode(),
funcDoc = currentParser.refs[func];
if (funcDoc) {
funcDoc.vars = func.vars || [];
funcDoc.vars.push(e.code.name);
}
}
if ( isValidJsdoc(e.comment) ) {
currentParser.fire('symbolFound', e, currentParser);
}
if (e.doclet) {
currentParser.refs['astnode'+e.code.val.hashCode()] = e.doclet; // allow lookup from value => doclet
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
}
}
else if (node.type == Token.FUNCTION/* && String(node.name) !== ''*/) {
@ -297,9 +339,17 @@
if ( isValidJsdoc(e.comment) ) {
currentParser.fire('symbolFound', e, currentParser);
}
if (e.doclet) {
currentParser.refs['astnode'+node.hashCode()] = e.doclet; // allow lookup from value => doclet
//dump(e.code.node.hashCode(), e.code, e.code.node.enclosingFunction? e.code.node.enclosingFunction.hashCode() : 'global')
currentParser.refs['astnode'+e.code.node.hashCode()] = e.doclet; // allow lookup from value => doclet
}
else if (!currentParser.refs['astnode'+e.code.node.hashCode()]) { // keep references to undocumented anonymous functions too as they might have scoped vars
currentParser.refs['astnode'+e.code.node.hashCode()] = {
//name: '<anonymous>',
longname: '<anonymous>',
meta: { code: e.code }
};
}
}
@ -331,6 +381,7 @@
if (node.type == Token.FUNCTION /*&& String(node.name) !== ''*/) {
about.name = '' + node.name;
about.type = 'function';
about.node = node;
return about;
}
@ -338,13 +389,13 @@
if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
about.name = nodeToString(node.target);
if (node.initializer) { // like var i = 0;
about.val = node.initializer;
about.value = nodeToString(about.val);
about.node = node.initializer;
about.value = nodeToString(about.node);
about.type = getTypeName(node.initializer);
}
else { // like var i;
about.val = node.target;
about.value = nodeToString(about.val);
about.node = node.target;
about.value = nodeToString(about.node);
about.type = 'undefined';
}
@ -353,8 +404,8 @@
if (node.type === Token.ASSIGN || node.type === Token.COLON) {
about.name = nodeToString(node.left);
about.val = node.right;
about.value = nodeToString(about.val);
about.node = node.right;
about.value = nodeToString(about.node);
about.type = getTypeName(node.right);
return about;
}

14
test/cases/innerscope.js Normal file
View File

@ -0,0 +1,14 @@
/** @constructor */
function Message(to) {
var headers = {};
/** document me */
headers.to = to;
(function() {
/** document me */
headers.from = '';
})()
}

16
test/cases/innerscope2.js Normal file
View File

@ -0,0 +1,16 @@
/** @constructor */
function Message(to) {
var headers = {};
/** document me */
headers.to = to;
(function() {
var headers = {};
/** document me */
headers.from = '';
})()
}

View File

@ -7,5 +7,4 @@ function Singer() {
/** document me */
this.isSinging = true; // setting a member of constructor Singer
};
}

View File

@ -1,7 +1,7 @@
/** @constructor */
function TemplateBuilder(templateType) {
/** document me */
this.templateType = templateType;
//** document me */
//this.templateType = templateType;
/** @constructor */
this.Template = function() { // nested constructor of constructor TemplateFactory

View File

@ -82,6 +82,8 @@ testFile('test/t/cases/this-and-objectlit.js');
testFile('test/t/cases/var.js');
testFile('test/t/cases/inner.js');
testFile('test/t/cases/innerscope.js');
testFile('test/t/cases/innerscope2.js');
testFile('test/t/cases/modules/data/mod-1.js');
testFile('test/t/cases/modules/data/mod-2.js');

View File

@ -0,0 +1,15 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/innerscope.js'),
to = docSet.getByLongname('Message~headers.to'),
from = docSet.getByLongname('Message~headers.from');
//dump(docSet);
test('When a member of a var member is documented.', function() {
assert.equal(to.length, 1, 'It is like Outer~inner.member.');
});
test('When a deeply nested member of a var member is documented.', function() {
assert.equal(from.length, 1, 'It is still like Outer~inner.member.');
});
})();

View File

@ -0,0 +1,11 @@
(function() {
var docSet = testhelpers.getDocSetFromFile('test/cases/innerscope2.js'),
to = docSet.getByLongname('Message~headers.to'),
from = docSet.getByLongname('<anonymous>~headers.from');
//dump(docSet);
test('When a var is masked by an inner var and a member of the inner is documented.', function() {
assert.equal(from.length, 1, 'It is still like Inner~inner.member.');
});
})();

View File

@ -3,7 +3,7 @@
found1 = docSet.getByLongname('Singer#tralala'),
found2 = docSet.getByLongname('Singer#isSinging');
//dump(docSet);
// dump(docSet);
test('When a member is attached to this in a constructor.', function() {
assert.equal(found1.length, 1, 'The longname should be like Constructor#member.');