jsduck 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,22 +1,26 @@
1
1
  require 'jsduck/json_duck'
2
+ require 'jsduck/null_object'
2
3
 
3
4
  module JsDuck
4
5
 
5
6
  # Reads in examples JSON file
6
7
  class Examples
7
- def initialize
8
- @examples = []
8
+ # Creates Examples object from filename.
9
+ def self.create(filename)
10
+ if filename
11
+ Examples.new(filename)
12
+ else
13
+ NullObject.new(:to_array => [])
14
+ end
9
15
  end
10
16
 
11
17
  # Parses examples config file
12
- def parse(filename)
18
+ def initialize(filename)
13
19
  @examples = JsonDuck.read(filename)
14
20
  end
15
21
 
16
22
  # Writes examples JSON file to dir
17
23
  def write(dir)
18
- return if @examples.length == 0
19
-
20
24
  FileUtils.mkdir(dir) unless File.exists?(dir)
21
25
  # Write the JSON to output dir, so it's available in released
22
26
  # version of docs and people can use it with JSDuck by themselves.
@@ -0,0 +1,65 @@
1
+ module JsDuck
2
+
3
+ # Reads categories info from config file
4
+ class FileCategories
5
+ def initialize(filename, relations)
6
+ @filename = filename
7
+ @relations = relations
8
+ end
9
+
10
+ # Parses categories in JSON file
11
+ def generate
12
+ @categories = JsonDuck.read(@filename)
13
+
14
+ # Don't crash if old syntax is used.
15
+ if @categories.is_a?(Hash) && @categories["categories"]
16
+ Logger.instance.warn(:old_cat_format, 'Update categories file to contain just the array inside {"categories": [...]}')
17
+ @categories = @categories["categories"]
18
+ end
19
+
20
+ # Perform expansion on all class names containing * wildcard
21
+ @categories.each do |cat|
22
+ cat["groups"].each do |group|
23
+ group["classes"] = group["classes"].map do |name|
24
+ expand(name) # name =~ /\*/ ? expand(name) : name
25
+ end.flatten
26
+ end
27
+ end
28
+
29
+ validate
30
+
31
+ @categories
32
+ end
33
+
34
+ # Expands class name like 'Foo.*' into multiple class names.
35
+ def expand(name)
36
+ re = Regexp.new("^" + name.split(/\*/, -1).map {|part| Regexp.escape(part) }.join('.*') + "$")
37
+ classes = @relations.to_a.find_all {|cls| re =~ cls[:name] && !cls[:private] }.map {|cls| cls[:name] }.sort
38
+ if classes.length == 0
39
+ Logger.instance.warn(:cat_no_match, "No class found matching a pattern '#{name}' in categories file.")
40
+ end
41
+ classes
42
+ end
43
+
44
+ # Prints warnings for missing classes in categories file
45
+ def validate
46
+ # Build a map of all classes listed in categories
47
+ listed_classes = {}
48
+ @categories.each do |cat|
49
+ cat["groups"].each do |group|
50
+ group["classes"].each do |cls_name|
51
+ listed_classes[cls_name] = true
52
+ end
53
+ end
54
+ end
55
+
56
+ # Check that each existing non-private class is listed
57
+ @relations.each do |cls|
58
+ unless listed_classes[cls[:name]] || cls[:private]
59
+ Logger.instance.warn(:cat_class_missing, "Class '#{cls[:name]}' not found in categories file")
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,29 @@
1
+ require 'jsduck/class'
2
+
3
+ module JsDuck
4
+
5
+ # Exporter for all the class docs.
6
+ class FullExporter
7
+ def initialize(relations, opts)
8
+ @relations = relations
9
+ end
10
+
11
+ # Returns all data in Class object as hash.
12
+ def export(cls)
13
+ h = cls.to_hash
14
+ h[:members] = {}
15
+ Class.default_members_hash.each_key do |key|
16
+ h[:members][key] = cls.members(key)
17
+ h[:statics][key] = cls.members(key, :statics)
18
+ end
19
+ h[:component] = cls.inherits_from?("Ext.Component")
20
+ h[:superclasses] = cls.superclasses.collect {|c| c.full_name }
21
+ h[:subclasses] = @relations.subclasses(cls).collect {|c| c.full_name }
22
+ h[:mixedInto] = @relations.mixed_into(cls).collect {|c| c.full_name }
23
+ h[:allMixins] = cls.all_mixins.collect {|c| c.full_name }
24
+ h
25
+ end
26
+
27
+ end
28
+
29
+ end
data/lib/jsduck/guides.rb CHANGED
@@ -1,26 +1,30 @@
1
1
  require 'jsduck/logger'
