jsduck 4.0.beta2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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 = '4.0.beta2'
6
- s.date = '2012-08-01'
5
+ s.version = '4.0.0'
6
+ s.date = '2012-08-09'
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,15 +16,13 @@ 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']
21
19
 
22
20
  s.executables = ["jsduck"]
23
21
 
24
22
  s.add_dependency 'rdiscount'
25
23
  s.add_dependency 'json'
26
24
  s.add_dependency 'parallel'
27
- s.add_dependency 'execjs'
25
+ s.add_dependency 'therubyracer'
28
26
 
29
27
  s.add_development_dependency 'rspec'
30
28
  s.add_development_dependency 'rake'
@@ -2,6 +2,7 @@ require 'jsduck/class'
2
2
  require 'jsduck/accessors'
3
3
  require 'jsduck/logger'
4
4
  require 'jsduck/enum'
5
+ require 'jsduck/override'
5
6
 
6
7
  module JsDuck
7
8
 
@@ -206,10 +207,7 @@ module JsDuck
206
207
  end
207
208
 
208
209
  # Appends Ext4 options parameter to each event parameter list.
209
- # But only when we are dealing with Ext4 codebase.
210
210
  def append_ext4_event_options
211
- return unless ext4?
212
-
213
211
  options = {
214
212
  :tagname => :param,
215
213
  :name => "eOpts",
@@ -234,6 +232,11 @@ module JsDuck
234
232
  Enum.new(@classes).process_all!
235
233
  end
236
234
 
235
+ # Processes all overrides
236
+ def process_overrides
237
+ Override.new(@classes, @documentation).process_all!
238
+ end
239
+
237
240
  # Are we dealing with ExtJS 4?
238
241
  # True if any of the classes is defined with Ext.define()
239
242
  def ext4?
@@ -47,7 +47,7 @@ module JsDuck
47
47
  result = aggregate(parsed_files)
48
48
  @relations = filter_classes(result)
49
49
  InheritDoc.new(@relations).resolve_all
50
- Importer.import(@opts.imports, @relations)
50
+ Importer.import(@opts.imports, @relations, @opts.new_since)
51
51
  Lint.new(@relations).run
52
52
 
53
53
  # Initialize guides, videos, examples, ...
@@ -109,7 +109,7 @@ module JsDuck
109
109
  begin
110
110
  SourceFile.new(JsDuck::IO.read(fname), fname, @opts)
111
111
  rescue
112
- Logger.instance.fatal("Error while parsing #{fname}", $!)
112
+ Logger.instance.fatal_backtrace("Error while parsing #{fname}", $!)
113
113
  exit(1)
114
114
  end
115
115
  end
@@ -126,8 +126,11 @@ module JsDuck
126
126
  agr.create_global_class
127
127
  agr.remove_ignored_classes
128
128
  agr.create_accessors
129
- agr.append_ext4_event_options
129
+ if @opts.ext4_events == true || (@opts.ext4_events == nil && agr.ext4?)
130
+ agr.append_ext4_event_options
131
+ end
130
132
  agr.process_enums
133
+ agr.process_overrides
131
134
  agr.result
132
135
  end
133
136
 
@@ -166,14 +169,15 @@ module JsDuck
166
169
  class_formatter.include_types = !@opts.export
167
170
  # Format all doc-objects in parallel
168
171
  formatted_classes = ParallelWrap.map(@relations.classes) do |cls|
169
- Logger.instance.log("Markdown formatting #{cls[:name]}")
172
+ files = cls[:files].map {|f| f[:filename] }.join(" ")
173
+ Logger.instance.log("Markdown formatting #{cls[:name]}", files)
170
174
  begin
171
175
  {
172
176
  :doc => class_formatter.format(cls.internal_doc),
173
177
  :images => doc_formatter.images
174
178
  }
175
179
  rescue
176
- Logger.instance.fatal("Error while formatting #{cls[:name]}", $!)
180
+ Logger.instance.fatal_backtrace("Error while formatting #{cls[:name]} #{files}", $!)
177
181
  exit(1)
178
182
  end
179
183
  end
@@ -230,7 +230,9 @@ module JsDuck
230
230
  each_pair_in_object_expression(ast["arguments"][1]) do |key, value, pair|
231
231
  case key
232
232
  when "extend"
233
- cls[:extends] = make_extends(value)
233
+ cls[:extends] = make_string(value)
234
+ when "override"
235
+ cls[:override] = make_string(value)
234
236
  when "requires"
235
237
  cls[:requires] = make_string_list(value)
236
238
  when "uses"
@@ -288,7 +290,7 @@ module JsDuck
288
290
  end
289
291
  end
290
292
 
291
- def make_extends(cfg_value)
293
+ def make_string(cfg_value)
292
294
  return nil unless cfg_value
293
295
 
294
296
  parent = to_value(cfg_value)
@@ -147,8 +147,10 @@ module JsDuck
147
147
  # Singletons have no static members
148
148
  if @doc[:singleton] && context == :statics
149
149
  # Warn if singleton has static members
150
- if @doc[context][type].length > 0
151
- Logger.instance.warn(:sing_static, "Singleton class #{@doc[:name]} can't have static members, remove the @static tag.")
150
+ @doc[context][type].each do |m|
151
+ ctx = m[:files][0]
152
+ msg = "Singleton class #{@doc[:name]} can't have static members, remove the @static tag."
153
+ Logger.instance.warn(:sing_static, msg, ctx[:filename], ctx[:linenr])
152
154
  end
153
155
  return {}
154
156
  end
@@ -185,7 +187,8 @@ module JsDuck
185
187
  hash1.delete(name)
186
188
  else
187
189
  ctx = m[:files][0]
188
- Logger.instance.warn(:hide, "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class", ctx[:filename], ctx[:linenr])
190
+ msg = "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class"
191
+ Logger.instance.warn(:hide, msg, ctx[:filename], ctx[:linenr])
189
192
  end
190
193
  else
191
194
  if hash1[name]
@@ -264,6 +267,11 @@ module JsDuck
264
267
  return ms
265
268
  end
266
269
 
270
+ # Call this when renaming or moving members inside class.
271
+ def reset_members_lookup!
272
+ @members_map = nil
273
+ end
274
+
267
275
  # Returns all members of class, including the inherited and mixed in ones
268
276
  def all_members
269
277
  all = []
@@ -27,14 +27,15 @@ module JsDuck
27
27
  @docs
28
28
  end
29
29
 
30
- # <code-block> := <mixin-declaration> | <var-declaration> | <nop>
30
+ # <code-block> := <mixin-declaration> | <var-declaration> | <property>
31
31
  def code_block
32
32
  if look("@mixin")
33
33
  mixin_declaration
34
34
  elsif look(:var, ":")
35
35
  var_declaration
36
36
  else
37
- {:tagname => :nop}
37
+ # Default to property like in JsParser.
38
+ {:tagname => :property}
38
39
  end
39
40
  end
40
41
 
@@ -52,6 +52,7 @@ module JsDuck
52
52
  :requires => detect_list(:requires, doc_map),
53
53
  :uses => detect_list(:uses, doc_map),
54
54
  :enum => detect_enum(doc_map),
55
+ :override => extract(doc_map, :override, :class),
55
56
  }, doc_map)
