jsduck 4.10.4 → 5.0.0.beta01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. data/.travis.yml +0 -1
  2. data/README.md +32 -6
  3. data/Rakefile +10 -18
  4. data/bin/compare +5 -5
  5. data/bin/jsduck +2 -3
  6. data/jsduck.gemspec +3 -4
  7. data/lib/jsduck/aggregator.rb +21 -80
  8. data/lib/jsduck/app.rb +7 -14
  9. data/lib/jsduck/app_data.rb +4 -5
  10. data/lib/jsduck/assets.rb +4 -7
  11. data/lib/jsduck/base_type.rb +53 -0
  12. data/lib/jsduck/batch_parser.rb +8 -87
  13. data/lib/jsduck/batch_processor.rb +77 -0
  14. data/lib/jsduck/categories/auto.rb +83 -0
  15. data/lib/jsduck/categories/class_name.rb +63 -0
  16. data/lib/jsduck/categories/factory.rb +113 -0
  17. data/lib/jsduck/categories/file.rb +75 -0
  18. data/lib/jsduck/class.rb +3 -9
  19. data/lib/jsduck/class_doc_expander.rb +1 -1
  20. data/lib/jsduck/css/lexer.rb +203 -0
  21. data/lib/jsduck/css/parser.rb +121 -0
  22. data/lib/jsduck/doc/comment.rb +40 -0
  23. data/lib/jsduck/doc/map.rb +23 -0
  24. data/lib/jsduck/doc/parser.rb +128 -0
  25. data/lib/jsduck/doc/processor.rb +52 -0
  26. data/lib/jsduck/doc/scanner.rb +76 -0
  27. data/lib/jsduck/doc/standard_tag_parser.rb +154 -0
  28. data/lib/jsduck/doc/subproperties.rb +64 -0
  29. data/lib/jsduck/docs_code_comparer.rb +31 -0
  30. data/lib/jsduck/export_writer.rb +2 -2
  31. data/lib/jsduck/exporter/app.rb +16 -4
  32. data/lib/jsduck/exporter/full.rb +2 -2
  33. data/lib/jsduck/format/batch.rb +58 -0
  34. data/lib/jsduck/format/class.rb +62 -0
  35. data/lib/jsduck/format/doc.rb +172 -0
  36. data/lib/jsduck/format/html_stack.rb +109 -0
  37. data/lib/jsduck/format/shortener.rb +55 -0
  38. data/lib/jsduck/format/subproperties.rb +64 -0
  39. data/lib/jsduck/guides.rb +32 -14
  40. data/lib/jsduck/index_html.rb +3 -1
  41. data/lib/jsduck/inline/auto_link.rb +2 -2
  42. data/lib/jsduck/inline/link.rb +4 -3
  43. data/lib/jsduck/inline/link_renderer.rb +2 -2
  44. data/lib/jsduck/inline/video.rb +8 -2
  45. data/lib/jsduck/js/ast.rb +361 -0
  46. data/lib/jsduck/js/esprima.rb +39 -0
  47. data/lib/jsduck/{esprima → js/esprima}/esprima.js +0 -0
  48. data/lib/jsduck/js/evaluator.rb +70 -0
  49. data/lib/jsduck/js/ext_patterns.rb +70 -0
  50. data/lib/jsduck/js/function.rb +206 -0
  51. data/lib/jsduck/js/node.rb +194 -0
  52. data/lib/jsduck/js/node_array.rb +36 -0
  53. data/lib/jsduck/js/parser.rb +223 -0
  54. data/lib/jsduck/js/serializer.rb +263 -0
  55. data/lib/jsduck/js/utils.rb +21 -0
  56. data/lib/jsduck/logger.rb +3 -13
  57. data/lib/jsduck/members_index.rb +3 -4
  58. data/lib/jsduck/merger.rb +25 -145
  59. data/lib/jsduck/options.rb +29 -132
  60. data/lib/jsduck/parser.rb +76 -0
  61. data/lib/jsduck/process/accessors.rb +133 -0
  62. data/lib/jsduck/process/circular_deps.rb +58 -0
  63. data/lib/jsduck/process/enums.rb +91 -0
  64. data/lib/jsduck/process/ext4_events.rb +43 -0
  65. data/lib/jsduck/process/global_members.rb +36 -0
  66. data/lib/jsduck/process/ignored_classes.rb +16 -0
  67. data/lib/jsduck/process/importer.rb +58 -0
  68. data/lib/jsduck/process/inherit_doc.rb +197 -0
  69. data/lib/jsduck/process/lint.rb +135 -0
  70. data/lib/jsduck/process/overrides.rb +99 -0
  71. data/lib/jsduck/process/return_values.rb +72 -0
  72. data/lib/jsduck/process/versions.rb +102 -0
  73. data/lib/jsduck/relations.rb +5 -0
  74. data/lib/jsduck/render/class.rb +144 -0
  75. data/lib/jsduck/render/sidebar.rb +97 -0
  76. data/lib/jsduck/render/signature.rb +94 -0
  77. data/lib/jsduck/render/subproperties.rb +99 -0
  78. data/lib/jsduck/render/tags.rb +38 -0
  79. data/lib/jsduck/search_data.rb +19 -13
  80. data/lib/jsduck/source/file.rb +8 -17
  81. data/lib/jsduck/tag/abstract.rb +4 -7
  82. data/lib/jsduck/tag/accessor.rb +10 -0
  83. data/lib/jsduck/tag/alias.rb +61 -0
  84. data/lib/jsduck/tag/alternate_class_names.rb +17 -0
  85. data/lib/jsduck/tag/aside.rb +28 -31
  86. data/lib/jsduck/tag/author.rb +9 -5
  87. data/lib/jsduck/tag/boolean_tag.rb +24 -0
  88. data/lib/jsduck/tag/cfg.rb +45 -0
  89. data/lib/jsduck/tag/chainable.rb +5 -7
  90. data/lib/jsduck/tag/class.rb +28 -0
  91. data/lib/jsduck/tag/class_list_tag.rb +40 -0
  92. data/lib/jsduck/tag/constructor.rb +24 -0
  93. data/lib/jsduck/tag/css_mixin.rb +17 -0
  94. data/lib/jsduck/tag/css_var.rb +29 -0
  95. data/lib/jsduck/tag/default.rb +31 -0
  96. data/lib/jsduck/tag/deprecated.rb +13 -27
  97. data/lib/jsduck/tag/deprecated_tag.rb +58 -0
  98. data/lib/jsduck/tag/doc.rb +32 -0
  99. data/lib/jsduck/tag/docauthor.rb +4 -5
  100. data/lib/jsduck/tag/enum.rb +70 -0
  101. data/lib/jsduck/tag/event.rb +28 -0
  102. data/lib/jsduck/tag/evented.rb +10 -0
  103. data/lib/jsduck/tag/extends.rb +45 -0
  104. data/lib/jsduck/tag/ftype.rb +18 -0
  105. data/lib/jsduck/tag/hide.rb +4 -11
  106. data/lib/jsduck/tag/ignore.rb +6 -7
  107. data/lib/jsduck/tag/inheritable.rb +10 -0
  108. data/lib/jsduck/tag/inheritdoc.rb +48 -0
  109. data/lib/jsduck/tag/markdown.rb +8 -6
  110. data/lib/jsduck/tag/member.rb +24 -0
  111. data/lib/jsduck/tag/method.rb +35 -0
  112. data/lib/jsduck/tag/mixins.rb +26 -0
  113. data/lib/jsduck/tag/name.rb +36 -0
  114. data/lib/jsduck/tag/new.rb +13 -27
  115. data/lib/jsduck/tag/override.rb +37 -0
  116. data/lib/jsduck/tag/overrides.rb +29 -0
  117. data/lib/jsduck/tag/param.rb +87 -0
  118. data/lib/jsduck/tag/preventable.rb +19 -10
  119. data/lib/jsduck/tag/private.rb +28 -13
  120. data/lib/jsduck/tag/property.rb +39 -0
  121. data/lib/jsduck/tag/protected.rb +5 -7
  122. data/lib/jsduck/tag/ptype.rb +18 -0
  123. data/lib/jsduck/tag/readonly.rb +4 -7
  124. data/lib/jsduck/tag/removed.rb +21 -29
  125. data/lib/jsduck/tag/required.rb +11 -9
  126. data/lib/jsduck/tag/requires.rb +12 -0
  127. data/lib/jsduck/tag/return.rb +47 -0
  128. data/lib/jsduck/tag/since.rb +19 -11
  129. data/lib/jsduck/tag/singleton.rb +15 -0
  130. data/lib/jsduck/tag/static.rb +5 -7
  131. data/lib/jsduck/tag/subproperties.rb +23 -0
  132. data/lib/jsduck/tag/tag.rb +208 -0
  133. data/lib/jsduck/tag/template.rb +14 -9
  134. data/lib/jsduck/tag/throws.rb +38 -0
  135. data/lib/jsduck/tag/type.rb +48 -0
  136. data/lib/jsduck/tag/uses.rb +12 -0
  137. data/lib/jsduck/tag/xtype.rb +30 -0
  138. data/lib/jsduck/tag_loader.rb +39 -0
  139. data/lib/jsduck/tag_registry.rb +189 -0
  140. data/lib/jsduck/type_parser.rb +3 -3
  141. data/lib/jsduck/web_writer.rb +2 -2
  142. data/lib/jsduck/welcome.rb +1 -1
  143. metadata +578 -538
  144. data/lib/jsduck/accessors.rb +0 -136
  145. data/lib/jsduck/ast.rb +0 -524
  146. data/lib/jsduck/auto_categories.rb +0 -80
  147. data/lib/jsduck/batch_formatter.rb +0 -60
  148. data/lib/jsduck/categories.rb +0 -73
  149. data/lib/jsduck/categories_class_name.rb +0 -37
  150. data/lib/jsduck/circular_deps.rb +0 -56
  151. data/lib/jsduck/class_formatter.rb +0 -102
  152. data/lib/jsduck/columns.rb +0 -56
  153. data/lib/jsduck/css_lexer.rb +0 -201
  154. data/lib/jsduck/css_parser.rb +0 -119
  155. data/lib/jsduck/doc_ast.rb +0 -319
  156. data/lib/jsduck/doc_formatter.rb +0 -142
  157. data/lib/jsduck/doc_parser.rb +0 -611
  158. data/lib/jsduck/doc_type.rb +0 -59
  159. data/lib/jsduck/enum.rb +0 -73
  160. data/lib/jsduck/esprima.rb +0 -51
  161. data/lib/jsduck/evaluator.rb +0 -69
  162. data/lib/jsduck/ext_patterns.rb +0 -58
  163. data/lib/jsduck/file_categories.rb +0 -76
  164. data/lib/jsduck/function_ast.rb +0 -206
  165. data/lib/jsduck/guide_anchors.rb +0 -32
  166. data/lib/jsduck/guide_toc.rb +0 -49
  167. data/lib/jsduck/html_stack.rb +0 -105
  168. data/lib/jsduck/importer.rb +0 -121
  169. data/lib/jsduck/inherit_doc.rb +0 -193
  170. data/lib/jsduck/js_parser.rb +0 -221
  171. data/lib/jsduck/lint.rb +0 -133
  172. data/lib/jsduck/meta_tag.rb +0 -88
  173. data/lib/jsduck/meta_tag_loader.rb +0 -67
  174. data/lib/jsduck/meta_tag_registry.rb +0 -111
  175. data/lib/jsduck/meta_tag_renderer.rb +0 -34
  176. data/lib/jsduck/news.rb +0 -128
  177. data/lib/jsduck/override.rb +0 -87
  178. data/lib/jsduck/renderer.rb +0 -361
  179. data/lib/jsduck/return_values.rb +0 -72
  180. data/lib/jsduck/serializer.rb +0 -262
  181. data/lib/jsduck/shortener.rb +0 -58
  182. data/lib/jsduck/signature_renderer.rb +0 -91
  183. data/lib/jsduck/source/file_parser.rb +0 -72
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'jsduck/util/html'
3
+
4
+ module JsDuck
5
+ module Format
6
+
7
+ # Little helper for shortening text
8
+ class Shortener
9
+ # Takes as parameter the maximum length for text that doesn't
10
+ # get shortened. Used for testing purposes.
11
+ def initialize(max_length = 120)
12
+ @max_length = max_length
13
+ end
14
+
15
+ # Shortens text
16
+ #
17
+ # 116 chars is also where ext-doc makes its cut, but unlike
18
+ # ext-doc we only make the cut when there's more than 120 chars.
19
+ #
20
+ # This way we don't get stupid expansions like:
21
+ #
22
+ # Blah blah blah some text...
23
+ #
24
+ # expanding to:
25
+ #
26
+ # Blah blah blah some text.
27
+ #
28
+ def shorten(input)
29
+ sent = first_sentence(Util::HTML.strip_tags(input).strip)
30
+ # Use u-modifier to correctly count multi-byte characters
31
+ chars = sent.scan(/./mu)
32
+ if chars.length > @max_length
33
+ chars[0..(@max_length-4)].join + "..."
34
+ else
35
+ sent + " ..."
36
+ end
37
+ end
38
+
39
+ # Returns the first sentence inside a string.
40
+ def first_sentence(str)
41
+ str.sub(/\A(.+?(\.|。))\s.*\z/mu, "\\1")
42
+ end
43
+
44
+ # Returns true when input should get shortened.
45
+ def too_long?(input)
46
+ stripped = Util::HTML.strip_tags(input).strip
47
+ # for sentence v/s full - compare byte length
48
+ # for full v/s max - compare char length
49
+ first_sentence(stripped).length < stripped.length || stripped.scan(/./mu).length > @max_length
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,64 @@
1
+ require 'jsduck/util/html'
2
+ require 'jsduck/logger'
3
+ require 'jsduck/type_parser'
4
+
5
+ module JsDuck
6
+ module Format
7
+
8
+ # Helper for recursively formatting subproperties.
9
+ class Subproperties
10
+
11
+ def initialize(formatter)
12
+ @formatter = formatter
13
+ @skip_type_parsing = false
14
+ end
15
+
16
+ # Set to true to skip parsing and formatting of types.
17
+ # Used to skip parsing of CSS typesdefs.
18
+ attr_accessor :skip_type_parsing
19
+
20
+ # Takes a hash of param, return value, throws value or subproperty.
21
+ #
22
+ # - Markdown-formats the :doc field in it.
23
+ # - Parses the :type field and saves HTML to :html_type.
24
+ # - Recursively does the same with all items in :properties field.
25
+ #
26
+ def format(item)
27
+ item[:doc] = @formatter.format(item[:doc]) if item[:doc]
28
+
29
+ if item[:type]
30
+ item[:html_type] = format_type(item[:type])
31
+ end
32
+
33
+ if item[:properties]
34
+ item[:properties].each {|p| format(p) }
35
+ end
36
+ end
37
+
38
+ # Formats the given type definition string using TypeParser.
39
+ #
40
+ # - On success returns HTML-version of the type definition.
41
+ # - On failure logs error and returns the type string with only HTML escaped.
42
+ #
43
+ def format_type(type)
44
+ # Skip the formatting entirely when type-parsing is turned off.
45
+ return Util::HTML.escape(type) if @skip_type_parsing
46
+
47
+ tp = TypeParser.new(@formatter)
48
+ if tp.parse(type)
49
+ tp.out
50
+ else
51
+ context = @formatter.doc_context
52
+ if tp.error == :syntax
53
+ Logger.warn(:type_syntax, "Incorrect type syntax #{type}", context[:filename], context[:linenr])
54
+ else
55
+ Logger.warn(:type_name, "Unknown type #{type}", context[:filename], context[:linenr])
56
+ end
57
+ Util::HTML.escape(type)
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
data/lib/jsduck/guides.rb CHANGED
@@ -4,8 +4,7 @@ require 'jsduck/util/io'
4
4
  require 'jsduck/util/null_object'
