actionview 4.2.11.1 → 7.0.2.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -215
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +116 -43
  6. data/lib/action_view/buffers.rb +20 -3
  7. data/lib/action_view/cache_expiry.rb +66 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  10. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  11. data/lib/action_view/dependency_tracker.rb +21 -122
  12. data/lib/action_view/digestor.rb +92 -85
  13. data/lib/action_view/flows.rb +15 -16
  14. data/lib/action_view/gem_version.rb +6 -4
  15. data/lib/action_view/helpers/active_model_helper.rb +17 -12
  16. data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
  17. data/lib/action_view/helpers/asset_url_helper.rb +180 -74
  18. data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
  19. data/lib/action_view/helpers/cache_helper.rb +156 -43
  20. data/lib/action_view/helpers/capture_helper.rb +21 -14
  21. data/lib/action_view/helpers/controller_helper.rb +16 -5
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  24. data/lib/action_view/helpers/date_helper.rb +288 -132
  25. data/lib/action_view/helpers/debug_helper.rb +9 -6
  26. data/lib/action_view/helpers/form_helper.rb +956 -173
  27. data/lib/action_view/helpers/form_options_helper.rb +178 -97
  28. data/lib/action_view/helpers/form_tag_helper.rb +220 -101
  29. data/lib/action_view/helpers/javascript_helper.rb +33 -19
  30. data/lib/action_view/helpers/number_helper.rb +88 -63
  31. data/lib/action_view/helpers/output_safety_helper.rb +38 -6
  32. data/lib/action_view/helpers/rendering_helper.rb +21 -10
  33. data/lib/action_view/helpers/sanitize_helper.rb +31 -32
  34. data/lib/action_view/helpers/tag_helper.rb +332 -71
  35. data/lib/action_view/helpers/tags/base.rb +123 -99
  36. data/lib/action_view/helpers/tags/check_box.rb +21 -20
  37. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  38. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  39. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  40. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  41. data/lib/action_view/helpers/tags/collection_select.rb +5 -3
  42. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  43. data/lib/action_view/helpers/tags/date_field.rb +3 -2
  44. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  45. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  46. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  47. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  48. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  49. data/lib/action_view/helpers/tags/file_field.rb +18 -0
  50. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  51. data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
  52. data/lib/action_view/helpers/tags/label.rb +7 -2
  53. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  54. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  55. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  56. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  57. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  58. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  59. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  60. data/lib/action_view/helpers/tags/select.rb +11 -10
  61. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  62. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  63. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  64. data/lib/action_view/helpers/tags/time_field.rb +12 -2
  65. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  66. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  67. data/lib/action_view/helpers/tags/translator.rb +15 -16
  68. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  69. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  70. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  71. data/lib/action_view/helpers/tags.rb +5 -2
  72. data/lib/action_view/helpers/text_helper.rb +80 -51
  73. data/lib/action_view/helpers/translation_helper.rb +120 -69
  74. data/lib/action_view/helpers/url_helper.rb +398 -171
  75. data/lib/action_view/helpers.rb +29 -27
  76. data/lib/action_view/layouts.rb +68 -63
  77. data/lib/action_view/log_subscriber.rb +77 -10
  78. data/lib/action_view/lookup_context.rb +137 -113
  79. data/lib/action_view/model_naming.rb +4 -2
  80. data/lib/action_view/path_set.rb +28 -32
  81. data/lib/action_view/railtie.rb +74 -13
  82. data/lib/action_view/record_identifier.rb +53 -26
  83. data/lib/action_view/render_parser.rb +188 -0
  84. data/lib/action_view/renderer/abstract_renderer.rb +152 -15
  85. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  86. data/lib/action_view/renderer/object_renderer.rb +34 -0
  87. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  88. data/lib/action_view/renderer/partial_renderer.rb +51 -333
  89. data/lib/action_view/renderer/renderer.rb +68 -11
  90. data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
  91. data/lib/action_view/renderer/template_renderer.rb +87 -74
  92. data/lib/action_view/rendering.rb +73 -47
  93. data/lib/action_view/ripper_ast_parser.rb +198 -0
  94. data/lib/action_view/routing_url_for.rb +35 -24
  95. data/lib/action_view/tasks/cache_digests.rake +25 -0
  96. data/lib/action_view/template/error.rb +151 -41
  97. data/lib/action_view/template/handlers/builder.rb +12 -13
  98. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  99. data/lib/action_view/template/handlers/erb.rb +29 -89
  100. data/lib/action_view/template/handlers/html.rb +11 -0
  101. data/lib/action_view/template/handlers/raw.rb +4 -4
  102. data/lib/action_view/template/handlers.rb +14 -10
  103. data/lib/action_view/template/html.rb +12 -13
  104. data/lib/action_view/template/inline.rb +22 -0
  105. data/lib/action_view/template/raw_file.rb +25 -0
  106. data/lib/action_view/template/renderable.rb +24 -0
  107. data/lib/action_view/template/resolver.rb +139 -300
  108. data/lib/action_view/template/sources/file.rb +17 -0
  109. data/lib/action_view/template/sources.rb +13 -0
  110. data/lib/action_view/template/text.rb +10 -12
  111. data/lib/action_view/template/types.rb +28 -26
  112. data/lib/action_view/template.rb +123 -91
  113. data/lib/action_view/template_details.rb +66 -0
  114. data/lib/action_view/template_path.rb +64 -0
  115. data/lib/action_view/test_case.rb +70 -53
  116. data/lib/action_view/testing/resolvers.rb +25 -35
  117. data/lib/action_view/unbound_template.rb +57 -0
  118. data/lib/action_view/version.rb +3 -1
  119. data/lib/action_view/view_paths.rb +73 -58
  120. data/lib/action_view.rb +16 -11
  121. data/lib/assets/compiled/rails-ujs.js +746 -0
  122. metadata +52 -32
  123. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  124. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module ActionView
