jsduck 5.0.0.beta3 → 5.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
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