jsduck 3.0.1 → 3.1.0

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.
@@ -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