5
5
  require 'jsduck/logger'
6
6
  require 'jsduck/grouped_asset'
7
- require 'jsduck/guide_toc'
8
- require 'jsduck/guide_anchors'
7
+ require 'jsduck/util/html'
9
8
  require 'jsduck/img/dir'
10
9
  require 'fileutils'
11
10
 
@@ -56,10 +55,6 @@ module JsDuck
56
55
  def load_guide(guide)
57
56
  return Logger.warn(:guide, "Guide not found", guide["url"]) unless File.exists?(guide["url"])
58
57
  return Logger.warn(:guide, "Guide not found", guide[:filename]) unless File.exists?(guide[:filename])
59
- unless js_ident?(guide["name"])
60
- # Guide name is also used as JSONP callback method name.
61
- return Logger.warn(:guide, "Guide name is not valid JS identifier: #{guide["name"]}", guide[:filename])
62
- end
63
58
 
64
59
  begin
65
60
  return format_guide(guide)
@@ -72,9 +67,7 @@ module JsDuck
72
67
  def format_guide(guide)
73
68
  @formatter.doc_context = {:filename => guide[:filename], :linenr => 0}
74
69
  @formatter.images = Img::Dir.new(guide["url"], "guides/#{guide["name"]}")
75
- html = @formatter.format(Util::IO.read(guide[:filename]))
76
- html = GuideToc.inject(html, guide['name'])
77
- html = GuideAnchors.transform(html, guide['name'])
70
+ html = add_toc(guide, @formatter.format(Util::IO.read(guide[:filename])))
78
71
 
