yard 0.9.38 → 0.9.44

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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -1
  3. data/LICENSE +1 -1
  4. data/README.md +19 -22
  5. data/docs/GettingStarted.md +41 -15
  6. data/docs/Parser.md +17 -42
  7. data/docs/Tags.md +26 -5
  8. data/docs/WhatsNew.md +59 -7
  9. data/docs/templates/default/yard_tags/html/setup.rb +1 -1
  10. data/lib/yard/autoload.rb +18 -0
  11. data/lib/yard/cli/diff.rb +7 -2
  12. data/lib/yard/code_objects/proxy.rb +1 -1
  13. data/lib/yard/handlers/processor.rb +1 -0
  14. data/lib/yard/handlers/rbs/attribute_handler.rb +79 -0
  15. data/lib/yard/handlers/rbs/base.rb +38 -0
  16. data/lib/yard/handlers/rbs/constant_handler.rb +18 -0
  17. data/lib/yard/handlers/rbs/method_handler.rb +327 -0
  18. data/lib/yard/handlers/rbs/mixin_handler.rb +20 -0
  19. data/lib/yard/handlers/rbs/namespace_handler.rb +26 -0
  20. data/lib/yard/handlers/ruby/attribute_handler.rb +7 -4
  21. data/lib/yard/handlers/ruby/constant_handler.rb +1 -0
  22. data/lib/yard/i18n/locale.rb +1 -1
  23. data/lib/yard/i18n/pot_generator.rb +1 -1
  24. data/lib/yard/parser/rbs/rbs_parser.rb +325 -0
  25. data/lib/yard/parser/rbs/statement.rb +75 -0
  26. data/lib/yard/parser/ruby/legacy/irb/slex.rb +19 -1
  27. data/lib/yard/parser/ruby/ruby_parser.rb +55 -3
  28. data/lib/yard/parser/source_parser.rb +3 -2
  29. data/lib/yard/registry_resolver.rb +7 -0
  30. data/lib/yard/rubygems/specification.rb +1 -1
  31. data/lib/yard/server/commands/base.rb +4 -10
  32. data/lib/yard/server/library_version.rb +1 -1
  33. data/lib/yard/server/static_caching.rb +36 -5
  34. data/lib/yard/server/templates/default/fulldoc/html/css/custom.css +168 -88
  35. data/lib/yard/server/templates/default/fulldoc/html/js/autocomplete.js +203 -12
  36. data/lib/yard/server/templates/default/layout/html/breadcrumb.erb +1 -17
  37. data/lib/yard/server/templates/default/method_details/html/permalink.erb +4 -2
  38. data/lib/yard/server/templates/doc_server/library_list/html/headers.erb +3 -3
  39. data/lib/yard/server/templates/doc_server/library_list/html/library_list.erb +2 -3
  40. data/lib/yard/server/templates/doc_server/processing/html/processing.erb +22 -16
  41. data/lib/yard/tags/directives.rb +7 -0
  42. data/lib/yard/tags/library.rb +3 -3
  43. data/lib/yard/tags/types_explainer.rb +165 -39
  44. data/lib/yard/templates/helpers/base_helper.rb +1 -1
  45. data/lib/yard/templates/helpers/html_helper.rb +16 -5
  46. data/lib/yard/templates/helpers/html_syntax_highlight_helper.rb +6 -1
  47. data/lib/yard/templates/helpers/markup/html_entities.rb +2140 -0
  48. data/lib/yard/templates/helpers/markup/hybrid_markdown.rb +2131 -0
  49. data/lib/yard/templates/helpers/markup_helper.rb +4 -2
  50. data/lib/yard/version.rb +1 -1
  51. data/po/ja.po +82 -82
  52. data/templates/default/fulldoc/html/css/common.css +1 -1
  53. data/templates/default/fulldoc/html/css/full_list.css +201 -53
  54. data/templates/default/fulldoc/html/css/style.css +988 -389
  55. data/templates/default/fulldoc/html/full_list.erb +4 -4
  56. data/templates/default/fulldoc/html/js/app.js +801 -395
  57. data/templates/default/fulldoc/html/js/full_list.js +332 -242
  58. data/templates/default/layout/html/headers.erb +1 -1
  59. data/templates/default/layout/html/layout.erb +3 -1
  60. data/templates/default/method/html/header.erb +3 -3
  61. data/templates/default/module/html/defines.erb +3 -3
  62. data/templates/default/module/html/inherited_methods.erb +1 -0
  63. data/templates/default/module/html/method_summary.erb +8 -0
  64. data/templates/default/module/setup.rb +20 -0
  65. data/templates/default/onefile/html/layout.erb +3 -4
  66. data/templates/guide/fulldoc/html/css/style.css +347 -97
  67. data/templates/guide/fulldoc/html/js/app.js +61 -33
  68. data/templates/guide/layout/html/layout.erb +69 -72
  69. metadata +15 -4
