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.
@@ -0,0 +1,135 @@
1
+ module JsDuck
2
+
3
+ # Expands class docset into one or more docsets.
4
+ #
5
+ # The resulting list can contain the following:
6
+ #
7
+ # - the base class docset itself (always present)
8
+ # - configs detected from comment
9
+ # - constructor detected from comment
10
+ # - members detected from code
11
+ #
12
+ class ClassDocExpander
13
+
14
+ # Expands class-docset into multiple docsets.
15
+ def expand(docset)
16
+ @constructor_found = false
17
+
18
+ expand_comment(docset) + expand_code(docset)
19
+ end
20
+
21
+ private
22
+
23
+ # Handles old syntax where configs and constructor are part of class
24
+ # doc-comment.
25
+ #
26
+ # Gathers all tags until first @cfg or @constructor into the first
27
+ # bare :class group. We have a special case for @xtype which in
28
+ # ExtJS comments often appears after @constructor - so we
29
+ # explicitly place it into :class group.
30
+ #
31
+ # Then gathers each @cfg and tags following it into :cfg group, so
32
+ # that it becomes array of arrays of tags. This is to allow some
33
+ # configs to be marked with @private or whatever else.
34
+ #
35
+ # Finally gathers tags after @constructor into its group.
36
+ def expand_comment(docset)
37
+ groups = {
38
+ :class => [],
39
+ :cfg => [],
40
+ :constructor => [],
41
+ }
42
+
43
+ # By default everything goes to :class group
44
+ group_name = :class
45
+
46
+ docset[:comment].each do |tag|
47
+ tagname = tag[:tagname]
48
+
49
+ if tagname == :cfg || tagname == :constructor
50
+ group_name = tagname
51
+ if tagname == :cfg
52
+ groups[:cfg] << []
53
+ end
54
+ end
55
+
56
+ if tagname == :alias
57
+ # For backwards compatibility allow @xtype after @constructor
58
+ groups[:class] << tag
59
+ elsif group_name == :cfg
60
+ groups[:cfg].last << tag
61
+ else
62
+ groups[group_name] << tag
63
+ end
64
+ end
65
+
66
+ groups_to_docsets(groups, docset)
67
+ end
68
+
69
+ # Turns groups hash into list of docsets
70
+ def groups_to_docsets(groups, docset)
71
+ results = []
72
+ results << {
73
+ :tagname => :class,
74
+ :type => docset[:type],
75
+ :comment => groups[:class],
76
+ :code => docset[:code],
77
+ :linenr => docset[:linenr],
78
+ }
79
+ groups[:cfg].each do |cfg|
80
+ results << {
81
+ :tagname => :cfg,
82
+ :type => docset[:type],
83
+ :comment => cfg,
84
+ :code => {},
85
+ :linenr => docset[:linenr],
86
+ }
87
+ end
88
+ if groups[:constructor].length > 0
89
+ # Remember that a constructor is already found and ignore if a
90
+ # constructor is detected from code.
91
+ @constructor_found = true
92
+
93
+ results << {
94
+ :tagname => :method,
95
+ :type => docset[:type],
96
+ :comment => groups[:constructor],
97
+ :code => {},
98
+ :linenr => docset[:linenr],
99
+ }
100
+ end
101
+ results
102
+ end
103
+
104
+ # Turns auto-detected class members into docsets in their own
105
+ # right.
106
+ def expand_code(docset)
107
+ results = []
108
+
109
+ if docset[:code]
110
+
111
+ (docset[:code][:members] || []).each do |m|
112
+ results << code_to_docset(m) unless @constructor_found && m[:name] == "constructor"
113
+ end
114
+
115
+ (docset[:code][:statics] || []).each do |m|
116
+ results << code_to_docset(m)
117
+ end
118
+ end
119
+
120
+ results
121
+ end
122
+
123
+ def code_to_docset(m)
124
+ return {
125
+ :tagname => m[:tagname],
126
+ :type => :no_comment,
127
+ :comment => [],
128
+ :code => m,
129
+ :linenr => m[:linenr],
130
+ }
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -121,7 +121,7 @@ module JsDuck
121
121
  :type => :doc_comment,
122
122
  # Calculate current line number, starting with 1
123
123
  :linenr => @input.string[0...@input.pos].count("\n") + 1,