79
72
  # Report unused images (but ignore the icon files)
80
73
  @formatter.images.get("icon.png")
@@ -109,11 +102,6 @@ module JsDuck
109
102
  end
110
103
  end
111
104
 
112
- # True when string is valid JavaScript identifier
113
- def js_ident?(str)
114
- /\A[$\w]+\z/ =~ str
115
- end
116
-
117
105
  # Ensures the guide dir contains icon.png.
118
106
  # When there isn't looks for icon-lg.png and renames it to icon.png.
119
107
  # When neither exists, copies over default icon.
@@ -127,6 +115,36 @@ module JsDuck
127
115
  end
128
116
  end
129
117
 
118
+ # Creates table of contents at the top of guide by looking for <h2> elements in HTML.
119
+ def add_toc(guide, html)
120
+ toc = [
121
+ "<div class='toc'>\n",
122
+ "<p><strong>Contents</strong></p>\n",
123
+ "<ol>\n",
124
+ ]
125
+ new_html = []
126
+ i = 0
127
+ html.each_line do |line|
128
+ if line =~ /^<h2>(.*)<\/h2>$/
129
+ i += 1
130
+ text = Util::HTML.strip_tags($1)
131
+ toc << "<li><a href='#!/guide/#{guide['name']}-section-#{i}'>#{text}</a></li>\n"
132
+ new_html << "<h2 id='#{guide['name']}-section-#{i}'>#{text}</h2>\n"
133
+ else
134
+ new_html << line
135
+ end
136
+ end
137
+ toc << "</ol>\n"
138
+ toc << "</div>\n"
139
+ # Inject TOC at below first heading if at least 2 items in TOC
140
+ if i >= 2
141
+ new_html.insert(1, toc)
142
+ new_html.flatten.join
143
+ else
144
+ html
145
+ end
146
+ end
147
+
130
148
  # Returns HTML listing of guides