@@ -2,29 +2,35 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <link rel="stylesheet" href="<%= abs_url('css', 'style.css') %>?<%= mtime('css/style.css') %>" type="text/css" media="screen" charset="utf-8" />
6
- <link rel="stylesheet" href="<%= abs_url('css', 'custom.css') %>?<%= mtime('css/custom.css') %>" type="text/css" media="screen" charset="utf-8" />
7
- <script type="text/javascript" charset="utf-8" src="<%= abs_url('js', 'jquery.js') %>?<%= mtime('js/jquery.js') %>"></script>
5
+ <link rel="stylesheet" href="<%= abs_url('css', 'style.css') %>?<%= mtime('css/style.css') %>" type="text/css" media="screen" charset="utf-8">
6
+ <link rel="stylesheet" href="<%= abs_url('css', 'custom.css') %>?<%= mtime('css/custom.css') %>" type="text/css" media="screen" charset="utf-8">
8
7
  <script type="text/javascript" charset="utf-8">
9
8
  function checkPage(process) {
10
- $.ajax({cache: false, url: "<%= router.request.path %>" + (process ? "?process=true" : ""),
11
- statusCode: { 200: function() { window.location.reload(true); } } });
12
- setTimeout('checkPage()', 2000);
9
+ fetch("<%= router.request.path %>" + (process ? "?process=true" : ""), { cache: "no-store" })
10
+ .then(function(response) {
11
+ if (response.status === 200) window.location.reload();
12
+ });
13
+ setTimeout(checkPage, 2000);
13
14
  }
14
15
  function setFade() {
15
16
  centerMessage();
16
- $(window).resize(function() { centerMessage(); });
17
- $('#fade').fadeIn();
18
- $('#processing').fadeIn();
17
+ window.addEventListener("resize", centerMessage);
18
+ document.getElementById('fade').style.display = 'block';
19
+ document.getElementById('processing').style.display = 'block';
19
20
  }
20
21
  function centerMessage() {
21
- $('#fade').css('width', $(window).width() + 'px');
22
- $('#fade').css('height', $(window).height() - parseInt($('#fade').css('top')) + 'px');
23
- $('#processing').css('left', ($(window).width() / 2 - $('#processing').width() / 2) + 'px');
24
- $('#processing').css('top', ($(window).height() / 2 - $('#processing').height() / 2) + 'px');
22
+ var fade = document.getElementById('fade');
23
+ var processing = document.getElementById('processing');
24
+ var fadeTop = parseInt(window.getComputedStyle(fade).top, 10) || 0;
25
+ fade.style.width = window.innerWidth + 'px';
26
+ fade.style.height = (window.innerHeight - fadeTop) + 'px';
27
+ processing.style.left = (window.innerWidth / 2 - processing.offsetWidth / 2) + 'px';
28
+ processing.style.top = (window.innerHeight / 2 - processing.offsetHeight / 2) + 'px';
25
29
  }
26
- $(function() { checkPage(true); });
27
- $(setFade);
30
+ document.addEventListener("DOMContentLoaded", function() {
31
+ checkPage(true);
32
+ setFade();
33
+ });
28
34
  </script>
29
35
  <style type="text/css" media="screen">
30
36
  body { overflow: hidden; margin: 12px; display: block; }
@@ -46,7 +52,7 @@
46
52
  <strong><%= @library.name %></strong> <% if @library.version %>(<%= @library.version %>)<% end %> is being processed.
47
53
  You'll be redirected when the pages are built, it shouldn't take much longer.
48
54
  </p>
49
- <img src="<%= abs_url('images', 'processing.gif') %>?<%= mtime('images/processing.gif') %>" align="center" />
55
+ <img src="<%= abs_url('images', 'processing.gif') %>?<%= mtime('images/processing.gif') %>" align="center">
50
56
  </div>
