jsduck 5.0.0.beta2 → 5.0.0.beta3

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.
Files changed (71) hide show
  1. data/Rakefile +14 -4
  2. data/bin/jsduck +3 -1
  3. data/jsduck.gemspec +2 -2
  4. data/lib/jsduck/app.rb +8 -0
  5. data/lib/jsduck/assets.rb +3 -0
  6. data/lib/jsduck/batch_processor.rb +2 -0
  7. data/lib/jsduck/categories/class_name.rb +2 -26
  8. data/lib/jsduck/categories/factory.rb +5 -43
  9. data/lib/jsduck/columns.rb +56 -0
  10. data/lib/jsduck/doc/delimited_parser.rb +105 -0
  11. data/lib/jsduck/doc/scanner.rb +2 -1
  12. data/lib/jsduck/doc/standard_tag_parser.rb +37 -71
  13. data/lib/jsduck/guide_anchors.rb +32 -0
  14. data/lib/jsduck/guide_toc.rb +49 -0
  15. data/lib/jsduck/guides.rb +14 -32
  16. data/lib/jsduck/inline/video.rb +2 -8
  17. data/lib/jsduck/js/ast.rb +13 -305
  18. data/lib/jsduck/js/class.rb +245 -0
  19. data/lib/jsduck/js/event.rb +34 -0
  20. data/lib/jsduck/js/fires.rb +42 -0
  21. data/lib/jsduck/js/method.rb +94 -0
  22. data/lib/jsduck/js/method_calls.rb +40 -0
  23. data/lib/jsduck/js/node.rb +29 -0
  24. data/lib/jsduck/js/property.rb +64 -0
  25. data/lib/jsduck/js/{function.rb → returns.rb} +8 -3
  26. data/lib/jsduck/js/scoped_traverser.rb +42 -0
  27. data/lib/jsduck/logger.rb +13 -1
  28. data/lib/jsduck/merger.rb +34 -27
  29. data/lib/jsduck/news.rb +128 -0
  30. data/lib/jsduck/options.rb +59 -2
  31. data/lib/jsduck/params_merger.rb +47 -0
  32. data/lib/jsduck/process/accessors.rb +8 -2
  33. data/lib/jsduck/process/fires.rb +71 -0
  34. data/lib/jsduck/process/importer.rb +19 -1
  35. data/lib/jsduck/render/class.rb +11 -4
  36. data/lib/jsduck/render/signature_util.rb +14 -0
  37. data/lib/jsduck/tag/alias.rb +0 -20
  38. data/lib/jsduck/tag/alternate_class_names.rb +0 -5
  39. data/lib/jsduck/tag/cfg.rb +30 -5
  40. data/lib/jsduck/tag/class.rb +45 -2
  41. data/lib/jsduck/tag/css_mixin.rb +8 -4
  42. data/lib/jsduck/tag/css_var.rb +26 -5
  43. data/lib/jsduck/tag/default.rb +2 -8
  44. data/lib/jsduck/tag/enum.rb +7 -10
  45. data/lib/jsduck/tag/event.rb +12 -4
  46. data/lib/jsduck/tag/extends.rb +0 -6
  47. data/lib/jsduck/tag/fires.rb +53 -0
  48. data/lib/jsduck/tag/icons/cfg.png +0 -0
  49. data/lib/jsduck/tag/icons/css_mixin.png +0 -0
  50. data/lib/jsduck/tag/icons/css_var.png +0 -0
  51. data/lib/jsduck/tag/icons/event.png +0 -0
  52. data/lib/jsduck/tag/icons/method.png +0 -0
  53. data/lib/jsduck/tag/icons/property.png +0 -0
  54. data/lib/jsduck/tag/member_tag.rb +130 -0
  55. data/lib/jsduck/tag/method.rb +44 -4
  56. data/lib/jsduck/tag/param.rb +8 -60
  57. data/lib/jsduck/tag/property.rb +28 -5
  58. data/lib/jsduck/tag/tag.rb +3 -75
  59. data/lib/jsduck/tag/type.rb +1 -11
  60. data/lib/jsduck/tag_registry.rb +6 -48
  61. data/lib/jsduck/web/css.rb +8 -1
  62. data/lib/jsduck/web/data.rb +2 -1
  63. data/lib/jsduck/web/index_html.rb +1 -0
  64. data/lib/jsduck/web/member_icons.rb +43 -0
  65. data/lib/jsduck/web/search.rb +3 -2
  66. data/lib/jsduck/web/writer.rb +8 -0
  67. metadata +31 -27
  68. data/lib/jsduck/docs_code_comparer.rb +0 -44
  69. data/lib/jsduck/render/signature.rb +0 -94
  70. data/lib/jsduck/tag/autodetected.rb +0 -21
  71. data/lib/jsduck/tag/name.rb +0 -36