131
149
  def to_html(style="")
132
150
  html = @groups.map do |group|
@@ -37,10 +37,10 @@ module JsDuck
37
37
  "{data_path}" => File.basename(@opts.data_path),
38
38
  "{welcome}" => @assets.welcome.to_html("display:none"),
39
39
  "{categories}" => @assets.categories.to_html("display:none"),
40
- "{news}" => @assets.news.to_html("display:none"),
41
40
  "{guides}" => @assets.guides.to_html("display:none"),
42
41
  "{head_html}" => @opts.head_html,
43
42
  "{body_html}" => @opts.body_html,
43
+ "{head_css}" => TagRegistry.css,
44
44
  })
45
45
  end
46
46
 
@@ -48,6 +48,7 @@ module JsDuck
48
48
  write_template(in_file, out_file, {
49
49
  "{title}" => @opts.title,
50
50
  "{header}" => @opts.header,
51
+ "{head_css}" => TagRegistry.css,
51
52
  })
52
53
  end
53
54
 
@@ -60,6 +61,7 @@ module JsDuck
60
61
  "{header}" => @opts.header,
61
62
  "{categories}" => categories ? "<h1>API Documentation</h1> #{categories}" : "",
62
63
  "{guides}" => guides ? "<h1>Guides</h1> #{guides}" : "",
