actionview 6.1.3.2 → 7.0.0.alpha2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -262
  3. data/MIT-LICENSE +1 -1
  4. data/lib/action_view/base.rb +3 -3
  5. data/lib/action_view/buffers.rb +2 -2
  6. data/lib/action_view/cache_expiry.rb +46 -32
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  8. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  9. data/lib/action_view/dependency_tracker.rb +6 -147
  10. data/lib/action_view/digestor.rb +7 -4
  11. data/lib/action_view/flows.rb +4 -4
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +85 -30
  15. data/lib/action_view/helpers/asset_url_helper.rb +7 -7
  16. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  17. data/lib/action_view/helpers/cache_helper.rb +51 -3
  18. data/lib/action_view/helpers/capture_helper.rb +2 -2
  19. data/lib/action_view/helpers/controller_helper.rb +2 -2
  20. data/lib/action_view/helpers/csp_helper.rb +1 -1
  21. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  22. data/lib/action_view/helpers/date_helper.rb +5 -5
  23. data/lib/action_view/helpers/debug_helper.rb +3 -1
  24. data/lib/action_view/helpers/form_helper.rb +72 -12
  25. data/lib/action_view/helpers/form_options_helper.rb +65 -33
  26. data/lib/action_view/helpers/form_tag_helper.rb +73 -30
  27. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  28. data/lib/action_view/helpers/number_helper.rb +3 -4
  29. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  30. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  31. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  32. data/lib/action_view/helpers/tag_helper.rb +17 -4
  33. data/lib/action_view/helpers/tags/base.rb +2 -14
  34. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  35. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  36. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  37. data/lib/action_view/helpers/tags/weekday_select.rb +27 -0
  38. data/lib/action_view/helpers/tags.rb +3 -2
  39. data/lib/action_view/helpers/text_helper.rb +24 -13
  40. data/lib/action_view/helpers/translation_helper.rb +4 -3
  41. data/lib/action_view/helpers/url_helper.rb +122 -80
  42. data/lib/action_view/helpers.rb +25 -25
  43. data/lib/action_view/lookup_context.rb +33 -52
  44. data/lib/action_view/model_naming.rb +1 -1
  45. data/lib/action_view/path_set.rb +16 -22
  46. data/lib/action_view/railtie.rb +15 -2
  47. data/lib/action_view/render_parser.rb +188 -0
  48. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  49. data/lib/action_view/renderer/partial_renderer.rb +0 -34
  50. data/lib/action_view/renderer/renderer.rb +4 -4
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  52. data/lib/action_view/renderer/template_renderer.rb +6 -2
  53. data/lib/action_view/rendering.rb +2 -2
  54. data/lib/action_view/ripper_ast_parser.rb +198 -0
  55. data/lib/action_view/routing_url_for.rb +1 -1
  56. data/lib/action_view/template/error.rb +108 -13
  57. data/lib/action_view/template/handlers/erb.rb +6 -0
  58. data/lib/action_view/template/handlers.rb +3 -3
  59. data/lib/action_view/template/html.rb +3 -3
  60. data/lib/action_view/template/inline.rb +3 -3
  61. data/lib/action_view/template/raw_file.rb +3 -3
  62. data/lib/action_view/template/resolver.rb +84 -311
  63. data/lib/action_view/template/text.rb +3 -3
  64. data/lib/action_view/template/types.rb +14 -12
  65. data/lib/action_view/template.rb +10 -1
  66. data/lib/action_view/template_details.rb +66 -0
  67. data/lib/action_view/template_path.rb +64 -0
  68. data/lib/action_view/test_case.rb +6 -2
  69. data/lib/action_view/testing/resolvers.rb +11 -12
  70. data/lib/action_view/unbound_template.rb +33 -7
  71. data/lib/action_view.rb +3 -4
  72. data/lib/assets/compiled/rails-ujs.js +2 -2
  73. metadata +25 -18
@@ -18,7 +18,7 @@ module ActionView
18
18
  # sbuf << 5
19
19
  # puts sbuf # => "hello\u0005"
20
20
  #
21
- class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
21
+ class OutputBuffer < ActiveSupport::SafeBuffer # :nodoc:
22
22
  def initialize(*)
23
23
  super
24
24
  encode!
@@ -38,7 +38,7 @@ module ActionView
38
38
  alias :safe_append= :safe_concat
39
39
  end
40
40
 
41
- class StreamingBuffer #:nodoc:
41
+ class StreamingBuffer # :nodoc:
42
42
  def initialize(block)
43
43
  @block = block
44
44
  end
@@ -4,49 +4,63 @@ module ActionView
4
4
  class CacheExpiry
5
5
  class Executor
6
6
  def initialize(watcher:)
