jsduck 5.0.0.beta3 → 5.0.0.beta4

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/.travis.yml CHANGED
@@ -6,8 +6,7 @@ install:
6
6
  - gem install rdiscount
7
7
  - gem install json
8
8
  - gem install parallel
9
- - gem install execjs
10
- - gem install therubyracer -v 0.10.1
9
+ - gem install rkelly-remix -v 0.0.1
11
10
  - gem install rspec
12
11
  - gem install rake
13
12
  - gem install dimensions
data/jsduck.gemspec CHANGED
@@ -2,7 +2,7 @@ Gem::Specification.new do |s|
2
2
  s.required_rubygems_version = ">= 1.3.5"
3
3
 
4
4
  s.name = 'jsduck'
5
- s.version = '5.0.0.beta3'
5
+ s.version = '5.0.0.beta4'
6
6
  s.date = Time.new.strftime('%Y-%m-%d')
7
7
  s.summary = "Simple JavaScript Duckumentation generator"
8
8
  s.description = "Documentation generator for Sencha JS frameworks"
@@ -22,8 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.add_dependency 'rdiscount'
23
23
  s.add_dependency 'json'
24
24
  s.add_dependency 'parallel'
25
- s.add_dependency 'execjs'
26
- s.add_dependency 'therubyracer', '>= 0.10.0'
25
+ s.add_dependency 'rkelly-remix', '= 0.0.1'
27
26
  s.add_dependency 'dimensions'
28
27
 
29
28
  s.add_development_dependency 'rspec'
@@ -158,7 +158,9 @@ module JsDuck
158
158
  # Creates classes for orphans that have :owner property defined,
159
159
  # and then inserts orphans to these classes.
160
160
  def classify_orphans
161
- @orphans.each do |orph|
161
+ # Clone the orphans array first to avoid problems with
162
+ # #inster_orphan method deleting items from @orphans array.
163
+ @orphans.clone.each do |orph|
162
164
  if orph[:owner]
163
165
  class_name = orph[:owner]
164
166
  if !@classes[class_name]
@@ -75,7 +75,7 @@ module JsDuck
75
75
  # normal Markdown, which often causes nested <pre>-blocks.
76
76
  #
77
77
  # To prevent this, we always add extra newline before <pre>.
78
- input.gsub!(/([^\n])<pre>/, "\\1\n<pre>")
78
+ input.gsub!(/([^\n])<pre>((<code>)?$)/, "\\1\n<pre>\\2")
79
79
 
80
80
  # But we remove trailing newline after <pre> to prevent
81
81
  # code-blocks beginning with empty line.
@@ -1,14 +1,12 @@
1
- require 'jsduck/js/esprima'
2
1
  require 'jsduck/logger'
3
2
 
4
3
  module JsDuck
5
4
  module Js
6
5
 
7
- # JavaScript parser that internally uses Esprima.js
8
- class Parser
6
+ # Associates comments with syntax nodes.
7
+ class Associator
9
8
 
10
- # Initializes the parser with JavaScript source code to be parsed.
11
- def initialize(input, options = {})
9
+ def initialize(input)
12
10
  @input = input
13
11
 
14
12
  # Initialize line number counting
@@ -16,7 +14,8 @@ module JsDuck
16
14
  @start_linenr = 1
17
15
  end
18
16
 
19
- # Parses JavaScript source code and returns array of hashes like this:
17
+ # Analyzes the comments and AST nodes and returns array of
18
+ # hashes like this:
20
19
  #
21
20
  # {
22
21
  # :comment => "The contents of the comment",
@@ -25,8 +24,8 @@ module JsDuck
25
24
  # :type => :doc_comment, // or :plain_comment
26
25
  # }
27
26
  #
28
- def parse
29
- @ast = Js::Esprima.parse(@input)
27
+ def associate(ast)
28
+ @ast = ast
30
29
 
31
30
  @ast["comments"] = merge_comments(@ast["comments"])
32
31
  locate_comments
