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/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +45 -0
- data/README.md +0 -3
- data/Rakefile +5 -2
- data/js-classes/String.js +1 -1
- data/jsduck.gemspec +5 -2
- data/lib/jsduck/accessors.rb +1 -0
- data/lib/jsduck/aggregator.rb +3 -0
- data/lib/jsduck/app.rb +3 -6
- data/lib/jsduck/ast.rb +446 -0
- data/lib/jsduck/class_doc_expander.rb +135 -0
- data/lib/jsduck/css_lexer.rb +1 -1
- data/lib/jsduck/css_parser.rb +8 -11
- data/lib/jsduck/doc_ast.rb +305 -0
- data/lib/jsduck/doc_parser.rb +33 -28
- data/lib/jsduck/doc_type.rb +58 -0
- data/lib/jsduck/esprima.rb +32 -0
- data/lib/jsduck/evaluator.rb +69 -0
- data/lib/jsduck/guides.rb +12 -11
- data/lib/jsduck/inherit_doc.rb +80 -14
- data/lib/jsduck/js_parser.rb +162 -373
- data/lib/jsduck/lexer.rb +1 -1
- data/lib/jsduck/logger.rb +0 -2
- data/lib/jsduck/merger.rb +89 -435
- data/lib/jsduck/options.rb +4 -14
- data/lib/jsduck/serializer.rb +262 -0
- data/lib/jsduck/source_file.rb +5 -18
- data/lib/jsduck/source_file_parser.rb +72 -0
- metadata +33 -9
- data/lib/jsduck/js_literal_builder.rb +0 -21
- data/lib/jsduck/js_literal_parser.rb +0 -106
- data/lib/jsduck/tag/chainable.rb +0 -14
@@ -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
|
data/lib/jsduck/css_lexer.rb
CHANGED
@@ -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
|
data/lib/jsduck/css_parser.rb
CHANGED
@@ -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 =>
|
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
|
-
{:
|
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
|
-
:
|
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
|
-
:
|
56
|
+
:tagname => :css_var,
|
58
57
|
:name => name,
|
59
|
-
:
|
60
|
-
|
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
|