64
+ "{head_css}" => TagRegistry.css,
63
65
  })
64
66
  end
65
67
 
@@ -71,7 +71,7 @@ module JsDuck
71
71
  cls2, member2 = split_to_cls_and_member(cls)
72
72
  if @relations[cls2] && @renderer.get_matching_member(cls2, {:name => member2})
73
73
  return @renderer.link(cls2, member2, cls2+"."+member2)
74
- elsif cls =~ /\.(js|css|html|php)\Z/
74
+ elsif cls =~ /\.(js|css|html|php)\z/
75
75
  # Ignore common filenames
76
76
  else
77
77
  warn_magic_link("#{cls} links to non-existing class")
@@ -80,7 +80,7 @@ module JsDuck
80
80
  else
81
81
  if @renderer.get_matching_member(@class_context, {:name => member})
82
82
  return @renderer.link(@class_context, member, member)
83
- elsif member =~ /\A([A-F0-9]{3}|[A-F0-9]{6})\Z/i || member =~ /\A[0-9]/
83
+ elsif member =~ /\A([A-F0-9]{3}|[A-F0-9]{6})\z/i || member =~ /\A[0-9]/
84
84
  # Ignore HEX color codes and
85
85
  # member names beginning with number
86
86
  else
@@ -1,4 +1,5 @@
1
1
  require 'jsduck/logger'
2
+ require 'jsduck/tag_registry'
2
3
 
3
4
  module JsDuck
4
5
  module Inline
@@ -40,7 +41,7 @@ module JsDuck
40
41
 
41
42
  # applies the link template
42
43
  def apply_tpl(target, text, full_link)
43
- if target =~ /^(.*)#(static-)?(?:(cfg|property|method|event|css_var|css_mixin)-)?(.*)$/
44
+ if target =~ /^(.*)#(static-)?#{TagRegistry.member_type_regex}?(.*)$/
44
45
  cls = $1.empty? ? @class_context : $1
45
46
  static = $2 ? true : nil
46
47
  type = $3 ? $3.intern : nil
@@ -78,12 +79,12 @@ module JsDuck
78
79
  # one when we ignore the static members. If there's more,
79
80
  # report ambiguity. If there's only static members, also
80
81
  # report ambiguity.
81
- instance_ms = ms.find_all {|m| !m[:meta][:static] }
82
+ instance_ms = ms.find_all {|m| !m[:static] }
82
83
  if instance_ms.length > 1
83
84
  alternatives = instance_ms.map {|m| "#{m[:tagname]} in #{m[:owner]}" }.join(", ")
84
85
  Logger.warn(:link_ambiguous, "#{full_link} is ambiguous: "+alternatives, file, line)
85
86
  elsif instance_ms.length == 0
