From e4852bcb159fe4c6e844dc1c3a0f8fb235f0f7ea Mon Sep 17 00:00:00 2001 From: Michael Mathews Date: Thu, 10 Feb 2011 23:23:12 +0000 Subject: [PATCH] Improved detection of outer/inner scoped variables. --- modules/jsdoc/doclet.js | 6 +++ modules/jsdoc/name.js | 9 ++++ modules/jsdoc/src/parser.js | 87 +++++++++++++++++++++++++++++-------- test/cases/innerscope.js | 14 ++++++ test/cases/innerscope2.js | 16 +++++++ test/cases/this.js | 1 - test/cases/this2.js | 4 +- test/runner.js | 2 + test/t/cases/innerscope.js | 15 +++++++ test/t/cases/innerscope2.js | 11 +++++ test/t/cases/this.js | 2 +- 11 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 test/cases/innerscope.js create mode 100644 test/cases/innerscope2.js create mode 100644 test/t/cases/innerscope.js create mode 100644 test/t/cases/innerscope2.js diff --git a/modules/jsdoc/doclet.js b/modules/jsdoc/doclet.js index 28fdd651..94ce356e 100644 --- a/modules/jsdoc/doclet.js +++ b/modules/jsdoc/doclet.js @@ -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; diff --git a/modules/jsdoc/name.js b/modules/jsdoc/name.js index ac4ced0d..324b9634 100644 --- a/modules/jsdoc/name.js +++ b/modules/jsdoc/name.js @@ -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); } diff --git a/modules/jsdoc/src/parser.js b/modules/jsdoc/src/parser.js index 246b94dc..47eb765c 100644 --- a/modules/jsdoc/src/parser.js +++ b/modules/jsdoc/src/parser.js @@ -125,7 +125,7 @@ memberof.id = 'astnode'+astnode.enclosingFunction.hashCode(); memberof.doclet = this.refs[memberof.id]; if (!memberof.doclet) { - return '[[anonymous]]~'; + return '~'; } 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 ''; // 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: '', + longname: '', + 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; } diff --git a/test/cases/innerscope.js b/test/cases/innerscope.js new file mode 100644 index 00000000..6a623f34 --- /dev/null +++ b/test/cases/innerscope.js @@ -0,0 +1,14 @@ +/** @constructor */ +function Message(to) { + + var headers = {}; + + /** document me */ + headers.to = to; + + (function() { + /** document me */ + headers.from = ''; + })() +} + diff --git a/test/cases/innerscope2.js b/test/cases/innerscope2.js new file mode 100644 index 00000000..791bcf37 --- /dev/null +++ b/test/cases/innerscope2.js @@ -0,0 +1,16 @@ +/** @constructor */ +function Message(to) { + + var headers = {}; + + /** document me */ + headers.to = to; + + (function() { + var headers = {}; + + /** document me */ + headers.from = ''; + })() +} + diff --git a/test/cases/this.js b/test/cases/this.js index ff76fdf4..7c167606 100644 --- a/test/cases/this.js +++ b/test/cases/this.js @@ -7,5 +7,4 @@ function Singer() { /** document me */ this.isSinging = true; // setting a member of constructor Singer }; - } \ No newline at end of file diff --git a/test/cases/this2.js b/test/cases/this2.js index 640eb917..88fd9fa6 100644 --- a/test/cases/this2.js +++ b/test/cases/this2.js @@ -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 diff --git a/test/runner.js b/test/runner.js index 4a36488f..d17dfead 100644 --- a/test/runner.js +++ b/test/runner.js @@ -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'); diff --git a/test/t/cases/innerscope.js b/test/t/cases/innerscope.js new file mode 100644 index 00000000..df8ef3ce --- /dev/null +++ b/test/t/cases/innerscope.js @@ -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.'); + }); +})(); \ No newline at end of file diff --git a/test/t/cases/innerscope2.js b/test/t/cases/innerscope2.js new file mode 100644 index 00000000..c13e2aa3 --- /dev/null +++ b/test/t/cases/innerscope2.js @@ -0,0 +1,11 @@ +(function() { + var docSet = testhelpers.getDocSetFromFile('test/cases/innerscope2.js'), + to = docSet.getByLongname('Message~headers.to'), + from = docSet.getByLongname('~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.'); + }); +})(); \ No newline at end of file diff --git a/test/t/cases/this.js b/test/t/cases/this.js index 8e9a9b9c..212907da 100644 --- a/test/t/cases/this.js +++ b/test/t/cases/this.js @@ -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.');