actionview 6.1.7.2 → 7.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +268 -254
  3. data/MIT-LICENSE +1 -0
  4. data/README.rdoc +2 -2
  5. data/lib/action_view/base.rb +4 -7
  6. data/lib/action_view/buffers.rb +2 -2
  7. data/lib/action_view/cache_expiry.rb +46 -32
  8. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  9. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  10. data/lib/action_view/dependency_tracker.rb +6 -147
  11. data/lib/action_view/digestor.rb +7 -4
  12. data/lib/action_view/flows.rb +4 -4
  13. data/lib/action_view/gem_version.rb +5 -5
  14. data/lib/action_view/helpers/active_model_helper.rb +2 -2
  15. data/lib/action_view/helpers/asset_tag_helper.rb +95 -39
  16. data/lib/action_view/helpers/asset_url_helper.rb +16 -16
  17. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  18. data/lib/action_view/helpers/cache_helper.rb +52 -3
  19. data/lib/action_view/helpers/capture_helper.rb +4 -4
  20. data/lib/action_view/helpers/controller_helper.rb +2 -2
  21. data/lib/action_view/helpers/csp_helper.rb +1 -1
  22. data/lib/action_view/helpers/csrf_helper.rb +2 -2
  23. data/lib/action_view/helpers/date_helper.rb +111 -43
  24. data/lib/action_view/helpers/debug_helper.rb +3 -1
  25. data/lib/action_view/helpers/form_helper.rb +211 -85
  26. data/lib/action_view/helpers/form_options_helper.rb +70 -33
  27. data/lib/action_view/helpers/form_tag_helper.rb +150 -53
  28. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  29. data/lib/action_view/helpers/number_helper.rb +17 -16
  30. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  31. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  32. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  33. data/lib/action_view/helpers/tag_helper.rb +37 -8
  34. data/lib/action_view/helpers/tags/base.rb +5 -25
  35. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  36. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  37. data/lib/action_view/helpers/tags/file_field.rb +16 -0
  38. data/lib/action_view/helpers/tags/select.rb +1 -1
  39. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  40. data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
  41. data/lib/action_view/helpers/tags.rb +3 -2
  42. data/lib/action_view/helpers/text_helper.rb +25 -14
  43. data/lib/action_view/helpers/translation_helper.rb +12 -43
  44. data/lib/action_view/helpers/url_helper.rb +194 -123
  45. data/lib/action_view/helpers.rb +25 -25
  46. data/lib/action_view/layouts.rb +7 -4
  47. data/lib/action_view/lookup_context.rb +33 -52
  48. data/lib/action_view/model_naming.rb +2 -2
  49. data/lib/action_view/path_set.rb +16 -22
  50. data/lib/action_view/railtie.rb +19 -7
  51. data/lib/action_view/record_identifier.rb +1 -1
  52. data/lib/action_view/render_parser.rb +188 -0
  53. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  54. data/lib/action_view/renderer/partial_renderer.rb +1 -35
  55. data/lib/action_view/renderer/renderer.rb +4 -4
  56. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  57. data/lib/action_view/renderer/template_renderer.rb +6 -2
  58. data/lib/action_view/rendering.rb +3 -3
  59. data/lib/action_view/ripper_ast_parser.rb +198 -0
  60. data/lib/action_view/routing_url_for.rb +8 -5
  61. data/lib/action_view/template/error.rb +108 -13
  62. data/lib/action_view/template/handlers/erb.rb +6 -0
  63. data/lib/action_view/template/handlers.rb +3 -3
  64. data/lib/action_view/template/html.rb +3 -3
  65. data/lib/action_view/template/inline.rb +3 -3
  66. data/lib/action_view/template/raw_file.rb +3 -3
  67. data/lib/action_view/template/resolver.rb +89 -314
  68. data/lib/action_view/template/text.rb +3 -3
  69. data/lib/action_view/template/types.rb +14 -12
  70. data/lib/action_view/template.rb +18 -2
  71. data/lib/action_view/template_details.rb +66 -0
  72. data/lib/action_view/template_path.rb +64 -0
  73. data/lib/action_view/test_case.rb +7 -3
  74. data/lib/action_view/testing/resolvers.rb +11 -12
  75. data/lib/action_view/unbound_template.rb +33 -7
  76. data/lib/action_view/version.rb +1 -1
  77. data/lib/action_view/view_paths.rb +4 -4
  78. data/lib/action_view.rb +2 -3
  79. data/lib/assets/compiled/rails-ujs.js +36 -5
  80. metadata +23 -16
@@ -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
@@ -6,14 +6,14 @@ module ActionView
6
6
  module RoutingUrlFor
7
7
  # Returns the URL for the set of +options+ provided. This takes the
8
8
  # same options as +url_for+ in Action Controller (see the
9
- # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
10
- # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
11
- # instead of the fully qualified URL like "http://example.com/controller/action".
9
+ # documentation for ActionDispatch::Routing::UrlFor#url_for). Note that by default
10
+ # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative <tt>"/controller/action"</tt>
11
+ # instead of the fully qualified URL like <tt>"http://example.com/controller/action"</tt>.
12
12
  #
13
13
  # ==== Options
14
14
  # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
15
15
  # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
16
- # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
16
+ # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in <tt>"/archive/2005/"</tt>. Note that this
17
17
  # is currently not recommended since it breaks caching.
18
18
  # * <tt>:host</tt> - Overrides the default (current) host if provided.
19
19
  # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
@@ -47,6 +47,9 @@ module ActionView
47
47
  # <%= url_for(action: 'jump', anchor: 'tax&ship') %>
48
48
  # # => /testing/jump/#tax&ship
49
49
  #
