Skip to content

Commit

Permalink
Merge pull request rails#54050 from skipkayhil/hm-rail-inspector-prism
Browse files Browse the repository at this point in the history
Replace SyntaxTree with Prism in `rail_inspector`
  • Loading branch information
byroot authored Dec 29, 2024
2 parents db4451e + 5a9ef5c commit 2eec91b
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 178 deletions.
4 changes: 0 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ gem "uri", ">= 0.13.1", require: false

gem "prism"

group :lint do
gem "syntax_tree", "6.1.1", require: false
end

group :rubocop do
gem "rubocop", ">= 1.25.1", require: false
gem "rubocop-minitest", require: false
Expand Down
4 changes: 0 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,6 @@ GEM
racc
path_expander (1.1.3)
pg (1.5.8)
prettier_print (1.2.1)
prism (1.2.0)
propshaft (1.1.0)
actionpack (>= 7.0.0)
Expand Down Expand Up @@ -608,8 +607,6 @@ GEM
stringio (3.1.1)
sucker_punch (3.2.0)
concurrent-ruby (~> 1.0)
syntax_tree (6.1.1)
prettier_print (>= 1.2.0)
tailwindcss-rails (3.0.0)
railties (>= 7.0.0)
tailwindcss-ruby
Expand Down Expand Up @@ -737,7 +734,6 @@ DEPENDENCIES
stackprof
stimulus-rails
sucker_punch
syntax_tree (= 6.1.1)
tailwindcss-rails
terser (>= 1.1.4)
thruster
Expand Down
2 changes: 1 addition & 1 deletion guides/source/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Below are the default values associated with each target version. In cases of co