@@ -0,0 +1,511 @@
1
+ require 'rkelly'
2
+
3
+ module JsDuck
4
+ module Js
5
+
6
+ # Converts RKelly AST into Esprima AST.
7
+ class RKellyAdapter
8
+ def adapt(node)
9
+ ast = adapt_root(node)
10
+ ast["comments"] = node.comments.map {|c| adapt_comment(c) }
11
+ ast
12
+ end
13
+
14
+ private
15
+
16
+ def adapt_comment(comment)
17
+ if comment.value =~ /\A\/\*/
18
+ {
19
+ "type" => "Block",
20
+ "value" => comment.value.sub(/\A\/\*/, "").sub(/\*\/\z/, ""),
21
+ "range" => [comment.range.from.index, comment.range.to.index+1],
22
+ }
23
+ else
24
+ {
25
+ "type" => "Line",
26
+ "value" => comment.value.sub(/\A\/\//, ""),
27
+ "range" => [comment.range.from.index, comment.range.to.index+1],
28
+ }
29
+ end
30
+ end
31
+
32
+ def adapt_root(node)
33
+ make(node, {
34
+ "type" => "Program",
35
+ "body" => adapt_node(node),
36
+ })
37
+ end
38
+
39
+ def adapt_node(node)
40
+ case
41
+ # Empty node
42
+ when node.nil?
43
+ nil
44
+
45
+ # Fall-through nodes
46
+ when FALL_THROUGH_NODES[node.class]
47
+ adapt_node(node.value)
48
+ when FALL_THROUGH_ARRAY_NODES[node.class]
49
+ node.value.map {|v| adapt_node(v) }
50
+
51
+ # Identifiers
52
+ when RKelly::Nodes::ResolveNode == node.class
53
+ make(node, {
54
+ "type" => "Identifier",
55
+ "name" => node.value,
56
+ })
57
+
58
+ # Literals
59
+ when RKelly::Nodes::NumberNode == node.class
60
+ make(node, {
61
+ "type" => "Literal",
62
+ "value" => node.value,
63
+ "raw" => node.value.to_s,
64
+ })
65
+ when RKelly::Nodes::StringNode == node.class
66
+ make(node, {
67
+ "type" => "Literal",
68
+ "value" => eval(node.value),
69
+ "raw" => node.value,
70
+ })
71
+ when RKelly::Nodes::RegexpNode == node.class
72
+ make(node, {
73
+ "type" => "Literal",
74
+ "value" => node.value,
75
+ "raw" => node.value,
76
+ })
77
+ when RKelly::Nodes::TrueNode == node.class
78
+ make(node, {
79
+ "type" => "Literal",
80
+ "value" => true,
81
+ "raw" => "true",
82
+ })
83
+ when RKelly::Nodes::FalseNode == node.class
84
+ make(node, {
85
+ "type" => "Literal",
86
+ "value" => false,
87
+ "raw" => "false",
88
+ })
89
+ when RKelly::Nodes::NullNode == node.class
90
+ make(node, {
91
+ "type" => "Literal",
92
+ "value" => nil,
93
+ "raw" => "null",
94
+ })
95
+
96
+ # Expressions
97
+ when BINARY_NODES[node.class]
98
+ make(node, {
99
+ "type" => "BinaryExpression",
100
+ "operator" => BINARY_NODES[node.class],
101
+ "left" => adapt_node(node.left),
102
+ "right" => adapt_node(node.value),
103
+ })
104
+ when UNARY_NODES[node.class]
105
+ make(node, {
106
+ "type" => "UnaryExpression",
107
+ "operator" => UNARY_NODES[node.class],
108
+ "argument" => adapt_node(node.value),
109
+ })
110
+ when ASSIGNMENT_NODES[node.class]
111
+ make(node, {
112
+ "type" => "AssignmentExpression",
113
+ "operator" => ASSIGNMENT_NODES[node.class],
114
+ "left" => adapt_node(node.left),
115
+ "right" => adapt_node(node.value),
116
+ })
117
+ when RKelly::Nodes::FunctionExprNode == node.class
118
+ make(node, {
119
+ "type" => "FunctionExpression",
120
+ "id" => node.value == "function" ? nil : {
121
+ "type" => "Identifier",
122
+ "name" => node.value,
123
+ "range" => offset_range(node, :value, "function "),
124
+ },
125
+ "params" => node.arguments.map {|a| adapt_node(a) },
126
+ "body" => adapt_node(node.function_body),
127
+ })
128
+ when RKelly::Nodes::ThisNode == node.class
129
+ make(node, {
130
+ "type" => "ThisExpression",
131
+ })
132
+ when RKelly::Nodes::DotAccessorNode == node.class
133
+ make(node, {
134
+ "type" => "MemberExpression",
135
+ "computed" => false,
136
+ "object" => adapt_node(node.value),
137
+ "property" => {
138
+ "type" => "Identifier",
139
+ "name" => node.accessor,
140
+ "range" => offset_range(node, :accessor),
141
+ },
142
+ })
143
+ when RKelly::Nodes::BracketAccessorNode == node.class
144
+ make(node, {
145
+ "type" => "MemberExpression",
146
+ "computed" => true,
147
+ "object" => adapt_node(node.value),
148
+ "property" => adapt_node(node.accessor),
149
+ })
150
+ when RKelly::Nodes::FunctionCallNode == node.class
151
+ make(node, {
152
+ "type" => "CallExpression",
153
+ "callee" => adapt_node(node.value),
154
+ "arguments" => adapt_node(node.arguments),
155
+ })
156
+ when RKelly::Nodes::NewExprNode == node.class
157
+ make(node, {
158
+ "type" => "NewExpression",
159
+ "callee" => adapt_node(node.value),
160
+ "arguments" => adapt_node(node.arguments),
161
+ })
162
+ when RKelly::Nodes::PrefixNode == node.class
163
+ make(node, {
164
+ "type" => "UpdateExpression",
165
+ "operator" => node.value,
166
+ "argument" => adapt_node(node.operand),
167
+ "prefix" => true,
168
+ })
169
+ when RKelly::Nodes::PostfixNode == node.class
170
+ make(node, {
171
+ "type" => "UpdateExpression",
172
+ "operator" => node.value,
173
+ "argument" => adapt_node(node.operand),
174
+ "prefix" => false,
175
+ })
176
+ when RKelly::Nodes::ParameterNode == node.class
177
+ make(node, {
178
+ "type" => "Identifier",
179
+ "name" => node.value,
180
+ })
181
+ when RKelly::Nodes::ConditionalNode == node.class
182
+ make(node, {
183
+ "type" => "ConditionalExpression",
184
+ "test" => adapt_node(node.conditions),
185
+ "consequent" => adapt_node(node.value),
186
+ "alternate" => adapt_node(node.else),
187
+ })
188
+ when RKelly::Nodes::CaseClauseNode == node.class
189
+ make(node, {
190
+ "type" => "SwitchCase",
191
+ "test" => adapt_node(node.left),
192
+ "consequent" => adapt_node(node.value),
193
+ })
194
+ when RKelly::Nodes::CommaNode == node.class
195
+ make(node, {
196
+ "type" => "SequenceExpression",
197
+ "expressions" => flatten_sequence(node).map {|v| adapt_node(v) },
198
+ })
199
+ when RKelly::Nodes::ArrayNode == node.class
200
+ make(node, {
201
+ "type" => "ArrayExpression",
202
+ "elements" => node.value.map {|v| adapt_node(v) },
203
+ })
204
+ when RKelly::Nodes::ObjectLiteralNode == node.class
205
+ make(node, {
206
+ "type" => "ObjectExpression",
207
+ "properties" => node.value.map {|v| adapt_node(v) },
208
+ })
209
+ when RKelly::Nodes::PropertyNode == node.class
210
+ make(node, {
211
+ "type" => "Property",
212
+ "key" =>
213
+ if node.name.is_a?(Numeric)
214
+ {
215
+ "type" => "Literal",
216
+ "value" => node.name,
217
+ "raw" => node.name.to_s,
218
+ "range" => offset_range(node, :name),
219
+ }
220
+ elsif node.name =~ /['"]/
221
+ {
222
+ "type" => "Literal",
223
+ "value" => eval(node.name),
224
+ "raw" => node.name,
225
+ "range" => offset_range(node, :name),
226
+ }
227
+ else
228
+ {
229
+ "type" => "Identifier",
230
+ "name" => node.name,
231
+ "range" => offset_range(node, :name),
232
+ }
233
+ end,
234
+ "value" => adapt_node(node.value),
235
+ "kind" => "init",
236
+ })
237
+
238
+ # Statements
239
+ when RKelly::Nodes::ExpressionStatementNode == node.class
240
+ make(node, {
241
+ "type" => "ExpressionStatement",
242
+ "expression" => adapt_node(node.value),
243
+ })
244
+ when RKelly::Nodes::IfNode == node.class
245
+ make(node, {
246
+ "type" => "IfStatement",
247
+ "test" => adapt_node(node.conditions),
248
+ "consequent" => adapt_node(node.value),
249
+ "alternate" => adapt_node(node.else),
250
+ })
251
+ when RKelly::Nodes::WhileNode == node.class
252
+ make(node, {
253
+ "type" => "WhileStatement",
254
+ "test" => adapt_node(node.left),
255
+ "body" => adapt_node(node.value),
256
+ })
257
+ when RKelly::Nodes::DoWhileNode == node.class
258
+ make(node, {
259
+ "type" => "DoWhileStatement",
260
+ "test" => adapt_node(node.left),
261
+ "body" => adapt_node(node.value),
262
+ })
263
+ when RKelly::Nodes::ForNode == node.class
264
+ make(node, {
265
+ "type" => "ForStatement",
266
+ "init" => adapt_node(node.init),
267
+ "test" => adapt_node(node.test),
268
+ "update" => adapt_node(node.counter),
269
+ "body" => adapt_node(node.value),
270
+ })
271
+ when RKelly::Nodes::ForInNode == node.class
272
+ make(node, {
273
+ "type" => "ForInStatement",
274
+ "left" => adapt_node(node.left),
275
+ "right" => adapt_node(node.right),
276
+ "body" => adapt_node(node.value),
277
+ "each" => false,
278
+ })
279
+ when RKelly::Nodes::WithNode == node.class
280
+ make(node, {
281
+ "type" => "WithStatement",
282
+ "object" => adapt_node(node.left),
283
+ "body" => adapt_node(node.value),
284
+ })
285
+ when RKelly::Nodes::SwitchNode == node.class
286
+ make(node, {
287
+ "type" => "SwitchStatement",
288
+ "discriminant" => adapt_node(node.left),
289
+ "cases" => adapt_node(node.value),
290
+ })
291
+ when RKelly::Nodes::ReturnNode == node.class
292
+ make(node, {
293
+ "type" => "ReturnStatement",
294
+ "argument" => adapt_node(node.value),
295
+ })
296
+ when RKelly::Nodes::BreakNode == node.class
297
+ make(node, {
298
+ "type" => "BreakStatement",
299
+ "label" => node.value ? {
300
+ "type" => "Identifier",
301
+ "name" => node.value,
302
+ "range" => offset_range(node, :value, "break "),
303
+ } : nil,
304
+ })
305
+ when RKelly::Nodes::ContinueNode == node.class
306
+ make(node, {
307
+ "type" => "ContinueStatement",
308
+ "label" => node.value ? {
309
+ "type" => "Identifier",
310
+ "name" => node.value,
311
+ "range" => offset_range(node, :value),
312
+ } : nil,
313
+ })
314
+ when RKelly::Nodes::TryNode == node.class
315
+ make(node, {
316
+ "type" => "TryStatement",
317
+ "block" => adapt_node(node.value),
318
+ "guardedHandlers" => [],
319
+ "handlers" => node.catch_block ? [catch_clause(node)] : [],
320
+ "finalizer" => adapt_node(node.finally_block),
321
+ })
322
+ when RKelly::Nodes::ThrowNode == node.class
323
+ make(node, {
324
+ "type" => "ThrowStatement",
325
+ "argument" => adapt_node(node.value),
326
+ })
327
+ when RKelly::Nodes::LabelNode == node.class
328
+ make(node, {
329
+ "type" => "LabeledStatement",
330
+ "label" => {
331
+ "type" => "Identifier",
332
+ "name" => node.name,
333
+ "range" => offset_range(node, :name),
334
+ },
335
+ "body" => adapt_node(node.value),
336
+ })
337
+ when RKelly::Nodes::BlockNode == node.class
338
+ make(node, {
339
+ "type" => "BlockStatement",
340
+ "body" => adapt_node(node.value),
341
+ })
342
+ when RKelly::Nodes::FunctionBodyNode == node.class
343
+ make(node, {
344
+ "type" => "BlockStatement",
345
+ "body" => adapt_node(node.value),
346
+ })
347
+ when RKelly::Nodes::EmptyStatementNode == node.class
348
+ if node.value == "debugger"
349
+ make(node, {
350
+ "type" => "DebuggerStatement",
351
+ })
352
+ else
353
+ make(node, {
354
+ "type" => "EmptyStatement",
355
+ })
356
+ end
357
+
358
+ # Declarations
359
+ when RKelly::Nodes::VarStatementNode == node.class
360
+ make(node, {
361
+ "type" => "VariableDeclaration",
362
+ "kind" => "var",
363
+ "declarations" => node.value.map {|v| adapt_node(v) },
364
+ })
365
+ when RKelly::Nodes::ConstStatementNode == node.class
366
+ make(node, {
367
+ "type" => "VariableDeclaration",
368
+ "kind" => "const",
369
+ "declarations" => node.value.map {|v| adapt_node(v) },
370
+ })
371
+ when RKelly::Nodes::VarDeclNode == node.class
372
+ make(node, {
373
+ "type" => "VariableDeclarator",
374
+ "id" => {
375
+ "type" => "Identifier",
376
+ "name" => node.name,
377
+ "range" => offset_range(node, :name),
378
+ },
379
+ "init" => adapt_node(node.value),
380
+ })
381
+ when RKelly::Nodes::FunctionDeclNode == node.class
382
+ make(node, {
383
+ "type" => "FunctionDeclaration",
384
+ "id" => {
385
+ "type" => "Identifier",
386
+ "name" => node.value,
387
+ "range" => offset_range(node, :value, "function "),
388
+ },
389
+ "params" => node.arguments.map {|a| adapt_node(a) },
390
+ "body" => adapt_node(node.function_body),
391
+ })
392
+
393
+ else
394
+ # Unexpected node type
395
+ node
396
+ end
397
+ end
398
+
399
+ # augments node data with range info.
400
+ def make(node, config)
401
+ config["range"] = [node.range.from.index, node.range.to.index+1, node.range.from.line]
402
+ config
403
+ end
404
+
405
+ # Calculates "range" array from the start position of the node,
406
+ # its field and given offset prefix (amount of characters to
407
+ # discard from the beginning).
408
+ def offset_range(node, field, prefix="")
409
+ line = node.range.from.line
410
+ i = node.range.from.index
411
+ offset = prefix.length
412
+ length = node.send(field).to_s.length
413
+ return [i + offset, i + offset + length, line]
414
+ end
415
+
416
+ def flatten_sequence(node)
417
+ if node.is_a?(RKelly::Nodes::CommaNode)
418
+ [flatten_sequence(node.left), flatten_sequence(node.value)].flatten
419
+ else
420
+ node
421
+ end
422
+ end
423
+
424
+ def catch_clause(node)
425
+ {
426
+ "type" => "CatchClause",
427
+ "param" => {
428
+ "type" => "Identifier",
429
+ "name" => node.catch_var,
430
+ "range" => [
431
+ node.catch_block.range.from.index - (") ".length + node.catch_var.length),
432
+ node.catch_block.range.from.index - (") ".length),
433
+ node.catch_block.range.from.line,
434
+ ]
435
+ },
436
+ "body" => adapt_node(node.catch_block),
437
+ "range" => [
438
+ node.catch_block.range.from.index - ("catch () ".length + node.catch_var.length),
439
+ node.catch_block.range.to.index+1,
440
+ node.catch_block.range.from.line,
441
+ ]
442
+ }
443
+ end
444
+
445
+ BINARY_NODES = {
446
+ RKelly::Nodes::SubtractNode => "-",
447
+ RKelly::Nodes::LessOrEqualNode => "<=",
448
+ RKelly::Nodes::GreaterOrEqualNode => ">=",
449
+ RKelly::Nodes::AddNode => "+",
450
+ RKelly::Nodes::MultiplyNode => "*",
451
+ RKelly::Nodes::NotEqualNode => "!=",
452
+ RKelly::Nodes::LogicalAndNode => "&&",
453
+ RKelly::Nodes::UnsignedRightShiftNode => ">>>",
454
+ RKelly::Nodes::ModulusNode => "%",
455
+ RKelly::Nodes::NotStrictEqualNode => "!==",
456
+ RKelly::Nodes::LessNode => "<",
457
+ RKelly::Nodes::InNode => "in",
458
+ RKelly::Nodes::GreaterNode => ">",
459
+ RKelly::Nodes::BitOrNode => "|",
460
+ RKelly::Nodes::StrictEqualNode => "===",
461
+ RKelly::Nodes::LogicalOrNode => "||",
462
+ RKelly::Nodes::BitXOrNode => "^",
463
+ RKelly::Nodes::LeftShiftNode => "<"+"<",
464
+ RKelly::Nodes::EqualNode => "==",
465
+ RKelly::Nodes::BitAndNode => "&",
466
+ RKelly::Nodes::InstanceOfNode => "instanceof",
467
+ RKelly::Nodes::DivideNode => "/",
468
+ RKelly::Nodes::RightShiftNode => ">>",
469
+ }
470
+
471
+ UNARY_NODES = {
472
+ RKelly::Nodes::UnaryMinusNode => "-",
473
+ RKelly::Nodes::UnaryPlusNode => "+",
474
+ RKelly::Nodes::LogicalNotNode => "!",
475
+ RKelly::Nodes::BitwiseNotNode => "~",
476
+ RKelly::Nodes::TypeOfNode => "typeof",
477
+ RKelly::Nodes::DeleteNode => "delete",
478
+ RKelly::Nodes::VoidNode => "void",
479
+ }
480
+
481
+ ASSIGNMENT_NODES = {
482
+ RKelly::Nodes::OpEqualNode => "=",
483
+ RKelly::Nodes::OpMultiplyEqualNode => "*=",
484
+ RKelly::Nodes::OpDivideEqualNode => "/=",
485
+ RKelly::Nodes::OpLShiftEqualNode => "<<=",
486
+ RKelly::Nodes::OpMinusEqualNode => "-=",
487
+ RKelly::Nodes::OpPlusEqualNode => "+=",
488
+ RKelly::Nodes::OpModEqualNode => "%=",
489
+ RKelly::Nodes::OpXOrEqualNode => "^=",
490
+ RKelly::Nodes::OpRShiftEqualNode => ">>=",
491
+ RKelly::Nodes::OpAndEqualNode => "&=",
492
+ RKelly::Nodes::OpURShiftEqualNode => ">>>=",
493
+ RKelly::Nodes::OpOrEqualNode => "|=",
494
+ }
495
+
496
+ FALL_THROUGH_NODES = {
497
+ RKelly::Nodes::AssignExprNode => true,
498
+ RKelly::Nodes::ParentheticalNode => true,
499
+ RKelly::Nodes::ElementNode => true,
500
+ }
501
+
502
+ FALL_THROUGH_ARRAY_NODES = {
503
+ RKelly::Nodes::SourceElementsNode => true,
504
+ RKelly::Nodes::ArgumentsNode => true,
505
+ RKelly::Nodes::CaseBlockNode => true,
506
+ }
507
+
508
+ end
509
+
510
+ end
511
+ end