56
57
  end
57
58
 
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require 'rubygems'
3
3
  require 'strscan'
4
- require 'jsduck/markdown'
4
+ require 'rdiscount'
5
5
  require 'jsduck/logger'
6
6
  require 'jsduck/inline_img'
7
7
  require 'jsduck/inline_video'
@@ -95,7 +95,7 @@ module JsDuck
95
95
  out += replace_link_tag(s.scan(@link_re))
96
96
  elsif substitute = @inline_img.replace(s)
97
97
  out += substitute
98
- elsif substitute = @inline_video.replace(s)
98
+ elsif substitute = @inline_video.replace(s, @doc_context)
99
99
  out += substitute
100
100
  elsif s.check(/[{]/)
101
101
  # There might still be "{" that doesn't begin {@link} or {@img} - ignore it
@@ -310,7 +310,7 @@ module JsDuck
310
310
  # code-blocks beginning with empty line.
311
311
  input.gsub!(/<pre>(<code>)?\n?/, "<pre>\\1")
312
312
 
313
- replace(JsDuck::Markdown.to_html(input))
313
+ replace(RDiscount.new(input).to_html)
314
314
  end
315
315
 
316
316
  # Shortens text
@@ -132,6 +132,8 @@ module JsDuck
132
132
  at_throws
133
133
  elsif look(/@enum\b/)
134
134
  at_enum
135
+ elsif look(/@override\b/)
136
+ at_override
135
137
  elsif look(/@inheritable\b/)
136
138
  boolean_at_tag(/@inheritable/, :inheritable)
137
139
  elsif look(/@accessor\b/)
@@ -294,6 +296,14 @@ module JsDuck
294
296
  skip_white
295
297
  end
296
298
 
299
+ # matches @override name ...
300
+ def at_override
301
+ match(/@override/)
302
+ add_tag(:override)
303
+ maybe_ident_chain(:class)
304
+ skip_white
305
+ end
306
+
297
307
  # matches @type {type} or @type type
298
308
  #
299
309
  # The presence of @type implies that we are dealing with property.
@@ -1,12 +1,10 @@
1
- require 'execjs'
1
+ require 'v8'
2
2
  require 'json'
3
3
  require 'singleton'
4
4
 
5
5
  module JsDuck
6
6
 
7
- # Runs Esprima.js through execjs (this will select any available
8
- # JavaScript runtime - preferably therubyracer on MRI and JScript
9
- # on Windows).
7
+ # Runs Esprima.js through V8.
10
8
  #
11
9
  # Initialized as singleton to avoid loading the esprima.js more
12
10
  # than once - otherwise performace will severely suffer.
@@ -14,17 +12,17 @@ module JsDuck
14
12
  include Singleton
15
13
 
16
14
  def initialize
17
- esprima_path = File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__))))+"/esprima/esprima.js";
18
- esprima = IO.read(esprima_path)
19
- helper = "function runEsprima(js) { return JSON.stringify(esprima.parse(js, {comment: true, range: true, raw: true})); }"
20
- @context = ExecJS.compile(esprima + "\n\n" + helper)
15
+ @v8 = V8::Context.new
16
+ esprima = File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__))))+"/esprima/esprima.js";
17
+ @v8.load(esprima)
21
18
  end
