Skip to content

Commit 268fcce

Browse files
committed
Infer module membership for [module.]exports (fixes #113)
1 parent 341ab4a commit 268fcce

File tree

3 files changed

+114
-18
lines changed

3 files changed

+114
-18
lines changed

lib/infer/membership.js

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
var types = require('ast-types'),
4+
pathParse = require('parse-filepath'),
45
isJSDocComment = require('../../lib/is_jsdoc_comment'),
56
parse = require('../../lib/parse');
67

@@ -56,22 +57,15 @@ function extractIdentifiers(path) {
5657
}
5758

5859
/**
59-
* Set `memberof` and `instance`/`static` tags on `comment` based on the
60-
* array of `identifiers`. If the last element of the `identifiers` is
61-
* `"prototype"`, it is assumed to be an instance member; otherwise static.
62-
*
63-
* @param {Object} comment comment for which to infer memberships
60+
* Test whether some identifiers refer to a module export (`exports` or `module.exports`).
6461
* @param {Array<string>} identifiers array of identifier names
65-
* @returns {undefined} mutates `comment`
66-
* @private
62+
* @returns {boolean} true if identifiers refer to a module export
6763
*/
68-
function inferMembershipFromIdentifiers(comment, identifiers) {
69-
if (identifiers[identifiers.length - 1] === 'prototype') {
70-
comment.memberof = identifiers.slice(0, -1).join('.');
71-
comment.scope = 'instance';
72-
} else {
73-
comment.memberof = identifiers.join('.');
74-
comment.scope = 'static';
64+
function isModuleExport(identifiers) {
65+
switch (identifiers.length) {
66+
case 1: return identifiers[0] === 'exports';
67+
case 2: return identifiers[0] === 'module' && identifiers[1] === 'exports';
68+
default: return false;
7569
}
7670
}
7771

@@ -84,7 +78,41 @@ function inferMembershipFromIdentifiers(comment, identifiers) {
8478
* @returns {Object} comment with membership inferred
8579
*/
8680
module.exports = function () {
81+
var currentModule;
82+
83+
function inferModuleName(comment) {
84+
return (comment.module && comment.module.name) ||
85+
pathParse(comment.context.file).name;
86+
}
87+
88+
/**
89+
* Set `memberof` and `instance`/`static` tags on `comment` based on the
90+
* array of `identifiers`. If the last element of the `identifiers` is
91+
* `"prototype"`, it is assumed to be an instance member; otherwise static.
92+
*
93+
* @param {Object} comment comment for which to infer memberships
94+
* @param {Array<string>} identifiers array of identifier names
95+
* @returns {undefined} mutates `comment`
96+
* @private
97+
*/
98+
function inferMembershipFromIdentifiers(comment, identifiers) {
99+
if (isModuleExport(identifiers)) {
100+
comment.memberof = inferModuleName(currentModule || comment);
101+
comment.scope = 'static';
102+
} else if (identifiers[identifiers.length - 1] === 'prototype') {
103+
comment.memberof = identifiers.slice(0, -1).join('.');
104+
comment.scope = 'instance';
105+
} else {
106+
comment.memberof = identifiers.join('.');
107+
comment.scope = 'static';
108+
}
109+
}
110+
87111
return function inferMembership(comment) {
112+
if (comment.module) {
113+
currentModule = comment;
114+
}
115+
88116
if (comment.memberof) {
89117
return comment;
90118
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"mdast-toc": "^1.1.0",
3636
"micromatch": "^2.1.6",
3737
"module-deps": "^3.7.3",
38+
"parse-filepath": "^0.6.3",
3839
"remote-origin-url": "^0.4.0",
3940
"resolve": "^1.1.6",
4041
"slugg": "^0.1.2",

test/lib/infer/membership.js

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ var test = require('tap').test,
55
parse = require('../../../lib/parsers/javascript'),
66
inferMembership = require('../../../lib/infer/membership')();
77

8-
function toComment(fn, filename) {
8+
function toComment(fn, file) {
99
return parse({
10-
file: filename,
10+
file: file,
1111
source: fn instanceof Function ? '(' + fn.toString() + ')' : fn
1212
});
1313
}
1414

15-
function evaluate(fn, callback) {
16-
return toComment(fn, callback).map(inferMembership);
15+
function evaluate(fn, file) {
16+
return toComment(fn, file).map(inferMembership);
1717
}
1818

1919
function Foo() {}
@@ -186,3 +186,70 @@ test('inferMembership', function (t) {
186186

187187
t.end();
188188
});
189+
190+
test('inferMembership - exports', function (t) {
191+
var result = evaluate(function () {
192+
/**
193+
* @module mod
194+
*/
195+
/** Test */
196+
exports.foo = 1;
197+
});
198+
199+
t.equal(result.length, 2);
200+
t.equal(result[1].memberof, 'mod');
201+
t.end();
202+
});
203+
204+
test('inferMembership - module.exports', function (t) {
205+
var result = evaluate(function () {
206+
/**
207+
* @module mod
208+
*/
209+
/** Test */
210+
module.exports.foo = 1;
211+
});
212+
213+
t.equal(result.length, 2);
214+
t.equal(result[1].memberof, 'mod');
215+
t.end();
216+
});
217+
218+
test('inferMembership - not module exports', function (t) {
219+
var result = evaluate(function () {
220+
/**
221+
* @module mod
222+
*/
223+
/** Test */
224+
global.module.exports.foo = 1;
225+
}, '/path/mod.js');
226+
227+
t.equal(result.length, 2);
228+
t.notEqual(result[0].memberof, 'mod');
229+
t.end();
230+
});
231+
232+
test('inferMembership - anonymous module', function (t) {
233+
var result = evaluate(function () {
234+
/**
235+
* @module
236+
*/
237+
/** Test */
238+
exports.foo = 1;
239+
}, '/path/mod.js');
240+
241+
t.equal(result.length, 2);
242+
t.equal(result[1].memberof, 'mod');
243+
t.end();
244+
});
245+
246+
test('inferMembership - no module', function (t) {
247+
var result = evaluate(function () {
248+
/** Test */
249+
exports.foo = 1;
250+
}, '/path/mod.js');
251+
252+
t.equal(result.length, 1);
253+
t.equal(result[0].memberof, 'mod');
254+
t.end();
255+
});

0 commit comments

Comments
 (0)