jsduck 3.11.2 → 4.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/lib/jsduck/lexer.rb CHANGED
@@ -145,7 +145,7 @@ module JsDuck
145
145
  :type => :doc_comment,
146
146
  # Calculate current line number, starting with 1
147
147
  :linenr => @input.string[0...@input.pos].count("\n") + 1,
148
- :value => @input.scan_until(/\*\/|\Z/)
148
+ :value => @input.scan_until(/\*\/|\Z/).sub(/\A\/\*\*/, "").sub(/\*\/\Z/, "")
149
149
  }
150
150
  elsif @input.check(/\/\*/)
151
151
  # skip multiline comment
data/lib/jsduck/logger.rb CHANGED
@@ -101,8 +101,6 @@ module JsDuck
101
101
  elsif !@warnings.has_key?(type)
102
102
  warn(nil, "Unknown warning type #{type}")
103
103
  end
104
-
105
- return false
106
104
  end
107
105
 
108
106
  # Formats filename and line number for output
data/lib/jsduck/merger.rb CHANGED
@@ -1,246 +1,97 @@
1
- require 'jsduck/logger'
2
- require 'jsduck/meta_tag_registry'
1
+ require 'jsduck/class'
3
2
 
4
3
  module JsDuck
5
4
 
6
- # Takes data from doc-comment and code that follows it and combines
7
- # these to pieces of information into one. The code comes from
8
- # JsDuck::Parser and doc-comment from JsDuck::DocParser.
5
+ # Takes data from comment and code that follows it and combines
6
+ # these two pieces of information into one. The code comes from
7
+ # JsDuck::Ast and comment from JsDuck::DocAst.
9
8
  #
10
9
  # The main method merge() produces a hash as a result.
11
10
  class Merger
12
- # Allow passing in filename and line for error reporting
13
- attr_accessor :filename
14
- attr_accessor :linenr
15
11
 
16
- def initialize
17
- @filename = ""
18
- @linenr = 0
19
- @meta_tags = MetaTagRegistry.instance
20
- end
12
+ # Takes a docset and merges the :comment and :code inside it,
13
+ # producing hash as a result.
14
+ def merge(docset)
15
+ docs = docset[:comment]
16
+ code = docset[:code]
21
17
 
22
- def merge(docs, code)
23
- case detect_doc_type(docs, code)
18
+ case docset[:tagname]
24
19
  when :class
25
- create_class(docs, code)
26
- when :event
27
- create_event(docs, code)
28
- when :method
29
- create_method(docs, code)
30
- when :cfg
31
- create_cfg(docs, code)
32
- when :property
33
- create_property(docs, code)
34
- when :css_var
35
- create_css_var(docs, code)
36
- when :css_mixin
37
- create_css_mixin(docs, code)
20
+ result = merge_class(docs, code)
21
+ when :method, :event, :css_mixin
22
+ result = merge_like_method(docs, code)
23
+ when :cfg, :property, :css_var
24
+ result = merge_like_property(docs, code)
38
25
  end
39
- end
40
26
 
41
- # Detects whether the doc-comment is for class, cfg, event, method or property.
42
- def detect_doc_type(docs, code)
43
- doc_map = build_doc_map(docs)
27
+ result[:linenr] = docset[:linenr]
44
28
 
45
- if doc_map[:class]
46
- :class
47
- elsif doc_map[:event]
48
- :event
49
- elsif doc_map[:method]
50
- :method
51
- elsif doc_map[:property] || doc_map[:type]
52
- :property
53
- elsif doc_map[:css_var]
54
- :css_var
55
- elsif doc_map[:cfg] && doc_map[:cfg].length == 1
56
- # When just one @cfg, avoid treating it as @class
57
- :cfg
58
- elsif code[:type] == :ext_define
59
- :class
60
- elsif code[:type] == :assignment && class_name?(*code[:left])
61
- :class
62
- elsif code[:type] == :function && class_name?(code[:name])
63
- :class
64
- elsif code[:type] == :css_mixin
65
- :css_mixin
66
- elsif code[:type] == :css_var
67
- :css_var
68
- elsif doc_map[:cfg]
69
- :cfg
70
- elsif code[:type] == :function
71
- :method
72
- elsif code[:type] == :assignment && code[:right] && code[:right][:type] == :function
73
- :method
74
- elsif doc_map[:return] || doc_map[:param]
75
- :method
76
- else
77
- :property
78
- end
29
+ result
79
30
  end