6
+ class RenderParser
7
+ module RipperASTParser # :nodoc:
8
+ class Node < ::Array # :nodoc:
9
+ attr_reader :type
10
+
11
+ def initialize(type, arr, opts = {})
12
+ @type = type
13
+ super(arr)
14
+ end
15
+
16
+ def children
17
+ to_a
18
+ end
19
+
20
+ def inspect
21
+ typeinfo = type && type != :list ? ":" + type.to_s + ", " : ""
22
+ "s(" + typeinfo + map(&:inspect).join(", ") + ")"
23
+ end
24
+
25
+ def fcall?
26
+ type == :command || type == :fcall
27
+ end
28
+
29
+ def fcall_named?(name)
30
+ fcall? &&
31
+ self[0].type == :@ident &&
32
+ self[0][0] == name
33
+ end
34
+
35
+ def argument_nodes
36
+ raise unless fcall?
37
+ return [] if self[1].nil?
38
+ if self[1].last == false || self[1].last.type == :vcall
39
+ self[1][0...-1]
40
+ else
41
+ self[1][0..-1]
42
+ end
43
+ end
44
+
45
+ def string?
46
+ type == :string_literal
47
+ end
48
+
49
+ def variable_reference?
50
+ type == :var_ref
51
+ end
52
+
53
+ def vcall?
54
+ type == :vcall
55
+ end
56
+
57
+ def call?
58
+ type == :call
59
+ end
60
+
61
+ def variable_name
62
+ self[0][0]
63
+ end
64
+
65
+ def call_method_name
66
+ self.last.first
67
+ end
68
+
69
+ def to_string
70
+ raise unless string?
71
+ self[0][0][0]
72
+ end
73
+
74
+ def hash?
75
+ type == :bare_assoc_hash || type == :hash
76
+ end
77
+
78
+ def to_hash
79
+ if type == :bare_assoc_hash
80
+ hash_from_body(self[0])
81
+ elsif type == :hash && self[0] == nil
82
+ {}
83
+ elsif type == :hash && self[0].type == :assoclist_from_args
84
+ hash_from_body(self[0][0])
85
+ end
86
+ end
87
+
88
+ def hash_from_body(body)
89
+ body.map do |hash_node|
90
+ return nil if hash_node.type != :assoc_new
91
+
92
+ [hash_node[0], hash_node[1]]
93
+ end.to_h
94
+ end
95
+
96
+ def symbol?
97
+ type == :@label || type == :symbol_literal
98
+ end
99
+
100
+ def to_symbol
101
+ if type == :@label && self[0] =~ /\A(.+):\z/
102
+ $1.to_sym
103
+ elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
104
+ self[0][0][0].to_sym
105
+ else
106
+ raise "not a symbol?: #{self.inspect}"
107
+ end
108
+ end
109
+ end
110
+
111
+ class NodeParser < ::Ripper # :nodoc:
112
+ PARSER_EVENTS.each do |event|
113
+ arity = PARSER_EVENT_TABLE[event]
114
+ if arity == 0 && event.to_s.end_with?("_new")
115
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
116
+ def on_#{event}(*args)
117
+ Node.new(:list, args, lineno: lineno(), column: column())
118
+ end
119
+ eof
120
+ elsif event.to_s.match?(/_add(_.+)?\z/)
121
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
122
+ begin; undef on_#{event}; rescue NameError; end
123
+ def on_#{event}(list, item)
124
+ list.push(item)
125
+ list
126
+ end
127
+ eof
128
+ else
129
+ module_eval(<<-eof, __FILE__, __LINE__ + 1)
130
+ begin; undef on_#{event}; rescue NameError; end
131
+ def on_#{event}(*args)
132
+ Node.new(:#{event}, args, lineno: lineno(), column: column())
133
+ end
134
+ eof
135
+ end
136
+ end
137
+
138
+ SCANNER_EVENTS.each do |event|
139
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
140
+ def on_#{event}(tok)
141
+ Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
142
+ end
143
+ End
144
+ end
145
+ end
146
+
147
+ class RenderCallExtractor < NodeParser # :nodoc:
148
+ attr_reader :render_calls
149
+
150
+ METHODS_TO_PARSE = %w(render render_to_string)
151
+
152
+ def initialize(*args)
153
+ super
154
+
155
+ @render_calls = []
156
+ end
157
+
158
+ private
159
+ def on_fcall(name, *args)
160
+ on_render_call(super)
161
+ end
162
+
163
+ def on_command(name, *args)
164
+ on_render_call(super)
165
+ end
166
+
167
+ def on_render_call(node)
168
+ METHODS_TO_PARSE.each do |method|
169
+ if node.fcall_named?(method)
170
+ @render_calls << [method, node]
171
+ return node
172
+ end
173
+ end
174
+ node
175
+ end
176
+
177
+ def on_arg_paren(content)
178
+ content
179
+ end
180
+
181
+ def on_paren(content)
182
+ content
183
+ end
184
+ end
185
+
186
+ extend self
187
+
188
+ def parse_render_nodes(code)
189
+ parser = RenderCallExtractor.new(code)
190
+ parser.parse
191
+
192
+ parser.render_calls.group_by(&:first).collect do |method, nodes|
193
+ [ method.to_sym, nodes.collect { |v| v[1] } ]
194
+ end.to_h
195
+ end
196
+ end
197
+ end
198
+ end
@@ -1,8 +1,9 @@
1
- require 'action_dispatch/routing/polymorphic_routes'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/routing/polymorphic_routes"
2
4
 
3
5
  module ActionView
4
6
  module RoutingUrlFor
5
-
6
7
  # Returns the URL for the set of +options+ provided. This takes the
7
8
  # same options as +url_for+ in Action Controller (see the
8
9
  # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
@@ -32,7 +33,7 @@ module ActionView
32
33
  #
33
34
  # ==== Examples
34
35
  # <%= url_for(action: 'index') %>
35
- # # => /blog/
36
+ # # => /blogs/
36
37
  #
37
38
  # <%= url_for(action: 'find', controller: 'books') %>
38
39
  # # => /books/find
@@ -83,27 +84,28 @@ module ActionView
83
84
  super(only_path: _generate_paths_by_default)
84
85
  when Hash
85
86
  options = options.symbolize_keys
86
- unless options.key?(:only_path)
87
- if options[:host].nil?
88
- options[:only_path] = _generate_paths_by_default
89
- else
90
- options[:only_path] = false
91
- end
92
- end
87
+ ensure_only_path_option(options)
88
+
89
+ super(options)
90
+ when ActionController::Parameters
91
+ ensure_only_path_option(options)
93
92
 
94
93
  super(options)
95
94
  when :back
96
95
  _back_url
97
96
  when Array
98
97
  components = options.dup
99
- if _generate_paths_by_default
100
- polymorphic_path(components, components.extract_options!)
98
+ options = components.extract_options!
99
+ ensure_only_path_option(options)
100
+
101
+ if options[:only_path]
102
+ polymorphic_path(components, options)
101
103
  else
102
- polymorphic_url(components, components.extract_options!)
104
+ polymorphic_url(components, options)
103
105
  end
104
106
  else
105
107
  method = _generate_paths_by_default ? :path : :url
106
- builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
108
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.public_send(method)
107
109
 
108
110
  case options
109
111
  when Symbol
@@ -116,20 +118,29 @@ module ActionView
116
118
  end
117
119
  end
118
120
 
119
- def url_options #:nodoc:
121
+ def url_options # :nodoc:
120
122
  return super unless controller.respond_to?(:url_options)
121
123
  controller.url_options
122
124
  end
123
125
 
124
- def _routes_context #:nodoc:
125
- controller
126
- end
127
- protected :_routes_context
126
+ private
127
+ def _routes_context
128
+ controller
129
+ end
128
130
 
129
- def optimize_routes_generation? #:nodoc:
130
- controller.respond_to?(:optimize_routes_generation?, true) ?
131
- controller.optimize_routes_generation? : super
132
- end
133
- protected :optimize_routes_generation?
131
+ def optimize_routes_generation?
132
+ controller.respond_to?(:optimize_routes_generation?, true) ?
133
+ controller.optimize_routes_generation? : super
134
+ end
135
+
136
+ def _generate_paths_by_default
137
+ true
138
+ end
139
+
140
+ def ensure_only_path_option(options)
141
+ unless options.key?(:only_path)
142
+ options[:only_path] = _generate_paths_by_default unless options[:host]
143
+ end
144
+ end
134
145
  end
135
146
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :cache_digests do
4
+ desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
5
+ task nested_dependencies: :environment do
6
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
7
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
8
+ end
9
+
10
+ desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
11
+ task dependencies: :environment do
12
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
13
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
14
+ end
15
+
16
+ class CacheDigests
17
+ def self.template_name
18
+ ENV["TEMPLATE"].split(".", 2).first
19
+ end
20
+
21
+ def self.finder
22
+ ApplicationController.new.lookup_context
23
+ end
24
+ end
25
+ end
@@ -1,17 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/enumerable"
2
4
 
3
5
  module ActionView
4
6
  # = Action View Errors
5
- class ActionViewError < StandardError #:nodoc:
6
- end
7
-
8
- class EncodingError < StandardError #:nodoc:
7
+ class ActionViewError < StandardError # :nodoc:
9
8
  end
10
9
 
11
- class MissingRequestError < StandardError #:nodoc:
10
+ class EncodingError < StandardError # :nodoc:
12
11
  end
13
12
 
14
- class WrongEncodingError < EncodingError #:nodoc:
13
+ class WrongEncodingError < EncodingError # :nodoc:
15
14
  def initialize(string, encoding)
16
15
  @string, @encoding = string, encoding
17
16
  end
@@ -27,45 +26,141 @@ module ActionView
27
26
  end
28
27
  end
29
28
 
30
- class MissingTemplate < ActionViewError #:nodoc:
31
- attr_reader :path
29
+ class MissingTemplate < ActionViewError # :nodoc:
30
+ attr_reader :path, :paths, :prefixes, :partial
32
31
 
33
32
  def initialize(paths, path, prefixes, partial, details, *)
33
+ if partial && path.present?
34
+ path = path.sub(%r{([^/]+)$}, "_\\1")
35
+ end
36
+
34
37
  @path = path
35
- prefixes = Array(prefixes)
38
+ @paths = paths
39
+ @prefixes = Array(prefixes)
40
+ @partial = partial
36
41
  template_type = if partial
37
42
  "partial"
38
- elsif path =~ /layouts/i
39
- 'layout'
43
+ elsif /layouts/i.match?(path)
44
+ "layout"
40
45
  else
41
- 'template'
46
+ "template"
42
47
  end
43
48
 
44
- if partial && path.present?
45
- path = path.sub(%r{([^/]+)$}, "_\\1")
46
- end
47
- searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
49
+ searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") }
48
50
 
49
- out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
51
+ out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}.\n\nSearched in:\n"
50
52
  out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
