jsduck 3.11.2 → 4.0.beta

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,4 +3,5 @@ template/extjs
3
3
  template/resources/css
4
4
  template/resources/sass/.sass-cache
5
5
  template-min/
6
+ esprima/
6
7
  sdk-vars.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ jsduck (3.11.0)
5
+ execjs
6
+ json
7
+ parallel
8
+ rdiscount
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ chunky_png (1.2.5)
14
+ compass (0.12.2)
15
+ chunky_png (~> 1.2)
16
+ fssm (>= 0.2.7)
17
+ sass (~> 3.1)
18
+ diff-lcs (1.1.3)
19
+ execjs (1.4.0)
20
+ multi_json (~> 1.0)
21
+ fssm (0.2.9)
22
+ json (1.7.3)
23
+ multi_json (1.3.6)
24
+ parallel (0.5.17)
25
+ rake (0.9.2.2)
26
+ rdiscount (1.6.8)
27
+ rspec (2.10.0)
28
+ rspec-core (~> 2.10.0)
29
+ rspec-expectations (~> 2.10.0)
30
+ rspec-mocks (~> 2.10.0)
31
+ rspec-core (2.10.1)
32
+ rspec-expectations (2.10.0)
33
+ diff-lcs (~> 1.1.3)
34
+ rspec-mocks (2.10.1)
35
+ sass (3.1.19)
36
+
37
+ PLATFORMS
38
+ ruby
39
+ x86-mingw32
40
+
41
+ DEPENDENCIES
42
+ compass
43
+ jsduck!
44
+ rake
45
+ rspec
data/README.md CHANGED
@@ -41,10 +41,7 @@ For **Windows** users out there, you can download the binary version,
41
41
  which includes Ruby interpreter and all dependencies bundled in a
42
42
  single .exe file. Grab it from the [download page][].
43
43
 
44
- If you are brave enough: [try out JSDuck 4.0 beta.][beta]
45
-
46
44
  [download page]: https://github.com/senchalabs/jsduck/downloads
47
- [beta]: https://github.com/senchalabs/jsduck/wiki/4.0-beta
48
45
 
49
46
  Usage
50
47
  -----
data/Rakefile CHANGED
@@ -220,6 +220,7 @@ task :ext4 => :sass do
220
220
  runner = JsDuckRunner.new
221
221
  runner.add_ext4
222
222
  runner.add_debug
223
+ runner.add_options("--tests")
223
224
  runner.run
224
225
 
225
226
  system("cp -r #{EXT_BUILD} #{OUT_DIR}/extjs-build")
@@ -232,7 +233,8 @@ task :sdk => :sass do
232
233
  "--output", OUT_DIR,
233
234
  "--config", "#{SDK_DIR}/extjs/docs/config.json",
234
235
  "--examples-base-url", "extjs-build/examples/",
235
- "--seo"
236
+ "--seo",
237
+ "--tests"
236
238
  )
237
239
  runner.add_debug
238
240
  runner.add_comments('ext-js', '4')
@@ -248,7 +250,8 @@ task :touch2 => :sass do
248
250
  "--output", OUT_DIR,
249
251
  "--config", "#{SDK_DIR}/touch/docs/config.json",
250
252
  "--examples-base-url", "touch-build/examples/production/",
251
- "--seo"
253
+ "--seo",
254
+ "--tests"
252
255
  )
253
256
 
254
257
  runner.add_debug
data/js-classes/String.js CHANGED
@@ -949,7 +949,7 @@
949
949
  *
950
950
  * The following example displays the string "sencha":
951
951
  *
952
- * var upperText="SENCHA";
952
+ * var upperText="sencha";
953
953
  * document.write(upperText.toLocaleLowerCase());
954
954
  *
955
955
  * @return {String} Returns value of the string in lowercase.
data/jsduck.gemspec CHANGED
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
2
2
  s.required_rubygems_version = ">= 1.3.5"
3
3
 
4
4
  s.name = 'jsduck'
5
- s.version = '3.11.2'
6
- s.date = '2012-08-07'
5
+ s.version = '4.0.beta'
6
+ s.date = '2012-06-27'
7
7
  s.summary = "Simple JavaScript Duckumentation generator"
8
8
  s.description = "Documentation generator for Sencha JS frameworks"
9
9
  s.homepage = "https://github.com/senchalabs/jsduck"
@@ -16,12 +16,15 @@ Gem::Specification.new do |s|
16
16
  end
17
17
  # Add files not in git