80
31
 
81
- # Class name begins with upcase char
82
- def class_name?(*name_chain)
83
- return name_chain.last =~ /\A[A-Z]/
84
- end
32
+ private
85
33
 
86
- def create_class(docs, code)
87
- groups = group_class_docs(docs)
88
- result = create_bare_class(groups[:class], code)
89
- result[:members] = create_class_members(groups, result[:name])
90
- result[:statics] = Class.default_members_hash
91
- result
92
- end
34
+ def merge_class(docs, code)
35
+ h = do_merge(docs, code, {
36
+ :mixins => [],
37
+ :alternateClassNames => [],
38
+ :requires => [],
39
+ :uses => [],
40
+ :singleton => false,
41
+ })
93
42
 
94
- # Gathers all tags until first @cfg or @constructor into the first
95
- # bare :class group. We have a special case for @xtype which in
96
- # ExtJS comments often appears after @constructor - so we
97
- # explicitly place it into :class group.
98
- #
99
- # Then gathers each @cfg and tags following it into :cfg group, so
100
- # that it becomes array of arrays of tags. This is to allow some
101
- # configs to be marked with @private or whatever else.
102
- #
103
- # Finally gathers tags after @constructor into its group.
104
- def group_class_docs(docs)
105
- groups = {:class => [], :cfg => [], :constructor => []}
106
- group_name = :class
107
- docs.each do |tag|
108
- if tag[:tagname] == :cfg || tag[:tagname] == :constructor
109
- group_name = tag[:tagname]
110
- if tag[:tagname] == :cfg
111
- groups[:cfg] << []
112
- end
113
- end
43
+ # Ignore extending of the Object class
44
+ h[:extends] = nil if h[:extends] == "Object"
114
45
 
115
- if tag[:tagname] == :alias
116
- groups[:class] << tag
117
- elsif group_name == :cfg
118
- groups[:cfg].last << tag
119
- else
120
- groups[group_name] << tag
121
- end
122
- end
123
- groups
124
- end
46
+ h[:aliases] = build_aliases_hash(h[:aliases] || [])
47
+ # Used by Aggregator to determine if we're dealing with Ext4 code
48
+ h[:code_type] = code[:tagname]
49
+ h[:members] = Class.default_members_hash
50
+ h[:statics] = Class.default_members_hash
125
51
 
126
- def create_bare_class(docs, code)
127
- doc_map = build_doc_map(docs)
128
- return add_shared({
129
- :tagname => :class,
130
- :name => detect_name(:class, doc_map, code, :full_name),
131
- :doc => detect_doc(docs),
132
- :extends => detect_extends(doc_map, code),
133
- :mixins => detect_list(:mixins, doc_map, code),
134
- :alternateClassNames => detect_list(:alternateClassNames, doc_map, code),
135
- :aliases => detect_aliases(doc_map, code),
136
- :singleton => detect_singleton(doc_map, code),
137
- :requires => detect_list(:requires, doc_map, code),
138
- :uses => detect_list(:uses, doc_map, code),
139
- # Used by Aggregator to determine if we're dealing with Ext4 code
140
- :code_type => code[:type],
141
- }, doc_map)
52
+ h
142
53
  end
143
54
 
144
- def create_class_members(groups, owner)
145
- members = Class.default_members_hash
146
- members[:cfg] = groups[:cfg].map { |tags| create_cfg(tags, {}, owner) }
147
- if groups[:constructor].length > 0
148
- constr = create_method(groups[:constructor], {})
149
- constr[:owner] = owner
150
- members[:method] << constr
151
- end
152
- members
55
+ def merge_like_method(docs, code)
56
+ h = do_merge(docs, code)
57
+ h[:params] = merge_params(docs, code)
58
+ h
153
59
  end