51
57
  </body>
52
58
  </html>
@@ -543,6 +543,7 @@ module YARD
543
543
  # @since 0.8.0
544
544
  class ParseDirective < Directive
545
545
  def call
546
+ existing = YARD::Registry.all.lazy.map(&:path).to_set if handler
546
547
  lang = tag.types ? tag.types.first.to_sym :
547
548
  (handler ? handler.parser.parser_type : :ruby)
548
549
  if handler && lang == handler.parser.parser_type
@@ -555,6 +556,12 @@ module YARD
555
556
  src_parser.file = handler.parser.file if handler
556
557
  src_parser.parse(StringIO.new(tag.text))
557
558
  end
559
+ return unless handler
560
+ YARD::Registry.all.each do |obj|
561
+ next if existing.include? obj.path
562
+ obj.files.each { |entry| entry[1] = handler.statement.line if entry[0] == handler.parser.file.to_s }
563
+ obj.source = handler.statement.source
564
+ end
558
565
  end
559
566
  end
560
567
 
@@ -507,7 +507,7 @@ module YARD
507
507
  #
508
508
  # @example
509
509
  # # Synchronizes system time using NTP.
510
- # # @see http://ntp.org/documentation.html NTP Documentation
510
+ # # @see https://ntp.org/documentation.html NTP Documentation
511
511
  # # @see NTPHelperMethods
512
512
  # class NTPUpdater; end
513
513
  define_tag "See Also", :see, :with_name
@@ -541,7 +541,7 @@ module YARD
541
541
  # @example
542
542
  # # @todo Add support for Jabberwocky service.
543
543
  # # There is an open source Jabberwocky library available
544
- # # at http://jbrwcky.org that can be easily integrated.
544
+ # # at https://jbrwcky.org that can be easily integrated.
545
545
  # class Wonderlander; end
546
546
  # @see tag:note
547
547
  define_tag "Todo Item", :todo
@@ -554,7 +554,7 @@ module YARD
554
554
  # of a specific object.
555
555
  #
556
556
  # @example
557
- # # The public REST API for http://jbrwcky.org
557
+ # # The public REST API for https://jbrwcky.org
558
558
  # # @version 2.0
559
559
  # class JabberwockyAPI; end
560
560
  define_tag "Version", :version
@@ -4,6 +4,9 @@ require 'strscan'
4
4
  module YARD
5
5
  module Tags
6
6
  class TypesExplainer
7
+ # Regular expression to match symbol and string literals
8
+ LITERALMATCH = /:\w+|'[^']*'|"[^"]*"/
9
+
7
10
  # (see Tag#explain_types)
8
11
  # @param types [Array<String>] a list of types to parse and summarize
9
12
  def self.explain(*types)
@@ -31,16 +34,14 @@ module YARD
31
34
  end
32
35
 
33
36
  def to_s(singular = true)
34
- if name[0, 1] == "#"
35
- (singular ? "an object that responds to " : "objects that respond to ") + list_join(name.split(/ *& */), with: "and")
36
- elsif name[0, 1] =~ /[A-Z]/
37
+ if name[0, 1] =~ /[A-Z]/
37
38
  singular ? "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} " + name : "#{name}#{name[-1, 1] =~ /[A-Z]/ ? "'" : ''}s"
38
39
  else
39
40
  name
40
41
  end
41
42
  end
42
43
 
43
- private
44
+ protected
44
45
 
45
46
  def list_join(list, with: "or")
46
47
  index = 0
@@ -54,6 +55,20 @@ module YARD
54
55
  end
55
56
  end
56
57
 
58
+ # @private
59
+ class LiteralType < Type
60
+ def to_s(_singular = true)
61
+ "a literal value #{name}"
62
+ end
63
+ end
64
+
65
+ # @private
66
+ class DuckType < Type
67
+ def to_s(singular = true)
68
+ (singular ? "an object that responds to " : "objects that respond to ") + list_join(name.split(/ *& */), with: "and")
69
+ end
70
+ end
71
+
57
72
  # @private
58
73
  class CollectionType < Type
59
74
  attr_accessor :types
@@ -77,18 +92,56 @@ module YARD
77
92
 
78
93
  # @private