18
18
  s.files += Dir['template-min/**/*']
19
+ # Add Esprima
20
+ s.files += Dir['esprima/esprima.js']
19
21
 
20
22
  s.executables = ["jsduck"]
21
23
 
22
24
  s.add_dependency 'rdiscount'
23
25
  s.add_dependency 'json'
24
26
  s.add_dependency 'parallel'
27
+ s.add_dependency 'execjs'
25
28
 
26
29
  s.add_development_dependency 'rspec'
27
30
  s.add_development_dependency 'rake'
@@ -110,6 +110,7 @@ module JsDuck
110
110
  :owner => cfg[:owner],
111
111
  :files => cfg[:files],
112
112
  :private => cfg[:private],
113
+ :autodetected => cfg[:autodetected],
113
114
  :meta => clone_meta(cfg),
114
115
  })
115
116
  end
@@ -205,7 +205,10 @@ module JsDuck
205
205
  end
206
206
 
207
207
  # Appends Ext4 options parameter to each event parameter list.
208
+ # But only when we are dealing with Ext4 codebase.
208
209
  def append_ext4_event_options
210
+ return unless ext4?
211
+
209
212
  options = {
210
213
  :tagname => :param,
211
214
  :name => "eOpts",
data/lib/jsduck/app.rb CHANGED
@@ -124,9 +124,7 @@ module JsDuck
124
124
  agr.create_global_class
125
125
  agr.remove_ignored_classes
126
126
  agr.create_accessors
127
- if @opts.ext4_events == true || (@opts.ext4_events == nil && agr.ext4?)
128
- agr.append_ext4_event_options
129
- end
127
+ agr.append_ext4_event_options
130
128
  agr.result
131
129
  end
132
130
 
@@ -165,15 +163,14 @@ module JsDuck
165
163
  class_formatter.include_types = !@opts.export
166
164
  # Format all doc-objects in parallel
167
165
  formatted_classes = @parallel.map(@relations.classes) do |cls|
168
- files = cls[:files].map {|f| f[:filename] }.join(" ")
169
- Logger.instance.log("Markdown formatting #{cls[:name]}", files)
166
+ Logger.instance.log("Markdown formatting #{cls[:name]}")
170
167
  begin
171
168
  {
172
169
  :doc => class_formatter.format(cls.internal_doc),
173
170
  :images => doc_formatter.images
174
171
  }
175
172
  rescue
176
- Logger.instance.fatal("Error while formatting #{cls[:name]} #{files}", $!)
173
+ Logger.instance.fatal("Error while formatting #{cls[:name]}", $!)
177
174
  exit(1)
178
175
  end
179
176
  end
data/lib/jsduck/ast.rb ADDED
@@ -0,0 +1,446 @@
1
+ require "jsduck/serializer"
2
+ require "jsduck/evaluator"
3
+
4
+ module JsDuck
5
+
6
+ # Analyzes the AST produced by EsprimaParser.
7
+ class Ast
8
+ # Should be initialized with EsprimaParser#parse result.
9
+ def initialize(docs = [], options = {})
10
+ @serializer = JsDuck::Serializer.new
11
+ @evaluator = JsDuck::Evaluator.new
12
+ @ext_define_patterns = build_ext_define_patterns(options[:ext_namespaces] || ["Ext"])
13
+ @docs = docs
14
+ end
15
+
16
+ # Given Array of alternate Ext namespaces builds list of patterns
17
+ # for detecting Ext.define:
18
+ #
19
+ # ["Ext","Foo"] --> ["Ext.define", "Ext.ClassManager.create", "Foo.define", "Foo.ClassManager.create"]
20
+ #
21
+ def build_ext_define_patterns(namespaces)
22
+ namespaces.map do |ns|
23
+ [ns + ".define", ns + ".ClassManager.create"]
24
+ end.flatten
25
+ end
26
+
27
+ # Performs the detection of code in all docsets.
28
+ #
29
+ # @returns the processed array of docsets. (But it does it
30
+ # destructively by modifying the passed-in docsets.)
31
+ #
32
+ def detect_all!
33
+ # First deal only with doc-comments
34
+ doc_comments = @docs.find_all {|d| d[:type] == :doc_comment }
35
+
36
+ # Detect code in each docset. Sometimes a docset has already
37
+ # been detected as part of detecting some previous docset (like
38
+ # Class detecting all of its configs) - in such case, skip.
39
+ doc_comments.each do |docset|
40
+ code = docset[:code]
41
+ docset[:code] = detect(code) unless code && code[:tagname]
42
+ end
43
+
44
+ # Return all doc-comments + other comments for which related
45
+ # code was detected.
46
+ @docs.find_all {|d| d[:type] == :doc_comment || d[:code] && d[:code][:tagname] }
47
+ end
48
+
49
+ # Given Esprima-produced syntax tree, detects documentation data.
50
+ #
51
+ # This method is exposed for testing purposes only, JSDuck itself
52
+ # only calls the above #detect_all method.
53
+ #
54
+ # @param ast :code from Result of EsprimaParser
55
+ # @returns Hash consisting of the detected :tagname, :name, and
56
+ # other properties relative to the tag. Like so:
57
+ #
58
+ # { :tagname => :method, :name => "foo", ... }
59
+ #
60
+ def detect(ast)
61
+ ast = ast || {}
62
+
63
+ exp = expression?(ast) ? ast["expression"] : nil
64
+ var = var?(ast) ? ast["declarations"][0] : nil
65
+
66
+ # Ext.define("Class", {})
67
+ if exp && ext_define?(exp)
68
+ make_class(to_value(exp["arguments"][0]), exp)
69
+
70
+ # foo = Ext.extend("Parent", {})
71
+ elsif exp && assignment?(exp) && ext_extend?(exp["right"])
72
+ make_class(to_s(exp["left"]), exp["right"])
73
+
74
+ # Foo = ...
75
+ elsif exp && assignment?(exp) && class_name?(to_s(exp["left"]))
76
+ make_class(to_s(exp["left"]))
77
+
78
+ # var foo = Ext.extend("Parent", {})
79
+ elsif var && var["init"] && ext_extend?(var["init"])
80
+ make_class(to_s(var["id"]), var["init"])
81
+
82
+ # var Foo = ...
83
+ elsif var && class_name?(to_s(var["id"]))
84
+ make_class(to_s(var["id"]))
85
+
86
+ # function Foo() {}
87
+ elsif function?(ast) && class_name?(to_s(ast["id"]))
88
+ make_class(to_s(ast["id"]))
89
+
90
+ # function foo() {}
91
+ elsif function?(ast)
92
+ make_method(to_s(ast["id"]), ast)
93
+
94
+ # foo = function() {}
95
+ elsif exp && assignment?(exp) && function?(exp["right"])
96
+ make_method(to_s(exp["left"]), exp["right"])
97
+
98
+ # var foo = function() {}
99
+ elsif var && var["init"] && function?(var["init"])
100
+ make_method(to_s(var["id"]), var["init"])
101
+
102
+ # (function() {})
103
+ elsif exp && function?(exp)
104
+ make_method(exp["id"] ? to_s(exp["id"]) : "", exp)
105
+
106
+ # foo: function() {}
107
+ elsif property?(ast) && function?(ast["value"])
108
+ make_method(key_value(ast["key"]), ast["value"])
109
+
110
+ # foo = ...
111
+ elsif exp && assignment?(exp)
112
+ make_property(to_s(exp["left"]), exp["right"])
113
+
114
+ # var foo = ...
115
+ elsif var
116
+ make_property(to_s(var["id"]), var["init"])
117
+
118
+ # foo: ...
119
+ elsif property?(ast)
120
+ make_property(key_value(ast["key"]), ast["value"])
121
+
122
+ # foo;
123
+ elsif exp && ident?(exp)
124
+ make_property(to_s(exp))
125
+
126
+ # "foo" (inside some expression)
127
+ elsif string?(ast)
128
+ make_property(to_value(ast))
129
+
130
+ # "foo"; (as a statement of it's own)
131
+ elsif exp && string?(exp)
132
+ make_property(to_value(exp))
133
+
134
+ else
135
+ make_property()
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def expression?(ast)
142
+ ast["type"] == "ExpressionStatement"
143
+ end
144
+
145
+ def call?(ast)
146
+ ast["type"] == "CallExpression"
147
+ end
148
+
149
+ def assignment?(ast)
150
+ ast["type"] == "AssignmentExpression"
151
+ end
152
+
153
+ def ext_define?(ast)
154
+ call?(ast) && @ext_define_patterns.include?(to_s(ast["callee"]))
155
+ end
156
+
157
+ def ext_extend?(ast)
158
+ call?(ast) && to_s(ast["callee"]) == "Ext.extend"
159
+ end
160
+
161
+ def function?(ast)
162
+ ast["type"] == "FunctionDeclaration" || ast["type"] == "FunctionExpression" || empty_fn?(ast)
163
+ end
164
+
165
+ def empty_fn?(ast)
166
+ ast["type"] == "MemberExpression" && to_s(ast) == "Ext.emptyFn"
167
+ end
168
+
169
+ def var?(ast)
170
+ ast["type"] == "VariableDeclaration"
171
+ end
172
+
173
+ def property?(ast)
174
+ ast["type"] == "Property"
175
+ end
176
+
177
+ def ident?(ast)
178
+ ast["type"] == "Identifier"
179
+ end
180
+
181
+ def string?(ast)
182
+ ast["type"] == "Literal" && ast["value"].is_a?(String)
183
+ end
184
+
185
+ # Class name begins with upcase char
186
+ def class_name?(name)
187
+ return name.split(/\./).last =~ /\A[A-Z]/
188
+ end
189
+
190
+ def make_class(name, ast=nil)
191
+ cls = {
192
+ :tagname => :class,
193
+ :name => name,
194
+ }
195
+
196
+ # apply information from Ext.extend or Ext.define
197
+ if ast
198
+ if ext_extend?(ast)
199
+ cls[:extends] = to_s(ast["arguments"][0])
200
+ elsif ext_define?(ast)
201
+ detect_ext_define(cls, ast)
202
+ end
203
+ end
204
+
205
+ return cls
206
+ end
207
+
208
+ # Inspects Ext.define() and copies detected properties over to the
209
+ # given cls Hash
210
+ def detect_ext_define(cls, ast)
211
+ # defaults
212
+ cls[:extends] = "Ext.Base"
213
+ cls[:requires] = []
214
+ cls[:uses] = []
215
+ cls[:alternateClassNames] = []
216
+ cls[:mixins] = []
217
+ cls[:aliases] = []
218
+ cls[:members] = []
219
+ cls[:statics] = []
220
+
221
+ each_pair_in_object_expression(ast["arguments"][1]) do |key, value, pair|
222
+ case key
223
+ when "extend"
224
+ cls[:extends] = make_extends(value)
225
+ when "requires"
226
+ cls[:requires] = make_string_list(value)
227
+ when "uses"
228
+ cls[:uses] = make_string_list(value)
229
+ when "alternateClassName"
230
+ cls[:alternateClassNames] = make_string_list(value)
231
+ when "mixins"
232
+ cls[:mixins] = make_mixins(value)
233
+ when "singleton"
234
+ cls[:singleton] = make_singleton(value)
235
+ when "alias"
236
+ cls[:aliases] += make_string_list(value)
237
+ when "xtype"
238
+ cls[:aliases] += make_string_list(value).map {|xtype| "widget."+xtype }
239
+ when "config"
240
+ cls[:members] += make_configs(value, {:accessor => true})
241
+ when "cachedConfig"
242
+ cls[:members] += make_configs(value, {:accessor => true})
243
+ when "eventedConfig"
244
+ cls[:members] += make_configs(value, {:accessor => true, :evented => true})
245
+ when "statics"
246
+ cls[:statics] += make_statics(value)
247
+ when "inheritableStatics"
248
+ cls[:statics] += make_statics(value, {:inheritable => true})
249
+ else
250
+ if function?(value)
251
+ m = make_method(key, value)
252
+ cls[:members] << m if apply_autodetected(m, pair)
253
+ else
254
+ p = make_property(key, value)
255
+ cls[:members] << p if apply_autodetected(p, pair)
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ def make_extends(cfg_value)
262
+ return nil unless cfg_value
263
+
264
+ parent = to_value(cfg_value)
265
+
266
+ return parent.is_a?(String) ? parent : nil
267
+ end
268
+
269
+ def make_string_list(cfg_value)
270
+ return [] unless cfg_value
271
+
272
+ classes = Array(to_value(cfg_value))
273
+
274
+ return classes.all? {|c| c.is_a? String } ? classes : []
275
+ end
276
+
277
+ def make_mixins(cfg_value)
278
+ return [] unless cfg_value
279
+
280
+ v = to_value(cfg_value)
281
+ classes = v.is_a?(Hash) ? v.values : Array(v)
282
+
283
+ return classes.all? {|c| c.is_a? String } ? classes : []
284
+ end
285
+
286
+ def make_singleton(cfg_value)
287
+ cfg_value && to_value(cfg_value) == true
288
+ end
289
+
290
+ def make_configs(ast, defaults={})
291
+ configs = []
292
+
293
+ each_pair_in_object_expression(ast) do |name, value, pair|
294
+ cfg = make_property(name, value, :cfg)
295
+ cfg.merge!(defaults)
296
+ configs << cfg if apply_autodetected(cfg, pair)
297
+ end
298
+
299
+ configs
300
+ end
301
+
302
+ def make_statics(ast, defaults={})
303
+ statics = []
304
+
305
+ each_pair_in_object_expression(ast) do |name, value, pair|
306
+ if function?(value)
307
+ s = make_method(name, value)
308
+ else
309
+ s = make_property(name, value)
310
+ end
311
+
312
+ s[:meta] = {:static => true}
313
+ s.merge!(defaults)
314
+
315
+ statics << s if apply_autodetected(s, pair, defaults[:inheritable])
316
+ end
317
+
318
+ statics
319
+ end
320
+
321
+ # Sets auto-detection related properties :autodetected and
322
+ # :inheritdoc on the given member Hash.
323
+ #
324
+ # When member has a comment, adds code to the related docset and
325
+ # returns false.
326
+ #
327
+ # Otherwise detects the line number of member and returns true.
328
+ def apply_autodetected(m, pair, inheritable=true)
329
+ docset = find_docset(pair)
330
+
331
+ if !docset || docset[:type] != :doc_comment
332
+ if inheritable
333
+ m[:inheritdoc] = {}
334
+ else
335
+ m[:private] = true
336
+ end
337
+ m[:autodetected] = true
338
+ end
339
+
340
+ if docset
341
+ docset[:code] = m
342
+ return false
343
+ else
344
+ # Get line number from third place at range array.
345
+ # This third item exists in forked EsprimaJS at
346
+ # https://github.com/nene/esprima/tree/linenr-in-range
347
+ m[:linenr] = pair["range"][2]
348
+ return true
349
+ end
350
+ end
351
+
352
+ # Looks up docset associated with given AST node.
353
+ # A dead-stupid and -slow implementation, but works.
354
+ def find_docset(ast)
355
+ @docs.find do |docset|
356
+ docset[:code] == ast
357
+ end
358
+ end
359
+
360
+ def make_method(name, ast=nil)
361
+ return {
362
+ :tagname => :method,
363
+ :name => name,
364
+ :params => make_params(ast)
365
+ }
366
+ end
367
+
368
+ def make_params(ast)
369
+ if ast && !empty_fn?(ast)
370
+ ast["params"].map {|p| {:name => to_s(p)} }
371
+ else
372
+ []
373
+ end
374
+ end
375
+
376
+ def make_property(name=nil, ast=nil, tagname=:property)
377
+ return {
378
+ :tagname => tagname,
379
+ :name => name,
380
+ :type => make_value_type(ast),
381
+ :default => make_default(ast),
382
+ }
383
+ end
384
+
385
+ def make_default(ast)
386
+ ast && to_value(ast) != nil ? to_s(ast) : nil
387
+ end
388
+
389
+ def make_value_type(ast)
390
+ if ast
391
+ v = to_value(ast)
392
+ if v.is_a?(String)
393
+ "String"
394
+ elsif v.is_a?(Numeric)
395
+ "Number"
396
+ elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
397
+ "Boolean"
398
+ elsif v.is_a?(Array)
399
+ "Array"
400
+ elsif v.is_a?(Hash)
401
+ "Object"
402
+ elsif v == :regexp
403
+ "RegExp"
404
+ else
405
+ nil
406
+ end
407
+ else
408
+ nil
409
+ end
410
+ end
411
+
412
+ # -- various helper methods --
413
+
414
+ # Iterates over keys and values in ObjectExpression. The keys
415
+ # are turned into strings, but values are left as is for further
416
+ # processing.
417
+ def each_pair_in_object_expression(ast)
418
+ return unless ast && ast["type"] == "ObjectExpression"
419
+
420
+ ast["properties"].each do |p|
421
+ yield(key_value(p["key"]), p["value"], p)
422
+ end
423
+ end
424
+
425
+ # Converts object expression property key to string value
426
+ def key_value(key)
427
+ @evaluator.key_value(key)
428
+ end
429
+
430
+ # Fully serializes the node
431
+ def to_s(ast)
432
+ @serializer.to_s(ast)
433
+ end
434
+
435
+ # Converts AST node into a value.
436
+ def to_value(ast)
437
+ begin
438
+ @evaluator.to_value(ast)
439
+ rescue
440
+ nil
441
+ end
442
+ end
443
+ end
444
+
445
+ end
446
+