154
60
 
155
- def create_method(docs, code)
156
- doc_map = build_doc_map(docs)
157
- name = detect_name(:method, doc_map, code)
158
- return add_shared({
159
- :tagname => :method,
160
- :name => name,
161
- :owner => detect_owner(doc_map),
162
- :doc => detect_doc(docs),
163
- :params => detect_params(:method, doc_map, code),
164
- :return => detect_return(doc_map, name == "constructor" ? "Object" : "undefined"),
165
- :throws => detect_throws(doc_map),
166
- }, doc_map)
167
- end
61
+ def merge_like_property(docs, code)
62
+ h = do_merge(docs, code)
168
63
 
169
- def create_event(docs, code)
170
- doc_map = build_doc_map(docs)
171
- return add_shared({
172
- :tagname => :event,
173
- :name => detect_name(:event, doc_map, code),
174
- :owner => detect_owner(doc_map),
175
- :doc => detect_doc(docs),
176
- :params => detect_params(:event, doc_map, code),
177
- }, doc_map)
178
- end
64
+ h[:type] = merge_if_code_matches(:type, docs, code)
65
+ if h[:type] == nil
66
+ h[:type] = code[:tagname] == :method ? "Function" : "Object"
67
+ end
179
68
 
180
- def create_cfg(docs, code, owner = nil)
181
- doc_map = build_doc_map(docs)
182
- return add_shared({
183
- :tagname => :cfg,
184
- :name => detect_name(:cfg, doc_map, code),
185
- :owner => detect_owner(doc_map) || owner,
186
- :type => detect_type(:cfg, doc_map, code),
187
- :doc => detect_doc(docs),
188
- :default => detect_default(:cfg, doc_map, code),
189
- :properties => detect_subproperties(docs, :cfg),
190
- :accessor => !!doc_map[:accessor],
191
- :evented => !!doc_map[:evented],
192
- }, doc_map)
69
+ h[:default] = merge_if_code_matches(:default, docs, code)
70
+ h
193
71
  end
194
72
 
195
- def create_property(docs, code)
196
- doc_map = build_doc_map(docs)
197
- return add_shared({
198
- :tagname => :property,
199
- :name => detect_name(:property, doc_map, code),
200
- :owner => detect_owner(doc_map),
201
- :type => detect_type(:property, doc_map, code),
202
- :doc => detect_doc(docs),
203
- :default => detect_default(:property, doc_map, code),
204
- :properties => detect_subproperties(docs, :property),
205
- }, doc_map)
206
- end
73
+ # --- helpers ---
207
74
 
208
- def create_css_var(docs, code)
209
- doc_map = build_doc_map(docs)
210
- return add_shared({
211
- :tagname => :css_var,
212
- :name => detect_name(:css_var, doc_map, code),
213
- :owner => detect_owner(doc_map),
214
- :type => detect_type(:css_var, doc_map, code),
215
- :default => detect_default(:css_var, doc_map, code),
216
- :doc => detect_doc(docs),
217
- }, doc_map)
218
- end
75
+ def do_merge(docs, code, defaults={})
76
+ h = {}
77
+ docs.each_pair do |key, value|
78
+ h[key] = docs[key] || code[key] || defaults[key]
79
+ end
219
80
 
220
- def create_css_mixin(docs, code)
221
- doc_map = build_doc_map(docs)
222
- return add_shared({
223
- :tagname => :css_mixin,
224
- :name => detect_name(:css_mixin, doc_map, code),
225
- :owner => detect_owner(doc_map),
226
- :doc => detect_doc(docs),
227
- :params => detect_params(:css_mixin, doc_map, code),
228
- }, doc_map)
229
- end
81
+ h[:name] = merge_name(docs, code)
82
+ h[:id] = create_member_id(h)
230
83
 