79
94
  class HashCollectionType < Type
80
- attr_accessor :key_types, :value_types
95
+ attr_accessor :key_value_pairs
81
96
 
82
- def initialize(name, key_types, value_types)
97
+ def initialize(name, key_types_or_pairs, value_types = nil)
83
98
  @name = name
84
- @key_types = key_types
85
- @value_types = value_types
99
+
100
+ if value_types.nil?
101
+ # New signature: (name, key_value_pairs)
102
+ @key_value_pairs = key_types_or_pairs || []
103
+ else
104
+ # Old signature: (name, key_types, value_types)
105
+ @key_value_pairs = [[key_types_or_pairs, value_types]]
106
+ end
107
+ end
108
+
109
+ # Backward compatibility accessors
110
+ def key_types
111
+ return [] if @key_value_pairs.empty?
112
+ @key_value_pairs.first[0] || []
113
+ end
114
+
115
+ def key_types=(types)
116
+ if @key_value_pairs.empty?
117
+ @key_value_pairs = [[types, []]]
118
+ else
119
+ @key_value_pairs[0][0] = types
120
+ end
121
+ end
122
+
123
+ def value_types
124
+ return [] if @key_value_pairs.empty?
125
+ @key_value_pairs.first[1] || []
126
+ end
127
+
128
+ def value_types=(types)
129
+ if @key_value_pairs.empty?
130
+ @key_value_pairs = [[[], types]]
131
+ else
132
+ @key_value_pairs[0][1] = types
133
+ end
86
134
  end
87
135
 
88
136
  def to_s(_singular = true)
89
- "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} #{name} with keys made of (" +
90
- list_join(key_types.map {|t| t.to_s(false) }) +
91
- ") and values of (" + list_join(value_types.map {|t| t.to_s(false) }) + ")"
137
+ return "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} #{name}" if @key_value_pairs.empty?
138
+
139
+ result = "a#{name[0, 1] =~ /[aeiou]/i ? 'n' : ''} #{name} with "
140
+ parts = @key_value_pairs.map do |keys, values|
141
+ "keys made of (" + list_join(keys.map {|t| t.to_s(false) }) +
142
+ ") and values of (" + list_join(values.map {|t| t.to_s(false) }) + ")"
143
+ end
144
+ result + parts.join(" and ")
92
145
  end
93
146
  end
94
147
 
@@ -101,12 +154,15 @@ module YARD
101
154
  :collection_end => />/,
102
155
  :fixed_collection_start => /\(/,
103
156
  :fixed_collection_end => /\)/,
104
- :type_name => /#{ISEP}#{METHODNAMEMATCH}|#{NAMESPACEMATCH}|\w+/,
105
- :type_next => /[,;]/,
157
+ :type_name => /#{ISEP}#{METHODNAMEMATCH}|#{NAMESPACEMATCH}|#{LITERALMATCH}|\w+/,
158
+ :symbol => /:#{METHODNAMEMATCH}/,
159
+ :type_next => /[,]/,
106
160
  :whitespace => /\s+/,
107
161
  :hash_collection_start => /\{/,
108
- :hash_collection_next => /=>/,
162
+ :hash_collection_value => /=>/,
163
+ :hash_collection_value_end => /;/,
109
164
  :hash_collection_end => /\}/,
165
+ # :symbol_start => /:/,
110
166
  :parse_end => nil
111
167
  }
112
168
 
@@ -118,10 +174,45 @@ module YARD
118
174
  @scanner = StringScanner.new(string)
119
175
  end
120
176
 
121
- def parse
122
- types = []
177
+ # @return [Array(Boolean, Array<Type>)] - finished, types
178
+ def parse(until_tokens: [:parse_end])
179
+ current_parsed_types = []
123
180
  type = nil
124
181
  name = nil
