1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionview/lib/action_view/dependency_tracker.rb
John Hawthorn 997c76cf80 Resolve wildcards depends using all_template_paths
This switches wildcard dependencies from using its custom
find_all_with_query method to use all_template_paths, the same method
now used for DidYouMean template suggestions.

This also removes related cache for wildcard queries. Since it was only
used during digesting, the digest cache should be sufficient.
2021-04-13 15:04:17 -07:00

185 lines
6.1 KiB
Ruby

# frozen_string_literal: true
require "concurrent/map"
require "action_view/path_set"
module ActionView
class DependencyTracker # :nodoc:
@trackers = Concurrent::Map.new
def self.find_dependencies(name, template, view_paths = nil)
tracker = @trackers[template.handler]
return [] unless tracker
tracker.call(name, template, view_paths)
end
def self.register_tracker(extension, tracker)
handler = Template.handler_for_extension(extension)
if tracker.respond_to?(:supports_view_paths?)
@trackers[handler] = tracker
else
@trackers[handler] = lambda { |name, template, _|
tracker.call(name, template)
}
end
end
def self.remove_tracker(handler)
@trackers.delete(handler)
end
class ERBTracker # :nodoc:
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
# A valid ruby identifier - suitable for class, method and specially variable names
IDENTIFIER = /
[[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
[[:word:]]* # followed by optional letters, numbers or underscores
/x
# Any kind of variable name. e.g. @instance, @@class, $global or local.
# Possibly following a method call chain
VARIABLE_OR_METHOD_CHAIN = /
(?:\$|@{1,2})? # optional global, instance or class variable indicator
(?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
(?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
/x
# A simple string literal. e.g. "School's out!"
STRING = /
(?<quote>['"]) # an opening quote
(?<static>.*?) # with anything inside, captured as STATIC
\k<quote> # and a matching closing quote
/x
# Part of any hash containing the :partial key
PARTIAL_HASH_KEY = /
(?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
\s* # followed by optional spaces
/x
# Part of any hash containing the :layout key
LAYOUT_HASH_KEY = /
(?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
\s* # followed by optional spaces
/x
# Matches:
# partial: "comments/comment", collection: @all_comments => "comments/comment"
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
#
# "comments/comments"
# 'comments/comments'
# ('comments/comments')
#
# (@topic) => "topics/topic"
# topics => "topics/topic"
# (message.topics) => "topics/topic"
RENDER_ARGUMENTS = /\A
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
(?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
LAYOUT_DEPENDENCY = /\A
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
(?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
def self.supports_view_paths? # :nodoc:
true
end
def self.call(name, template, view_paths = nil)
new(name, template, view_paths).dependencies
end
def initialize(name, template, view_paths = nil)
@name, @template, @view_paths = name, template, view_paths
end
def dependencies
render_dependencies + explicit_dependencies
end
attr_reader :name, :template
private :name, :template
private
def source
template.source
end
def directory
name.split("/")[0..-2].join("/")
end
def render_dependencies
render_dependencies = []
render_calls = source.split(/\brender\b/).drop(1)
render_calls.each do |arguments|
add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
end
render_dependencies.uniq
end
def add_dependencies(render_dependencies, arguments, pattern)
arguments.scan(pattern) do
match = Regexp.last_match
add_dynamic_dependency(render_dependencies, match[:dynamic])
add_static_dependency(render_dependencies, match[:static], match[:quote])
end
end
def add_dynamic_dependency(dependencies, dependency)
if dependency
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
end
end
def add_static_dependency(dependencies, dependency, quote_type)
if quote_type == '"'
# Ignore if there is interpolation
return if dependency.include?('#{')
end
if dependency
if dependency.include?("/")
dependencies << dependency
else
dependencies << "#{directory}/#{dependency}"
end
end
end
def resolve_directories(wildcard_dependencies)
return [] unless @view_paths
return [] if wildcard_dependencies.empty?
# Remove trailing "*"
prefixes = wildcard_dependencies.map { |query| query[0..-2] }
@view_paths.flat_map(&:all_template_paths).uniq.select { |path|
prefixes.any? do |prefix|
path.start_with?(prefix) && !path.index("/", prefix.size)
end
}.sort
end
def explicit_dependencies
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("*") }
(explicits + resolve_directories(wildcards)).uniq
end
end
register_tracker :erb, ERBTracker
end
end