50
+ # <%= url_for(Workshop) %>
51
+ # # => /workshops
52
+ #
50
53
  # <%= url_for(Workshop.new) %>
51
54
  # # relies on Workshop answering a persisted? call (and in this case returning false)
52
55
  # # => /workshops
@@ -118,7 +121,7 @@ module ActionView
118
121
  end
119
122
  end
120
123
 
121
- def url_options #:nodoc:
124
+ def url_options # :nodoc:
122
125
  return super unless controller.respond_to?(:url_options)
123
126
  controller.url_options
124
127
  end
@@ -4,13 +4,13 @@ require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActionView
6
6
  # = Action View Errors
7
- class ActionViewError < StandardError #:nodoc:
7
+ class ActionViewError < StandardError # :nodoc:
8
8
  end
9
9
 
10
- class EncodingError < StandardError #:nodoc:
10
+ class EncodingError < StandardError # :nodoc:
11
11
  end
12
12
 
13
- class WrongEncodingError < EncodingError #:nodoc:
13
+ class WrongEncodingError < EncodingError # :nodoc:
14
14
  def initialize(string, encoding)
15
15
  @string, @encoding = string, encoding
16
16
  end
@@ -26,12 +26,18 @@ module ActionView
26
26
  end
27
27
  end
28
28
 
29
- class MissingTemplate < ActionViewError #:nodoc:
30
- attr_reader :path
29
+ class MissingTemplate < ActionViewError # :nodoc:
30
+ attr_reader :path, :paths, :prefixes, :partial
31
31
 
32
32
  def initialize(paths, path, prefixes, partial, details, *)
33
+ if partial && path.present?
34
+ path = path.sub(%r{([^/]+)$}, "_\\1")
35
+ end
36
+
33
37
  @path = path
34
- prefixes = Array(prefixes)
38
+ @paths = paths
39
+ @prefixes = Array(prefixes)
40
+ @partial = partial
35
41
  template_type = if partial
36
42
  "partial"
37
43
  elsif /layouts/i.match?(path)
@@ -40,22 +46,111 @@ module ActionView
40
46
  "template"
41
47
  end
42
48
 
43
- if partial && path.present?
44
- path = path.sub(%r{([^/]+)$}, "_\\1")
45
- end
46
- searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
49
+ searched_paths = @prefixes.map { |prefix| [prefix, path].join("/") }
47
50
 
48
- 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"
49
52
  out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
50
53
  super out
51
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
52
147
  end
53
148
 
54
149
  class Template
55
150
  # The Template::Error exception is raised when the compilation or rendering of the template
56
151
  # fails. This exception then gathers a bunch of intimate details and uses it to report a
57
152
  # precise exception message.
58
- class Error < ActionViewError #:nodoc:
153
+ class Error < ActionViewError # :nodoc:
59
154
  SOURCE_CODE_RADIUS = 3
60
155
 
61
156
  # Override to prevent #cause resetting during re-raise.
@@ -134,7 +229,7 @@ module ActionView
134
229
 
135
230
  TemplateError = Template::Error
136
231
 
137
- class SyntaxErrorInTemplate < TemplateError #:nodoc
232
+ class SyntaxErrorInTemplate < TemplateError # :nodoc:
138
233
  def initialize(template, offending_code_string)
139
234
  @offending_code_string = offending_code_string
140
235
  super(template)
@@ -16,6 +16,9 @@ module ActionView
16
16
  # Do not escape templates of these mime types.
17
17
  class_attribute :escape_ignore_list, default: ["text/plain"]
18
18
 
19
+ # Strip trailing newlines from rendered output
20
+ class_attribute :strip_trailing_newlines, default: false
21
+
19
22
  ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
20
23
 
21
24
  def self.call(template, source)
@@ -45,6 +48,9 @@ module ActionView
45
48
  # Always make sure we return a String in the default_internal
46
49
  erb.encode!
47
50
 
51
+ # Strip trailing newlines from the template if enabled
52
+ erb.chomp! if strip_trailing_newlines
53
+
48
54
  options = {
49
55
  escape: (self.class.escape_ignore_list.include? template.type),
50
56
  trim: (self.class.erb_trim_mode == "-")
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
3
+ module ActionView # :nodoc:
4
4
  # = Action View Template Handlers
5
- class Template #:nodoc:
6
- module Handlers #:nodoc:
5
+ class Template # :nodoc:
6
+ module Handlers # :nodoc:
7
7
  autoload :Raw, "action_view/template/handlers/raw"
8
8
  autoload :ERB, "action_view/template/handlers/erb"
9
9
  autoload :Html, "action_view/template/handlers/html"
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
3
+ module ActionView # :nodoc:
4
4
  # = Action View HTML Template
5
- class Template #:nodoc:
6
- class HTML #:nodoc:
5
+ class Template # :nodoc:
6
+ class HTML # :nodoc:
7
7
  attr_reader :type
8
8
 
9
9
  def initialize(string, type)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
4
- class Template #:nodoc:
5
- class Inline < Template #:nodoc:
3
+ module ActionView # :nodoc:
4
+ class Template # :nodoc:
5
+ class Inline < Template # :nodoc:
6
6
  # This finalizer is needed (and exactly with a proc inside another proc)
7
7
  # otherwise templates leak in development.
8
8
  Finalizer = proc do |method_name, mod| # :nodoc:
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActionView #:nodoc:
3
+ module ActionView # :nodoc:
4
4
  # = Action View RawFile Template
5
- class Template #:nodoc:
6
- class RawFile #:nodoc:
5
+ class Template # :nodoc:
6
+ class RawFile # :nodoc:
7
7
  attr_accessor :type, :format
8
8
 
9
9
  def initialize(filename)