diff --git a/lib/infer/membership.js b/lib/infer/membership.js index 2b8c8c154..7def3e895 100644 --- a/lib/infer/membership.js +++ b/lib/infer/membership.js @@ -31,6 +31,45 @@ function findLendsIdentifiers(node) { } } +/** + * Extract and return the identifiers for expressions of type this.foo + * + * @param {NodePath} path AssignmentExpression, MemberExpression, or Identifier + * @returns {Array} identifiers + * @private + */ +function extractThis(path) { + var identifiers = []; + + path.traverse({ + /** + * Add the resolved identifier of this in a path to the identifiers array + * @param {Object} path ast path + * @returns {undefined} has side-effects + * @private + */ + ThisExpression: function (path) { + var scope = path.scope; + + while (n.isBlockStatement(scope.block)) { + scope = scope.parent; + } + + if (n.isClassMethod(scope.block)) { + identifiers.push(scope.path.parentPath.parentPath.node.id.name, 'prototype'); + } + + if (n.isFunctionDeclaration(scope.block) || + n.isFunctionExpression(scope.block)) { + identifiers.push(scope.block.id.name , 'prototype'); + } + } + }); + + return identifiers; +} + + /** * Extract and return the chain of identifiers from the left hand side of expressions * of the forms `Foo = ...`, `Foo.bar = ...`, `Foo.bar.baz = ...`, etc. @@ -76,6 +115,33 @@ function countModuleIdentifiers(comment, identifiers) { return 0; } +/** + * Returns the comment object after normalizing Foo.prototype and Foo# expressions + * @param {Object} comment parsed comment + * @returns {Object} the normalized comment + */ +function normalizeMemberof(comment) { + var memberof = comment.memberof; + + var isPrototype = /.prototype$/; + + if (memberof.match(isPrototype) !== null) { + comment.memberof = memberof.replace(isPrototype, ''); + comment.scope = 'instance'; + + return comment; + } + + var isInstanceMember = /#$/; + + if (memberof.match(isInstanceMember) !== null) { + comment.memberof = memberof.replace(isInstanceMember, ''); + comment.scope = 'instance'; + } + + return comment; +} + /** * Uses code structure to infer `memberof`, `instance`, and `static` * tags from the placement of JSDoc @@ -147,7 +213,7 @@ module.exports = function () { } if (comment.memberof) { - return comment; + return normalizeMemberof(comment); } if (!comment.context.ast) { @@ -176,7 +242,10 @@ module.exports = function () { // Foo.prototype.bar = ...; // Foo.bar.baz = ...; if (n.isMemberExpression(path.node)) { - identifiers = extractIdentifiers(path); + identifiers = [].concat( + extractThis(path), + extractIdentifiers(path) + ); if (identifiers.length >= 2) { inferMembershipFromIdentifiers(comment, identifiers.slice(0, -1)); } diff --git a/test/lib/infer/membership.js b/test/lib/infer/membership.js index 895811c6e..6f3c7730f 100644 --- a/test/lib/infer/membership.js +++ b/test/lib/infer/membership.js @@ -38,7 +38,29 @@ test('inferMembership - explicit', function (t) { })[0], ['memberof', 'scope']), { memberof: 'Bar', scope: 'static' - }, 'explicit'); + }, 'explicit, static'); + + t.deepEqual(pick(evaluate(function () { + /** + * Test + * @memberof Bar# + */ + Foo.bar = 0; + })[0], ['memberof', 'scope']), { + memberof: 'Bar', + scope: 'instance' + }, 'explicit, instance'); + + t.deepEqual(pick(evaluate(function () { + /** + * Test + * @memberof Bar.prototype + */ + Foo.bar = 0; + })[0], ['memberof', 'scope']), { + memberof: 'Bar', + scope: 'instance' + }, 'explicit, prototype'); t.deepEqual(pick(evaluate(function () { /** Test */ @@ -123,6 +145,44 @@ test('inferMembership - explicit', function (t) { scope: 'static' }, 'variable object assignment, function'); + t.deepEqual(pick(evaluate(function () { + function Foo() { + { + /** */ + this.bar = 0; + } + } + })[0], ['memberof', 'scope']), { + memberof: 'Foo', + scope: 'instance' + }, 'constructor function declaration assignment'); + + t.deepEqual(pick(evaluate(function () { + var Foo = function Bar() { + { + /** */ + this.baz = 0; + } + }; + })[0], ['memberof', 'scope']), { + memberof: 'Bar', + scope: 'instance' + }, 'constructor function expression assignment'); + + t.deepEqual(pick(evaluate(function () { + class Foo { + constructor() { + { + /** */ + this.bar = 0; + } + } + } + })[0], ['memberof', 'scope']), { + memberof: 'Foo', + scope: 'instance' + }, 'constructor assignment'); + t.deepEqual(pick(evaluate(function () { /** Test */ module.exports = function () {};