22
19
 
23
20
  # Parses JavaScript source code using Esprima.js
24
21
  #
25
22
  # Returns the resulting AST
26
23
  def parse(input)
27
- json = @context.call("runEsprima", input)
24
+ @v8['js'] = input
25
+ json = @v8.eval("JSON.stringify(esprima.parse(js, {comment: true, range: true, raw: true}))")
28
26
  return JSON.parse(json, :max_nesting => false)
29
27
  end
30
28
 
@@ -20,7 +20,7 @@ module JsDuck
20
20
  @groups = JsonDuck.read(filename)
21
21
  @opts = opts
22
22
  fix_examples_data
23
- build_map_by_name("Two examples have the same name")
23
+ build_map_by_name("Two examples have the same name", filename)
24
24
  end
25
25
 
26
26
  # Prefix all relative URL-s in examples list with path given in --examples-base-url
@@ -13,7 +13,7 @@ module JsDuck
13
13
 
14
14
  # Don't crash if old syntax is used.
15
15
  if @categories.is_a?(Hash) && @categories["categories"]
16
- Logger.instance.warn(nil, 'Update categories file to contain just the array inside {"categories": [...]}')
16
+ Logger.instance.warn(nil, 'Update categories file to contain just the array inside {"categories": [...]}', @filename)
17
17
  @categories = @categories["categories"]