124
- :value => @input.scan_until(/\*\/|\Z/)
124
+ :value => @input.scan_until(/\*\/|\Z/).sub(/\A\/\*\*/, "").sub(/\*\/\Z/, "")
125
125
  }
126
126
  elsif @input.check(/\/\*/)
127
127
  # skip multiline comment
@@ -1,12 +1,10 @@
1
1
  require 'jsduck/css_lexer'
2
- require 'jsduck/doc_parser'
3
2
 
4
3
  module JsDuck
5
4
 
6
5
  class CssParser
7
6
  def initialize(input, options = {})
8
7
  @lex = CssLexer.new(input)
9
- @doc_parser = DocParser.new
10
8
  @docs = []
11
9
  end
12
10
 
@@ -17,9 +15,10 @@ module JsDuck
17
15
  if look(:doc_comment)
18
16
  comment = @lex.next(true)
19
17
  @docs << {
20
- :comment => @doc_parser.parse(comment[:value]),
18
+ :comment => comment[:value],
21
19
  :linenr => comment[:linenr],
22
- :code => code_block
20
+ :code => code_block,
21
+ :type => :doc_comment,
23
22
  }
24
23
  else
25
24
  @lex.next
@@ -35,7 +34,7 @@ module JsDuck
35
34
  elsif look(:var, ":")
36
35
  var_declaration
37
36
  else
38
- {:type => :nop}
37
+ {:tagname => :nop}
39
38
  end
40
39
  end
41
40
 
@@ -43,7 +42,7 @@ module JsDuck
43
42
  def mixin_declaration
44
43
  match("@mixin")
45
44
  return {
46
- :type => :css_mixin,
45
+ :tagname => :css_mixin,
47
46
  :name => look(:ident) ? match(:ident) : nil,
48
47
  }
49
48
  end
@@ -54,12 +53,10 @@ module JsDuck
54
53
  match(":")
55
54
  value_list = css_value
56
55
  return {
57
- :type => :css_var,
56
+ :tagname => :css_var,
58
57
  :name => name,
59
- :value => {
60
- :default => value_list.map {|v| v[:value] }.join(" "),
61
- :type => value_type(value_list),
62
- }
58
+ :default => value_list.map {|v| v[:value] }.join(" "),
59
+ :type => value_type(value_list),
63
60
  }
64
61
  end
65
62
 