@@ -80,6 +80,35 @@ module JsDuck
80
80
  end
81
81
  end
82
82
 
83
+ # Returns the type of node.
84
+ def type
85
+ @node["type"]
86
+ end
87
+
88
+ # Extracts all sub-statements and sub-expressions from AST node.
89
+ # Without looking at the type of node, we just take all the
90
+ # sub-hashes and -arrays.
91
+ #
92
+ # A downside of this simple algorithm is that the statements can
93
+ # end up in different order than they are in source code. For
94
+ # example the IfStatement has three parts in the following
95
+ # order: "test", "consequent", "alternate": But because we're
96
+ # looping over a hash, they might end up in a totally different
97
+ # order.
98
+ def body
99
+ body = []
100
+ @node.each_pair do |key, value|
101
+ if key == "type" || key == "range"
102
+ # ignore
103
+ elsif value.is_a?(Array)
104
+ body.concat(value.map {|v| Js::Node.create(v) })
105
+ elsif value.is_a?(Hash)
106
+ body << Js::Node.create(value)
107
+ end
108
+ end
109
+ body
110
+ end
111
+
83
112
  # Iterates over keys and values in ObjectExpression. The keys
84
113
  # are turned into strings, but values are left as is for further
85
114
  # processing.
@@ -0,0 +1,64 @@
1
+ require "jsduck/util/singleton"
2
+
3
+ module JsDuck
4
+ module Js
5
+
6
+ # Auto-detection of properties.
7
+ class Property
8
+ include Util::Singleton
9
+
10
+ # Checks if AST node is a property, and if so, returns doc-hash
11
+ # with property name and various auto-detected attributes.
12
+ # When not a property returns nil.
13
+ def detect(ast)
14
+ exp = ast.expression_statement? ? ast["expression"] : nil
15
+ var = ast.variable_declaration? ? ast["declarations"][0] : nil
16
+
17
+ # foo = ...
18
+ if exp && exp.assignment_expression?
19
+ make(exp["left"].to_s, exp["right"])
20
+
21
+ # var foo = ...
22
+ elsif var
23
+ make(var["id"].to_s, var["init"])
24
+
25
+ # foo: ...
26
+ elsif ast.property?
27
+ make(ast["key"].key_value, ast["value"])
28
+
29
+ # foo;
30
+ elsif exp && exp.identifier?
31
+ make(exp.to_s)
32
+
33
+ # "foo" (inside some expression)
34
+ elsif ast.string?
35
+ make(ast.to_value)
36
+
37
+ # "foo"; (as a statement of it's own)
38
+ elsif exp && exp.string?
39
+ make(exp.to_value)
40
+
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ # Produces a doc-hash for a property.
47
+ def make(name=nil, ast=nil)
48
+ return {
49
+ :tagname => :property,
50
+ :name => name,
51
+ :type => ast && ast.value_type,
52
+ :default => ast && default(ast),
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ def default(ast)
59
+ ast.to_value != nil ? ast.to_s : nil
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -3,10 +3,15 @@ require "jsduck/util/singleton"
3
3
  module JsDuck
4
4
  module Js
5
5
 
6
- # Analyzes the AST of a FunctionDeclaration or FunctionExpression.
7
- class Function
6
+ # Analyzes the AST of a Function for possible return values.
7
+ class Returns
8
8
  include Util::Singleton
9
9
 
10
+ # True when function always finishes with returning this.
11
+ def chainable?(ast)
12
+ detect(ast) == [:this]
13
+ end
14
+
10
15
  # Detects possible return types of the given function.
11
16
  #
12
17
  # For now there are three possible detected return values:
@@ -18,7 +23,7 @@ module JsDuck
18
23
  #
19
24
  # * :other - some other value is returned.
20
25
  #
21
- def return_types(ast)
26
+ def detect(ast)
22
27
  h = return_types_hash(ast["body"]["body"])
23
28
 
24
29
  # Replace the special :void value that signifies possibility of
@@ -0,0 +1,42 @@
1
+ module JsDuck
2
+ module Js
3
+
4
+ # Traverses syntax tree while keeping track of variables that are
5
+ # bound to `this`.
6
+ class ScopedTraverser
7
+ def initialize
8
+ @this_map = {
9
+ "this" => true
10
+ }
11
+ end
12
+
13
+ # Loops recursively over all the sub-nodes of the given node,
14
+ # calling the provided block for each sub-node.
15
+ def traverse(node, &block)
16
+ node.body.each do |child|
17
+ yield child
18
+
19
+ if this_var?(child)
20
+ var_name = child["id"].to_s
21
+ @this_map[var_name] = true
22
+ end
23
+
24
+ traverse(child, &block)
25
+ end
26
+ end
27
+
28
+ # True when variable with given name is bound to `this`.
29
+ def this?(var_name)
30
+ @this_map[var_name]
31
+ end
32
+
33
+ private
34
+
35
+ # True when initialization of variable with `this`
36
+ def this_var?(node)
37
+ node.type == "VariableDeclarator" && node["init"].type == "ThisExpression"
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -10,8 +10,14 @@ module JsDuck
10
10
  # Set to true to enable verbose logging
11
11
  attr_accessor :verbose
12
12
 
13
+ # Set true to force colored output.
14
+ # Set false to force no colors.
15
+ attr_accessor :colors
16
+
13
17
  def initialize
14
18
  @verbose = false
19
+ @colors = nil
20
+
15
21
  @warning_docs = [
16
22
  [:global, "Member doesn't belong to any class"],
17
23
  [:inheritdoc, "@inheritdoc referring to unknown class or member"],
@@ -36,6 +42,7 @@ module JsDuck
36
42
  [:type_syntax, "Syntax error in {type definition}"],
37
43
  [:type_name, "Unknown type referenced in {type definition}"],
38
44
  [:enum, "Enum with invalid values or no values at all"],
45
+ [:fires, "@fires references unknown event"],
39
46
 
40
47
  [:image, "{@img} referring to missing file"],
41
48
  [:image_unused, "An image exists in --images dir that's not used"],
@@ -173,6 +180,11 @@ module JsDuck
173
180
  $stderr.puts error.backtrace
174
181
  end
175
182
 
183
+ # True when at least one warning was logged.
184
+ def warnings_logged?
185
+ @shown_warnings.length > 0
186
+ end
187
+
176
188
  private
177
189
 
178
190
  COLORS = {
@@ -193,7 +205,7 @@ module JsDuck
193
205
  # Only does color output when STDERR is attached to TTY
194
206
  # i.e. is not piped/redirected.
195
207
  def paint(color_name, msg)
196
- if Util::OS.windows? || !$stderr.tty?
208
+ if @colors == false || @colors == nil && (Util::OS.windows? || !$stderr.tty?)
197
209
  msg
198
210
  else
199
211
  COLORS[color_name] + msg + CLEAR
@@ -14,15 +14,17 @@ module JsDuck
14
14
  # producing hash as a result.
15
15
  def merge(docset, filename="", linenr=0)
16
16
  docs = docset[:comment]
17
- code = docset[:code]
17
+ code = process_code(docset[:tagname], docset[:code])
18
18
 
19
19
  h = {
20
20
  :tagname => docset[:tagname],
21
+ :name => docs[:name] || code[:name] || "",
22
+ :autodetected => code[:autodetected] || {},
21
23
  :files => [{:filename => filename, :linenr => linenr}],
22
24
  }
23
25
 
24
- invoke_merge_in_tags(h, docs, code)
25
26
  general_merge(h, docs, code)
27
+ invoke_merge_in_member_tag(h, docs, code)
26
28
 
27
29
  # Needs to be calculated last, as it relies on the existance of
28
30
  # :name, :static and :tagname fields.
@@ -33,42 +35,47 @@ module JsDuck
33
35
 
34
36
  private
35
37
 
36
- # Invokes the #merge methods of tags registered for the given
37
- # merge context.
38
- def invoke_merge_in_tags(h, docs, code)
39
- TagRegistry.mergers(h[:tagname]).each do |tag|
40
- tag.merge(h, docs, code)
41
- end
38
+ # Applies processing to extract fields relevant to the member type.
39
+ def process_code(tagname, code)
40
+ TagRegistry.get_by_name(tagname).process_code(code)
41
+ end
42
+
43
+ # Invokes the #merge method in corresponding member or :class tag.
44
+ def invoke_merge_in_member_tag(h, docs, code)
45
+ TagRegistry.get_by_name(h[:tagname]).merge(h, docs, code)
42
46
  end
43
47
 
44
48
  # Applies default merge algorithm to the rest of the data.
45
49
  def general_merge(h, docs, code)
46
- # Merge in all items in docs that don't occour already in result.
50
+ # Add all items in docs not already in result.
47
51
  docs.each_pair do |key, value|
48
- h[key] = value unless h.has_key?(key) || Merger::explicit?(key)
52
+ h[key] = value unless h[key]
49
53
  end
50
- # Then add all in the items from code not already in result.
51
- code.each_pair do |key, value|
52
- h[key] = value unless h.has_key?(key) || Merger::explicit?(key)
54
+
55
+ # Add all items in code not already in result and mark them as
56
+ # auto-detected. But only if the explicit and auto-detected
57
+ # names don't conflict.
58
+ if Merger.can_be_autodetected?(docs, code)
59
+ code.each_pair do |key, value|
60
+ unless h[key]
61
+ h[key] = value
62
+ mark_autodetected(h, key)
63
+ end
64
+ end
53
65
  end
54
66
  end
55
67
 
56
- # True when given key gets merged explicitly and should therefore
57
- # be skipped when auto-merging.
58
- def self.explicit?(key)
59
- @explicit = explictly_merged_fields unless @explicit
60
- @explicit[key]
68
+ # True if the name detected from code matches with explicitly
69
+ # documented name. Also true when no explicit name documented.
70
+ #
71
+ # Note: This method is also called from ParamsMerger.
72
+ def self.can_be_autodetected?(docs, code)
73
+ docs[:name] == nil || docs[:name] == code[:name]
61
74
  end
62
75
 
63
- # Generates a lookup-hash of tagnames which are explicitly merged.
64
- def self.explictly_merged_fields
65
- mergers = {}
66
- member_types = TagRegistry.member_type_names + [:class]
67
- tags = member_types.map {|type| TagRegistry.mergers(type) }.flatten.uniq
68
- tags.each do |tag|
69
- mergers[tag.tagname] = true
70
- end
71
- mergers
76
+ # Stores the key as flag into h[:autodetected]
77
+ def mark_autodetected(h, key)
78
+ h[:autodetected][key] = true
72
79
  end
73
80
 
74
81
  end
@@ -0,0 +1,128 @@
1
+ require 'jsduck/util/null_object'
2
+ require 'jsduck/columns'
3
+
4
+ module JsDuck
5
+
6
+ class News
7
+ # Creates News object from relations data when --import option
8
+ # specified.
9
+ def self.create(relations, doc_formatter, opts)
10
+ if opts[:imports].length > 0
11
+ News.new(relations, doc_formatter)
12
+ else
13
+ Util::NullObject.new(:to_html => "")
14
+ end
15
+ end
16
+
17
+ # Generates list of new classes & members in this version.
18
+ def initialize(relations, doc_formatter)
19
+ @doc_formatter = doc_formatter
20
+ @columns = Columns.new(:members)
21
+ @new_items = filter_new_items(relations)
22
+ end
23
+
24
+ # Returns the HTML
25
+ def to_html(style="")
26
+ return [
27
+ "<div id='news-content' style='#{style}'>",
28
+ "<div class='section'>",
29
+ "<h1>New in this version</h1>",
30
+ render_columns(@new_items),
31
+ "<div style='clear:both'></div>",
32
+ "</div>",
33
+ "</div>",
34
+ ].flatten.join("\n")
35
+ end
36
+
37
+ private
38
+
39
+ def filter_new_items(relations)
40
+ classes = []
41
+ new_items = []
42
+
43
+ relations.each do |cls|
44
+ if !cls[:private]
45
+ if cls[:new]
46
+ classes << cls
47
+ else
48
+ members = filter_new_members(cls)
49
+ if members.length > 0
50
+ new_items << {:name => cls[:name], :members => members}
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ new_items.sort! {|a, b| a[:name] <=> b[:name] }
57
+
58
+ # Place the new classes section at the beginning
59
+ if classes.length > 0
60
+ new_items.unshift({:name => "New classes", :members => classes})
61
+ end
62
+
63
+ new_items
64
+ end
65
+
66
+ def filter_new_members(cls)
67
+ members = []
68
+ cls.all_local_members.each do |m|
69
+ members << m if m[:new] && visible?(m)
70
+ end
71
+ members = discard_accessors(members)
72
+ members.sort! {|a, b| a[:name] <=> b[:name] }
73
+ end
74
+
75
+ def visible?(member)
76
+ !member[:private] && !member[:hide]
77
+ end
78
+
79
+ def discard_accessors(members)
80
+ accessors = {}
81
+ members.find_all {|m| m[:accessor] }.each do |cfg|
82
+ accessors["set" + upcase_first(cfg[:name])] = true
83
+ accessors["get" + upcase_first(cfg[:name])] = true
84
+ accessors[cfg[:name].downcase + "change"] = true if cfg[:evented]
85
+ end
86
+
87
+ members.reject {|m| accessors[m[:name]] }
88
+ end
89
+
90
+ def upcase_first(str)
91
+ str[0,1].upcase + str[1..-1]
92
+ end
93
+
94
+ def render_columns(new_items)
95
+ align = ["left-column", "middle-column", "right-column"]
96
+ i = -1
97
+ return @columns.split(new_items, 3).map do |col|
98
+ i += 1
99
+ [
100
+ "<div class='#{align[i]}'>",
101
+ render_col(col),
102
+ "</div>",
103
+ ]
104
+ end
105
+ end
106
+
107
+ def render_col(col)
108
+ return col.map do |item|
109
+ [
110
+ "<h3>#{item[:name]}</h3>",
111
+ "<ul class='links'>",
112
+ item[:members].map {|m| "<li>" + link(m) + "</li>" },
113
+ "</ul>",
114
+ ]
115
+ end
116
+ end
117
+
118
+ def link(m)
119
+ if m[:tagname] == :class
120
+ @doc_formatter.link(m[:name], nil, m[:name])
121
+ else
122
+ @doc_formatter.link(m[:owner], m[:name], m[:name], m[:tagname], m[:static])
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -42,9 +42,11 @@ module JsDuck
42
42
  attr_accessor :tests
43
43
  attr_accessor :comments_url
44
44
  attr_accessor :comments_domain
45
+ attr_accessor :search
45
46
  attr_accessor :ignore_html
46
47
 
47
48
  # Debugging
49
+ attr_accessor :warnings_exit_nonzero
48
50
  attr_accessor :template_dir
49
51
  attr_accessor :template_links
50
52
  attr_accessor :extjs_path
@@ -92,8 +94,7 @@ module JsDuck
92
94
  ]
93
95
  @ext4_events = nil
94
96
 
95
- @version = "5.0.0.beta2"
96
-
97
+ @version = "5.0.0.beta3"
97
98
  # Customizing output
98
99
  @title = "Documentation - JSDuck"
99
100
  @header = "<strong>Documentation</strong> JSDuck"
@@ -121,9 +122,11 @@ module JsDuck
121
122
  @tests = false
122
123
  @comments_url = nil
123
124
  @comments_domain = nil
125
+ @search = {}
124
126
  @ignore_html = {}
125
127
 
126
128
  # Debugging
129
+ @warnings_exit_nonzero = false
127
130
  @root_dir = File.dirname(File.dirname(File.dirname(__FILE__)))
128
131
  @template_dir = @root_dir + "/template-min"
129
132
  @template_links = false
@@ -140,6 +143,7 @@ module JsDuck
140
143
  Logger.set_warning(:all, true)
141
144
  Logger.set_warning(:link_auto, false)
142
145
  Logger.set_warning(:param_count, false)
146
+ Logger.set_warning(:fires, false)
143
147
 
144
148
  @optparser = create_option_parser
145
149
  end
@@ -450,6 +454,8 @@ module JsDuck
450
454
  end
451
455
 
452
456
  opts.on('--comments-domain=STRING',
457
+ "A name identifying the subset of comments.",
458
+ "",
453
459
  "A string consisting of <name>/<version>.",
454
460
  "",
455
461
  "For example: ext-js/4",
@@ -458,6 +464,37 @@ module JsDuck
458
464
  @comments_domain = domain
459
465
  end
460
466
 
467
+ opts.on('--search-url=URL',
468
+ "Address of guides search server.",
469
+ "",
470
+ "When supplied, the search for guides is performed through this",
471
+ "external service and the results merged together with API search.",
472
+ "The search server must respond to JSONP requests.",
473
+ "",
474
+ "For example: http://sencha.com/docsearch",
475
+ "",
476
+ "Must be used together with --search-domain option.",
477
+ "",
478
+ "This option is EXPERIMENTAL.") do |url|
479
+ @search[:url] = url
480
+ end
481
+
482
+ opts.on('--search-domain=STRING',
483
+ "A name identifying the subset to search from.",
484
+ "",
485
+ "A string consisting of <name>/<version>.",
486
+ "",
487
+ "Tells the search engine which product and version",
488
+ "to include into search.",
489
+ "",
490
+ "For example: Ext JS/4.2.0",
491
+ "",
492
+ "Must be used together with --search-url option.",
493
+ "",
494
+ "This option is EXPERIMENTAL.") do |domain|
495
+ @search[:product], @search[:version] = domain.split(/\//)
496
+ end
497
+
461
498
  opts.separator ""
462
499
  opts.separator "Tweaking:"
463
500
  opts.separator ""
@@ -653,6 +690,26 @@ module JsDuck
653
690
  end
654
691
  end
655
692
 
693
+ opts.on('--warnings-exit-nonzero',
694
+ "Exits with non-zero exit code on warnings.",
695
+ "",
696
+ "By default JSDuck only exits with non-zero exit code",
697
+ "when a fatal error is encountered (code 1).",
698
+ "",
699
+ "With this option the exit code will be 2 when any warning",
700
+ "gets printed.") do
701
+ @warnings_exit_nonzero = true
702
+ end
703
+
704
+ opts.on('--[no-]color',
705
+ "Turn on/off colorized terminal output.",
706
+ "",
707
+ "By default the colored output is on, but gets disabled",
708
+ "automatically when output is not an interactive terminal",
709
+ "(or when running on Windows system).") do |on|
710
+ Logger.colors = on
711
+ end
712
+
656
713
  opts.on('-p', '--processes=COUNT',
657
714
  "The number of parallel processes to use.",
658
715
  "",