jsduck 3.11.2 → 4.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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