jsduck 4.10.4 → 5.0.0.beta01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. data/.travis.yml +0 -1
  2. data/README.md +32 -6
  3. data/Rakefile +10 -18
  4. data/bin/compare +5 -5
  5. data/bin/jsduck +2 -3
  6. data/jsduck.gemspec +3 -4
  7. data/lib/jsduck/aggregator.rb +21 -80
  8. data/lib/jsduck/app.rb +7 -14
  9. data/lib/jsduck/app_data.rb +4 -5
  10. data/lib/jsduck/assets.rb +4 -7
  11. data/lib/jsduck/base_type.rb +53 -0
  12. data/lib/jsduck/batch_parser.rb +8 -87
  13. data/lib/jsduck/batch_processor.rb +77 -0
  14. data/lib/jsduck/categories/auto.rb +83 -0
  15. data/lib/jsduck/categories/class_name.rb +63 -0
  16. data/lib/jsduck/categories/factory.rb +113 -0
  17. data/lib/jsduck/categories/file.rb +75 -0
  18. data/lib/jsduck/class.rb +3 -9
  19. data/lib/jsduck/class_doc_expander.rb +1 -1
  20. data/lib/jsduck/css/lexer.rb +203 -0
  21. data/lib/jsduck/css/parser.rb +121 -0
  22. data/lib/jsduck/doc/comment.rb +40 -0
  23. data/lib/jsduck/doc/map.rb +23 -0
  24. data/lib/jsduck/doc/parser.rb +128 -0
  25. data/lib/jsduck/doc/processor.rb +52 -0
  26. data/lib/jsduck/doc/scanner.rb +76 -0
  27. data/lib/jsduck/doc/standard_tag_parser.rb +154 -0
  28. data/lib/jsduck/doc/subproperties.rb +64 -0
  29. data/lib/jsduck/docs_code_comparer.rb +31 -0
  30. data/lib/jsduck/export_writer.rb +2 -2
  31. data/lib/jsduck/exporter/app.rb +16 -4
  32. data/lib/jsduck/exporter/full.rb +2 -2
  33. data/lib/jsduck/format/batch.rb +58 -0
  34. data/lib/jsduck/format/class.rb +62 -0
  35. data/lib/jsduck/format/doc.rb +172 -0
  36. data/lib/jsduck/format/html_stack.rb +109 -0
  37. data/lib/jsduck/format/shortener.rb +55 -0
  38. data/lib/jsduck/format/subproperties.rb +64 -0
  39. data/lib/jsduck/guides.rb +32 -14
  40. data/lib/jsduck/index_html.rb +3 -1
  41. data/lib/jsduck/inline/auto_link.rb +2 -2
  42. data/lib/jsduck/inline/link.rb +4 -3
  43. data/lib/jsduck/inline/link_renderer.rb +2 -2
  44. data/lib/jsduck/inline/video.rb +8 -2
  45. data/lib/jsduck/js/ast.rb +361 -0
  46. data/lib/jsduck/js/esprima.rb +39 -0
  47. data/lib/jsduck/{esprima → js/esprima}/esprima.js +0 -0
  48. data/lib/jsduck/js/evaluator.rb +70 -0
  49. data/lib/jsduck/js/ext_patterns.rb +70 -0
  50. data/lib/jsduck/js/function.rb +206 -0
  51. data/lib/jsduck/js/node.rb +194 -0
  52. data/lib/jsduck/js/node_array.rb +36 -0
  53. data/lib/jsduck/js/parser.rb +223 -0
  54. data/lib/jsduck/js/serializer.rb +263 -0
  55. data/lib/jsduck/js/utils.rb +21 -0
  56. data/lib/jsduck/logger.rb +3 -13
  57. data/lib/jsduck/members_index.rb +3 -4
  58. data/lib/jsduck/merger.rb +25 -145
  59. data/lib/jsduck/options.rb +29 -132
  60. data/lib/jsduck/parser.rb +76 -0
  61. data/lib/jsduck/process/accessors.rb +133 -0
  62. data/lib/jsduck/process/circular_deps.rb +58 -0
  63. data/lib/jsduck/process/enums.rb +91 -0
  64. data/lib/jsduck/process/ext4_events.rb +43 -0
  65. data/lib/jsduck/process/global_members.rb +36 -0
  66. data/lib/jsduck/process/ignored_classes.rb +16 -0
  67. data/lib/jsduck/process/importer.rb +58 -0
  68. data/lib/jsduck/process/inherit_doc.rb +197 -0
  69. data/lib/jsduck/process/lint.rb +135 -0
  70. data/lib/jsduck/process/overrides.rb +99 -0
  71. data/lib/jsduck/process/return_values.rb +72 -0
  72. data/lib/jsduck/process/versions.rb +102 -0
  73. data/lib/jsduck/relations.rb +5 -0
  74. data/lib/jsduck/render/class.rb +144 -0
  75. data/lib/jsduck/render/sidebar.rb +97 -0
  76. data/lib/jsduck/render/signature.rb +94 -0
  77. data/lib/jsduck/render/subproperties.rb +99 -0
  78. data/lib/jsduck/render/tags.rb +38 -0
  79. data/lib/jsduck/search_data.rb +19 -13
  80. data/lib/jsduck/source/file.rb +8 -17
  81. data/lib/jsduck/tag/abstract.rb +4 -7
  82. data/lib/jsduck/tag/accessor.rb +10 -0
  83. data/lib/jsduck/tag/alias.rb +61 -0
  84. data/lib/jsduck/tag/alternate_class_names.rb +17 -0
  85. data/lib/jsduck/tag/aside.rb +28 -31
  86. data/lib/jsduck/tag/author.rb +9 -5
  87. data/lib/jsduck/tag/boolean_tag.rb +24 -0
  88. data/lib/jsduck/tag/cfg.rb +45 -0
  89. data/lib/jsduck/tag/chainable.rb +5 -7
  90. data/lib/jsduck/tag/class.rb +28 -0
  91. data/lib/jsduck/tag/class_list_tag.rb +40 -0
  92. data/lib/jsduck/tag/constructor.rb +24 -0
  93. data/lib/jsduck/tag/css_mixin.rb +17 -0
  94. data/lib/jsduck/tag/css_var.rb +29 -0
  95. data/lib/jsduck/tag/default.rb +31 -0
  96. data/lib/jsduck/tag/deprecated.rb +13 -27
  97. data/lib/jsduck/tag/deprecated_tag.rb +58 -0
  98. data/lib/jsduck/tag/doc.rb +32 -0
  99. data/lib/jsduck/tag/docauthor.rb +4 -5
  100. data/lib/jsduck/tag/enum.rb +70 -0
  101. data/lib/jsduck/tag/event.rb +28 -0
  102. data/lib/jsduck/tag/evented.rb +10 -0
  103. data/lib/jsduck/tag/extends.rb +45 -0
  104. data/lib/jsduck/tag/ftype.rb +18 -0
  105. data/lib/jsduck/tag/hide.rb +4 -11
  106. data/lib/jsduck/tag/ignore.rb +6 -7
  107. data/lib/jsduck/tag/inheritable.rb +10 -0
  108. data/lib/jsduck/tag/inheritdoc.rb +48 -0
  109. data/lib/jsduck/tag/markdown.rb +8 -6
  110. data/lib/jsduck/tag/member.rb +24 -0
  111. data/lib/jsduck/tag/method.rb +35 -0
  112. data/lib/jsduck/tag/mixins.rb +26 -0
  113. data/lib/jsduck/tag/name.rb +36 -0
  114. data/lib/jsduck/tag/new.rb +13 -27
  115. data/lib/jsduck/tag/override.rb +37 -0
  116. data/lib/jsduck/tag/overrides.rb +29 -0
  117. data/lib/jsduck/tag/param.rb +87 -0
  118. data/lib/jsduck/tag/preventable.rb +19 -10
  119. data/lib/jsduck/tag/private.rb +28 -13
  120. data/lib/jsduck/tag/property.rb +39 -0
  121. data/lib/jsduck/tag/protected.rb +5 -7
  122. data/lib/jsduck/tag/ptype.rb +18 -0
  123. data/lib/jsduck/tag/readonly.rb +4 -7
  124. data/lib/jsduck/tag/removed.rb +21 -29
  125. data/lib/jsduck/tag/required.rb +11 -9
  126. data/lib/jsduck/tag/requires.rb +12 -0
  127. data/lib/jsduck/tag/return.rb +47 -0
  128. data/lib/jsduck/tag/since.rb +19 -11
  129. data/lib/jsduck/tag/singleton.rb +15 -0
  130. data/lib/jsduck/tag/static.rb +5 -7
  131. data/lib/jsduck/tag/subproperties.rb +23 -0
  132. data/lib/jsduck/tag/tag.rb +208 -0
  133. data/lib/jsduck/tag/template.rb +14 -9
  134. data/lib/jsduck/tag/throws.rb +38 -0
  135. data/lib/jsduck/tag/type.rb +48 -0
  136. data/lib/jsduck/tag/uses.rb +12 -0
  137. data/lib/jsduck/tag/xtype.rb +30 -0
  138. data/lib/jsduck/tag_loader.rb +39 -0
  139. data/lib/jsduck/tag_registry.rb +189 -0
  140. data/lib/jsduck/type_parser.rb +3 -3
  141. data/lib/jsduck/web_writer.rb +2 -2
  142. data/lib/jsduck/welcome.rb +1 -1
  143. metadata +578 -538
  144. data/lib/jsduck/accessors.rb +0 -136
  145. data/lib/jsduck/ast.rb +0 -524
  146. data/lib/jsduck/auto_categories.rb +0 -80
  147. data/lib/jsduck/batch_formatter.rb +0 -60
  148. data/lib/jsduck/categories.rb +0 -73
  149. data/lib/jsduck/categories_class_name.rb +0 -37
  150. data/lib/jsduck/circular_deps.rb +0 -56
  151. data/lib/jsduck/class_formatter.rb +0 -102
  152. data/lib/jsduck/columns.rb +0 -56
  153. data/lib/jsduck/css_lexer.rb +0 -201
  154. data/lib/jsduck/css_parser.rb +0 -119
  155. data/lib/jsduck/doc_ast.rb +0 -319
  156. data/lib/jsduck/doc_formatter.rb +0 -142
  157. data/lib/jsduck/doc_parser.rb +0 -611
  158. data/lib/jsduck/doc_type.rb +0 -59
  159. data/lib/jsduck/enum.rb +0 -73
  160. data/lib/jsduck/esprima.rb +0 -51
  161. data/lib/jsduck/evaluator.rb +0 -69
  162. data/lib/jsduck/ext_patterns.rb +0 -58
  163. data/lib/jsduck/file_categories.rb +0 -76
  164. data/lib/jsduck/function_ast.rb +0 -206
  165. data/lib/jsduck/guide_anchors.rb +0 -32
  166. data/lib/jsduck/guide_toc.rb +0 -49
  167. data/lib/jsduck/html_stack.rb +0 -105
  168. data/lib/jsduck/importer.rb +0 -121
  169. data/lib/jsduck/inherit_doc.rb +0 -193
  170. data/lib/jsduck/js_parser.rb +0 -221
  171. data/lib/jsduck/lint.rb +0 -133
  172. data/lib/jsduck/meta_tag.rb +0 -88
  173. data/lib/jsduck/meta_tag_loader.rb +0 -67
  174. data/lib/jsduck/meta_tag_registry.rb +0 -111
  175. data/lib/jsduck/meta_tag_renderer.rb +0 -34
  176. data/lib/jsduck/news.rb +0 -128
  177. data/lib/jsduck/override.rb +0 -87
  178. data/lib/jsduck/renderer.rb +0 -361
  179. data/lib/jsduck/return_values.rb +0 -72
  180. data/lib/jsduck/serializer.rb +0 -262
  181. data/lib/jsduck/shortener.rb +0 -58
  182. data/lib/jsduck/signature_renderer.rb +0 -91
  183. data/lib/jsduck/source/file_parser.rb +0 -72