2
2
  require 'jsduck/json_duck'
3
+ require 'jsduck/null_object'
3
4
  require 'fileutils'
4
5
 
5
6
  module JsDuck
6
7
 
7
8
  # Reads in guides and converts them to JsonP files
8
9
  class Guides
9
- def initialize(formatter)
10
- @formatter = formatter
11
- @guides = []
10
+ # Creates Guides object from filename and formatter
11
+ def self.create(filename, formatter)
12
+ if filename
13
+ Guides.new(filename, formatter)
14
+ else
15
+ NullObject.new(:to_array => [], :to_html => "")
16
+ end
12
17
  end
13
18
 
14
19
  # Parses guides config file
15
- def parse(filename)
20
+ def initialize(filename, formatter)
16
21
  @path = File.dirname(filename)
17
22
  @guides = JsonDuck.read(filename)
23
+ @formatter = formatter
18
24
  end
19
25
 
20
26
  # Writes all guides to given dir in JsonP format
21
27
  def write(dir)
22
- return if @guides.length == 0
23
-
24
28
  FileUtils.mkdir(dir) unless File.exists?(dir)
25
29
  @guides.each {|group| group["items"].each {|g| write_guide(g, dir) } }
26
30
  # Write the JSON to output dir, so it's available in released
@@ -38,11 +42,11 @@ module JsDuck
38
42
  elsif File.exists?(tutorial_dir)
39
43
  in_dir = tutorial_dir
40
44
  else
41
- return Logger.instance.warn("Guide #{guide_dir} / #{tutorial_dir} not found")
45
+ return Logger.instance.warn(:guide, "Guide #{guide_dir} / #{tutorial_dir} not found")
42
46
  end
43
47
 
44
48
  guide_file = in_dir + "/README.md"
45
- return Logger.instance.warn("README.md not found in #{in_dir}") unless File.exists?(guide_file)
49
+ return Logger.instance.warn(:guide, "README.md not found in #{in_dir}") unless File.exists?(guide_file)
46
50
 
47
51
  Logger.instance.log("Writing guide", out_dir)
48
52
  # Copy the whole guide dir over
@@ -63,8 +67,6 @@ module JsDuck
63
67
 
64
68
  # Returns HTML listing of guides
65
69
  def to_html
66
- return "" if @guides.length == 0
67
-
68
70
  html = @guides.map do |group|