@@ -0,0 +1,305 @@
1
+ require 'jsduck/logger'
2
+ require 'jsduck/meta_tag_registry'
3
+
4
+ module JsDuck
5
+
6
+ # Detects docs info directly from comment.
7
+ class DocAst
8
+ # Allow passing in filename and line for error reporting
9
+ attr_accessor :filename
10
+ attr_accessor :linenr
11
+
12
+ def initialize
13
+ @filename = ""
14
+ @linenr = 0
15
+ @meta_tags = MetaTagRegistry.instance
16
+ end
17
+
18
+ # Given tagname and array of tags from DocParser, produces docs
19
+ # of the type determined by tagname.
20
+ def detect(tagname, docs)
21
+ case tagname
22
+ when :class
23
+ create_class(docs)
24
+ when :event
25
+ create_event(docs)
26
+ when :method
27
+ create_method(docs)
28
+ when :cfg
29
+ create_cfg(docs)
30
+ when :property
31
+ create_property(docs)
32
+ when :css_var
33
+ create_css_var(docs)
34
+ when :css_mixin
35
+ create_css_mixin(docs)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def create_class(docs)
42
+ doc_map = build_doc_map(docs)
43
+ return add_shared({
44
+ :tagname => :class,
45
+ :name => detect_name(:class, doc_map),
46
+ :doc => detect_doc(docs),
47
+ :extends => detect_extends(doc_map),
48
+ :mixins => detect_list(:mixins, doc_map),
49
+ :alternateClassNames => detect_list(:alternateClassNames, doc_map),
50
+ :aliases => detect_aliases(doc_map),
51
+ :singleton => !!doc_map[:singleton],
52
+ :requires => detect_list(:requires, doc_map),
53
+ :uses => detect_list(:uses, doc_map),
54
+ }, doc_map)
55
+ end
56
+
57
+ def create_method(docs)
58
+ doc_map = build_doc_map(docs)
59
+ name = detect_name(:method, doc_map)
60
+ return add_shared({
61
+ :tagname => :method,
62
+ :name => name,
63
+ :owner => detect_owner(doc_map),
64
+ :doc => detect_doc(docs),
65
+ :params => detect_params(doc_map),
66
+ :return => detect_return(doc_map, name == "constructor" ? "Object" : "undefined"),
67
+ :throws => detect_throws(doc_map),
68
+ }, doc_map)
69
+ end
70
+
71
+ def create_event(docs)
72
+ doc_map = build_doc_map(docs)
73
+ return add_shared({
74
+ :tagname => :event,
75
+ :name => detect_name(:event, doc_map),
76
+ :owner => detect_owner(doc_map),
77
+ :doc => detect_doc(docs),
78
+ :params => detect_params(doc_map),
79
+ }, doc_map)
80
+ end
81
+
82
+ def create_cfg(docs)
83
+ doc_map = build_doc_map(docs)
84
+ return add_shared({
85
+ :tagname => :cfg,
86
+ :name => detect_name(:cfg, doc_map),
87
+ :owner => detect_owner(doc_map),
88
+ :type => detect_type(:cfg, doc_map),
89
+ :doc => detect_doc(docs),
90
+ :default => detect_default(:cfg, doc_map),
91
+ :properties => detect_subproperties(:cfg, docs),
92
+ :accessor => !!doc_map[:accessor],
93
+ :evented => !!doc_map[:evented],
94
+ }, doc_map)
95
+ end
96
+
97
+ def create_property(docs)
98
+ doc_map = build_doc_map(docs)
99
+ return add_shared({
100
+ :tagname => :property,
101
+ :name => detect_name(:property, doc_map),
102
+ :owner => detect_owner(doc_map),
103
+ :type => detect_type(:property, doc_map),
104
+ :doc => detect_doc(docs),
105
+ :default => detect_default(:property, doc_map),
106
+ :properties => detect_subproperties(:property, docs),
107
+ }, doc_map)
108
+ end
109
+
110
+ def create_css_var(docs)
111
+ doc_map = build_doc_map(docs)
112
+ return add_shared({
113
+ :tagname => :css_var,
114
+ :name => detect_name(:css_var, doc_map),
115
+ :owner => detect_owner(doc_map),
116
+ :type => detect_type(:css_var, doc_map),
117
+ :default => detect_default(:css_var, doc_map),
118
+ :doc => detect_doc(docs),
119
+ }, doc_map)
120
+ end
121
+
122
+ def create_css_mixin(docs)
123
+ doc_map = build_doc_map(docs)
124
+ return add_shared({
125
+ :tagname => :css_mixin,
126
+ :name => detect_name(:css_mixin, doc_map),
127
+ :owner => detect_owner(doc_map),
128
+ :doc => detect_doc(docs),
129
+ :params => detect_params(doc_map),
130
+ }, doc_map)
131
+ end
132
+
133
+ # Detects properties common for each doc-object and adds them
134
+ def add_shared(hash, doc_map)
135
+ hash.merge!({
136
+ :inheritable => !!doc_map[:inheritable],
137
+ :inheritdoc => extract(doc_map, :inheritdoc),
138
+ :meta => detect_meta(doc_map),
139
+ })
140
+
141
+ # copy :private also to main hash
142
+ hash[:private] = hash[:meta][:private] ? true : nil
143
+
144
+ return hash
145
+ end
146
+
147
+ def detect_name(tagname, doc_map)
148
+ name = extract(doc_map, tagname, :name)
149
+ if name
150
+ name
151
+ else
152
+ doc_map[:constructor] ? "constructor" : nil
153
+ end
154
+ end
155
+
156
+ def extract(doc_map, tagname, propname = nil)
157
+ tag = doc_map[tagname] ? doc_map[tagname].first : nil
158
+ if tag && propname
159
+ tag[propname]
160
+ else
161
+ tag
162
+ end
163
+ end
164
+
165
+ def detect_owner(doc_map)
166
+ extract(doc_map, :member, :member)
167
+ end
168
+
169
+ def detect_type(tagname, doc_map)
170
+ extract(doc_map, tagname, :type) || extract(doc_map, :type, :type)
171
+ end
172
+
173
+ def detect_extends(doc_map)
174
+ extract(doc_map, :extends, :extends)
175
+ end
176
+
177
+ def detect_default(tagname, doc_map)
178
+ extract(doc_map, tagname, :default)
179
+ end
180
+
181
+ # for detecting mixins and alternateClassNames
182
+ def detect_list(type, doc_map)
183
+ if doc_map[type]
184
+ doc_map[type].map {|d| d[type] }.flatten
185
+ else
186
+ nil
187
+ end
188
+ end
189
+
190
+ def detect_aliases(doc_map)
191
+ if doc_map[:alias]
192
+ doc_map[:alias].map {|tag| tag[:name] }
193
+ else
194
+ nil
195
+ end
196
+ end
197
+
198
+ def detect_meta(doc_map)
199
+ meta = {}
200
+ (doc_map[:meta] || []).map do |tag|
201
+ meta[tag[:name]] = [] unless meta[tag[:name]]
202
+ meta[tag[:name]] << tag[:doc]
203
+ end
204
+
205
+ meta.each_pair do |key, value|
206
+ tag = @meta_tags[key]
207
+ meta[key] = tag.to_value(tag.boolean ? true : value)
208
+ end
209
+
210
+ meta[:required] = true if detect_required(doc_map)
211
+ meta
212
+ end
213
+
214
+ def detect_required(doc_map)
215
+ doc_map[:cfg] && doc_map[:cfg].first[:optional] == false
216
+ end
217
+
218
+ def detect_params(doc_map)
219
+ combine_properties(doc_map[:param] || [])
220
+ end
221
+
222
+ def detect_subproperties(tagname, docs)
223
+ prop_docs = docs.find_all {|tag| tag[:tagname] == tagname}
224
+ prop_docs.length > 0 ? combine_properties(prop_docs)[0][:properties] : []
225
+ end
226
+
227
+ def combine_properties(raw_items)
228
+ # First item can't be namespaced, if it is ignore the rest.
229
+ if raw_items[0] && raw_items[0][:name] =~ /\./
230
+ return [raw_items[0]]
231
+ end
232
+
233
+ # build name-index of all items
234
+ index = {}
235
+ raw_items.each {|it| index[it[:name]] = it }
236
+
237
+ # If item name has no dots, add it directly to items array.
238
+ # Otherwise look up the parent of item and add it as the
239
+ # property of that parent.
240
+ items = []
241
+ raw_items.each do |it|
242
+ if it[:name] =~ /^(.+)\.([^.]+)$/
243
+ it[:name] = $2
244
+ parent = index[$1]
245
+ if parent
246
+ parent[:properties] = [] unless parent[:properties]
247
+ parent[:properties] << it
248
+ else
249
+ Logger.instance.warn(:subproperty, "Ignoring subproperty #{$1}.#{$2}, no parent found with name '#{$1}'.", @filename, @linenr)
250
+ end
251
+ else
252
+ items << it
253
+ end
254
+ end
255
+ items
256
+ end
257
+
258
+ def detect_return(doc_map, default_type="undefined")
259
+ ret = extract(doc_map, :return) || {}
260
+ return {
261
+ :type => ret[:type] || default_type,
262
+ :name => ret[:name] || "return",
263
+ :doc => ret[:doc] || "",
264
+ :properties => doc_map[:return] ? detect_subproperties(:return, doc_map[:return]) : []
265
+ }
266
+ end
267
+
268
+ def detect_throws(doc_map)
269
+ return unless doc_map[:throws]
270
+
271
+ doc_map[:throws].map do |throws|
272
+ {
273
+ :type => throws[:type] || "Object",
274
+ :doc => throws[:doc] || "",
275
+ }
276
+ end
277
+ end
278
+
279
+ # Combines :doc-s of most tags
280
+ # Ignores tags that have doc comment themselves and subproperty tags
281
+ def detect_doc(docs)
282
+ ignore_tags = [:param, :return, :meta]
283
+ doc_tags = docs.find_all { |tag| !ignore_tags.include?(tag[:tagname]) && !subproperty?(tag) }
284
+ doc_tags.map { |tag| tag[:doc] }.compact.join(" ")
285
+ end
286
+
287
+ def subproperty?(tag)
288
+ (tag[:tagname] == :cfg || tag[:tagname] == :property) && tag[:name] =~ /\./
289
+ end
290
+
291
+ # Build map of at-tags for quick lookup
292
+ def build_doc_map(docs)
293
+ map = {}
294
+ docs.each do |tag|
295
+ if map[tag[:tagname]]
296
+ map[tag[:tagname]] << tag
297
+ else
298
+ map[tag[:tagname]] = [tag]
299
+ end
300
+ end
301
+ map
302
+ end
303
+ end
304
+
305
+ end