Much work to get correct namepaths calculated when user doesn`t provide @name or @isa.

This commit is contained in:
Michael Mathews 2010-07-11 22:51:30 +01:00
parent 4deae8c570
commit 3220279b30
12 changed files with 416 additions and 53 deletions

View File

@ -50,7 +50,7 @@
postprocess(doclet);
name.resolve(doclet);
return doclet
}
@ -214,6 +214,20 @@
return o;
}
Doclet.prototype.isInner = function() {
var access = this.tagValue('access');
if (!access) {
return false;
}
else if (typeof access === 'string') {
return (access === 'inner');
}
else {
return (access.indexOf('inner') > -1);
}
}
/**
Remove JsDoc comment slash-stars. Trims white space.
@private

View File

@ -31,8 +31,6 @@
path,
shortname,
prefix;
//,
//supportedNamespaces = ['module', 'event', 'file'];
// only keep the first word of the first tagged name
name = name.split(/\s+/g)[0];
@ -121,34 +119,44 @@
Resolve how to document the `this.` portion of a symbol name.
*/
exports.resolveThis = function(name, node, doclet) {
var enclosing,
enclosingDoc,
memberof = (doclet.tagValue('memberof') || '').replace(/\.prototype\.?/g, '#');
if (node.parent && node.parent.type === Token.OBJECTLIT) {
if (enclosing = node.parent) {
enclosingDoc = exports.docFromNode(enclosing) || {};
memberof = (enclosingDoc.tagValue('path') || '').replace(/\.prototype\.?/g, '#');
if (!memberof) {
memberof = enclosingDoc.path;
memberof = memberof || '[[anonymousObject]]';
}
if (memberof) {
name = memberof + (memberof[memberof.length-1] === '#'?'':'.') + name;
enclosingDoc = exports.docFromNode(enclosing);
if (enclosingDoc) {
memberof = (enclosingDoc.tagValue('path') || '').replace(/\.prototype\.?/g, '#');
if (!memberof) {
memberof = enclosingDoc.path;
memberof = memberof || '[[anonymousObject]]';
}
if (memberof) {
name = memberof + (memberof[memberof.length-1] === '#'?'':'.') + name;
}
}
}
}
else if (name.indexOf('this.') === 0) { // assume `this` refers to innermost constructor
else if (name.indexOf('this.') === 0) {
if (!memberof || memberof === 'this') {
enclosing = node.getEnclosingFunction()
enclosingDoc = exports.docFromNode(enclosing);
memberof = enclosingDoc? enclosingDoc.tagValue('path') : '';
if (enclosingDoc) {
if (enclosingDoc.isInner()) memberof = ''; // inner functions have `this` scope of global
else memberof = enclosingDoc.tagValue('path');
}
else {
memberof = '';
}
if (enclosing && !memberof) {
memberof = ''; //[[anonymousFunction]]
memberof = ''; // [[anonymousFunction]]
name = name.slice(5); // remove `this.`
}
else if (!enclosing) {
@ -156,22 +164,44 @@
}
if (memberof || !enclosing) {
// `this` refers to nearest instance in the name path
// `this` refers to nearest non-inner member in the name path
if (enclosingDoc && enclosingDoc.tagValue('isa') !== 'constructor') {
var parts = memberof.split('#');
parts.pop();
memberof = parts.join('#');
var parts = memberof.split(/[#~.]/);
var suffix = parts.pop();
memberof = memberof.slice(0, -suffix.length); // remove suffix from memberof
}
name = memberof + (memberof? '#':'') + name.slice(5); // replace `this.` with memberof
var joiner = (memberof === '')? '' : (/[#~.]$/.test(memberof))? '' : '#';
name = memberof + joiner + name.slice(5); // replace `this.` with memberof
}
}
else {
name = name.slice(5);
name = name.slice(5); // this means global?
}
}
return name;
}
var G = this;
/**
Resolve how to document the name of an inner symbol.
*/
exports.resolveInner = function(name, node, doclet) {
var enclosing = node.getEnclosingFunction(),
enclosingDoc = exports.docFromNode(enclosing);
if (enclosingDoc) {
memberof = enclosingDoc.tagValue('path');
}
else {
memberof = (enclosing && enclosing.name == '')? '[[anonymous]]' : '';
}
if (memberof) {
name = memberof + '~' + name;
}
return name;
}
/**
Keep track of anonymous functions that have been assigned to documented symbols.
@ -187,7 +217,7 @@
return exports.refs[i][1];
}
}
return null;
}
// tuples, like [ [noderef, doclet], [noderef, doclet] ]

View File

@ -34,23 +34,35 @@
}
// like function foo() {}
if (node.type == Token.FUNCTION) {
if (node.type == Token.FUNCTION && String(node.name) !== '') {
commentSrc = (node.jsDoc)? String(node.jsDoc) : '';
if (node.jsDoc) {
commentSrc = '' + node.jsDoc;
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagValue('path');
if (commentSrc) {
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagValue('path');
if (thisDoclet.hasTag('isa') && !thisDocletName) {
thisDoclet.setName('' + node.name);
doclets.addDoclet(thisDoclet);
}
name.refs.push([node, thisDoclet]);
if (!thisDoclet.hasTag('isa')) { // guess isa from the source code
thisDoclet.addTag('isa', 'method')
}
if (!thisDocletName) { // guess name from the source code
thisDocletName = name.resolveInner(node.name, node, thisDoclet);
thisDoclet.setName(thisDocletName);
doclets.addDoclet(thisDoclet);
}
name.refs.push([node, thisDoclet]);
}
else { // an uncommented function?
// this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
thisDoclet = doclet.makeDoclet('[[undocumented]]', node, currentSourceName);
nodeName = name.resolveThis(node.name, node, thisDoclet);
thisDoclet.setName(nodeName);
name.refs.push([
node,
thisDoclet
]);
}
return true;
@ -58,50 +70,93 @@
// like foo = function(){} or foo: function(){}
if (node.type === Token.ASSIGN || node.type === Token.COLON) {
var nodeName = nodeToString(node.left),
nodeKind = '';
commentSrc = node.jsDoc || node.left.jsDoc;
if (commentSrc) {
commentSrc = '' + commentSrc;
thisDoclet = doclet.makeDoclet(commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagValue('name');
nodeKind = thisDoclet.tagValue('isa');
if (thisDoclet.hasTag('isa') && !thisDocletName) {
nodeName = name.resolveThis( nodeName, node, thisDoclet );
if (!thisDoclet.hasTag('isa')) { // guess isa from the source code
if (node.right.type == Token.FUNCTION) { // assume it's a method
thisDoclet.addTag('isa', 'method');
}
else {
thisDoclet.addTag('isa', 'property');
}
}
if (!thisDocletName) { // guess name from the source code
nodeName = name.resolveThis(nodeName, node, thisDoclet);
thisDoclet.setName(nodeName);
doclets.addDoclet(thisDoclet);
}
name.refs.push([node.right, thisDoclet]);
}
else { // an uncommented objlit or anonymous function?
// this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
thisDoclet = doclet.makeDoclet('[[undocumented]]', node, currentSourceName);
nodeName = name.resolveThis(nodeName, node, thisDoclet);
thisDoclet.setName(nodeName);
name.refs.push([
node.right,
thisDoclet
]);
}
return true;
}
// like var foo = function(){} or var bar = {}
if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
if (node.type == Token.VAR || node.type == Token.LET || node.type == Token.CONST) {
var counter = 0,
nodeKind;
if (node.variables) for each (var n in node.variables.toArray()) {
if (n.target.type === Token.NAME && n.initializer) {
if (n.target.type === Token.NAME) {
var val = n.initializer;
commentSrc = (counter++ === 0 && !n.jsDoc)? node.jsDoc : n.jsDoc;
if (commentSrc) {
thisDoclet = doclet.makeDoclet('' + commentSrc, node, currentSourceName);
thisDocletName = thisDoclet.tagValue('path');
nodeKind = thisDoclet.tagValue('isa');
if (thisDoclet.hasTag('isa') && !thisDocletName ) {
thisDocletName = n.target.string;
if (!thisDoclet.hasTag('isa') && val) { // guess isa from the source code
if (val.type == Token.FUNCTION) {
thisDoclet.addTag('isa', 'method');
}
else {
thisDoclet.addTag('isa', 'property');
}
}
if (!thisDocletName ) { // guess name from the source code
//thisDocletName = n.target.string;
thisDocletName = name.resolveInner(n.target.string, node, thisDoclet);
thisDoclet.setName(thisDocletName);
doclets.addDoclet(thisDoclet);
}
if (val) name.refs.push([val, thisDoclet]);
}
else { // an uncommented objlit or anonymous function?
var nodeName = nodeToString(n.target);
// this thing may have commented members, so keep a ref to the thing but don't add it to the doclets list
thisDoclet = doclet.makeDoclet('[[undocumented]]', n.target, currentSourceName);
nodeName = name.resolveInner(nodeName, n.target, thisDoclet);
thisDoclet.setName(nodeName);
if (val) name.refs.push([val, thisDoclet]);
}
}
name.refs.push([n.initializer, thisDoclet]);
}
return true;
}

View File

@ -24,7 +24,9 @@
'augments': 'extends',
'throws': 'exception',
'class': 'classdesc',
'this': 'thisobj'
'this': 'thisobj',
'preserve': 'ignore',
'license': 'ignore'
};
TagDictionary.resolveSynonyms = function(name) {

View File

@ -4,7 +4,8 @@ load(BASEDIR + '/test/tests/03_jsdoc_parser.js');
load(BASEDIR + '/test/tests/04_jsdoc_docset.js');
load(BASEDIR + '/test/tests/05_jsdoc_doclet.js');
load(BASEDIR + '/test/tests/06_jsdoc_tag.js');
load(BASEDIR + '/test/tests/07_tag_param.js');
load(BASEDIR + '/test/tests/07_jsdoc_resolvefunc.js');
load(BASEDIR + '/test/tests/07_jsdoc_resolvevar.js');
load(BASEDIR + '/test/tests/08_tag_name.js');
load(BASEDIR + '/test/tests/09_tag_desc.js');
load(BASEDIR + '/test/tests/10_tag_constructor.js');
@ -16,7 +17,7 @@ load(BASEDIR + '/test/tests/15_tag_type.js');
load(BASEDIR + '/test/tests/16_tag_return.js');
load(BASEDIR + '/test/tests/17_tag_example.js');
load(BASEDIR + '/test/tests/18_tag_class.js');
load(BASEDIR + '/test/tests/19_tag_param.js');
load(BASEDIR + '/test/tests/20_tag_file.js');
load(BASEDIR + '/test/tests/21_tag_const.js');
load(BASEDIR + '/test/tests/22_tag_preserve.js');

View File

@ -0,0 +1,42 @@
// /** A function that does stuff. */
// function doStuff() {
// }
//
// /**
// * Shape is an abstract base class. It is defined simply
// * to have something to inherit from for geometric
// * subclasses
// * @constructor
// */
// function Shape(color){
// this.color = color;
// }
//
// /**
// * Get the name of the color for this shape
// * @returns A color string for this shape
// */
// Shape.prototype.getColor = function() {
// return this.color;
// }
//
// var x,
// /** @return {number} */
// getx = function(){},
// y;
//
ShapeFactory.prototype = {
util: {
/**
* Creates a new {@link Shape} instance.
* @return A new {@link Shape}
* @type Shape
*/
createShape: function() {
/** Track the most recent shape created. */
this.lastShape = new Shape();
return this.lastShape;
}
}
}

View File

@ -0,0 +1,51 @@
// undocumented
ShapeFactory.prototype = {
// undocumented
util: {
// resolves to: @method ShapeFactory#util.createShape
/**
* Creates a new {@link Shape} instance.
* @return A new {@link Shape}
* @type Shape
*/
createShape: function() {
// resolves to: @property ShapeFactory#util.lastShape
/** Track the most recent shape created. */
this.lastShape = new Shape();
return this.lastShape;
}
}
}
// undocumented
foo = function() {
// resolves to: @property g
/** @type {number} */
this.g = 1;
}
/** @constructor */
Foo = function() {
// resolves to: @method Foo#bar
/** two bar */
this.bar = function(){};
// resolves to: @method Foo~inner
/** an inner function */
function inner() {
// resolves to: @method Foo~inner~deep
/** an nested inner function */
function deep() {
// resolves to: @property globalProp
/** set a property */
this.globalProp = 1;
}
}
}
// resolves to: @method globalFunction
/** a global function */
this.globalFunc = function() {
}

View File

@ -0,0 +1,39 @@
// undocumented
this.globalprop = {
/** a child property */
child1: {
/** a nested child func */
child2: {}
}
};
(function ($) {
var io = {
/** @property */
ip: function(){}
}
})(mylib);
var go = {
/** @variable */
gp: true
};
/** @variable */
var foo,
/** @variable */
bar;
// undocumented
function globalFunc() {
/** an inner property */
var innerProp = 1;
// an inner function */
var innerFunc = function() {
/** a nested child func */
var nestedProp = 1;
}
}

View File

@ -37,7 +37,6 @@ function Shape(){
/**
* This is an inner method, just used here as an example
* @method Shape~addReference
* @private
* @since version 0.5
* @author Sue Smart

View File

@ -0,0 +1,113 @@
(function() {
var jsdoc,
doclets;
JSpec.describe('jsdoc/name:resolvefunc', function() {
before(function() {
// docsets can only be created by parsers
jsdoc = {
tag: require('jsdoc/tag'),
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/samples/jsdoc_resolvefunc.js');
doclets = jsdoc.parser.result.map(function($){ return $.toObject(); });
});
describe('A doclet that has no @name, that is a property of a nested, undocumented objlit', function() {
it('should be given a path that includes membership in the enclosing object', function() {
var doclet = doclets[0];
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'createShape');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'ShapeFactory#util.createShape');
});
});
describe('A doclet that has no @name whose name starts with `this.`, that is a property of a nested objlit', function() {
it('should correctly resolve `this` to the nearest enclosing object', function() {
var doclet = doclets[1];
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'lastShape');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'ShapeFactory#util.lastShape');
});
});
describe('A property doclet that has no @name or @isa whose name starts with `this.`, that is scoped to a non-constructor global function', function() {
it('should correctly resolve to a property of the global scope', function() {
var doclet = doclets[2];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'property');
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'g');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'g');
});
});
describe('A method doclet that has no @name or @isa whose name starts with `this.`, that is scoped to a constructor function', function() {
it('should correctly resolve to a method of the constructor', function() {
var doclet = doclets[4];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'method');
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'bar');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'Foo#bar');
});
});
describe('An inner method doclet that has no @name or @isa, and is scoped to a constructor function', function() {
it('should correctly resolve to an inner method of the constructor', function() {
var doclet = doclets[5];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'method');
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'inner');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'Foo~inner');
expect(doclet).to(have_property, 'access');
expect(doclet.access).to(eql, 'inner');
});
});
describe('A nested inner method doclet that has no @name or @isa, and is scoped to an inner method', function() {
it('should correctly resolve to an inner method of the a inner method', function() {
var doclet = doclets[6];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'method');
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'deep');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'Foo~inner~deep');
expect(doclet).to(have_property, 'access');
expect(doclet.access).to(eql, 'inner');
});
});
describe('A property doclet whose name starts with `this.`, that has no @name or @isa, and is scoped to an inner method', function() {
it('should correctly resolve to a property of the global scope', function() {
var doclet = doclets[7];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'property');
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'globalProp');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'globalProp');
});
});
describe('A global method whose name starts with `this.`, that has no @name or @isa', function() {
it('should correctly resolve to a property of the global scope', function() {
var doclet = doclets[8];
expect(doclet).to(have_property, 'isa');
expect(doclet.isa).to(eql, 'method');
expect(doclet).to(have_property, 'name');
expect(doclet.name).to(eql, 'globalFunc');
expect(doclet).to(have_property, 'path');
expect(doclet.path).to(eql, 'globalFunc');
});
});
});
})();

View File

@ -0,0 +1,17 @@
(function() {
var jsdoc,
doclets;
JSpec.describe('jsdoc/name:resolvevar', function() {
before(function() {
// docsets can only be created by parsers
jsdoc = {
tag: require('jsdoc/tag'),
parser: require('jsdoc/parser')
};
jsdoc.parser.parseFiles(BASEDIR + 'test/samples/jsdoc_resolvevar.js');
doclets = jsdoc.parser.result.map(function($){ return $.toObject(); });
});
});
})();