@@ -0,0 +1,36 @@
1
+ require "jsduck/js/node"
2
+
3
+ module JsDuck
4
+ module Js
5
+
6
+ # Wraps around array of AST nodes.
7
+ class NodeArray
8
+ # Initialized with array of AST Hashes from Esprima.
9
+ def initialize(nodes)
10
+ @nodes = nodes || []
11
+ end
12
+
13
+ # Returns a child AST node as AstNode class.
14
+ def [](i)
15
+ Js::Node.create(@nodes[i])
16
+ end
17
+
18
+ # The length of array
19
+ def length
20
+ @nodes.length
21
+ end
22
+
23
+ # Iterates over all the AstNodes in array.
24
+ def each
25
+ @nodes.each {|p| yield(Js::Node.create(p)) }
26
+ end
27
+
28
+ # Maps over all the AstNodes in array.
29
+ def map
30
+ @nodes.map {|p| yield(Js::Node.create(p)) }
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,223 @@
1
+ require 'jsduck/js/esprima'
2
+ require 'jsduck/logger'
3
+
4
+ module JsDuck
5
+ module Js
6
+
7
+ # JavaScript parser that internally uses Esprima.js
8
+ class Parser
9
+
10
+ # Initializes the parser with JavaScript source code to be parsed.
11
+ def initialize(input, options = {})
12
+ @input = input
13
+
14
+ # Initialize line number counting
15
+ @start_index = 0
16
+ @start_linenr = 1
17
+ end
18
+
19
+ # Parses JavaScript source code and returns array of hashes like this:
20
+ #
21
+ # {
22
+ # :comment => "The contents of the comment",
23
+ # :code => {...AST data structure for code following the comment...},
24
+ # :linenr => 12, // Beginning with 1
25
+ # :type => :doc_comment, // or :plain_comment
26
+ # }
27
+ #
28
+ def parse
29
+ @ast = Js::Esprima.parse(@input)
30
+
31
+ @ast["comments"] = merge_comments(@ast["comments"])
32
+ locate_comments
33
+ end
34
+
35
+ private
36
+
37
+ # Merges consecutive line-comments and Establishes links between
38
+ # comments, so we can easily use comment["next"] to get to the
39
+ # next comment.
40
+ def merge_comments(original_comments)
41
+ result = []
42
+
43
+ comment = original_comments[0]
44
+ i = 0
45
+
46
+ while comment
47
+ i += 1
48
+ next_comment = original_comments[i]
49
+
50
+ if next_comment && mergeable?(comment, next_comment)
51
+ # Merge next comment to current one
52
+ comment["value"] += "\n" + next_comment["value"]
53
+ comment["range"][1] = next_comment["range"][1]
54
+ else
55
+ # Create a link and continue with next comment
56
+ comment["next"] = next_comment
57
+ result << comment
58
+ comment = next_comment
59
+ end
60
+ end
61
+
62
+ result
63
+ end
64
+
65
+ # Two comments can be merged if they are both line-comments and
66
+ # they are separated only by whitespace (only one newline at the
67
+ # end of the first comment is allowed)
68
+ def mergeable?(c1, c2)
69
+ if c1["type"] == "Line" && c2["type"] == "Line"
70
+ /\A(\r\n|\n|\r)?[ \t]*\z/ =~ @input.slice((c1["range"][1])..(c2["range"][0]-1))
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ def locate_comments
77
+ @ast["comments"].map do |comment|
78
+ # Detect comment type and strip * at the beginning of doc-comment
79
+ value = comment["value"]
80
+ if comment["type"] == "Block" && value =~ /\A\*/
81
+ type = :doc_comment
82
+ value = value.slice(1, value.length-1)
83
+ else
84
+ type = :plain_comment
85
+ end
86
+
87
+ {
88
+ :comment => value,
89
+ :code => stuff_after(comment),
90
+ :linenr => line_number(comment["range"][0]),
91
+ :type => type,
92
+ }
93
+ end
94
+ end
95
+
96
+ # Given index inside input string, returns the corresponding line number
97
+ def line_number(index)
98
+ # To speed things up, remember the index until which we counted,
99
+ # then next time just begin counting from there. This way we
100
+ # only count each line once.
101
+ @start_linenr = @input[@start_index...index].count("\n") + @start_linenr
102
+ @start_index = index
103
+ return @start_linenr
104
+ end
105
+
106
+ # Sees if there is some code following the comment.
107
+ # Returns the code found. But if the comment is instead
108
+ # followed by another comment, returns nil.
109
+ def stuff_after(comment)
110
+ code = code_after(comment["range"], @ast)
111
+ if code && comment["next"]
112
+ return code["range"][0] < comment["next"]["range"][0] ? code : nil
113
+ else
114
+ code
115
+ end
116
+ end
117
+
118
+ # Looks for code following the given range.
119
+ #
120
+ # The second argument is the parent node within which we perform
121
+ # our search.
122
+ def code_after(range, parent)
123
+ # Look through all child nodes of parent...
124
+ child_nodes(parent).each do |node|
125
+ if less(range, node["range"])
126
+ # If node is after our range, then that's it. There could
127
+ # be comments in our way, but that's taken care of in
128
+ # #stuff_after method.
129
+ return node
130
+ elsif within(range, node["range"])
131
+ # Our range is within the node --> recurse
132
+ return code_after(range, node)
133
+ end
134
+ end
135
+
136
+ return nil
137
+ end
138
+
139
+
140
+ # True if range A is less than range B
141
+ def less(a, b)
142
+ return a[1] <= b[0]
143
+ end
144
+
145
+ # True if range A is greater than range B
146
+ def greater(a, b)
147
+ return a[0] >= b[1]
148
+ end
149
+
150
+ # True if range A is within range B
151
+ def within(a, b)
152
+ return b[0] <= a[0] && a[1] <= b[1]
153
+ end
154
+
155
+
156
+ # Returns array of child nodes of given node
157
+ def child_nodes(node)
158
+ properties = NODE_TYPES[node["type"]]
159
+
160
+ unless properties
161
+ Logger.fatal("Unknown node type: "+node["type"])
162
+ exit(1)
163
+ end
164
+
165
+ properties.map {|p| node[p] }.compact.flatten
166
+ end
167
+
168
+ # All possible node types in Esprima-created abstract syntax tree
169
+ #
170
+ # Each node type maps to list of properties of that node into
171
+ # which we can recurse for further parsing.
172
+ NODE_TYPES = {
173
+ "Program" => ["body"],
174
+
175
+ "BlockStatement" => ["body"],
176
+ "BreakStatement" => [],
177
+ "ContinueStatement" => [],
178
+ "DoWhileStatement" => ["body", "test"],
179
+ "DebuggerStatement" => [],
180
+ "EmptyStatement" => [],
181
+ "ExpressionStatement" => ["expression"],
182
+ "ForStatement" => ["init", "test", "update", "body"],
183
+ "ForInStatement" => ["left", "right", "body"],
184
+ "IfStatement" => ["test", "consequent", "alternate"],
185
+ "LabeledStatement" => ["body"],
186
+ "ReturnStatement" => ["argument"],
187
+ "SwitchStatement" => ["discriminant", "cases"],
188
+ "SwitchCase" => ["test", "consequent"],
189
+ "ThrowStatement" => ["argument"],
190
+ "TryStatement" => ["block", "handlers", "finalizer"],
191
+ "CatchClause" => ["param", "body"],
192
+ "WhileStatement" => ["test", "body"],
193
+ "WithStatement" => ["object", "body"],
194
+
195
+ "FunctionDeclaration" => ["id", "params", "body"],
196
+ "VariableDeclaration" => ["declarations"],
197
+ "VariableDeclarator" => ["id", "init"],
198
+
199
+ "AssignmentExpression" => ["left", "right"],
200
+ "ArrayExpression" => ["elements"],
201
+ "BinaryExpression" => ["left", "right"],
202
+ "CallExpression" => ["callee", "arguments"],
203
+ "ConditionalExpression" => ["test", "consequent", "alternate"],
204
+ "FunctionExpression" => ["body"],
205
+
206
+ "LogicalExpression" => ["left", "right"],
207
+ "MemberExpression" => ["object", "property"],
208
+ "NewExpression" => ["callee", "arguments"],
209
+ "ObjectExpression" => ["properties"],
210
+ "Property" => ["key", "value"],
211
+
212
+ "SequenceExpression" => ["expressions"],
213
+ "ThisExpression" => [],
214
+ "UnaryExpression" => ["argument"],
215
+ "UpdateExpression" => ["argument"],
216
+
217
+ "Identifier" => [],
218
+ "Literal" => [],
219
+ }
220
+ end
221
+
222
+ end
223
+ end
@@ -0,0 +1,263 @@
1
+ module JsDuck
2
+ module Js
3
+
4
+ # Transforms Esprima AST into string
5
+ class Serializer
6
+
7
+ # Turns AST node into string
8
+ def to_s(ast)
9
+ case ast["type"]
10
+ when "Program"
11
+ ast["body"].map {|s| to_s(s) }.join
12
+
13
+ # Statements
14
+
15
+ when "BlockStatement"
16
+ "{" + ast["body"].map {|s| to_s(s) }.join + "}"
17
+
18
+ when "BreakStatement"
19
+ "break" + (ast["label"] ? " " + to_s(ast["label"]) : "") + ";"
20
+
21
+ when "ContinueStatement"
22
+ "continue" + (ast["label"] ? " " + to_s(ast["label"]) : "") + ";"
23
+
24
+ when "DoWhileStatement"
25
+ "do " + to_s(ast["body"]) + " while (" + to_s(ast["test"]) + ");"
26
+
27
+ when "DebuggerStatement"
28
+ "debugger;"
29
+
30
+ when "EmptyStatement"
31
+ ";"
32
+
33
+ when "ExpressionStatement"
34
+ to_s(ast["expression"]) + ";"
35
+
36
+ when "ForStatement"
37
+ init = ast["init"] ? to_s(ast["init"]).sub(/;\z/, "") : ""
38
+ test = ast["test"] ? to_s(ast["test"]) : ""
39
+ update = ast["update"] ? to_s(ast["update"]) : ""
40
+ "for (" + init + "; " + test + "; " + update + ") " + to_s(ast["body"])
41
+
42
+ when "ForInStatement"
43
+ left = to_s(ast["left"]).sub(/;\z/, "")
44
+ right = to_s(ast["right"])
45
+ "for (" + left + " in " + right + ") " + to_s(ast["body"])
46
+
47
+ when "IfStatement"
48
+ alternate = ast["alternate"] ? " else " + to_s(ast["alternate"]) : ""
49
+ "if (" + to_s(ast["test"]) + ") " + to_s(ast["consequent"]) + alternate
50
+
51
+ when "LabeledStatement"
52
+ to_s(ast["label"]) + ": " + to_s(ast["body"])
53
+
54
+ when "ReturnStatement"
55
+ arg = ast["argument"] ? to_s(ast["argument"]) : ""
56
+ "return " + arg + ";"
57
+
58
+ when "SwitchStatement"
59
+ "switch (" + to_s(ast["discriminant"]) + ") {" + ast["cases"].map {|c| to_s(c) }.join + "}"
60
+
61
+ when "SwitchCase"
62
+ test = ast["test"] ? "case " + to_s(ast["test"]) : "default"
63
+ test + ": " + ast["consequent"].map {|c| to_s(c) }.join
64
+
65
+ when "ThrowStatement"
66
+ "throw " + to_s(ast["argument"]) + ";"
67
+
68
+ when "TryStatement"
69
+ handlers = ast["handlers"].map {|h| to_s(h) }.join
70
+ finalizer = ast["finalizer"] ? " finally " + to_s(ast["finalizer"]) : ""
71
+ "try " + to_s(ast["block"]) + handlers + finalizer
72
+
73
+ when "CatchClause"
74
+ param = ast["param"] ? to_s(ast["param"]) : ""
75
+ " catch (" + param + ") " + to_s(ast["body"])
76
+
77
+ when "WhileStatement"
78
+ "while (" + to_s(ast["test"]) + ") " + to_s(ast["body"])
79
+
80
+ when "WithStatement"
81
+ "with (" + to_s(ast["object"]) + ") " + to_s(ast["body"])
82
+
83
+
84
+ # Declarations
85
+
86
+ when "FunctionDeclaration"
87
+ function(ast)
88
+
89
+ when "VariableDeclaration"
90
+ ast["kind"] + " " + list(ast["declarations"]) + ";"
91
+
92
+ when "VariableDeclarator"
93
+ if ast["init"]
94
+ to_s(ast["id"]) + " = " + to_s(ast["init"])
95
+ else
96
+ to_s(ast["id"])
97
+ end
98
+
99
+ # Expressions
100
+
101
+ when "AssignmentExpression"
102
+ parens(ast, ast["left"]) + " " + ast["operator"] + " " + to_s(ast["right"])
103
+
104
+ when "ArrayExpression"
105
+ "[" + list(ast["elements"]) + "]"
106
+
107
+ when "BinaryExpression"
108
+ binary(ast)
109
+
110
+ when "CallExpression"
111
+ call(ast)
112
+
113
+ when "ConditionalExpression"
114
+ parens(ast, ast["test"]) + " ? " + to_s(ast["consequent"]) + " : " + to_s(ast["alternate"])
115
+
116
+ when "FunctionExpression"
117
+ function(ast)
118
+
119
+ when "LogicalExpression"
120
+ binary(ast)
121
+
122
+ when "MemberExpression"
123
+ if ast["computed"]
124
+ parens(ast, ast["object"]) + "[" + to_s(ast["property"]) + "]"
125
+ else
126
+ parens(ast, ast["object"]) + "." + to_s(ast["property"])
127
+ end
128
+
129
+ when "NewExpression"
130
+ "new " + call(ast)
131
+
132
+ when "ObjectExpression"
133
+ "{" + list(ast["properties"]) + "}"
134
+
135
+ when "Property"
136
+ to_s(ast["key"]) + ": " + to_s(ast["value"])
137
+
138
+ when "SequenceExpression"
139
+ list(ast["expressions"])
140
+
141
+ when "ThisExpression"
142
+ "this"
143
+
144
+ when "UnaryExpression"
145
+ ast["operator"] + parens(ast, ast["argument"])
146
+
147
+ when "UpdateExpression"
148
+ if ast["prefix"]
149
+ ast["operator"] + parens(ast, ast["argument"])
150
+ else
151
+ parens(ast, ast["argument"]) + ast["operator"]
152
+ end
153
+
154
+ # Basics
155
+
156
+ when "Identifier"
157
+ ast["name"]
158
+
159
+ when "Literal"
160
+ ast["raw"]
161
+
162
+ else
163
+ throw "Unknown node type: " + (ast["type"] || "nil")
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ # serializes function declaration or expression
170
+ def function(ast)
171
+ params = list(ast["params"])
172
+ id = ast["id"] ? to_s(ast["id"]) : ""
173
+ "function " + id + "(" + params + ") " + to_s(ast["body"])
174
+ end
175
+
176
+ # serializes list of comma-separated items
177
+ def list(array)
178
+ array.map {|x| to_s(x) }.join(", ")
179
+ end
180
+
181
+ # serializes call- and new-expression
182
+ def call(ast)
183
+ parens(ast, ast["callee"]) + "(" + list(ast["arguments"]) + ")"
184
+ end
185
+
186
+ # Handles both binary- and logical-expression
187
+ def binary(ast)
188
+ parens(ast, ast["left"]) + " " + ast["operator"] + " " + parens(ast, ast["right"])
189
+ end
190
+
191
+ # serializes child node and wraps it inside parenthesis if the
192
+ # precedence rules compared to parent node would require so.
193
+ def parens(parent, child)
194
+ if precedence(parent) >= precedence(child)
195
+ to_s(child)
196
+ else
197
+ "(" + to_s(child) + ")"
198
+ end
199
+ end
200
+
201
+ # Returns the precedence of operator represented by given AST node
202
+ def precedence(ast)
203
+ p = PRECEDENCE[ast["type"]]
204
+ if p.is_a? Fixnum
205
+ p
206
+ elsif p.is_a? Hash
207
+ p[ast["operator"]]
208
+ else
209
+ 0
210
+ end
211
+ end
212
+
213
+ # Precedence rules of JavaScript operators.
214
+ #
215
+ # Taken from: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence
216
+ #
217
+ PRECEDENCE = {
218
+ "SequenceExpression" => 17,
219
+ "AssignmentExpression" => 16,
220
+ "ConditionalExpression" => 15,
221
+ "LogicalExpression" => {
222
+ "||" => 14,
223
+ "&&" => 13,
224
+ },
225
+ "BinaryExpression" => {
226
+ "|" => 12,
227
+ "^" => 11,
228
+ "&" => 10,
229
+
230
+ "==" => 9,
231
+ "!=" => 9,
232
+ "===" => 9,
233
+ "!==" => 9,
234
+
235
+ "<" => 8,
236
+ "<=" => 8,
237
+ ">" => 8,
238
+ ">=" => 8,
239
+ "in" => 8,
240
+ "instanceof" => 8,
241
+
242
+ "<<" => 7,
243
+ ">>" => 7,
244
+ ">>>" => 7,
245
+
246
+ "+" => 6,
247
+ "-" => 6,
248
+
249
+ "*" => 5,
250
+ "/" => 5,
251
+ "%" => 5,
252
+ },
253
+ "UnaryExpression" => 4,
254
+ "UpdateExpression" => 3,
255
+ "CallExpression" => 2,
256
+ "MemberExpression" => 1,
257
+ "NewExpression" => 1,
258
+ }
259
+
260
+ end
261
+
262
+ end
263
+ end