- [`config.active_record.postgresql_adapter_decode_dates`](#config-active-record-postgresql-adapter-decode-dates): `true`
- [`config.active_record.validate_migration_timestamps`](#config-active-record-validate-migration-timestamps): `true`
- [`config.active_storage.web_image_content_types`](#config-active-storage-web-image-content-types): `%w[image/png image/jpeg image/gif image/webp]`
- [`config.active_storage.web_image_content_types`](#config-active-storage-web-image-content-types): `%w( image/png image/jpeg image/gif image/webp )`
- [`config.yjit`](#config-yjit): `true`

#### Default Values for Target Version 7.1
Expand Down
2 changes: 1 addition & 1 deletion tools/rail_inspector/lib/rail_inspector/configuring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def initialize
end

def call(path)
@cache[path] ||= SyntaxTree.parse(SyntaxTree.read(path))
@cache[path] ||= Prism.parse_file(path.to_s).value
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(checker)
def call
visitor = Visitor::Attribute.new
visitor.visit(app_config_tree)
visitor.attribute_map[APP_CONFIG_CONST]["attr_accessor"]
visitor.attribute_map[APP_CONFIG_CONST][:attr_accessor]
end

private
Expand Down
19 changes: 9 additions & 10 deletions tools/rail_inspector/lib/rail_inspector/visitor/attribute.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# frozen_string_literal: true

require "syntax_tree"
require "prism"

module RailInspector
module Visitor
class Attribute < SyntaxTree::Visitor
class Attribute < Prism::Visitor
attr_reader :attribute_map

def initialize
Expand All @@ -13,31 +13,30 @@ def initialize
end

def with_namespace(node)
@namespace_stack << node.constant.constant.value
@namespace_stack << node.constant_path.name
visit_child_nodes(node)
@namespace_stack.pop
end

visit_method alias_method :visit_module, :with_namespace
alias visit_class_node with_namespace
alias visit_module_node with_namespace

visit_method alias_method :visit_class, :with_namespace

visit_method def visit_command(node)
attr_access = node.message.value
def visit_call_node(node)
attr_access = node.name
return unless ATTRIBUTE_METHODS.include?(attr_access)

full_namespace = @namespace_stack.join("::")

@attribute_map[full_namespace] ||= {}
@attribute_map[full_namespace][attr_access] ||= Set.new

attributes = node.arguments.parts.map { |p| p.value.value }
attributes = node.arguments.arguments.map { |p| p.value }

@attribute_map[full_namespace][attr_access].merge(attributes)
end

private
ATTRIBUTE_METHODS = %w[attr_accessor attr_reader attr_writer]
ATTRIBUTE_METHODS = %i[attr_accessor attr_reader attr_writer]
end
end
end
166 changes: 79 additions & 87 deletions tools/rail_inspector/lib/rail_inspector/visitor/framework_default.rb
Original file line number Diff line number Diff line change
@@ -1,137 +1,129 @@
# frozen_string_literal: true

require "syntax_tree"
require "prism"

require_relative "./hash_to_string"
require_relative "./multiline_to_string"

module RailInspector
module Visitor
class FrameworkDefault
TargetVersionCaseFinder =
SyntaxTree::Search.new(
->(node) do
node in SyntaxTree::Case[
value: SyntaxTree::CallNode[
receiver: SyntaxTree::VarRef[
value: SyntaxTree::Ident[value: "target_version"]
]
]
]
end
)

attr_reader :config_map

def initialize
@config_map = {}
end

def visit(node)
case_node, *others = TargetVersionCaseFinder.scan(node).to_a
raise "#{others.length} other cases?" unless others.empty?
target_version_case = node.breadth_first_search do |n|
n in Prism::CaseNode[
predicate: Prism::CallNode[receiver: Prism::LocalVariableReadNode[name: :target_version]]
]
end

visit_when(case_node.consequent)
target_version_case.conditions.each { |cond| visit_when(cond) }
end

private
def visit_when(node)
version = node.arguments.parts[0].parts[0].value

config_map[version] = VersionedConfig.new.config_for(node.statements)
version = node.conditions[0].unescaped

visit_when(node.consequent) if node.consequent.is_a? SyntaxTree::When
config_map[version] = VersionedConfig.new.tap { |v| v.visit(node.statements) }.configs
end

class VersionedConfig < SyntaxTree::Visitor
class VersionedConfig < Prism::Visitor
attr_reader :configs

def initialize
@configs = {}
@framework_stack = []
end

def config_for(node)
visit(node)
@configs
end
def visit_if_node(node)
unless new_framework = respond_to_framework?(node.predicate)
return visit_child_nodes(node)
end

visit_methods do
def visit_if(node)
unless new_framework = respond_to_framework?(node.predicate)
return super
end
if ENV["STRICT"] && current_framework
raise "Potentially nested framework? Current: '#{current_framework}', found: '#{new_framework}'"
end

if ENV["STRICT"] && current_framework
raise "Potentially nested framework? Current: '#{current_framework}', found: '#{new_framework}'"
end
@framework_stack << new_framework
visit_child_nodes(node)
@framework_stack.pop
end

def visit_call_node(node)
name = node.name.to_s

@framework_stack << new_framework
super
@framework_stack.pop
unless name.end_with? "="
return super
end

def visit_assign(node)
assert_framework(node)

target = SyntaxTree::Formatter.format(nil, node.target)
value =
case node.value
when SyntaxTree::HashLiteral
HashToString.new.tap { |v| v.visit(node.value) }.to_s
when SyntaxTree::StringConcat
MultilineToString.new.tap { |v| v.visit(node.value) }.to_s
else
SyntaxTree::Formatter.format(nil, node.value)
end
@configs[target] = value
handle_assignment(node, name[...-1], node.arguments.arguments[0])
end

def visit_call_or_write_node(node)
name = node.write_name.to_s

unless name.end_with? "="
return super
end

def visit_opassign(node)
if node.operator.name == :"||="
visit_assign(node)
else
super
handle_assignment(node, node.read_name.to_s, node.value)
end

def handle_assignment(node, name, value)
prefix = case node.receiver
in Prism::ConstantReadNode[name: constant_name]
constant_name
in Prism::SelfNode
"self"
in Prism::CallNode[receiver: nil, name: framework]
framework_string = framework.to_s

unless current_framework == framework_string
raise "expected: #{current_framework}, actual: #{framework_string}"
end

framework_string
else
node.receiver.location.slice
end
end

private
def assert_framework(node)
framework =
case node.target.parent
in { value: SyntaxTree::Const } |
{ value: SyntaxTree::Kw[value: "self"] }
nil
in receiver: { value: { value: framework } }
framework
in value: { value: framework }
framework
end

return if current_framework == framework

raise "Expected #{current_framework} to match #{framework}"
target = "#{prefix}.#{name}"

string_value = case value
in Prism::ConstantPathNode
value.full_name
in Prism::HashNode
HashToString.new.tap { |v| v.visit(value) }.to_s
in Prism::InterpolatedStringNode
"\"#{value.parts.map(&:content).join("")}\""
in Prism::FalseNode
"false"
in Prism::TrueNode
"true"
else
value.location.slice
end

@configs[target] = string_value
end

private
def current_framework
@framework_stack.last
end

def respond_to_framework?(node)
if node in SyntaxTree::CallNode[
receiver: nil,
message: SyntaxTree::Ident[value: "respond_to?"],
arguments: SyntaxTree::ArgParen[
arguments: SyntaxTree::Args[
parts: [
SyntaxTree::SymbolLiteral[
value: SyntaxTree::Ident[value: new_framework]
]
]
]
]
]
if node in Prism::CallNode[
name: :respond_to?,
arguments: Prism::ArgumentsNode[
arguments: [
Prism::SymbolNode[unescaped: new_framework]
]
]
]
new_framework
end
end
Expand Down
Loading

0 comments on commit 2eec91b

Please sign in to comment.