mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
997c76cf80
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.
185 lines
6.1 KiB
Ruby
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
|