86
- static_ms = ms.find_all {|m| m[:meta][:static] }
87
+ static_ms = ms.find_all {|m| m[:static] }
87
88
  alternatives = static_ms.map {|m| "static " + m[:tagname].to_s }.join(", ")
88
89
  Logger.warn(:link_ambiguous, "#{full_link} is ambiguous: "+alternatives, file, line)
89
90
  end
@@ -52,8 +52,8 @@ module JsDuck
52
52
  def get_matching_member(cls, query)
53
53
  ms = find_members(cls, query)
54
54
  if ms.length > 1
55
- instance_ms = ms.find_all {|m| !m[:meta][:static] }
56
- instance_ms.length > 0 ? instance_ms[0] : ms.find_all {|m| m[:meta][:static] }[0]
55
+ instance_ms = ms.find_all {|m| !m[:static] }
56
+ instance_ms.length > 0 ? instance_ms[0] : ms.find_all {|m| m[:static] }[0]
57
57
  else
58
58
  ms[0]
59
59
  end
@@ -16,8 +16,14 @@ module JsDuck
16
16
  @templates = {
17
17
  "html5" => '<video src="%u">%a</video>',
18
18
  "vimeo" => [
19
- '<p><iframe src="http://player.vimeo.com/video/%u" width="640" height="360" frameborder="0" ',
20
- 'webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe></p>'
19
+ '<p><object width="640" height="360">',
20
+ '<param name="allowfullscreen" value="true" />',
21
+ '<param name="allowscriptaccess" value="always" />',
22
+ '<param name="flashvars" value="api=1" />',
23
+ '<param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=%u&amp;server=vimeo.com&amp;color=4CC208&amp;fullscreen=1" />',
24
+ '<embed src="http://vimeo.com/moogaloop.swf?clip_id=%u&amp;server=vimeo.com&amp;color=4CC208&amp;fullscreen=1" ',
25
+ 'type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="640" height="360"></embed>',
26
+ '</object></p>',
21
27
  ].join
22
28
  }
23
29
 
