Skip to content

Commit 473a7a2

Browse files
chkttmcw
authored andcommitted
Membership inference improvements (#558)
* Add test for memberof normalization: @memberof Foo#, @memberof Foo.prototype * Add memberof normalization for @memberof Foo#, @memberof Foo.prototype * Add test to infer membership of properties defined inside constructor * Added inference of membership for properties defined inside constructor * Fix Membership spelling, switch let for var to keep node compat broad * Change memberof normalization to use RegExp
1 parent 58c6892 commit 473a7a2

File tree

2 files changed

+132
-3
lines changed

2 files changed

+132
-3
lines changed

lib/infer/membership.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,45 @@ function findLendsIdentifiers(node) {
3131
}
3232
}
3333

34+
/**
35+
* Extract and return the identifiers for expressions of type this.foo
36+
*
37+
* @param {NodePath} path AssignmentExpression, MemberExpression, or Identifier
38+
* @returns {Array<string>} identifiers
39+
* @private
40+
*/
41+
function extractThis(path) {
42+
var identifiers = [];
43+
44+
path.traverse({
45+
/**
46+
* Add the resolved identifier of this in a path to the identifiers array
47+
* @param {Object} path ast path
48+
* @returns {undefined} has side-effects
49+
* @private
50+
*/
51+
ThisExpression: function (path) {
52+
var scope = path.scope;
53+
54+
while (n.isBlockStatement(scope.block)) {
55+
scope = scope.parent;
56+
}
57+
58+
if (n.isClassMethod(scope.block)) {
59+
identifiers.push(scope.path.parentPath.parentPath.node.id.name, 'prototype');
60+
}
61+
62+
if (n.isFunctionDeclaration(scope.block) ||
63+
n.isFunctionExpression(scope.block)) {
64+
identifiers.push(scope.block.id.name , 'prototype');
65+
}
66+
}
67+
});
68+
69+
return identifiers;
70+
}
71+
72+
3473
/**
3574
* Extract and return the chain of identifiers from the left hand side of expressions
3675
* of the forms `Foo = ...`, `Foo.bar = ...`, `Foo.bar.baz = ...`, etc.
@@ -76,6 +115,33 @@ function countModuleIdentifiers(comment, identifiers) {
76115
return 0;
77116
}
78117

118+
/**
119+
* Returns the comment object after normalizing Foo.prototype and Foo# expressions
120+
* @param {Object} comment parsed comment
121+
* @returns {Object} the normalized comment
122+
*/
123+
function normalizeMemberof(comment) {
124+
var memberof = comment.memberof;
125+
126+
var isPrototype = /.prototype$/;
127+
128+
if (memberof.match(isPrototype) !== null) {
129+
comment.memberof = memberof.replace(isPrototype, '');
130+
comment.scope = 'instance';
131+
132+
return comment;
133+
}
134+
135+
var isInstanceMember = /#$/;
136+
137+
if (memberof.match(isInstanceMember) !== null) {
138+
comment.memberof = memberof.replace(isInstanceMember, '');
139+
comment.scope = 'instance';
140+
}
141+
142+
return comment;
143+
}
144+
79145
/**
80146
* Uses code structure to infer `memberof`, `instance`, and `static`
81147
* tags from the placement of JSDoc
@@ -147,7 +213,7 @@ module.exports = function () {
147213
}
148214

149215
if (comment.memberof) {
150-
return comment;
216+
return normalizeMemberof(comment);
151217
}
152218

153219
if (!comment.context.ast) {
@@ -176,7 +242,10 @@ module.exports = function () {
176242
// Foo.prototype.bar = ...;
177243
// Foo.bar.baz = ...;
178244
if (n.isMemberExpression(path.node)) {
179-
identifiers = extractIdentifiers(path);
245+
identifiers = [].concat(
246+
extractThis(path),
247+
extractIdentifiers(path)
248+
);
180249
if (identifiers.length >= 2) {
181250
inferMembershipFromIdentifiers(comment, identifiers.slice(0, -1));
182251
}

test/lib/infer/membership.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,29 @@ test('inferMembership - explicit', function (t) {
3838
})[0], ['memberof', 'scope']), {
3939
memberof: 'Bar',
4040
scope: 'static'
41-
}, 'explicit');
41+
}, 'explicit, static');
42+
43+
t.deepEqual(pick(evaluate(function () {
44+
/**
45+
* Test
46+
* @memberof Bar#
47+
*/
48+
Foo.bar = 0;
49+
})[0], ['memberof', 'scope']), {
50+
memberof: 'Bar',
51+
scope: 'instance'
52+
}, 'explicit, instance');
53+
54+
t.deepEqual(pick(evaluate(function () {
55+
/**
56+
* Test
57+
* @memberof Bar.prototype
58+
*/
59+
Foo.bar = 0;
60+
})[0], ['memberof', 'scope']), {
61+
memberof: 'Bar',
62+
scope: 'instance'
63+
}, 'explicit, prototype');
4264

4365
t.deepEqual(pick(evaluate(function () {
4466
/** Test */
@@ -123,6 +145,44 @@ test('inferMembership - explicit', function (t) {
123145
scope: 'static'
124146
}, 'variable object assignment, function');
125147

148+
t.deepEqual(pick(evaluate(function () {
149+
function Foo() {
150+
{
151+
/** */
152+
this.bar = 0;
153+
}
154+
}
155+
})[0], ['memberof', 'scope']), {
156+
memberof: 'Foo',
157+
scope: 'instance'
158+
}, 'constructor function declaration assignment');
159+
160+
t.deepEqual(pick(evaluate(function () {
161+
var Foo = function Bar() {
162+
{
163+
/** */
164+
this.baz = 0;
165+
}
166+
};
167+
})[0], ['memberof', 'scope']), {
168+
memberof: 'Bar',
169+
scope: 'instance'
170+
}, 'constructor function expression assignment');
171+
172+
t.deepEqual(pick(evaluate(function () {
173+
class Foo {
174+
constructor() {
175+
{
176+
/** */
177+
this.bar = 0;
178+
}
179+
}
180+
}
181+
})[0], ['memberof', 'scope']), {
182+
memberof: 'Foo',
183+
scope: 'instance'
184+
}, 'constructor assignment');
185+
126186
t.deepEqual(pick(evaluate(function () {
127187
/** Test */
128188
module.exports = function () {};

0 commit comments

Comments
 (0)