18
18
  end
19
19
 
@@ -36,7 +36,7 @@ module JsDuck
36
36
  re = Regexp.new("^" + name.split(/\*/, -1).map {|part| Regexp.escape(part) }.join('.*') + "$")
37
37
  classes = @relations.to_a.find_all {|cls| re =~ cls[:name] && !cls[:private] }.map {|cls| cls[:name] }.sort
38
38
  if classes.length == 0
39
- Logger.instance.warn(:cat_no_match, "No class found matching a pattern '#{name}' in categories file.")
39
+ Logger.instance.warn(:cat_no_match, "No class found matching a pattern '#{name} in categories file'", @filename)
40
40
  end
41
41
  classes
42
42
  end
@@ -56,7 +56,7 @@ module JsDuck
56
56
  # Check that each existing non-private class is listed
57
57
  @relations.each do |cls|
58
58
  unless listed_classes[cls[:name]] || cls[:private]
59
- Logger.instance.warn(:cat_class_missing, "Class '#{cls[:name]}' not found in categories file")
59
+ Logger.instance.warn(:cat_class_missing, "Class '#{cls[:name]}' not found in categories file", @filename)
60
60
  end
61
61
  end
62
62
  end
@@ -13,13 +13,13 @@ module JsDuck
13
13
  #
14
14
  # Prints warning when there is a duplicate item within a group.
15
15
  # The warning message should say something like "duplicate <asset type>"
16
- def build_map_by_name(warning_msg)
16
+ def build_map_by_name(warning_msg, filename)
17
17
  @map_by_name = {}
18
18
  @groups.each do |group|
19
19
  group_map = {}
20
20
  group["items"].each do |item|
21
21
  if group_map[item["name"]]
22
- Logger.instance.warn(:dup_asset, "#{warning_msg} '#{item['name']}'")
22
+ Logger.instance.warn(:dup_asset, "#{warning_msg} '#{item['name']}'", filename)
23
23
  end
24
24
  @map_by_name[item["name"]] = item
25
25
  group_map[item["name"]] = item
@@ -24,7 +24,7 @@ module JsDuck
24
24
  def initialize(filename, formatter, opts)
25
25
  @path = File.dirname(filename)
26
26
  @groups = JsonDuck.read(filename)
27
- build_map_by_name("Two guides have the same name")
27
+ build_map_by_name("Two guides have the same name", filename)
28
28
  @formatter = formatter
29
29
  @opts = opts
30
30
  end
@@ -58,11 +58,11 @@ module JsDuck
58
58
  def load_guide(guide)
59
59
  in_dir = @path + "/guides/" + guide["name"]
60
60
 
61
- return Logger.instance.warn(:guide, "Guide #{in_dir} not found") unless File.exists?(in_dir)
61
+ return Logger.instance.warn(:guide, "Guide not found", in_dir) unless File.exists?(in_dir)
62
62
 
63
63
  guide_file = in_dir + "/README.md"
64
64
 
65
- return Logger.instance.warn(:guide, "README.md not found in #{in_dir}") unless File.exists?(guide_file)
65
+ return Logger.instance.warn(:guide, "Guide not found", guide_file) unless File.exists?(guide_file)
66
66
 
67
67
  begin
68
68
  @formatter.doc_context = {:filename => guide_file, :linenr => 0}
@@ -71,7 +71,7 @@ module JsDuck
71
71
 
72
72
  return add_toc(guide, @formatter.format(JsDuck::IO.read(guide_file)))
73
73
  rescue
74
- Logger.instance.fatal("Error while reading/formatting guide #{in_dir}", $!)
74
+ Logger.instance.fatal_backtrace("Error while reading/formatting guide #{in_dir}", $!)
75
75
  exit(1)