51
53
  super out
52
54
  end
55
+
56
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::Jaro)
57
+ include DidYouMean::Correctable
58
+
59
+ class Results # :nodoc:
60
+ Result = Struct.new(:path, :score)
61
+
62
+ def initialize(size)
63
+ @size = size
64
+ @results = []
65
+ end
66
+
67
+ def to_a
68
+ @results.map(&:path)
69
+ end
70
+
71
+ def should_record?(score)
72
+ if @results.size < @size
73
+ true
74
+ else
75
+ score < @results.last.score
76
+ end
77
+ end
78
+
79
+ def add(path, score)
80
+ if should_record?(score)
81
+ @results << Result.new(path, score)
82
+ @results.sort_by!(&:score)
83
+ @results.pop if @results.size > @size
84
+ end
85
+ end
86
+ end
87
+
88
+ # Apps may have thousands of candidate templates so we attempt to
89
+ # generate the suggestions as efficiently as possible.
90
+ # First we split templates into prefixes and basenames, so that those can
91
+ # be matched separately.
92
+ def corrections
93
+ candidates = paths.flat_map(&:all_template_paths).uniq
94
+
95
+ if partial
96
+ candidates.select!(&:partial?)
97
+ else
98
+ candidates.reject!(&:partial?)
99
+ end
100
+
101
+ # Group by possible prefixes
102
+ files_by_dir = candidates.group_by(&:prefix)
103
+ files_by_dir.transform_values! do |files|
104
+ files.map do |file|
105
+ # Remove prefix
106
+ File.basename(file.to_s)
107
+ end
108
+ end
109
+
110
+ # No suggestions if there's an exact match, but wrong details
111
+ if prefixes.any? { |prefix| files_by_dir[prefix]&.include?(path) }
112
+ return []
113
+ end
114
+
115
+ cached_distance = Hash.new do |h, args|
116
+ h[args] = -DidYouMean::Jaro.distance(*args)
117
+ end
118
+
119
+ results = Results.new(6)
120
+
121
+ files_by_dir.keys.index_with do |dirname|
122
+ prefixes.map do |prefix|
123
+ cached_distance[[prefix, dirname]]
124
+ end.min
125
+ end.sort_by(&:last).each do |dirname, dirweight|
126
+ # If our directory's score makes it impossible to find a better match
127
+ # we can prune this search branch.
128
+ next unless results.should_record?(dirweight - 1.0)
129
+
130
+ files = files_by_dir[dirname]
131
+
132
+ files.each do |file|
133
+ fileweight = cached_distance[[path, file]]
134
+ score = dirweight + fileweight
135
+
136
+ results.add(File.join(dirname, file), score)
137
+ end
138
+ end
139
+
140
+ if partial
141
+ results.to_a.map { |res| res.sub(%r{_([^/]+)\z}, "\\1") }
142
+ else
143
+ results.to_a
144
+ end
145
+ end
146
+ end
53
147
  end