231
- # Detects properties common for each doc-object and adds them
232
- def add_shared(hash, doc_map)
233
- hash.merge!({
234
- :inheritable => !!doc_map[:inheritable],
235
- :inheritdoc => doc_map[:inheritdoc] ? doc_map[:inheritdoc].first : nil,
236
- :meta => detect_meta(doc_map),
237
- })
238
- # copy :private also to main hash
239
- hash[:private] = true if hash[:meta][:private]
84
+ # Copy private to meta
85
+ h[:meta][:private] = h[:private] if h[:private]
240
86
 
241
- hash[:id] = create_member_id(hash)
87
+ # Copy :static and :inheritable flags from code if present
88
+ h[:meta][:static] = true if code[:meta] && code[:meta][:static]
89
+ h[:inheritable] = true if code[:inheritable]
242
90
 
243
- return hash
91
+ # Remember auto-detection info
92
+ h[:autodetected] = code[:autodetected] if code[:autodetected]
93
+
94
+ h
244
95
  end
245
96
 
246
97
  def create_member_id(m)
@@ -249,117 +100,10 @@ module JsDuck
249
100
  "#{m[:meta][:static] ? 'static-' : ''}#{m[:tagname]}-#{name}"
250
101
  end
251
102
 
252
- def detect_name(tagname, doc_map, code, name_type = :last_name)
253
- main_tag = doc_map[tagname] ? doc_map[tagname].first : {}
254
- if main_tag[:name]
255
- main_tag[:name]
256
- elsif doc_map[:constructor]
257
- "constructor"
258
- elsif code[:type] == :function || code[:type] == :css_mixin || code[:type] == :css_var
259
- code[:name]
260
- elsif code[:type] == :assignment
261
- name_type == :full_name ? code[:left].join(".") : code[:left].last
262
- elsif code[:type] == :ext_define
263
- name_type == :full_name ? code[:name] : code[:name].split(/\./).last
264
- else
265
- ""
266
- end
267
- end
268
-
269
- def detect_owner(doc_map)
270
- if doc_map[:member]
271
- doc_map[:member].first[:member]
272
- else
273
- nil
274
- end
275
- end
276
-
277
- def detect_type(tagname, doc_map, code)
278
- main_tag = doc_map[tagname] ? doc_map[tagname].first : {}
279
- if main_tag[:type]
280
- return main_tag[:type]
281
- elsif doc_map[:type]
282
- return doc_map[:type].first[:type]
283
- elsif code_matches_doc?(tagname, doc_map, code)
284
- if code[:type] == :function
285
- return "Function"
286
- elsif code[:type] == :assignment && code[:right]
287
- if code[:right][:type] == :function
288
- return "Function"
289
- elsif code[:right][:type] == :literal && code[:right][:class] != nil
290
- return code[:right][:class]
291
- end
292
- elsif code[:type] == :css_var && code[:value][:type] != nil
293
- return code[:value][:type]
294
- end
295
- end
296
- return "Object"
297
- end
298
-
299
- def detect_extends(doc_map, code)
300
- if doc_map[:extends]
301
- cls = doc_map[:extends].first[:extends]
302
- elsif code[:type] == :assignment && code[:right] && code[:right][:type] == :ext_extend
303
- cls = code[:right][:extend].join(".")
304
- elsif code[:type] == :ext_define
305
- # Classes defined with Ext.define will automatically inherit from Ext.Base
306
- cls = code[:extend] || "Ext.Base"
307
- else
308
- cls = nil
309
- end
310
- # Ignore extending of the Object class
311
- cls == "Object" ? nil : cls
312
- end
313
-
314
- def detect_default(tagname, doc_map, code)
315
- main_tag = doc_map[tagname] ? doc_map[tagname].first : {}
316
- if main_tag[:default]
317
- main_tag[:default]
318
- elsif code_matches_doc?(tagname, doc_map, code) && code[:type] == :assignment && code[:right]
319
- code[:right][:value]
320
- elsif code_matches_doc?(tagname, doc_map, code) && code[:type] == :css_var && code[:value][:default]
321
- code[:value][:default]
322
- end
323
- end
324
-
325
- # True if the name detected from code matches with explicitly documented name.
326
- # Also true when no explicit name documented.
327
- def code_matches_doc?(tagname, doc_map, code)
328
- explicit_name = detect_name(tagname, doc_map, {})
329
- implicit_name = detect_name(tagname, {}, code)
330
- return explicit_name == "" || explicit_name == implicit_name
331
- end
332
-
333
- # for detecting mixins and alternateClassNames
334
- def detect_list(type, doc_map, code)
335
- if doc_map[type]
336
- doc_map[type].map {|d| d[type] }.flatten
337
- elsif code[:type] == :ext_define && code[type]
338
- code[type]
339
- else
340
- []
341
- end
342
- end
343
-
344
- def detect_aliases(doc_map, code)
345
- if doc_map[:alias]
346
- build_aliases_hash(doc_map[:alias].map {|tag| tag[:name] })
347
- elsif code[:xtype] || code[:alias]
348
- hash = {}
349
- build_aliases_hash(code[:xtype].map {|xtype| "widget."+xtype }, hash) if code[:xtype]
350
- build_aliases_hash(code[:alias], hash) if code[:alias]
351
- hash
352
- else
353
- {}
354
- end
355
- end
356
-
357
103
  # Given array of full alias names like "foo.bar", "foo.baz"
