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
data/lib/jsduck/doc_parser.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
require 'strscan'
|
2
|
-
require 'jsduck/js_literal_parser'
|
3
|
-
require 'jsduck/js_literal_builder'
|
4
2
|
require 'jsduck/meta_tag_registry'
|
5
3
|
|
6
4
|
module JsDuck
|
@@ -50,9 +48,7 @@ module JsDuck
|
|
50
48
|
# Extracts content inside /** ... */
|
51
49
|
def purify(input)
|
52
50
|
result = []
|
53
|
-
#
|
54
|
-
input = input.sub(/\A\/\*\* ?/, "").sub(/ ?\*\/\Z/, "")
|
55
|
-
# Now we are left with only two types of lines:
|
51
|
+
# We can have two types of lines:
|
56
52
|
# - those beginning with *
|
57
53
|
# - and those without it
|
58
54
|
indent = nil
|
@@ -141,7 +137,7 @@ module JsDuck
|
|
141
137
|
elsif look(/@evented\b/)
|
142
138
|
boolean_at_tag(/@evented/, :evented)
|
143
139
|
elsif look(/@/)
|
144
|
-
|
140
|
+
match(/@/)
|
145
141
|
tag = @meta_tags[look(/\w+/)]
|
146
142
|
if tag
|
147
143
|
meta_at_tag(tag)
|
@@ -149,7 +145,7 @@ module JsDuck
|
|
149
145
|
@current_tag[:doc] += "@"
|
150
146
|
end
|
151
147
|
elsif look(/[^@]/)
|
152
|
-
@current_tag[:doc] +=
|
148
|
+
@current_tag[:doc] += match(/[^@]+/)
|
153
149
|
end
|
154
150
|
end
|
155
151
|
end
|
@@ -174,7 +170,7 @@ module JsDuck
|
|
174
170
|
else
|
175
171
|
# Fors singleline tags, scan to the end of line and finish the
|
176
172
|
# tag.
|
177
|
-
@current_tag[:doc] =
|
173
|
+
@current_tag[:doc] = match(/.*$/).strip
|
178
174
|
skip_white
|
179
175
|
@current_tag = prev_tag
|
180
176
|
end
|
@@ -300,7 +296,7 @@ module JsDuck
|
|
300
296
|
@current_tag[:type] = tdf[:type]
|
301
297
|
@current_tag[:optional] = true if tdf[:optional]
|
302
298
|
elsif look(/\S/)
|
303
|
-
@current_tag[:type] =
|
299
|
+
@current_tag[:type] = match(/\S+/)
|
304
300
|
end
|
305
301
|
skip_white
|
306
302
|
end
|
@@ -343,14 +339,14 @@ module JsDuck
|
|
343
339
|
end
|
344
340
|
|
345
341
|
if look(/#\w/)
|
346
|
-
|
342
|
+
match(/#/)
|
347
343
|
if look(/static-/)
|
348
344
|
@current_tag[:static] = true
|
349
|
-
|
345
|
+
match(/static-/)
|
350
346
|
end
|
351
347
|
if look(/(cfg|property|method|event|css_var|css_mixin)-/)
|
352
348
|
@current_tag[:type] = ident.to_sym
|
353
|
-
|
349
|
+
match(/-/)
|
354
350
|
end
|
355
351
|
@current_tag[:member] = ident
|
356
352
|
end
|
@@ -418,7 +414,7 @@ module JsDuck
|
|
418
414
|
def maybe_name
|
419
415
|
skip_horiz_white
|
420
416
|
if look(@ident_pattern)
|
421
|
-
@current_tag[:name] =
|
417
|
+
@current_tag[:name] = match(@ident_pattern)
|
422
418
|
end
|
423
419
|
end
|
424
420
|
|
@@ -430,17 +426,15 @@ module JsDuck
|
|
430
426
|
end
|
431
427
|
end
|
432
428
|
|
433
|
-
#
|
434
|
-
#
|
429
|
+
# Attempts to allow balanced braces in default value.
|
430
|
+
# When the nested parsing doesn't finish at closing "]",
|
431
|
+
# roll back to beginning and simply grab anything up to closing "]".
|
435
432
|
def default_value
|
436
433
|
start_pos = @input.pos
|
437
|
-
|
438
|
-
if
|
439
|
-
|
440
|
-
JsLiteralBuilder.new.to_s(lit)
|
434
|
+
value = parse_balanced(/\[/, /\]/, /[^\[\]]*/)
|
435
|
+
if look(/\]/)
|
436
|
+
value
|
441
437
|
else
|
442
|
-
# Otherwise reset parsing position to where we started
|
443
|
-
# and rescan up to "]" using simple regex.
|
444
438
|
@input.pos = start_pos
|
445
439
|
match(/[^\]]*/)
|
446
440
|
end
|
@@ -449,14 +443,8 @@ module JsDuck
|
|
449
443
|
# matches {...=} and returns text inside brackets
|
450
444
|
def typedef
|
451
445
|
match(/\{/)
|
452
|
-
name = @input.scan(/[^{}]*/)
|
453
446
|
|
454
|
-
|
455
|
-
# In such case we parse the definition so that the braces are balanced.
|
456
|
-
while @input.check(/[{]/)
|
457
|
-
name += "{" + typedef[:type] +"}"
|
458
|
-
name += @input.scan(/[^{}]*/)
|
459
|
-
end
|
447
|
+
name = parse_balanced(/\{/, /\}/, /[^{}]*/)
|
460
448
|
|
461
449
|
if name =~ /=$/
|
462
450
|
name = name.chop
|
@@ -470,6 +458,23 @@ module JsDuck
|
|
470
458
|
return {:type => name, :optional => optional}
|
471
459
|
end
|
472
460
|
|
461
|
+
# Helper method to parse a string up to a closing brace,
|
462
|
+
# balancing opening-closing braces in between.
|
463
|
+
#
|
464
|
+
# @param re_open The beginning brace regex
|
465
|
+
# @param re_close The closing brace regex
|
466
|
+
# @param re_rest Regex to match text without any braces
|
467
|
+
def parse_balanced(re_open, re_close, re_rest)
|
468
|
+
result = match(re_rest)
|
469
|
+
while look(re_open)
|
470
|
+
result += match(re_open)
|
471
|
+
result += parse_balanced(re_open, re_close, re_rest)
|
472
|
+
result += match(re_close)
|
473
|
+
result += match(re_rest)
|
474
|
+
end
|
475
|
+
result
|
476
|
+
end
|
477
|
+
|
473
478
|
# matches <ident_chain> <ident_chain> ... until line end
|
474
479
|
def class_list
|
475
480
|
skip_horiz_white
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module JsDuck
|
2
|
+
|
3
|
+
# Detects the type of documentation object: class, method, cfg, etc
|
4
|
+
class DocType
|
5
|
+
# Given parsed documentation and code, returns the tagname for
|
6
|
+
# documentation item.
|
7
|
+
#
|
8
|
+
# @param docs Result from DocParser
|
9
|
+
# @param code Result from Ast#detect or CssParser#parse
|
10
|
+
# @returns One of: :class, :method, :event, :cfg, :property, :css_var, :css_mixin
|
11
|
+
#
|
12
|
+
def detect(docs, code)
|
13
|
+
doc_map = build_doc_map(docs)
|
14
|
+
|
15
|
+
if doc_map[:class]
|
16
|
+
:class
|
17
|
+
elsif doc_map[:event]
|
18
|
+
:event
|
19
|
+
elsif doc_map[:method]
|
20
|
+
:method
|
21
|
+
elsif doc_map[:property] || doc_map[:type]
|
22
|
+
:property
|
23
|
+
elsif doc_map[:css_var]
|
24
|
+
:css_var
|
25
|
+
elsif doc_map[:cfg] && doc_map[:cfg].length == 1
|
26
|
+
# When just one @cfg, avoid treating it as @class
|
27
|
+
:cfg
|
28
|
+
elsif code[:tagname] == :class
|
29
|
+
:class
|
30
|
+
elsif code[:tagname] == :css_mixin
|
31
|
+
:css_mixin
|
32
|
+
elsif doc_map[:cfg]
|
33
|
+
:cfg
|
34
|
+
elsif doc_map[:constructor]
|
35
|
+
:method
|
36
|
+
else
|
37
|
+
code[:tagname]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Build map of at-tags for quick lookup
|
44
|
+
def build_doc_map(docs)
|
45
|
+
map = {}
|
46
|
+
docs.each do |tag|
|
47
|
+
if map[tag[:tagname]]
|
48
|
+
map[tag[:tagname]] << tag
|
49
|
+
else
|
50
|
+
map[tag[:tagname]] = [tag]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
map
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'execjs'
|
2
|
+
require 'json'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module JsDuck
|
6
|
+
|
7
|
+
# Runs Esprima.js through execjs (this will select any available
|
8
|
+
# JavaScript runtime - preferably therubyracer on MRI and JScript
|
9
|
+
# on Windows).
|
10
|
+
#
|
11
|
+
# Initialized as singleton to avoid loading the esprima.js more
|
12
|
+
# than once - otherwise performace will severely suffer.
|
13
|
+
class Esprima
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
esprima_path = File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__))))+"/esprima/esprima.js";
|
18
|
+
esprima = IO.read(esprima_path)
|
19
|
+
helper = "function runEsprima(js) { return JSON.stringify(esprima.parse(js, {comment: true, range: true, raw: true})); }"
|
20
|
+
@context = ExecJS.compile(esprima + "\n\n" + helper)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Parses JavaScript source code using Esprima.js
|
24
|
+
#
|
25
|
+
# Returns the resulting AST
|
26
|
+
def parse(input)
|
27
|
+
json = @context.call("runEsprima", input)
|
28
|
+
return JSON.parse(json, :max_nesting => false)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module JsDuck
|
2
|
+
|
3
|
+
# Evaluates Esprima AST node into Ruby object
|
4
|
+
class Evaluator
|
5
|
+
|
6
|
+
# Converts AST node into a value.
|
7
|
+
#
|
8
|
+
# - String literals become Ruby strings
|
9
|
+
# - Number literals become Ruby numbers
|
10
|
+
# - Regex literals become :regexp symbols
|
11
|
+
# - Array expressions become Ruby arrays
|
12
|
+
# - etc
|
13
|
+
#
|
14
|
+
# For anything it doesn't know how to evaluate (like a function
|
15
|
+
# expression) it throws exception.
|
16
|
+
#
|
17
|
+
def to_value(ast)
|
18
|
+
case ast["type"]
|
19
|
+
when "ArrayExpression"
|
20
|
+
ast["elements"].map {|e| to_value(e) }
|
21
|
+
when "ObjectExpression"
|
22
|
+
h = {}
|
23
|
+
ast["properties"].each do |p|
|
24
|
+
key = key_value(p["key"])
|
25
|
+
value = to_value(p["value"])
|
26
|
+
h[key] = value
|
27
|
+
end
|
28
|
+
h
|
29
|
+
when "BinaryExpression"
|
30
|
+
if ast["operator"] == "+"
|
31
|
+
to_value(ast["left"]) + to_value(ast["right"])
|
32
|
+
else
|
33
|
+
throw "Unable to handle operator: " + ast["operator"]
|
34
|
+
end
|
35
|
+
when "MemberExpression"
|
36
|
+
if base_css_prefix?(ast)
|
37
|
+
"x-"
|
38
|
+
else
|
39
|
+
throw "Unable to handle this MemberExpression"
|
40
|
+
end
|
41
|
+
when "Literal"
|
42
|
+
if ast["raw"] =~ /\A\//
|
43
|
+
:regexp
|
44
|
+
else
|
45
|
+
ast["value"]
|
46
|
+
end
|
47
|
+
else
|
48
|
+
throw "Unknown node type: " + ast["type"]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Turns object property key into string value
|
53
|
+
def key_value(key)
|
54
|
+
key["type"] == "Identifier" ? key["name"] : key["value"]
|
55
|
+
end
|
56
|
+
|
57
|
+
# True when MemberExpression == Ext.baseCSSPrefix
|
58
|
+
def base_css_prefix?(ast)
|
59
|
+
ast["computed"] == false &&
|
60
|
+
ast["object"]["type"] == "Identifier" &&
|
61
|
+
ast["object"]["name"] == "Ext" &&
|
62
|
+
ast["property"]["type"] == "Identifier" &&
|
63
|
+
ast["property"]["name"] == "baseCSSPrefix"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
data/lib/jsduck/guides.rb
CHANGED
@@ -36,13 +36,15 @@ module JsDuck
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# Modified each_item that also loads HTML for each guide
|
39
|
-
def each_item
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
def each_item(&block)
|
40
|
+
unless @loaded
|
41
|
+
super do |guide|
|
42
|
+
guide[:html] = load_guide(guide)
|
43
|
+
end
|
44
|
+
@loaded = true
|
45
45
|
end
|
46
|
+
|
47
|
+
super(&block)
|
46
48
|
end
|
47
49
|
|
48
50
|
# Modified to_array that excludes the :html from guide nodes
|
@@ -58,13 +60,12 @@ module JsDuck
|
|
58
60
|
def load_guide(guide)
|
59
61
|
in_dir = @path + "/guides/" + guide["name"]
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
guide_file = in_dir + "/README.md"
|
63
|
+
begin
|
64
|
+
return Logger.instance.warn(:guide, "Guide #{in_dir} not found") unless File.exists?(in_dir)
|
64
65
|
|
65
|
-
|
66
|
+
guide_file = in_dir + "/README.md"
|
67
|
+
return Logger.instance.warn(:guide, "README.md not found in #{in_dir}") unless File.exists?(guide_file)
|
66
68
|
|
67
|
-
begin
|
68
69
|
@formatter.doc_context = {:filename => guide_file, :linenr => 0}
|
69
70
|
name = File.basename(in_dir)
|
70
71
|
@formatter.img_path = "guides/#{name}"
|
data/lib/jsduck/inherit_doc.rb
CHANGED
@@ -13,20 +13,54 @@ module JsDuck
|
|
13
13
|
def resolve_all
|
14
14
|
@relations.each do |cls|
|
15
15
|
resolve_class(cls) if cls[:inheritdoc]
|
16
|
+
|
17
|
+
new_cfgs = []
|
16
18
|
cls.all_local_members.each do |member|
|
17
|
-
if member[:inheritdoc]
|
18
|
-
resolve(member)
|
19
|
+
if member[:inheritdoc] || member[:autodetected]
|
20
|
+
resolve(member, new_cfgs)
|
19
21
|
end
|
20
22
|
end
|
23
|
+
move_cfgs(cls, new_cfgs)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
27
|
+
private
|
28
|
+
|
24
29
|
# Copy over doc/params/return from parent member.
|
25
|
-
def resolve(m)
|
30
|
+
def resolve(m, new_cfgs)
|
26
31
|
parent = find_parent(m)
|
27
|
-
|
28
|
-
m[:
|
29
|
-
|
32
|
+
|
33
|
+
if m[:inheritdoc] && parent
|
34
|
+
m[:doc] = (m[:doc] + "\n\n" + parent[:doc]).strip
|
35
|
+
m[:params] = parent[:params] if parent[:params]
|
36
|
+
m[:return] = parent[:return] if parent[:return]
|
37
|
+
|
38
|
+
# remember properties that have changed to configs
|
39
|
+
if m[:tagname] != parent[:tagname]
|
40
|
+
new_cfgs << m
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
resolve_visibility(m, parent)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Changes given properties into configs within class
|
48
|
+
def move_cfgs(cls, members)
|
49
|
+
members.each do |m|
|
50
|
+
m[:tagname] = :cfg
|
51
|
+
cls[:members][:property].delete(m)
|
52
|
+
cls[:members][:cfg] << m
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# For auto-detected members/classes (which have @private == :inherit)
|
57
|
+
# Use the visibility from parent class (defaulting to private when no parent).
|
58
|
+
def resolve_visibility(m, parent)
|
59
|
+
if m[:autodetected]
|
60
|
+
if !parent || parent[:private]
|
61
|
+
m[:meta][:private] = m[:private] = true
|
62
|
+
end
|
63
|
+
end
|
30
64
|
end
|
31
65
|
|
32
66
|
# Finds parent member of the given member. When @inheritdoc names
|
@@ -34,7 +68,8 @@ module JsDuck
|
|
34
68
|
#
|
35
69
|
# If the parent also has @inheritdoc, continues recursively.
|
36
70
|
def find_parent(m)
|
37
|
-
|
71
|
+
inherit = m[:inheritdoc] || {}
|
72
|
+
if inherit[:cls]
|
38
73
|
# @inheritdoc MyClass#member
|
39
74
|
parent_cls = @relations[m[:inheritdoc][:cls]]
|
40
75
|
return warn("class not found", m) unless parent_cls
|
@@ -42,7 +77,7 @@ module JsDuck
|
|
42
77
|
parent = lookup_member(parent_cls, m)
|
43
78
|
return warn("member not found", m) unless parent
|
44
79
|
|
45
|
-
elsif
|
80
|
+
elsif inherit[:member]
|
46
81
|
# @inheritdoc #member
|
47
82
|
parent = lookup_member(@relations[m[:owner]], m)
|
48
83
|
return warn("member not found", m) unless parent
|
@@ -53,7 +88,10 @@ module JsDuck
|
|
53
88
|
mixins = @relations[m[:owner]].mixins
|
54
89
|
|
55
90
|
# Warn when no parent or mixins at all
|
56
|
-
|
91
|
+
if !parent_cls && mixins.length == 0
|
92
|
+
warn("parent class not found", m) unless m[:autodetected]
|
93
|
+
return nil
|
94
|
+
end
|
57
95
|
|
58
96
|
# First check for the member in all mixins, because members
|
59
97
|
# from mixins override those from parent class. Looking first
|
@@ -69,21 +107,49 @@ module JsDuck
|
|
69
107
|
end
|
70
108
|
|
71
109
|
# Only when both parent and mixins fail, throw warning
|
72
|
-
|
110
|
+
if !parent
|
111
|
+
warn("parent member not found", m) unless m[:autodetected]
|
112
|
+
return nil
|
113
|
+
end
|
73
114
|
end
|
74
115
|
|
75
116
|
return parent[:inheritdoc] ? find_parent(parent) : parent
|
76
117
|
end
|
77
118
|
|
78
119
|
def lookup_member(cls, m)
|
79
|
-
inherit = m[:inheritdoc]
|
80
|
-
|
120
|
+
inherit = m[:inheritdoc] || {}
|
121
|
+
name = inherit[:member] || m[:name]
|
122
|
+
tagname = inherit[:type] || m[:tagname]
|
123
|
+
static = inherit[:static] || m[:meta][:static]
|
124
|
+
|
125
|
+
# Auto-detected properties can override either a property or a
|
126
|
+
# config. So look for both types.
|
127
|
+
if m[:autodetected] && tagname == :property
|
128
|
+
cfg = cls.get_members(name, :cfg, static)[0]
|
129
|
+
prop = cls.get_members(name, :property, static)[0]
|
130
|
+
|
131
|
+
if cfg && prop
|
132
|
+
prop
|
133
|
+
elsif cfg
|
134
|
+
cfg
|
135
|
+
elsif prop
|
136
|
+
prop
|
137
|
+
else
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
else
|
142
|
+
cls.get_members(name, tagname, static)[0]
|
143
|
+
end
|
81
144
|
end
|
82
145
|
|
83
146
|
# Copy over doc from parent class.
|
84
147
|
def resolve_class(cls)
|
85
148
|
parent = find_class_parent(cls)
|
86
|
-
|
149
|
+
|
150
|
+
if parent
|
151
|
+
cls[:doc] = (cls[:doc] + "\n\n" + parent[:doc]).strip
|
152
|
+
end
|
87
153
|
end
|
88
154
|
|
89
155
|
def find_class_parent(cls)
|
@@ -107,7 +173,7 @@ module JsDuck
|
|
107
173
|
msg = "@inheritdoc #{item[:inheritdoc][:cls]}"+ (i_member ? "#" + i_member : "") + " - " + msg
|
108
174
|
Logger.instance.warn(:inheritdoc, msg, context[:filename], context[:linenr])
|
109
175
|
|
110
|
-
return
|
176
|
+
return nil
|
111
177
|
end
|
112
178
|
|
113
179
|
end
|