actionview 6.1.4.1 → 7.0.0.rc2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +189 -248
- data/MIT-LICENSE +1 -1
- data/lib/action_view/base.rb +4 -7
- data/lib/action_view/buffers.rb +2 -2
- data/lib/action_view/cache_expiry.rb +46 -32
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/digestor.rb +7 -4
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +2 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +84 -29
- data/lib/action_view/helpers/asset_url_helper.rb +9 -9
- data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
- data/lib/action_view/helpers/cache_helper.rb +52 -3
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +2 -2
- data/lib/action_view/helpers/csp_helper.rb +1 -1
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +62 -7
- data/lib/action_view/helpers/debug_helper.rb +3 -1
- data/lib/action_view/helpers/form_helper.rb +190 -75
- data/lib/action_view/helpers/form_options_helper.rb +68 -33
- data/lib/action_view/helpers/form_tag_helper.rb +126 -36
- data/lib/action_view/helpers/javascript_helper.rb +3 -5
- data/lib/action_view/helpers/number_helper.rb +3 -4
- data/lib/action_view/helpers/output_safety_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +34 -6
- data/lib/action_view/helpers/tags/base.rb +4 -24
- data/lib/action_view/helpers/tags/check_box.rb +2 -2
- data/lib/action_view/helpers/tags/collection_select.rb +1 -1
- data/lib/action_view/helpers/tags/hidden_field.rb +4 -0
- data/lib/action_view/helpers/tags/time_field.rb +10 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
- data/lib/action_view/helpers/tags.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +24 -13
- data/lib/action_view/helpers/translation_helper.rb +10 -41
- data/lib/action_view/helpers/url_helper.rb +166 -91
- data/lib/action_view/helpers.rb +25 -25
- data/lib/action_view/lookup_context.rb +33 -52
- data/lib/action_view/model_naming.rb +2 -2
- data/lib/action_view/path_set.rb +16 -22
- data/lib/action_view/railtie.rb +19 -7
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +2 -2
- data/lib/action_view/renderer/partial_renderer.rb +0 -34
- data/lib/action_view/renderer/renderer.rb +4 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
- data/lib/action_view/renderer/template_renderer.rb +6 -2
- data/lib/action_view/rendering.rb +2 -2
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template/error.rb +108 -13
- data/lib/action_view/template/handlers/erb.rb +6 -0
- data/lib/action_view/template/handlers.rb +3 -3
- data/lib/action_view/template/html.rb +3 -3
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +3 -3
- data/lib/action_view/template/resolver.rb +84 -311
- data/lib/action_view/template/text.rb +3 -3
- data/lib/action_view/template/types.rb +14 -12
- data/lib/action_view/template.rb +18 -2
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +6 -2
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +33 -7
- data/lib/action_view.rb +3 -4
- metadata +22 -14
@@ -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
|
@@ -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
|
7
|
+
class ActionViewError < StandardError # :nodoc:
|
8
8
|
end
|
9
9
|
|
10
|
-
class EncodingError < StandardError
|
10
|
+
class EncodingError < StandardError # :nodoc:
|
11
11
|
end
|
12
12
|
|
13
|
-
class WrongEncodingError < EncodingError
|
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
|
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
|
-
|
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
|
-
|
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}
|
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
|
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
|
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
|
3
|
+
module ActionView # :nodoc:
|
4
4
|
# = Action View Template Handlers
|
5
|
-
class Template
|
6
|
-
module Handlers
|
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
|
3
|
+
module ActionView # :nodoc:
|
4
4
|
# = Action View HTML Template
|
5
|
-
class Template
|
6
|
-
class HTML
|
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
|
4
|
-
class Template
|
5
|
-
class Inline < Template
|
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
|
3
|
+
module ActionView # :nodoc:
|
4
4
|
# = Action View RawFile Template
|
5
|
-
class Template
|
6
|
-
class RawFile
|
5
|
+
class Template # :nodoc:
|
6
|
+
class RawFile # :nodoc:
|
7
7
|
attr_accessor :type, :format
|
8
8
|
|
9
9
|
def initialize(filename)
|