jsduck 3.11.2 → 4.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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