76
76
  end
77
77
  end
@@ -10,9 +10,9 @@ module JsDuck
10
10
  module_function
11
11
 
12
12
  # Loads in exported docs and generates @since and @new tags based on that data.
13
- def import(imports, relations)
13
+ def import(imports, relations, new_since=nil)
14
14
  if imports.length > 0
15
- generate_since_tags(read_all(imports), relations)
15
+ generate_since_tags(read_all(imports), relations, new_since)
16
16
  end
17
17
  end
18
18
 
@@ -60,22 +60,41 @@ module JsDuck
60
60
 
61
61
  # Using the imported versions data, adds @since tags to all
62
62
  # classes/members.
63
- def generate_since_tags(versions, relations)
64
- last_version = versions.last[:version]
63
+ def generate_since_tags(versions, relations, new_since=nil)
64
+ new_versions = build_new_versions_map(versions, new_since)
65
65
 
66
66
  relations.each do |cls|
67
67
  v = cls[:meta][:since] || class_since(versions, cls)
68
68
  cls[:meta][:since] = v
69
- cls[:meta][:new] = true if v == last_version
69
+ cls[:meta][:new] = true if new_versions[v]
70
70
 
71
71
  cls.all_local_members.each do |m|
72
72
  v = m[:meta][:since] || member_since(versions, cls, m)
73
73
  m[:meta][:since] = v
74
- m[:meta][:new] = true if v == last_version
74
+ m[:meta][:new] = true if new_versions[v]
75
75
  end
76
76
  end
77
77
  end
78
78
 
79
+ # Generates a lookup table of versions that we are going to label
80
+ # with @new tags. By default we use the latest version, otherwise
81
+ # use all versions since the latest.
82
+ def build_new_versions_map(versions, new_since=nil)
83
+ new_versions = {}
84
+
85
+ if new_since
86
+ versions.map {|v| v[:version] }.each do |v|
87
+ if v == new_since || !new_versions.empty?
88
+ new_versions[v] = true
89
+ end
90
+ end
91
+ else
92
+ new_versions[versions.last[:version]] = true
93
+ end
94
+
95
+ new_versions
96
+ end
97
+
79
98
  def member_since(versions, cls, m)
80
99
  versions.each do |ver|
81
100
  c = ver[:classes][cls[:name]]
@@ -55,6 +55,9 @@ module JsDuck
55
55
  cls[:members][:property].delete(m)
56
56
  cls[:members][:cfg] << m
57
57
  end
58
+ # The members lookup table inside class is no more valid, so
59
+ # reset it.
60
+ cls.reset_members_lookup!
58
61
  end
59
62
 
60
63
  # For auto-detected members/classes (which have @private == :inherit)
@@ -28,18 +28,18 @@ module JsDuck
28
28
  # Looks for inline tag at the current scan pointer position, when
29
29
  # found, moves scan pointer forward and performs the apporpriate
30
30
  # replacement.
31
- def replace(input)
31
+ def replace(input, doc_context)
32
32
  if input.check(@re)
33
- input.scan(@re).sub(@re) { apply_tpl($1, $2, $3) }
33
+ input.scan(@re).sub(@re) { apply_tpl($1, $2, $3, doc_context) }
34
34
  else
35
35
  false
36
36
  end
37
37
  end
38
38
 
39
39
  # applies the video template of the specified type
40
- def apply_tpl(type, url, alt_text)
40
+ def apply_tpl(type, url, alt_text, ctx)
41
41
  unless @templates.has_key?(type)
42
- Logger.instance.warn(nil, "Unknown video type #{type}")
42
+ Logger.instance.warn(nil, "Unknown video type #{type}", ctx[:filename], ctx[:linenr])
43
43
  end
44
44
 
45
45
  @templates[type].gsub(/(%\w)/) do