54
148
 
55
149
  class Template
56
150
  # The Template::Error exception is raised when the compilation or rendering of the template
57
151
  # fails. This exception then gathers a bunch of intimate details and uses it to report a
58
152
  # precise exception message.
59
- class Error < ActionViewError #:nodoc:
153
+ class Error < ActionViewError # :nodoc:
60
154
  SOURCE_CODE_RADIUS = 3
61
155
 
62
- attr_reader :original_exception
156
+ # Override to prevent #cause resetting during re-raise.
157
+ attr_reader :cause
63
158
 
64
- def initialize(template, original_exception)
65
- super(original_exception.message)
66
- @template, @original_exception = template, original_exception
67
- @sub_templates = nil
68
- set_backtrace(original_exception.backtrace)
159
+ def initialize(template)
160
+ super($!.message)
161
+ set_backtrace($!.backtrace)
162
+ @cause = $!
163
+ @template, @sub_templates = template, nil
69
164
  end
70
165
 
71
166
  def file_name
@@ -75,25 +170,25 @@ module ActionView
75
170
  def sub_template_message
76
171
  if @sub_templates
77
172
  "Trace of template inclusion: " +
78
- @sub_templates.collect { |template| template.inspect }.join(", ")
173
+ @sub_templates.collect(&:inspect).join(", ")
79
174
  else