@@ -0,0 +1,361 @@
1
+ require "jsduck/js/function"
2
+ require "jsduck/js/node"
3
+ require "jsduck/tag_registry"
4
+
5
+ module JsDuck
6
+ module Js
7
+
8
+ # Analyzes the AST produced by EsprimaParser.
9
+ class Ast
10
+ # Should be initialized with EsprimaParser#parse result.
11
+ def initialize(docs = [])
12
+ @docs = docs
13
+ end
14
+
15
+ # Performs the detection of code in all docsets.
16
+ #
17
+ # @returns the processed array of docsets. (But it does it
18
+ # destructively by modifying the passed-in docsets.)
19
+ #
20
+ def detect_all!
21
+ # First deal only with doc-comments
22
+ doc_comments = @docs.find_all {|d| d[:type] == :doc_comment }
23
+
24
+ # Detect code in each docset. Sometimes a docset has already
25
+ # been detected as part of detecting some previous docset (like
26
+ # Class detecting all of its configs) - in such case, skip.
27
+ doc_comments.each do |docset|
28
+ code = docset[:code]
29
+ docset[:code] = detect(code) unless code && code[:tagname]
30
+ end
31
+
32
+ # Return all doc-comments + other comments for which related
33
+ # code was detected.
34
+ @docs.find_all {|d| d[:type] == :doc_comment || d[:code] && d[:code][:tagname] }
35
+ end
36
+
37
+ # Given Esprima-produced syntax tree, detects documentation data.
38
+ #
39
+ # This method is exposed for testing purposes only, JSDuck itself
40
+ # only calls the above #detect_all method.
41
+ #
42
+ # @param ast :code from Result of EsprimaParser
43
+ # @returns Hash consisting of the detected :tagname, :name, and
44
+ # other properties relative to the tag. Like so:
45
+ #
46
+ # { :tagname => :method, :name => "foo", ... }
47
+ #
48
+ def detect(node)
49
+ ast = Js::Node.create(node)
50
+
51
+ exp = ast.expression_statement? ? ast["expression"] : nil
52
+ var = ast.variable_declaration? ? ast["declarations"][0] : nil
53
+
54
+ # Ext.define("Class", {})
55
+ if exp && exp.ext_define?
56
+ make_class(exp["arguments"][0].to_value, exp)
57
+
58
+ # Ext.override(Class, {})
59
+ elsif exp && exp.ext_override?
60
+ make_class("", exp)
61
+
62
+ # foo = Ext.extend(Parent, {})
63
+ elsif exp && exp.assignment_expression? && exp["right"].ext_extend?
64
+ make_class(exp["left"].to_s, exp["right"])
65
+
66
+ # Foo = ...
67
+ elsif exp && exp.assignment_expression? && class_name?(exp["left"].to_s)
68
+ make_class(exp["left"].to_s, exp["right"])
69
+
70
+ # var foo = Ext.extend(Parent, {})
71
+ elsif var && var["init"].ext_extend?
72
+ make_class(var["id"].to_s, var["init"])
73
+
74
+ # var Foo = ...
75
+ elsif var && class_name?(var["id"].to_s)
76
+ make_class(var["id"].to_s, var["right"])
77
+
78
+ # function Foo() {}
79
+ elsif ast.function? && class_name?(ast["id"].to_s || "")
80
+ make_class(ast["id"].to_s)
81
+
82
+ # { ... }
83
+ elsif ast.object_expression?
84
+ make_class("", ast)
85
+
86
+ # function foo() {}
87
+ elsif ast.function?
88
+ make_method(ast["id"].to_s || "", ast)
89
+
90
+ # foo = function() {}
91
+ elsif exp && exp.assignment_expression? && exp["right"].function?
92
+ make_method(exp["left"].to_s, exp["right"])
93
+
94
+ # var foo = function() {}
95
+ elsif var && var["init"] && var["init"].function?
96
+ make_method(var["id"].to_s, var["init"])
97
+
98
+ # (function() {})
99
+ elsif exp && exp.function?
100
+ make_method(exp["id"].to_s || "", exp)
101
+
102
+ # foo: function() {}
103
+ elsif ast.property? && ast["value"].function?
104
+ make_method(ast["key"].key_value, ast["value"])
105
+
106
+ # this.fireEvent("foo", ...)
107
+ elsif exp && exp.fire_event?
108
+ make_event(exp["arguments"][0].to_value)
109
+
110
+ # foo = ...
111
+ elsif exp && exp.assignment_expression?
112
+ make_property(exp["left"].to_s, exp["right"])
113
+
114
+ # var foo = ...
115
+ elsif var
116
+ make_property(var["id"].to_s, var["init"])
117
+
118
+ # foo: ...
119
+ elsif ast.property?
120
+ make_property(ast["key"].key_value, ast["value"])
121
+
122
+ # foo;
123
+ elsif exp && exp.identifier?
124
+ make_property(exp.to_s)
125
+
126
+ # "foo" (inside some expression)
127
+ elsif ast.string?
128
+ make_property(ast.to_value)
129
+
130
+ # "foo"; (as a statement of it's own)
131
+ elsif exp && exp.string?
132
+ make_property(exp.to_value)
133
+
134
+ else
135
+ make_property()
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ # Class name begins with upcase char
142
+ def class_name?(name)
143
+ return name.split(/\./).last =~ /\A[A-Z]/
144
+ end
145
+
146
+ def make_class(name, ast=nil)
147
+ cls = {
148
+ :tagname => :class,
149
+ :name => name,
150
+ }
151
+
152
+ # apply information from Ext.extend, Ext.define, or {}
153
+ if ast
154
+ if ast.ext_define?
155
+ detect_ext_define(cls, ast)
156
+ elsif ast.ext_extend?
157
+ detect_ext_something(:extends, cls, ast)
158
+ elsif ast.ext_override?
159
+ detect_ext_something(:override, cls, ast)
160
+ elsif ast.object_expression?
161
+ detect_class_members_from_object(cls, ast)
162
+ elsif ast.array_expression?
163
+ detect_class_members_from_array(cls, ast)
164
+ end
165
+ end
166
+
167
+ return cls
168
+ end
169
+
170
+ # Detection of Ext.extend() or Ext.override().
171
+ # The type parameter must be correspondingly either :extend or :override.
172
+ def detect_ext_something(type, cls, ast)
173
+ args = ast["arguments"]
174
+ cls[type] = args[0].to_s
175
+ if args.length == 2 && args[1].object_expression?
176
+ detect_class_members_from_object(cls, args[1])
177
+ end
178
+ end
179
+
180
+ # Inspects Ext.define() and copies detected properties over to the
181
+ # given cls Hash
182
+ def detect_ext_define(cls, ast)
183
+ # defaults
184
+ cls.merge!(TagRegistry.ext_define_defaults)
185
+ cls[:members] = []
186
+ cls[:code_type] = :ext_define
187
+
188
+ ast["arguments"][1].each_property do |key, value, pair|
189
+ if tag = TagRegistry.get_by_ext_define_pattern(key)
190
+ tag.parse_ext_define(cls, value)
191
+ else
192
+ case key
193
+ when "config"
194
+ cls[:members] += make_configs(value, {:accessor => true})
195
+ when "cachedConfig"
196
+ cls[:members] += make_configs(value, {:accessor => true})
197
+ when "eventedConfig"
198
+ cls[:members] += make_configs(value, {:accessor => true, :evented => true})
199
+ when "statics"
200
+ cls[:members] += make_statics(value)
201
+ when "inheritableStatics"
202
+ cls[:members] += make_statics(value, {:inheritable => true})
203
+ else
204
+ detect_method_or_property(cls, key, value, pair)
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ # Detects class members from object literal
211
+ def detect_class_members_from_object(cls, ast)
212
+ cls[:members] = []
213
+ ast.each_property do |key, value, pair|
214
+ detect_method_or_property(cls, key, value, pair)
215
+ end
216
+ end
217
+
218
+ # Detects class members from array literal
219
+ def detect_class_members_from_array(cls, ast)
220
+ cls[:members] = []
221
+
222
+ # This will most likely be an @enum class, in which case the
223
+ # enum will be for documentation purposes only.
224
+ cls[:enum] = {:doc_only => true}
225
+
226
+ ast["elements"].each do |el|
227
+ detect_method_or_property(cls, el.key_value, el, el)
228
+ end
229
+ end
230
+
231
+ # Detects item in object literal either as method or property
232
+ def detect_method_or_property(cls, key, value, pair)
233
+ if value.function?
234
+ m = make_method(key, value)
235
+ cls[:members] << m if apply_autodetected(m, pair)
236
+ else
237
+ p = make_property(key, value)
238
+ cls[:members] << p if apply_autodetected(p, pair)
239
+ end
240
+ end
241
+
242
+ def make_configs(ast, defaults={})
243
+ configs = []
244
+
245
+ ast.each_property do |name, value, pair|
246
+ cfg = make_property(name, value, :cfg)
247
+ cfg.merge!(defaults)
248
+ configs << cfg if apply_autodetected(cfg, pair)
249
+ end
250
+
251
+ configs
252
+ end
253
+
254
+ def make_statics(ast, defaults={})
255
+ statics = []
256
+
257
+ ast.each_property do |name, value, pair|
258
+ if value.function?
259
+ s = make_method(name, value)
260
+ else
261
+ s = make_property(name, value)
262
+ end
263
+
264
+ s[:static] = true
265
+ s.merge!(defaults)
266
+
267
+ statics << s if apply_autodetected(s, pair, defaults[:inheritable])
268
+ end
269
+
270
+ statics
271
+ end
272
+
273
+ # Sets auto-detection related properties :autodetected and
274
+ # :inheritdoc on the given member Hash.
275
+ #
276
+ # When member has a comment, adds code to the related docset and
277
+ # returns false.
278
+ #
279
+ # Otherwise detects the line number of member and returns true.
280
+ def apply_autodetected(m, ast, inheritable=true)
281
+ docset = find_docset(ast.raw)
282
+
283
+ if !docset || docset[:type] != :doc_comment
284
+ if inheritable
285
+ m[:inheritdoc] = {}
286
+ else
287
+ m[:private] = true
288
+ end
289
+ m[:autodetected] = true
290
+ end
291
+
292
+ if docset
293
+ docset[:code] = m
294
+ return false
295
+ else
296
+ m[:linenr] = ast.linenr
297
+ return true
298
+ end
299
+ end
300
+
301
+ # Looks up docset associated with given AST node.
302
+ # A dead-stupid and -slow implementation, but works.
303
+ #
304
+ # The comparison needs to be done between raw AST nodes - multiple
305
+ # Node instances can be created to wrap a single raw AST node,
306
+ # and they will then not be equal.
307
+ def find_docset(raw_ast)
308
+ @docs.find do |docset|
309
+ docset[:code] == raw_ast
310
+ end
311
+ end
312
+
313
+ def make_method(name, ast)
314
+ return {
315
+ :tagname => :method,
316
+ :name => name,
317
+ :params => make_params(ast),
318
+ :chainable => chainable?(ast) && name != "constructor",
319
+ }
320
+ end
321
+
322
+ def make_params(ast)
323
+ if ast.function? && !ast.ext_empty_fn?
324
+ ast["params"].map {|p| {:name => p.to_s} }
325
+ else
326
+ []
327
+ end
328
+ end
329
+
330
+ def chainable?(ast)
331
+ if ast.function? && !ast.ext_empty_fn?
332
+ Js::Function.return_types(ast.raw) == [:this]
333
+ else
334
+ false
335
+ end
336
+ end
337
+
338
+ def make_event(name)
339
+ return {
340
+ :tagname => :event,
341
+ :name => name,
342
+ }
343
+ end
344
+
345
+ def make_property(name=nil, ast=nil, tagname=:property)
346
+ return {
347
+ :tagname => tagname,
348
+ :name => name,
349
+ :type => ast && ast.value_type,
350
+ :default => ast && make_default(ast),
351
+ }
352
+ end
353
+
354
+ def make_default(ast)
355
+ ast.to_value != nil ? ast.to_s : nil
356
+ end
357
+
358
+ end
359
+
360
+ end
361
+ end