182
+ finished = false
183
+ parse_with_handlers do |token_type, token|
184
+ case token_type
185
+ when *until_tokens
186
+ raise SyntaxError, "expecting name, got '#{token}'" if name.nil?
187
+ type = create_type(name) unless type
188
+ current_parsed_types << type
189
+ finished = true
190
+ when :type_name
191
+ raise SyntaxError, "expecting END, got name '#{token}'" if name
192
+ name = token
193
+ when :type_next
194
+ raise SyntaxError, "expecting name, got '#{token}' at #{@scanner.pos}" if name.nil?
195
+ type = create_type(name) unless type
196
+ current_parsed_types << type
197
+ name = nil
198
+ type = nil
199
+ when :fixed_collection_start, :collection_start
200
+ name ||= "Array"
201
+ klass = token_type == :collection_start ? CollectionType : FixedCollectionType
202
+ type = klass.new(name, parse(until_tokens: [:fixed_collection_end, :collection_end, :parse_end]))
203
+ when :hash_collection_start
204
+ name ||= "Hash"
205
+ type = parse_hash_collection(name)
206
+ end
207
+
208
+ [finished, current_parsed_types]
209
+ end
210
+ end
211
+
212
+ private
213
+
214
+ # @return [Array<Type>]
215
+ def parse_with_handlers
125
216
  loop do
126
217
  found = false
127
218
  TOKENS.each do |token_type, match|
@@ -129,32 +220,67 @@ module YARD
129
220
  # rubocop:disable Lint/AssignmentInCondition
130
221
  next unless (match.nil? && @scanner.eos?) || (match && token = @scanner.scan(match))
131
222
  found = true
132
- case token_type
133
- when :type_name
134
- raise SyntaxError, "expecting END, got name '#{token}'" if name
135
- name = token
136
- when :type_next
137
- raise SyntaxError, "expecting name, got '#{token}' at #{@scanner.pos}" if name.nil?
138
- type = Type.new(name) unless type
139
- types << type
140
- type = nil
141
- name = nil
142
- when :fixed_collection_start, :collection_start
143
- name ||= "Array"
144
- klass = token_type == :collection_start ? CollectionType : FixedCollectionType
145
- type = klass.new(name, parse)
146
- when :hash_collection_start
147
- name ||= "Hash"
148
- type = HashCollectionType.new(name, parse, parse)
149
- when :hash_collection_next, :hash_collection_end, :fixed_collection_end, :collection_end, :parse_end
150
- raise SyntaxError, "expecting name, got '#{token}'" if name.nil?
151
- type = Type.new(name) unless type
152
- types << type
153
- return types
154
- end
223
+ # @type [Array<Type>]
224
+ finished, types = yield(token_type, token)
225
+ return types if finished
226
+ break
155
227
  end
156
228
  raise SyntaxError, "invalid character at #{@scanner.peek(1)}" unless found
157
229
  end