80
175
  ""
81
176
  end
82
177
  end
83
178
 
84
- def source_extract(indentation = 0, output = :console)
85
- return unless num = line_number
179
+ def source_extract(indentation = 0)
180
+ return [] unless num = line_number
86
181
  num = num.to_i
87
182
 
88
- source_code = @template.source.split("\n")
183
+ source_code = @template.encode!.split("\n")
89
184
 
90
185
  start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
91
186
  end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
92
187
 
93
188
  indent = end_on_line.to_s.size + indentation
94
- return unless source_code = source_code[start_on_line..end_on_line]
189
+ return [] unless source_code = source_code[start_on_line..end_on_line]
95
190
 
96
- formatted_code_for(source_code, start_on_line, indent, output)
191
+ formatted_code_for(source_code, start_on_line, indent)
97
192
  end
98
193
 
99
194
  def sub_template_of(template_path)
@@ -109,33 +204,48 @@ module ActionView
109
204
  end
110
205
  end
111
206
 
112
- def annoted_source_code
207
+ def annotated_source_code
113
208
  source_extract(4)
114
209
  end
115
210
 
116
211
  private
117
-
118
212
  def source_location
119
213
  if line_number
120
214
  "on line ##{line_number} of "
121
215
  else
122
- 'in '
216
+ "in "
123
217
  end + file_name