358
104
  # build hash like {"foo" => ["bar", "baz"]}
359
- #
360
- # When hash given as second argument, then merges the aliases into
361
- # it instead of creating a new hash.
362
- def build_aliases_hash(aliases, hash={})
105
+ def build_aliases_hash(aliases)
106
+ hash={}
363
107
  aliases.each do |a|
364
108
  if a =~ /^([^.]+)\.(.+)$/
365
109
  if hash[$1]
@@ -372,44 +116,19 @@ module JsDuck
372
116
  hash
373
117
  end
374
118
 
375
- def detect_meta(doc_map)
376
- meta = {}
377
- (doc_map[:meta] || []).map do |tag|
378
- meta[tag[:name]] = [] unless meta[tag[:name]]
379
- meta[tag[:name]] << tag[:doc]
380
- end
381
-
382
- meta.each_pair do |key, value|
383
- tag = @meta_tags[key]
384
- meta[key] = tag.to_value(tag.boolean ? true : value)
385
- end
386
-
387
- meta[:required] = true if detect_required(doc_map)
388
- meta
389
- end
390
-
391
- def detect_singleton(doc_map, code)
392
- !!(doc_map[:singleton] || code[:type] == :ext_define && code[:singleton])
393
- end
394
-
395
- def detect_required(doc_map)
396
- doc_map[:cfg] && doc_map[:cfg].first[:optional] == false
397
- end
398
-
399
- def detect_params(tagname, doc_map, code)
400
- implicit = code_matches_doc?(tagname, doc_map, code) ? detect_implicit_params(code) : []
401
- explicit = detect_explicit_params(doc_map)
119
+ def merge_params(docs, code)
120
+ explicit = docs[:params] || []
121
+ implicit = code_matches_doc?(docs, code) ? (code[:params] || []) : []
402
122
  # Override implicit parameters with explicit ones
403
123
  # But if explicit ones exist, don't append the implicit ones.
404
124
  params = []
405
125
  (explicit.length > 0 ? explicit.length : implicit.length).times do |i|
406
126
  im = implicit[i] || {}
407
127
  ex = explicit[i] || {}