7
- @cache_expiry = CacheExpiry.new(watcher: watcher)
7
+ @execution_lock = Concurrent::ReadWriteLock.new
8
+ @cache_expiry = ViewModificationWatcher.new(watcher: watcher) do
9
+ clear_cache
10
+ end
8
11
  end
9
12
 
10
- def before(target)
11
- @cache_expiry.clear_cache_if_necessary
13
+ def run
14
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
15
+ @cache_expiry.execute_if_updated
16
+ @execution_lock.acquire_read_lock
17
+ end
12
18
  end
13
- end
14
-
15
- def initialize(watcher:)
16
- @watched_dirs = nil
17
- @watcher_class = watcher
18
- @watcher = nil
19
- @mutex = Mutex.new
20
- end
21
19
 
22
- def clear_cache_if_necessary
23
- @mutex.synchronize do
24
- watched_dirs = dirs_to_watch
25
- return if watched_dirs.empty?
20
+ def complete(_)
21
+ @execution_lock.release_read_lock
22
+ end
26
23
 
27
- if watched_dirs != @watched_dirs
28
- @watched_dirs = watched_dirs
29
- @watcher = @watcher_class.new([], watched_dirs) do
30
- clear_cache
24
+ private
25
+ def clear_cache
26
+ @execution_lock.with_write_lock do
27
+ ActionView::LookupContext::DetailsKey.clear
31
28
  end
32
- @watcher.execute
33
- else
34
- @watcher.execute_if_updated
35
29
  end
36
- end
37
- end
38
-
39
- def clear_cache
40
- ActionView::LookupContext::DetailsKey.clear
41
30
  end
42
31
 
43
- private
44
- def dirs_to_watch
45
- all_view_paths.grep(FileSystemResolver).map!(&:path).tap(&:uniq!).sort!
32
+ class ViewModificationWatcher
33
+ def initialize(watcher:, &block)
34
+ @watched_dirs = nil
35
+ @watcher_class = watcher
36
+ @watcher = nil
37
+ @mutex = Mutex.new
38
+ @block = block
46
39
  end
47
40
 
48
- def all_view_paths
49
- ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
41
+ def execute_if_updated
42
+ @mutex.synchronize do
43
+ watched_dirs = dirs_to_watch
44
+ return if watched_dirs.empty?
45
+
46
+ if watched_dirs != @watched_dirs
47
+ @watched_dirs = watched_dirs
48
+ @watcher = @watcher_class.new([], watched_dirs, &@block)
49
+ @watcher.execute
50
+ else
51
+ @watcher.execute_if_updated
52
+ end
53
+ end
50
54
  end
55
+
56
+ private
57
+ def dirs_to_watch
58
+ all_view_paths.grep(FileSystemResolver).map!(&:path).tap(&:uniq!).sort!
59
+ end
60
+
61
+ def all_view_paths
62
+ ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
63
+ end
64
+ end
51
65
  end
52
66
  end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class ERBTracker # :nodoc:
6
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
7
+
8
+ # A valid ruby identifier - suitable for class, method and specially variable names
9
+ IDENTIFIER = /
10
+ [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
11
+ [[:word:]]* # followed by optional letters, numbers or underscores
12
+ /x
13
+
14
+ # Any kind of variable name. e.g. @instance, @@class, $global or local.
15
+ # Possibly following a method call chain
16
+ VARIABLE_OR_METHOD_CHAIN = /
17
+ (?:\$|@{1,2})? # optional global, instance or class variable indicator
18
+ (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
19
+ (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
20
+ /x
21
+
22
+ # A simple string literal. e.g. "School's out!"
23
+ STRING = /
24
+ (?<quote>['"]) # an opening quote
25
+ (?<static>.*?) # with anything inside, captured as STATIC
26
+ \k<quote> # and a matching closing quote
27
+ /x
28
+
29
+ # Part of any hash containing the :partial key
30
+ PARTIAL_HASH_KEY = /
31
+ (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
32
+ \s* # followed by optional spaces
33
+ /x
34
+
35
+ # Part of any hash containing the :layout key
36
+ LAYOUT_HASH_KEY = /
37
+ (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
38
+ \s* # followed by optional spaces
39
+ /x
40
+
41
+ # Matches:
42
+ # partial: "comments/comment", collection: @all_comments => "comments/comment"
43
+ # (object: @single_comment, partial: "comments/comment") => "comments/comment"
44
+ #
45
+ # "comments/comments"
46
+ # 'comments/comments'
47
+ # ('comments/comments')
48
+ #
49
+ # (@topic) => "topics/topic"
50
+ # topics => "topics/topic"
51
+ # (message.topics) => "topics/topic"
52
+ RENDER_ARGUMENTS = /\A
53
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
54
+ (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
55
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
56
+ /xm
57
+
58
+ LAYOUT_DEPENDENCY = /\A
59
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
60
+ (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
61
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
62
+ /xm
63
+
64
+ def self.supports_view_paths? # :nodoc:
65
+ true
66
+ end
67
+
68
+ def self.call(name, template, view_paths = nil)
69
+ new(name, template, view_paths).dependencies
70
+ end
71
+
72
+ def initialize(name, template, view_paths = nil)
73
+ @name, @template, @view_paths = name, template, view_paths
74
+ end
75
+
76
+ def dependencies
77
+ render_dependencies + explicit_dependencies
78
+ end
79
+
80
+ attr_reader :name, :template
81
+ private :name, :template
82
+
83
+ private
84
+ def source
85
+ template.source
86
+ end
87
+
88
+ def directory
89
+ name.split("/")[0..-2].join("/")
90
+ end
91
+
92
+ def render_dependencies
93
+ render_dependencies = []
94
+ render_calls = source.split(/\brender\b/).drop(1)
95
+
96
+ render_calls.each do |arguments|
97
+ add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
98
+ add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
99
+ end
100
+
101
+ render_dependencies.uniq
102
+ end
103
+
104
+ def add_dependencies(render_dependencies, arguments, pattern)
105
+ arguments.scan(pattern) do
106
+ match = Regexp.last_match
107
+ add_dynamic_dependency(render_dependencies, match[:dynamic])
108
+ add_static_dependency(render_dependencies, match[:static], match[:quote])
109
+ end
110
+ end
111
+
112
+ def add_dynamic_dependency(dependencies, dependency)
113
+ if dependency
114
+ dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
115
+ end
116
+ end
117
+
118
+ def add_static_dependency(dependencies, dependency, quote_type)
119
+ if quote_type == '"'
120
+ # Ignore if there is interpolation
121
+ return if dependency.include?('#{')
122
+ end
123
+
124
+ if dependency
125
+ if dependency.include?("/")
126
+ dependencies << dependency
127
+ else
128
+ dependencies << "#{directory}/#{dependency}"
129
+ end
130
+ end
131
+ end
132
+
133
+ def resolve_directories(wildcard_dependencies)
134
+ return [] unless @view_paths
135
+ return [] if wildcard_dependencies.empty?
136
+
137
+ # Remove trailing "/*"
138
+ prefixes = wildcard_dependencies.map { |query| query[0..-3] }
139
+
140
+ @view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
141
+ path.to_s if prefixes.include?(path.prefix)
142
+ }.sort
143
+ end
144
+
145
+ def explicit_dependencies
146
+ dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
147
+
148
+ wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
149
+
150
+ (explicits + resolve_directories(wildcards)).uniq
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class DependencyTracker # :nodoc:
5
+ class RipperTracker # :nodoc:
6
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
7
+
8
+ def self.call(name, template, view_paths = nil)
9
+ new(name, template, view_paths).dependencies
10
+ end
11
+
12
+ def dependencies
13
+ render_dependencies + explicit_dependencies
14
+ end
15
+
16
+ def self.supports_view_paths? # :nodoc:
17
+ true
18
+ end
19
+
20
+ def initialize(name, template, view_paths = nil)
21
+ @name, @template, @view_paths = name, template, view_paths
22
+ end
23
+
24
+ private
25
+ attr_reader :template, :name, :view_paths
26
+
27
+ def render_dependencies
28
+ return [] unless template.source.include?("render")
29
+
30
+ compiled_source = template.handler.call(template, template.source)
31
+
32
+ RenderParser.new(@name, compiled_source).render_calls.filter_map do |render_call|
33
+ next if render_call.end_with?("/_")
34
+ render_call.gsub(%r|/_|, "/")
35
+ end
36
+ end
37
+
38
+ def explicit_dependencies
39
+ dependencies = template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
40
+
41
+ wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
42
+
43
+ (explicits + resolve_directories(wildcards)).uniq
44
+ end
45
+
46
+ def resolve_directories(wildcard_dependencies)
47
+ return [] unless view_paths
48
+ return [] if wildcard_dependencies.empty?
49
+
50
+ # Remove trailing "/*"
51
+ prefixes = wildcard_dependencies.map { |query| query[0..-3] }
52
+
53
+ view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
54
+ path.to_s if prefixes.include?(path.prefix)
55
+ }.sort
56
+ end
57
+ end
58
+ end
59
+ end
@@ -2,9 +2,15 @@
2
2
 
3
3
  require "concurrent/map"
4
4
  require "action_view/path_set"
5
+ require "action_view/render_parser"
5
6
 
6
7
  module ActionView
7
8
  class DependencyTracker # :nodoc:
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :ERBTracker
12
+ autoload :RipperTracker
13
+
8
14
  @trackers = Concurrent::Map.new
9
15
 
10
16
  def self.find_dependencies(name, template, view_paths = nil)
@@ -29,153 +35,6 @@ module ActionView
29
35
  @trackers.delete(handler)
30
36
  end
31
37
 
32
- class ERBTracker # :nodoc:
33
- EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
34
-
35
- # A valid ruby identifier - suitable for class, method and specially variable names
36
- IDENTIFIER = /
37
- [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
38
- [[:word:]]* # followed by optional letters, numbers or underscores
39
- /x
40
-
41
- # Any kind of variable name. e.g. @instance, @@class, $global or local.
42
- # Possibly following a method call chain
43
- VARIABLE_OR_METHOD_CHAIN = /
44
- (?:\$|@{1,2})? # optional global, instance or class variable indicator
45
- (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
46
- (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
47
- /x
48
-
49
- # A simple string literal. e.g. "School's out!"
50
- STRING = /
51
- (?<quote>['"]) # an opening quote
52
- (?<static>.*?) # with anything inside, captured as STATIC
53
- \k<quote> # and a matching closing quote
54
- /x
55
-
56
- # Part of any hash containing the :partial key
57
- PARTIAL_HASH_KEY = /
58
- (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
59
- \s* # followed by optional spaces
60
- /x
61
-
62
- # Part of any hash containing the :layout key
63
- LAYOUT_HASH_KEY = /
64
- (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
65
- \s* # followed by optional spaces
66
- /x
67
-
68
- # Matches:
69
- # partial: "comments/comment", collection: @all_comments => "comments/comment"
70
- # (object: @single_comment, partial: "comments/comment") => "comments/comment"
71
- #
72
- # "comments/comments"
73
- # 'comments/comments'
74
- # ('comments/comments')
75
- #
76
- # (@topic) => "topics/topic"
77
- # topics => "topics/topic"
78
- # (message.topics) => "topics/topic"
79
- RENDER_ARGUMENTS = /\A
80
- (?:\s*\(?\s*) # optional opening paren surrounded by spaces
81
- (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
82
- (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
83
- /xm
84
-
85
- LAYOUT_DEPENDENCY = /\A
86
- (?:\s*\(?\s*) # optional opening paren surrounded by spaces
87
- (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
88
- (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
89
- /xm
90
-
91
- def self.supports_view_paths? # :nodoc:
92
- true
93
- end
94
-
95
- def self.call(name, template, view_paths = nil)
96
- new(name, template, view_paths).dependencies
97
- end
98
-
99
- def initialize(name, template, view_paths = nil)
100
- @name, @template, @view_paths = name, template, view_paths
101
- end
102
-
103
- def dependencies
104
- render_dependencies + explicit_dependencies
105
- end
106
-
107
- attr_reader :name, :template
108
- private :name, :template
109
-
110
- private
111
- def source
112
- template.source
113
- end
114
-
115
- def directory
116
- name.split("/")[0..-2].join("/")
117
- end
118
-
119
- def render_dependencies
120
- render_dependencies = []
121
- render_calls = source.split(/\brender\b/).drop(1)
122
-
123
- render_calls.each do |arguments|
124
- add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
125
- add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
126
- end
127
-
128
- render_dependencies.uniq
129
- end
130
-
131
- def add_dependencies(render_dependencies, arguments, pattern)
132
- arguments.scan(pattern) do
133
- match = Regexp.last_match
134
- add_dynamic_dependency(render_dependencies, match[:dynamic])
135
- add_static_dependency(render_dependencies, match[:static], match[:quote])
136
- end
137
- end
138
-
139
- def add_dynamic_dependency(dependencies, dependency)
140
- if dependency
141
- dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
142
- end
143
- end
144
-
145
- def add_static_dependency(dependencies, dependency, quote_type)
146
- if quote_type == '"'
147
- # Ignore if there is interpolation
148
- return if dependency.include?('#{')
149
- end
150
-
151
- if dependency
152
- if dependency.include?("/")
153
- dependencies << dependency
154
- else
155
- dependencies << "#{directory}/#{dependency}"
156
- end
157
- end
158
- end
159
-
160
- def resolve_directories(wildcard_dependencies)
161
- return [] unless @view_paths
162
-
163
- wildcard_dependencies.flat_map { |query, templates|
164
- @view_paths.find_all_with_query(query).map do |template|
165
- "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
166
- end
167
- }.sort
168
- end
169
-
170
- def explicit_dependencies
171
- dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
172
-
173
- wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("*") }
174
-
175
- (explicits + resolve_directories(wildcards)).uniq
176
- end
177
- end
178
-
179
38
  register_tracker :erb, ERBTracker
180
39
  end
181
40
  end