124
218
  end
125
219
 
126
- def formatted_code_for(source_code, line_counter, indent, output)
127
- start_value = (output == :html) ? {} : ""
128
- source_code.inject(start_value) do |result, line|
220
+ def formatted_code_for(source_code, line_counter, indent)
221
+ indent_template = "%#{indent}s: %s"
222
+ source_code.map do |line|
129
223
  line_counter += 1
130
- if output == :html
131
- result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
132
- else
133
- result << "%#{indent}s: %s\n" % [line_counter, line]
134
- end
224
+ indent_template % [line_counter, line]
135
225
  end
136
226
  end
137
227
  end
138
228
  end
139
229
 
140
230
  TemplateError = Template::Error
231
+
232
+ class SyntaxErrorInTemplate < TemplateError # :nodoc:
233
+ def initialize(template, offending_code_string)
234
+ @offending_code_string = offending_code_string
235
+ super(template)
236
+ end
237
+
238
+ def message
239
+ <<~MESSAGE
240
+ Encountered a syntax error while rendering template: check #{@offending_code_string}
241
+ MESSAGE
242
+ end
243
+
244
+ def annotated_source_code
245
+ @offending_code_string.split("\n").map.with_index(1) { |line, index|
246
+ indentation = " " * 4
247
+ "#{index}:#{indentation}#{line}"
248
+ }
249
+ end
250
+ end
141
251
  end
@@ -1,26 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Template::Handlers
3
5
  class Builder
4
- # Default format used by Builder.
5
- class_attribute :default_format
6
- self.default_format = :xml
6
+ class_attribute :default_format, default: :xml
7
7
 
8
- def call(template)
8
+ def call(template, source)
9
9
  require_engine
10
- "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
10
+ "xml = ::Builder::XmlMarkup.new(:indent => 2);" \
11
11
  "self.output_buffer = xml.target!;" +
12
- template.source +
12
+ source +
13
13
  ";xml.target!;"
14
14
  end
15
15
 
16
- protected
17
-
18
- def require_engine
19
- @required ||= begin
20
- require "builder"
21
- true
16
+ private
17
+ def require_engine # :doc:
18
+ @required ||= begin
19
+ require "builder"
20
+ true
21
+ end
22
22
  end
23
- end
24
23
  end
25
24
  end
26
25
  end