Add @lends support to inferMembership

Fixes #31
This commit is contained in:
John Firebaugh 2015-03-31 17:59:24 -07:00
parent f21863012f
commit 37dbda7a9b
4 changed files with 134 additions and 30 deletions

16
lib/is_jsdoc_comment.js Normal file
View File

@ -0,0 +1,16 @@
'use strict';
/**
* Detect whether a comment is a JSDoc comment: it must be a block
* comment which starts with two asterisks, not any other number of asterisks.
*
* The code parser automatically strips out the first asterisk that's
* required for the comment to be a comment at all, so we count the remaining
* comments.
* @param {Object} comment an ast-types node of the comment
* @return {boolean} whether it is valid
*/
module.exports = function isJSDocComment(comment) {
var asterisks = comment.value.match(/^(\*+)/);
return comment.type === 'Block' && asterisks && asterisks[ 1 ].length === 1;
};

View File

@ -2,7 +2,9 @@
var through = require('through'),
types = require('ast-types'),
n = types.namedTypes;
n = types.namedTypes,
isJSDocComment = require('../lib/is_jsdoc_comment'),
doctrine = require('doctrine');
/**
* Create a transform stream that uses code structure to infer
@ -21,6 +23,10 @@ module.exports = function () {
return;
}
if (findLendsTag(comment)) {
return;
}
/**
* Extract and return the chain of identifiers from the left hand side of expressions
* of the forms `Foo = ...`, `Foo.bar = ...`, `Foo.bar.baz = ...`, etc.
@ -85,6 +91,30 @@ module.exports = function () {
}
}
function findLendsTag(comment) {
for (var i = 0; i < comment.tags.length; i++) {
if (comment.tags[i].title == 'lends') {
return comment.tags[i];
}
}
}
function findLendsIdentifiers(node) {
if (!node || !node.leadingComments) {
return;
}
for (var i = 0; i < node.leadingComments.length; i++) {
var comment = node.leadingComments[i];
if (isJSDocComment(comment)) {
var lendsTag = findLendsTag(doctrine.parse(comment.value, { unwrap: true }));
if (lendsTag) {
return lendsTag.description.split('.');
}
}
}
}
var path = comment.context.ast;
var identifiers;
@ -106,11 +136,9 @@ module.exports = function () {
path = path.get('key');
}
/*
* Foo.bar = ...;
* Foo.prototype.bar = ...;
* Foo.bar.baz = ...;
*/
// Foo.bar = ...;
// Foo.prototype.bar = ...;
// Foo.bar.baz = ...;
if (n.MemberExpression.check(path.node)) {
identifiers = extractIdentifiers(path);
if (identifiers.length >= 2) {
@ -118,11 +146,22 @@ module.exports = function () {
}
}
/*
* Foo = { bar: ... };
* Foo.prototype = { bar: ... };
* Foo.bar = { baz: ... };
*/
// /** @lends Foo */{ bar: ... }
if (n.Identifier.check(path.node) &&
n.Property.check(path.parent.node) &&
n.ObjectExpression.check(path.parent.parent.node)) {
// The @lends comment is sometimes attached to the first property rather than
// the object expression itself.
identifiers = findLendsIdentifiers(path.parent.parent.node) ||
findLendsIdentifiers(path.parent.parent.node.properties[0]);
if (identifiers) {
inferMembership(identifiers);
}
}
// Foo = { bar: ... };
// Foo.prototype = { bar: ... };
// Foo.bar = { baz: ... };
if (n.Identifier.check(path.node) &&
n.Property.check(path.parent.node) &&
n.ObjectExpression.check(path.parent.parent.node) &&
@ -133,9 +172,7 @@ module.exports = function () {
}
}
/*
* var Foo = { bar: ... }
*/
// var Foo = { bar: ... }
if (n.Identifier.check(path.node) &&
n.Property.check(path.parent.node) &&
n.ObjectExpression.check(path.parent.parent.node) &&

View File

@ -4,22 +4,8 @@ var doctrine = require('doctrine'),
espree = require('espree'),
through = require('through'),
types = require('ast-types'),
extend = require('extend');
/**
* Detect whether a comment is a JSDoc comment: it must be a block
* comment which starts with two asterisks, not any other number of asterisks.
*
* The code parser automatically strips out the first asterisk that's
* required for the comment to be a comment at all, so we count the remaining
* comments.
* @param {Object} comment an ast-types node of the comment
* @return {boolean} whether it is valid
*/
function isJSDocComment(comment) {
var asterisks = comment.value.match(/^(\*+)/);
return comment.type === 'Block' && asterisks && asterisks[ 1 ].length === 1;
}
extend = require('extend'),
isJSDocComment = require('../lib/is_jsdoc_comment');
/**
* Comment-out a shebang line that may sit at the top of a file,

View File

@ -21,6 +21,7 @@ function evaluate(fn, callback) {
}
function Foo() {}
function lend() {}
test('inferMembership - explicit', function (t) {
evaluate(function () {
@ -158,3 +159,67 @@ test('inferMembership - simple', function (t) {
t.end();
});
});
test('inferMembership - lends, static', function (t) {
evaluate(function () {
lend(/** @lends Foo */{
/** Test */
bar: 0
});
}, function (result) {
t.equal(result[ 0 ].memberof, 'Foo');
t.equal(result[ 0 ].scope, 'static');
t.end();
});
});
test('inferMembership - lends, static, function', function (t) {
evaluate(function () {
lend(/** @lends Foo */{
/** Test */
bar: function () {}
});
}, function (result) {
t.equal(result[ 0 ].memberof, 'Foo');
t.equal(result[ 0 ].scope, 'static');
t.end();
});
});
test('inferMembership - lends, instance', function (t) {
evaluate(function () {
lend(/** @lends Foo.prototype */{
/** Test */
bar: 0
});
}, function (result) {
t.equal(result[ 0 ].memberof, 'Foo');
t.equal(result[ 0 ].scope, 'instance');
t.end();
});
});
test('inferMembership - lends, instance, function', function (t) {
evaluate(function () {
lend(/** @lends Foo.prototype */{
/** Test */
bar: function () {}
});
}, function (result) {
t.equal(result[ 0 ].memberof, 'Foo');
t.equal(result[ 0 ].scope, 'instance');
t.end();
});
});
test('inferMembership - lends applies only to following object', function (t) {
evaluate(function () {
lend(/** @lends Foo */{});
/** Test */
return 0;
}, function (result) {
t.equal(result.length, 1);
t.equal(result[ 0 ].memberof, undefined);
t.end();
});
});