@@ -7,24 +7,31 @@ var n = require('babel-types'),
7
7
isJSDocComment = require ( '../../lib/is_jsdoc_comment' ) ,
8
8
parse = require ( '../../lib/parse' ) ;
9
9
10
+ function inferModuleName ( comment ) {
11
+ return ( comment . kind === 'module' && comment . name ) ||
12
+ pathParse ( comment . context . file ) . name ;
13
+ }
14
+
10
15
/**
11
16
* Given an AST node, try to find a comment in front of it that
12
17
* has a `lends` tag, and if it has that, return the tag, split by
13
18
* .s.
14
19
*
15
20
* @private
16
- * @param {Object } node AST node
21
+ * @param {Object } path AST node
17
22
* @returns {string|undefined } lends identifier, if any
18
23
*/
19
- function findLendsIdentifiers ( node ) {
20
- if ( ! node || ! node . leadingComments ) {
24
+ function findLendsIdentifiers ( path ) {
25
+ if ( ! path || ! path . get ( ' leadingComments' ) ) {
21
26
return ;
22
27
}
23
28
24
- for ( var i = 0 ; i < node . leadingComments . length ; i ++ ) {
25
- var comment = node . leadingComments [ i ] ;
26
- if ( isJSDocComment ( comment ) ) {
27
- var lends = parse ( comment . value ) . lends ;
29
+ var leadingComments = path . get ( 'leadingComments' ) ;
30
+
31
+ for ( var i = 0 ; i < leadingComments . length ; i ++ ) {
32
+ var comment = leadingComments [ i ] ;
33
+ if ( isJSDocComment ( comment . node ) ) {
34
+ var lends = parse ( comment . node . value ) . lends ;
28
35
if ( lends ) {
29
36
return lends . split ( '.' ) ;
30
37
}
@@ -33,13 +40,16 @@ function findLendsIdentifiers(node) {
33
40
}
34
41
35
42
/**
36
- * Extract and return the identifiers for expressions of type this.foo
43
+ * Extract and return the identifiers for expressions of
44
+ * type this.foo
37
45
*
38
- * @param {NodePath } path AssignmentExpression, MemberExpression, or Identifier
46
+ * @param {NodePath } path AssignmentExpression, MemberExpression,
47
+ * or Identifier
48
+ * @param {Comment } comment
39
49
* @returns {Array<string> } identifiers
40
50
* @private
41
51
*/
42
- function extractThis ( path ) {
52
+ function extractThis ( path , comment ) {
43
53
var identifiers = [ ] ;
44
54
45
55
path . traverse ( {
@@ -60,14 +70,22 @@ function extractThis(path) {
60
70
identifiers . push ( scope . path . parentPath . parentPath . node . id . name , 'prototype' ) ;
61
71
}
62
72
73
+ // function OldClass() { this.foo = 1 }
63
74
if ( n . isFunctionDeclaration ( scope . block ) ) {
64
- identifiers . push ( scope . block . id . name , 'prototype' ) ;
65
- } else if ( n . isFunctionExpression ( scope . block ) ) {
66
- if ( n . isVariableDeclarator ( scope . path . parentPath ) ) {
75
+ // named function like
76
+ // function OldClass() { ... }
77
+ if ( scope . block . id ) {
78
+ identifiers . push ( scope . block . id . name , 'prototype' ) ;
79
+ } else if ( n . isExportDefaultDeclaration ( path . scope . parentBlock ) ) {
80
+ identifiers . push ( inferModuleName ( comment ) ) ;
81
+ }
82
+ // var Binding = function OldClass() { this.foo = 1 }
83
+ } else if ( n . isFunctionExpression ( ( scope . block ) ) ) {
84
+ if ( scope . path . parentPath . isVariableDeclarator ( ) ) {
67
85
/** var Bar = function(foo) { this.foo = foo; }; */
68
86
identifiers = identifiers
69
87
. concat ( scope . path . parentPath . get ( 'id' ) . node . name ) . concat ( 'prototype' ) ;
70
- } else if ( n . isAssignmentExpression ( scope . path . parentPath ) ) {
88
+ } else if ( scope . path . parentPath . isAssignmentExpression ( ) ) {
71
89
/** this.Bar = function(foo) { this.foo = foo; }; */
72
90
identifiers = identifiers
73
91
. concat ( extractIdentifiers ( scope . path . parentPath . get ( 'left' ) ) ) . concat ( 'prototype' ) ;
@@ -169,10 +187,6 @@ function normalizeMemberof(comment/*: Comment*/)/*: Comment */ {
169
187
module . exports = function ( ) {
170
188
var currentModule ;
171
189
172
- function inferModuleName ( comment ) {
173
- return ( comment . kind === 'module' && comment . name ) ||
174
- pathParse ( comment . context . file ) . name ;
175
- }
176
190
177
191
/**
178
192
* Set `memberof` and `instance`/`static` tags on `comment` based on the
@@ -185,13 +199,13 @@ module.exports = function () {
185
199
* @param {Array<string> } identifiers array of identifier names
186
200
* @param {string } explicitScope if derived from an es6 class, whether or
187
201
* not this method had the static keyword
188
- * @returns {undefined } mutates `comment`
202
+ * @returns {Comment } returns mutated `comment`
189
203
* @private
190
204
*/
191
205
function inferMembershipFromIdentifiers ( comment , identifiers , explicitScope ) {
192
206
if ( identifiers . length === 1 && identifiers [ 0 ] === 'module' && comment . name === 'exports' ) {
193
207
comment . name = inferModuleName ( currentModule || comment ) ;
194
- return ;
208
+ return comment ;
195
209
}
196
210
197
211
/*
@@ -215,142 +229,166 @@ module.exports = function () {
215
229
comment . scope = 'static' ;
216
230
}
217
231
}
232
+ return comment ;
218
233
}
219
234
220
- return function inferMembership ( comment /*: Comment */ ) {
221
-
235
+ function shouldSkipInference ( comment /*: Comment */ ) /*: boolean */ {
236
+ // If someone uses the @name tag, they explicitly ask for inference
237
+ // to be skipped.
222
238
if ( comment . tags . some ( tag => tag . title === 'name' ) ) {
223
- return comment ;
239
+ return true ;
224
240
}
225
241
226
- if ( comment . kind === 'module' ) {
227
- currentModule = comment ;
242
+ // Lends tags are go-betweens that let people reassign membership
243
+ // in bulk: they themselves don't get an inference step
244
+ if ( comment . lends ) {
245
+ return true ;
228
246
}
229
247
230
- if ( comment . lends ) {
248
+ // If this chunk doesn't have code attached, like if it was the result
249
+ // of a polyglot parse, don't try to infer anything.
250
+ if ( ! comment . context . ast ) {
251
+ return true ;
252
+ }
253
+
254
+ return false ;
255
+ }
256
+
257
+ return function inferMembership ( comment /*: Comment */ ) {
258
+
259
+ // First skip inference if the user indicates it or if it isn't possible.
260
+ if ( shouldSkipInference ( comment ) ) {
231
261
return comment ;
232
262
}
233
263
264
+ // If someone explicitly specifies the parent of this chunk, don't
265
+ // try to infer it, just return what they specified.
234
266
if ( comment . memberof ) {
235
267
return normalizeMemberof ( comment ) ;
236
268
}
237
269
238
- if ( ! comment . context . ast ) {
239
- return comment ;
270
+ if ( comment . kind === 'module' ) {
271
+ currentModule = comment ;
240
272
}
241
273
242
274
var path = comment . context . ast ;
243
- var identifiers ;
244
-
245
275
276
+ // INFERENCE ===============================================================
246
277
// Deal with an oddity of espree: the jsdoc comment is attached to a different
247
278
// node in the two expressions `a.b = c` vs `a.b = function () {}`.
248
- if ( n . isExpressionStatement ( path . node ) &&
249
- n . isAssignmentExpression ( path . node . expression ) &&
250
- n . isMemberExpression ( path . node . expression . left ) ) {
279
+ if ( path . isExpressionStatement ( ) &&
280
+ path . get ( ' expression' ) . isAssignmentExpression ( ) &&
281
+ path . get ( ' expression' ) . get ( ' left' ) . isMemberExpression ( ) ) {
251
282
path = path . get ( 'expression' ) . get ( 'left' ) ;
252
283
}
253
284
254
285
// Same as above but for `b: c` vs `b: function () {}`.
255
- if ( n . isObjectProperty ( path . node ) &&
256
- n . isIdentifier ( path . node . key ) ) {
286
+ if ( path . isObjectProperty ( ) &&
287
+ path . get ( ' key' ) . isIdentifier ( ) ) {
257
288
path = path . get ( 'key' ) ;
258
289
}
259
290
291
+ // Forms:
292
+ //
260
293
// Foo.bar = ...;
261
294
// Foo.prototype.bar = ...;
262
295
// Foo.bar.baz = ...;
263
- if ( n . isMemberExpression ( path . node ) ) {
264
- identifiers = [ ] . concat (
265
- extractThis ( path ) ,
296
+ //
297
+ // Lends is not supported in this codepath.
298
+ if ( path . isMemberExpression ( ) ) {
299
+ var memberIdentifiers = [ ] . concat (
300
+ extractThis ( path , comment ) ,
266
301
extractIdentifiers ( path )
267
302
) ;
268
- if ( identifiers . length >= 2 ) {
269
- inferMembershipFromIdentifiers ( comment , identifiers . slice ( 0 , - 1 ) ) ;
303
+ if ( memberIdentifiers . length >= 2 ) {
304
+ return inferMembershipFromIdentifiers ( comment , memberIdentifiers . slice ( 0 , - 1 ) ) ;
270
305
}
306
+ return comment ;
271
307
}
272
308
273
- // /** @lends Foo */{ bar: ... }
274
- if ( n . isIdentifier ( path . node ) &&
275
- n . isObjectProperty ( path . parentPath ) &&
276
- n . isObjectExpression ( path . parentPath . parentPath ) ) {
277
- // The @lends comment is sometimes attached to the first property rather than
278
- // the object expression itself.
279
- identifiers = findLendsIdentifiers ( path . parentPath . parentPath . node ) ||
280
- findLendsIdentifiers ( path . parentPath . parentPath . node . properties [ 0 ] ) ;
281
- if ( identifiers ) {
282
- inferMembershipFromIdentifiers ( comment , identifiers ) ;
283
- }
284
- }
309
+ // Like straight membership, classes don't need
310
+ // to support lends.
311
+ //
312
+ // class Foo { bar() { } }
313
+ // var Foo = class { bar() { } }
314
+ // class Foo { prop: T }
315
+ // var Foo = class { prop: T }
316
+ if ( ( path . isClassMethod ( ) || path . isClassProperty ( ) ) &&
317
+ path . parentPath . isClassBody ( ) &&
318
+ path . parentPath . parentPath . isClass ( ) ) {
285
319
286
- // Foo = { bar: ... };
287
- // Foo.prototype = { bar: ... };
288
- // Foo.bar = { baz: ... };
289
- if ( n . isIdentifier ( path . node ) &&
290
- n . isObjectProperty ( path . parentPath ) &&
291
- n . isObjectExpression ( path . parentPath . parentPath ) &&
292
- n . isAssignmentExpression ( path . parentPath . parentPath . parentPath ) ) {
293
- identifiers = extractIdentifiers ( path . parentPath . parentPath . parentPath . get ( 'left' ) ) ;
294
- if ( identifiers . length >= 1 ) {
295
- inferMembershipFromIdentifiers ( comment , identifiers ) ;
320
+ var scope = 'instance' ;
321
+ if ( path . node . static == true ) {
322
+ scope = 'static' ;
296
323
}
297
- }
298
324
299
- // Shorthand methods on ordinary objects
300
- if ( n . isObjectMethod ( path . node ) &&
301
- n . isObjectExpression ( path . parentPath ) ) {
302
-
303
- // Foo = { bar() {} };
304
- // Foo.prototype = { bar() {} };
305
- // Foo.bar = { baz() {} };
306
- if ( n . isAssignmentExpression ( path . parentPath . parentPath ) ) {
307
- identifiers = extractIdentifiers ( path . parentPath . parentPath . get ( 'left' ) ) ;
308
- if ( identifiers . length >= 1 ) {
309
- inferMembershipFromIdentifiers ( comment , identifiers ) ;
310
- }
325
+ if ( path . parentPath . parentPath . isExpression ( ) ) {
326
+ return inferMembershipFromIdentifiers ( comment ,
327
+ extractIdentifiers ( path . parentPath . parentPath . parentPath . get ( 'left' ) ) ,
328
+ scope ) ;
311
329
}
312
330
313
- // var Foo = { bar() {} };
314
- if ( n . isVariableDeclarator ( path . parentPath . parentPath ) ) {
315
- identifiers = [ path . parentPath . parentPath . get ( 'id' ) . node . name ] ;
316
- inferMembershipFromIdentifiers ( comment , identifiers ) ;
331
+ var declarationNode = path . parentPath . parentPath . node ;
332
+ if ( ! declarationNode . id ) {
333
+ // export default function () {}
334
+ // export default class {}
335
+ // Use module name instead.
336
+ return inferMembershipFromIdentifiers ( comment , [ pathParse ( comment . context . file ) . name ] , scope ) ;
317
337
}
338
+
339
+ return inferMembershipFromIdentifiers ( comment , [ declarationNode . id . name ] , scope ) ;
318
340
}
319
341
320
- // var Foo = { bar: ... }
321
- if ( n . isIdentifier ( path ) &&
322
- n . isObjectProperty ( path . parentPath ) &&
323
- n . isObjectExpression ( path . parentPath . parentPath ) &&
324
- n . isVariableDeclarator ( path . parentPath . parentPath . parentPath ) ) {
325
- identifiers = [ path . parentPath . parentPath . parentPath . node . id . name ] ;
326
- inferMembershipFromIdentifiers ( comment , identifiers ) ;
342
+ // Whether something is an ObjectMethod (shorthand like foo() {} )
343
+ // or ObjectProperty (old fashioned like foo: function() {} )
344
+ // doesn't matter for the membership phase, as long as we end up knowing
345
+ // that it belongs to an object. So we first establish objectParent,
346
+ // and then have the logic for the numerous ways an object can be named.
347
+ var objectParent ;
348
+
349
+ if ( path . isIdentifier ( ) &&
350
+ path . parentPath . isObjectProperty ( ) &&
351
+ path . parentPath . parentPath . isObjectExpression ( ) ) {
352
+ objectParent = path . parentPath . parentPath ;
353
+ } else if ( path . isObjectMethod ( ) &&
354
+ path . parentPath . isObjectExpression ( ) ) {
355
+ objectParent = path . parentPath ;
327
356
}
328
357
329
- // class Foo { bar() { } }
330
- // var Foo = class { bar() { } }
331
- // class Foo { prop: T }
332
- // var Foo = class { prop: T }
333
- if ( ( n . isClassMethod ( path ) || n . isClassProperty ( path ) ) &&
334
- n . isClassBody ( path . parentPath ) &&
335
- n . isClass ( path . parentPath . parentPath ) ) {
336
- if ( n . isExpression ( path . parentPath . parentPath ) ) {
337
- identifiers = extractIdentifiers ( path . parentPath . parentPath . parentPath . get ( 'left' ) ) ;
338
- } else {
339
- var declarationNode = path . parentPath . parentPath . node ;
340
- if ( ! declarationNode . id ) {
341
- // export default function () {}
342
- // export default class {}
343
- // Use module name instead.
344
- identifiers = [ pathParse ( comment . context . file ) . name ] ;
345
- } else {
346
- identifiers = [ declarationNode . id . name ] ;
347
- }
348
- }
349
- var scope = 'instance' ;
350
- if ( path . node . static == true ) {
351
- scope = 'static' ;
358
+ // Confirm that the thing being documented is a property of an object.
359
+ if ( objectParent ) {
360
+
361
+ // The @lends comment is sometimes attached to the first property rather than
362
+ // the object expression itself.
363
+ var lendsIdentifiers = findLendsIdentifiers ( objectParent ) ||
364
+ findLendsIdentifiers ( objectParent . get ( 'properties' ) [ 0 ] ) ;
365
+
366
+ if ( lendsIdentifiers ) {
367
+
368
+ return inferMembershipFromIdentifiers ( comment , lendsIdentifiers ) ;
369
+
370
+ } else if ( objectParent . parentPath . isAssignmentExpression ( ) ) {
371
+
372
+ // Foo = { ... };
373
+ // Foo.prototype = { ... };
374
+ // Foo.bar = { ... };
375
+ return inferMembershipFromIdentifiers ( comment ,
376
+ extractIdentifiers ( objectParent . parentPath . get ( 'left' ) ) ) ;
377
+
378
+ } else if ( objectParent . parentPath . isVariableDeclarator ( ) ) {
379
+
380
+ // var Foo = { ... };
381
+ return inferMembershipFromIdentifiers ( comment ,
382
+ [ objectParent . parentPath . get ( 'id' ) . node . name ] ) ;
383
+
384
+ } else if ( objectParent . parentPath . isExportDefaultDeclaration ( ) ) {
385
+
386
+ // export default { ... };
387
+ return inferMembershipFromIdentifiers ( comment ,
388
+ [ inferModuleName ( currentModule || comment ) ] ) ;
389
+
352
390
}
353
- inferMembershipFromIdentifiers ( comment , identifiers , scope ) ;
391
+
354
392
}
355
393
356
394
// var function Foo() {
0 commit comments