408
- doc = ex[:doc] || im[:doc] || ""
409
128
  params << {
410
129
  :type => ex[:type] || im[:type] || "Object",
411
130
  :name => ex[:name] || im[:name] || "",
412
- :doc => doc,
131
+ :doc => ex[:doc] || im[:doc] || "",
413
132
  :optional => ex[:optional] || false,
414
133
  :default => ex[:default],
415
134
  :properties => ex[:properties] || [],
@@ -418,101 +137,36 @@ module JsDuck
418
137
  params
419
138
  end
420
139
 
421
- def detect_implicit_params(code)
422
- if code[:type] == :function
423
- code[:params]
424
- elsif code[:type] == :assignment && code[:right] && code[:right][:type] == :function
425
- code[:right][:params]
426
- else
427
- []
428
- end
429
- end
430
-
431
- def detect_explicit_params(doc_map)
432
- combine_properties(doc_map[:param] || [])
433
- end
434
-
435
- def detect_subproperties(docs, tagname)
436
- prop_docs = docs.find_all {|tag| tag[:tagname] == tagname}
437
- prop_docs.length > 0 ? combine_properties(prop_docs)[0][:properties] : []
438
- end
439
-
440
- def combine_properties(raw_items)
441
- # First item can't be namespaced, if it is ignore the rest.
442
- if raw_items[0] && raw_items[0][:name] =~ /\./
443
- return [raw_items[0]]
444
- end
445
-
446
- # build name-index of all items
447
- index = {}
448
- raw_items.each {|it| index[it[:name]] = it }
449
-
450
- # If item name has no dots, add it directly to items array.
451
- # Otherwise look up the parent of item and add it as the
452
- # property of that parent.
453
- items = []
454
- raw_items.each do |it|
455
- if it[:name] =~ /^(.+)\.([^.]+)$/
456
- it[:name] = $2
457
- parent = index[$1]
458
- if parent
459
- parent[:properties] = [] unless parent[:properties]
460
- parent[:properties] << it
461
- else
462
- Logger.instance.warn(:subproperty, "Ignoring subproperty #{$1}.#{$2}, no parent found with name '#{$1}'.", @filename, @linenr)
463
- end
140
+ def merge_name(docs, code)
141
+ if docs[:name]
142
+ docs[:name]
143
+ elsif code[:name]
144
+ if docs[:tagname] == :class
145
+ code[:name]
464
146
  else
465
- items << it
147
+ code[:name].split(/\./).last
466
148
  end
149
+ else
150
+ ""
467
151
  end
468
- items
469
- end
470
-
471
- def detect_return(doc_map, default_type="undefined")
472
- ret = doc_map[:return] ? doc_map[:return].first : {}
473
- return {
474
- :type => ret[:type] || default_type,
475
- :name => ret[:name] || "return",
476
- :doc => ret[:doc] || "",
477
- :properties => doc_map[:return] ? detect_subproperties(doc_map[:return], :return) : []
478
- }
479
152
  end
480
153
 
481
- def detect_throws(doc_map)
482
- return unless doc_map[:throws]
483
-
484
- doc_map[:throws].map do |throws|
485
- {
486
- :type => throws[:type] || "Object",
487
- :doc => throws[:doc] || "",
488
- }
154
+ def merge_if_code_matches(key, docs, code, default=nil)
155
+ if docs[key]
156
+ docs[key]
157
+ elsif code[key] && code_matches_doc?(docs, code)
158
+ code[key]
159
+ else
160
+ default
489
161
  end
490
162
  end
491
163
 
492
- # Combines :doc-s of most tags
493
- # Ignores tags that have doc comment themselves and subproperty tags
494
- def detect_doc(docs)
495
- ignore_tags = [:param, :return, :meta]
496
- doc_tags = docs.find_all { |tag| !ignore_tags.include?(tag[:tagname]) && !subproperty?(tag) }
497
- doc_tags.map { |tag| tag[:doc] }.compact.join(" ")
498
- end
499
-
500
- def subproperty?(tag)
501
- (tag[:tagname] == :cfg || tag[:tagname] == :property) && tag[:name] =~ /\./
164
+ # True if the name detected from code matches with explicitly documented name.
165
+ # Also true when no explicit name documented.
166
+ def code_matches_doc?(docs, code)
167
+ return docs[:name] == nil || docs[:name] == code[:name]
502
168
  end
503
169
 
504
- # Build map of at-tags for quick lookup
505
- def build_doc_map(docs)
506
- map = {}
507
- docs.each do |tag|
508
- if map[tag[:tagname]]
509
- map[tag[:tagname]] << tag
510
- else
511
- map[tag[:tagname]] = [tag]
512
- end
513
- end
514
- map
515
- end
516
170
  end
517
171
 
518
172
  end