230
+ nil
231
+ end
232
+
233
+ def parse_hash_collection(name)
234
+ key_value_pairs = []
235
+ current_keys = []
236
+ finished = false
237
+
238
+ parse_with_handlers do |token_type, token|
239
+ case token_type
240
+ when :type_name
241
+ current_keys << create_type(token)
242
+ when :type_next
243
+ # Comma - continue collecting keys unless we just processed a value
244
+ # In that case, start a new key group
245
+ when :hash_collection_value
246
+ # => - current keys map to the next value(s)
247
+ raise SyntaxError, "no keys before =>" if current_keys.empty?
248
+ values = parse(until_tokens: [:hash_collection_value_end, :parse_end])
249
+ key_value_pairs << [current_keys, values]
250
+ current_keys = []
251
+ when :hash_collection_end, :parse_end
252
+ # End of hash
253
+ finished = true
254
+ when :whitespace
255
+ # Ignore whitespace
256
+ end
257
+
258
+ [finished, HashCollectionType.new(name, key_value_pairs)]
259
+ end
260
+ end
261
+
262
+ private
263
+
264
+ def create_type(name)
265
+ if name[0, 1] == ":" || (name[0, 1] =~ /['"]/ && name[-1, 1] =~ /['"]/)
266
+ LiteralType.new(name)
267
+ elsif name[0, 1] == "#"
268
+ DuckType.new(name)
269
+ else
270
+ Type.new(name)
271
+ end
272
+ end
273
+
274
+ private
275
+
276
+ def create_type(name)
277
+ if name[0, 1] == ":" || (name[0, 1] =~ /['"]/ && name[-1, 1] =~ /['"]/)
278
+ LiteralType.new(name)
279
+ elsif name[0, 1] == "#"
280
+ DuckType.new(name)
281
+ else
282
+ Type.new(name)
283
+ end
158
284
  end
159
285
  end
160
286
  end
@@ -45,7 +45,7 @@ module YARD::Templates::Helpers
45
45
  # method depending on the arguments passed in.
46
46
  #
47
47
  # @example Linking a URL
48
- # linkify('http://example.com')
48
+ # linkify('https://example.com')
49
49
  # @example Including docstring contents of an object
50
50
  # linkify('include:YARD::Docstring')
51
51
  # @example Linking to an extra file
@@ -93,10 +93,16 @@ module YARD
93
93
  :tables,
94
94
  :with_toc_data,
95
95
  :no_intraemphasis).to_html
96
+ when 'Commonmarker'
97
+ provider.to_html(text, :options => { }, :plugins => { :syntax_highlighter => nil })
96
98
  when 'CommonMarker'
97
- CommonMarker.render_html(text, %i[DEFAULT GITHUB_PRE_LANG], %i[autolink table])
99
+ provider.render_html(text, %i[DEFAULT GITHUB_PRE_LANG], %i[autolink table])
98
100
  else
99
- provider.new(text).to_html
101
+ if provider.respond_to?(:to_html)
102
+ provider.to_html(text)
103
+ else
104
+ provider.new(text).to_html
105
+ end
100
106
  end
101
107
  end
102
108
 
@@ -156,7 +162,7 @@ module YARD
156
162
  # @return [String] the output HTML
157
163
  # @since 0.6.0
158
164
  def html_markup_text(text)
159
- h(text).gsub(/\r?\n/, '<br/>')
165
+ h(text).gsub(/\r?\n/, '<br>')
160
166
  end
161
167
 
162
168
  # @return [String] the same text with no markup
@@ -228,7 +234,7 @@ module YARD
228
234
  # @return [String] HTML with linkified references
229
235
  def resolve_links(text)
230
236
  code_tags = 0
231
- text.gsub(%r{<(/)?(pre|code|tt)|(\\|!)?\{(?!\})(\S+?)(?:\s([^\}]*?\S))?\}(?=[\W<]|.+</|$)}m) do |str|
237
+ text.gsub(%r{<(/)?(pre|code|tt)|(\\|!)?\{(?!\})(\S+?)(?:\s([^\}]*?\S))?\}(?=\W|.+</|$)}m) do |str|
232
238
  closed = $1
233
239
  tag = $2
234
240
  escape = $3
@@ -650,7 +656,12 @@ module YARD
650
656
  language ||= detect_lang_in_codeblock_attributes($1, $2)
651
657
  language ||= object.source_type
652
658
 
653
- if options.highlight
659
+ # Skip re-highlighting if the block is already highlighted (e.g. from a recursive
660
+ # htmlify call via {include:} or {yard:include_tags}). Passing pre-highlighted HTML
661
+ # through CGI.unescapeHTML would corrupt deliberately-escaped entities inside spans.
662
+ # Note: this heuristic suppresses highlighting for code blocks in :html markup that
663
+ # contain a literal <span> tag in the source being documented (an uncommon edge case).
664
+ if options.highlight && string !~ HtmlSyntaxHighlightHelper::ALREADY_HIGHLIGHTED_RE
654
665
  string = html_syntax_highlight(CGI.unescapeHTML(string), language)
655
666
  end
656
667
  classes = ['code', language].compact.join(' ')
@@ -6,6 +6,11 @@ module YARD
6
6
  module HtmlSyntaxHighlightHelper
7
7
  include ModuleHelper
8
8
 
9
+ # Matches source that has already been highlighted (i.e. contains a span tag).
10
+ # Used to avoid double-processing pre-highlighted HTML in the rescue clause of
11
+ # {#html_syntax_highlight_ruby_ripper} and in the {HtmlHelper#parse_codeblocks} guard.
12
+ ALREADY_HIGHLIGHTED_RE = /<span[\s>]/
13
+
9
14
  # Highlights Ruby source
10
15
  # @param [String] source the Ruby source code
11
16
  # @return [String] the highlighted Ruby source
@@ -39,7 +44,7 @@ module YARD
39
44
  end
40
45
  output
41
46
  rescue Parser::ParserSyntaxError
42
- source =~ /^<span\s+class=/ ? source : h(source)
47
+ source =~ ALREADY_HIGHLIGHTED_RE ? source : h(source)
43
48
  end
44
49
 
45
50
  def html_syntax_highlight_ruby_legacy(source)