69
71
  [
70
72
  "<h3>#{group['title']}</h3>",
data/lib/jsduck/images.rb CHANGED
@@ -35,7 +35,7 @@ module JsDuck
35
35
  def copy(output_dir)
36
36
  @images.each_key do |img|
37
37
  unless copy_img(img, output_dir)
38
- Logger.instance.warn("Image not found.", img)
38
+ Logger.instance.warn(:image, "Image not found.", img)
39
39
  end
40
40
  end
41
41
  report_unused
@@ -62,7 +62,7 @@ module JsDuck
62
62
  def report_unused
63
63
  @paths.each_pair do |path, map|
64
64
  map.each_pair do |img, used|
65
- Logger.instance.warn("Image not used.", img) unless used
65
+ Logger.instance.warn(:image_unused, "Image not used.", img) unless used
66
66
  end
67
67
  end
68
68
  end
@@ -0,0 +1,67 @@
1
+ require 'jsduck/logger'
2
+ require 'fileutils'
3
+
4
+ module JsDuck
5
+
6
+ # Deals with creation of main HTML or PHP files.
7
+ class IndexHtml
8
+ attr_accessor :welcome
9
+ attr_accessor :categories
10
+ attr_accessor :guides
11
+
12
+ def initialize(opts)
13
+ @opts = opts
14
+ end
15
+
16
+ # In normal mode creates index.html.
17
+ #
18
+ # When --seo enabled, creates index.php, template.html and print-template.html.
19
+ def write
20
+ if @opts.seo
21
+ FileUtils.cp(@opts.template_dir+"/index.php", @opts.output_dir+"/index.php")
22
+ create_template_html(@opts.template_dir+"/template.html", @opts.output_dir+"/template.html")
23
+ create_print_template_html(@opts.template_dir+"/print-template.html", @opts.output_dir+"/print-template.html")
24
+ else
25
+ create_template_html(@opts.template_dir+"/template.html", @opts.output_dir+"/index.html")
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def create_template_html(in_file, out_file)
32
+ write_template(in_file, out_file, {
33
+ "{title}" => @opts.title,
34
+ "{header}" => @opts.header,
35
+ "{footer}" => "<div id='footer-content' style='display: none'>#{@opts.footer}</div>",
36
+ "{extjs_path}" => @opts.extjs_path,
37
+ "{local_storage_db}" => @opts.local_storage_db,
38
+ "{show_print_button}" => @opts.seo ? "true" : "false",
39
+ "{touch_examples_ui}" => @opts.touch_examples_ui ? "true" : "false",
40
+ "{welcome}" => @welcome.to_html,
41
+ "{categories}" => @categories.to_html,
42
+ "{guides}" => @guides.to_html,
43
+ "{head_html}" => @opts.head_html,
44
+ "{body_html}" => @opts.body_html,
45
+ })
46
+ end
47
+
48
+ def create_print_template_html(in_file, out_file)
49
+ write_template(in_file, out_file, {
50
+ "{title}" => @opts.title,
51
+ "{header}" => @opts.header,
52
+ })
53
+ end
54
+
55
+ # Opens in_file, replaces {keys} inside it, writes to out_file
56
+ def write_template(in_file, out_file, replacements)
57
+ Logger.instance.log("Writing", out_file)
58
+ html = IO.read(in_file)
59
+ html.gsub!(/\{\w+\}/) do |key|
60
+ replacements[key] ? replacements[key] : key
61
+ end
62
+ File.open(out_file, 'w') {|f| f.write(html) }
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,75 @@
1
+ require 'jsduck/logger'
2
+
3
+ module JsDuck
4
+
5
+ # Deals with inheriting documentation
6
+ class InheritDoc
7
+ def initialize(relations)
8
+ @relations = relations
9
+ end
10
+
11
+ # Performs all inheriting
12
+ def resolve_all
13
+ @relations.each do |cls|
14
+ cls.all_local_members.each do |member|
15
+ if member[:inheritdoc]
16
+ resolve(member)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # Copy over doc/params/return from parent member.
23
+ def resolve(m)
24
+ parent = find_parent(m)
25
+ m[:doc] = (m[:doc] + "\n\n" + parent[:doc]).strip
26
+ m[:params] = parent[:params] if parent[:params]
27
+ m[:return] = parent[:return] if parent[:return]
28
+ end
29
+
30
+ # Finds parent member of the given member. When @inheritdoc names
31
+ # a member to inherit from, finds that member instead.
32
+ #
33
+ # If the parent also has @inheritdoc, continues recursively.
34
+ def find_parent(m)
35
+ context = m[:files][0]
36
+ inherit = m[:inheritdoc]
37
+
38
+ if inherit[:cls]
39
+ parent_cls = @relations[inherit[:cls]]
40
+ unless parent_cls
41
+ warn("@inheritdoc #{inherit[:cls]}##{inherit[:member]} - class not found", context)
42
+ return m
43
+ end
44
+ parent = parent_cls.get_member(inherit[:member], inherit[:type] || m[:tagname])
45
+ unless parent
46
+ warn("@inheritdoc #{inherit[:cls]}##{inherit[:member]} - member not found", context)
47
+ return m
48
+ end
49
+ else
50
+ parent_cls = @relations[m[:owner]].parent
51
+ unless parent_cls
52
+ warn("@inheritdoc - parent class not found", context)
53
+ return m
54
+ end
55
+ parent = parent_cls.get_member(m[:name], m[:tagname])
56
+ unless parent
57
+ warn("@inheritdoc - parent member not found", context)
58
+ return m
59
+ end
60
+ end
61
+
62
+ if parent[:inheritdoc]
63
+ find_parent(parent)
64
+ else
65
+ parent
66
+ end
67
+ end
68
+
69
+ def warn(msg, context)
70
+ Logger.instance.warn(:inheritdoc, msg, context[:filename], context[:linenr])
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -172,7 +172,7 @@ module JsDuck
172
172
  # <literal> := ...see JsLiteralParser...
173
173
  def my_literal
174
174
  lit = literal
175
- return unless lit
175
+ return unless lit && literal_expression_end?
176
176
 
177
177
  cls_map = {
178
178
  :string => "String",
@@ -195,6 +195,13 @@ module JsDuck
195
195
  {:type => :literal, :class => cls, :value => value}
196
196
  end
197
197
 
198
+ # True when we're at the end of literal expression.
199
+ # ",", ";" and "}" are the normal closing symbols, but for
200
+ # our docs purposes doc-comment and file end work too.
201
+ def literal_expression_end?
202
+ look(",") || look(";") || look("}") || look(:doc_comment) || @lex.empty?
203
+ end
204
+
198
205
  # <ext-extend> := "Ext" "." "extend" "(" <ident-chain> "," ...
199
206
  def ext_extend
200
207
  match(:ident, ".", "extend", "(")
data/lib/jsduck/lint.rb CHANGED
@@ -22,8 +22,8 @@ module JsDuck
22
22
  def warn_globals
23
23
  global = @relations["global"]
24
24
  return unless global
25
- global.each_member do |member|
26
- warn("Global #{member[:tagname]}: #{member[:name]}", member)
25
+ global.all_local_members.each do |member|
26
+ warn(:global, "Global #{member[:tagname]}: #{member[:name]}", member)
27
27
  end
28
28
  end
29
29
 
@@ -31,11 +31,11 @@ module JsDuck
31
31
  def warn_unnamed
32
32
  each_member do |member|
33
33
  if !member[:name] || member[:name] == ""
34
- warn("Unnamed #{member[:tagname]}", member)
34
+ warn(:name_missing, "Unnamed #{member[:tagname]}", member)
35
35
  end
36
36
  (member[:params] || []).each do |p|
37
37
  if !p[:name] || p[:name] == ""
38
- warn("Unnamed parameter", member)
38
+ warn(:name_missing, "Unnamed parameter", member)
39
39
  end
40
40
  end
41
41
  end
@@ -48,7 +48,7 @@ module JsDuck
48
48
  optional_found = false
49
49
  member[:params].each do |p|
50
50
  if optional_found && !p[:optional]
51
- warn("Optional param can't be followed by regular param #{p[:name]}", member)
51
+ warn(:req_after_opt, "Optional param followed by regular param #{p[:name]}", member)
52
52
  end
53
53
  optional_found = optional_found || p[:optional]
54
54
  end
@@ -62,7 +62,7 @@ module JsDuck
62
62
  params = {}
63
63
  (member[:params] || []).each do |p|
64
64
  if params[p[:name]]
65
- warn("Duplicate parameter name #{p[:name]}", member)
65
+ warn(:dup_param, "Duplicate parameter name #{p[:name]}", member)
66
66
  end
67
67
  params[p[:name]] = true
68
68
  end
@@ -71,13 +71,13 @@ module JsDuck
71
71
 
72
72
  # Loops through all members of all classes
73
73
  def each_member(&block)
74
- @relations.each {|cls| cls.each_member(&block) }
74
+ @relations.each {|cls| cls.all_local_members.each(&block) }
75
75
  end
76
76
 
77
77
  # Prints warning + filename and linenumber from doc-context
78
- def warn(msg, member)
78
+ def warn(type, msg, member)
79
79
  context = member[:files][0]
80
- Logger.instance.warn(msg, context[:filename], context[:linenr])
80
+ Logger.instance.warn(type, msg, context[:filename], context[:linenr])
81
81
  end
82
82
 
83
83
  end
data/lib/jsduck/logger.rb CHANGED
@@ -7,12 +7,39 @@ module JsDuck
7
7
  class Logger
8
8
  include Singleton
9
9
 
10
+ # Set to true to enable verbose logging
10
11
  attr_accessor :verbose
11
- attr_accessor :warnings
12
12
 
13
13
  def initialize
14
14
  @verbose = false
15
- @warnings = true
15
+ @warning_docs = [
16
+ [:global, "Member doesn't belong to any class"],
17
+ [:inheritdoc, "@inheritdoc referring to unknown class or member"],
18
+ [:extend, "@extend or @mixin referring to unknown class"],
19
+ [:link, "{@link} to unknown class or member"],
20
+
21
+ [:alt_name, "Name used as both classname and alternate classname"],
22
+ [:name_missing, "Member or parameter has no name"],
23
+ [:dup_param, "Method has two parameters with same name"],
24
+ [:req_after_opt, "Required parameter comes after optional"],
25
+ [:subproperty, "@param foo.bar where foo param doesn't exist"],
26
+ [:sing_static, "Singleton class member marked as @static"],
27
+ [:type_syntax, "Syntax error in {type definition}"],
28
+ [:type_name, "Unknown type referenced in {type definition}"],
29
+
30
+ [:image, "{@img} referring to missing file"],
31
+ [:image_unused, "An image exists in --images dir that's not used"],
32
+ [:cat_old_format, "Categories file uses old deprecated format"],
33
+ [:cat_no_match, "Class pattern in categories file matches nothing"],
34
+ [:cat_class_missing, "Class is missing from categories file"],
35
+ [:guide, "Guide is missing from --guides dir"],
36
+ ]
37
+ # Turn on all warnings by default
38
+ @warnings = {}
39
+ @warning_docs.each do |w|
40
+ @warnings[w[0]] = true
41
+ end
42
+
16
43
  @shown_warnings = {}
17
44
  end
18
45
 
@@ -23,19 +50,50 @@ module JsDuck
23
50
  end
24
51
  end
25
52
 
53
+ # Enabled or disables a particular warning
54
+ # or all warnings when type == :all
55
+ def set_warning(type, enabled)
56
+ if type == :all
57
+ @warnings.each_key do |key|
58
+ @warnings[key] = enabled
59
+ end
60
+ elsif @warnings.has_key?(type)
61
+ @warnings[type] = enabled
62
+ else
63
+ warn(nil, "Warning of type '#{type} doesn't exist")
64
+ end
65
+ end
66
+
67
+ # get documentation for all warnings
68
+ def doc_warnings
69
+ @warning_docs.map {|w| "+#{w[0]} - #{w[1]}" } + [
70
+ " ",
71
+ "+all - to turn on all warnings (default)",
72
+ " ",
73
+ ]
74
+ end
75
+
26
76
  # Prints warning message.
27
77
  #
78
+ # The type must be one of predefined warning types which can be
79
+ # toggled on/off with command-line options, or it can be nil, in
80
+ # which case the warning is always shown.
81
+ #
28
82
  # Ignores duplicate warnings - only prints the first one.
29
83
  # Works best when --processes=0, but it reduces the amount of
30
84
  # warnings greatly also when run multiple processes.
31
85
  #
32
86
  # Optionally filename and line number will be inserted to message.
33
- def warn(msg, filename=nil, line=nil)
87
+ def warn(type, msg, filename=nil, line=nil)
34
88
  msg = "Warning: " + format(filename, line) + " " + msg
35
89
 
36
- if @warnings && !@shown_warnings[msg]
37
- $stderr.puts msg
38
- @shown_warnings[msg] = true
90
+ if type == nil || @warnings[type]
91
+ if !@shown_warnings[msg]
92
+ $stderr.puts msg
93
+ @shown_warnings[msg] = true
94
+ end
95
+ elsif !@warnings.has_key?(type)
96
+ warn(nil, "Unknown warning type #{type}")
39
97
  end
40
98
  end
41
99