Merge pull request #10298 from SeekingMeaning/ast
utils/ast: move helper functions from `FormulaAST` to `AST`
This commit is contained in:
commit
82901036f9
@ -574,7 +574,7 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
return [] unless args.keep_old?
|
return [] unless args.keep_old?
|
||||||
|
|
||||||
old_keys = Utils::AST::FormulaAST.body_children(bottle_node.body).map(&:method_name)
|
old_keys = Utils::AST.body_children(bottle_node.body).map(&:method_name)
|
||||||
old_bottle_spec = formula.bottle_specification
|
old_bottle_spec = formula.bottle_specification
|
||||||
mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"])
|
mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"])
|
||||||
if mismatches.present?
|
if mismatches.present?
|
||||||
|
|||||||
50
Library/Homebrew/test/utils/ast/ast_spec.rb
Normal file
50
Library/Homebrew/test/utils/ast/ast_spec.rb
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# typed: false
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "utils/ast"
|
||||||
|
|
||||||
|
describe Utils::AST do
|
||||||
|
describe ".stanza_text" do
|
||||||
|
let(:compound_license) do
|
||||||
|
<<~RUBY.chomp
|
||||||
|
license all_of: [
|
||||||
|
:public_domain,
|
||||||
|
"MIT",
|
||||||
|
"GPL-3.0-or-later" => { with: "Autoconf-exception-3.0" },
|
||||||
|
]
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts existing stanza text" do
|
||||||
|
expect(described_class.stanza_text(:revision, "revision 1")).to eq("revision 1")
|
||||||
|
expect(described_class.stanza_text(:license, "license :public_domain")).to eq("license :public_domain")
|
||||||
|
expect(described_class.stanza_text(:license, 'license "MIT"')).to eq('license "MIT"')
|
||||||
|
expect(described_class.stanza_text(:license, compound_license)).to eq(compound_license)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts a number as the stanza value" do
|
||||||
|
expect(described_class.stanza_text(:revision, 1)).to eq("revision 1")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts a symbol as the stanza value" do
|
||||||
|
expect(described_class.stanza_text(:license, :public_domain)).to eq("license :public_domain")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts a string as the stanza value" do
|
||||||
|
expect(described_class.stanza_text(:license, "MIT")).to eq('license "MIT"')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds indent to stanza text if specified" do
|
||||||
|
expect(described_class.stanza_text(:revision, "revision 1", indent: 2)).to eq(" revision 1")
|
||||||
|
expect(described_class.stanza_text(:license, 'license "MIT"', indent: 2)).to eq(' license "MIT"')
|
||||||
|
expect(described_class.stanza_text(:license, compound_license, indent: 2)).to eq(compound_license.indent(2))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not add indent if already indented" do
|
||||||
|
expect(described_class.stanza_text(:revision, " revision 1", indent: 2)).to eq(" revision 1")
|
||||||
|
expect(
|
||||||
|
described_class.stanza_text(:license, compound_license.indent(2), indent: 2),
|
||||||
|
).to eq(compound_license.indent(2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -46,50 +46,6 @@ describe Utils::AST::FormulaAST do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".stanza_text" do
|
|
||||||
let(:compound_license) do
|
|
||||||
<<~RUBY.chomp
|
|
||||||
license all_of: [
|
|
||||||
:public_domain,
|
|
||||||
"MIT",
|
|
||||||
"GPL-3.0-or-later" => { with: "Autoconf-exception-3.0" },
|
|
||||||
]
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts existing stanza text" do
|
|
||||||
expect(described_class.stanza_text(:revision, "revision 1")).to eq("revision 1")
|
|
||||||
expect(described_class.stanza_text(:license, "license :public_domain")).to eq("license :public_domain")
|
|
||||||
expect(described_class.stanza_text(:license, 'license "MIT"')).to eq('license "MIT"')
|
|
||||||
expect(described_class.stanza_text(:license, compound_license)).to eq(compound_license)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts a number as the stanza value" do
|
|
||||||
expect(described_class.stanza_text(:revision, 1)).to eq("revision 1")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts a symbol as the stanza value" do
|
|
||||||
expect(described_class.stanza_text(:license, :public_domain)).to eq("license :public_domain")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts a string as the stanza value" do
|
|
||||||
expect(described_class.stanza_text(:license, "MIT")).to eq('license "MIT"')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "adds indent to stanza text if specified" do
|
|
||||||
expect(described_class.stanza_text(:revision, "revision 1", indent: 2)).to eq(" revision 1")
|
|
||||||
expect(described_class.stanza_text(:license, 'license "MIT"', indent: 2)).to eq(' license "MIT"')
|
|
||||||
expect(described_class.stanza_text(:license, compound_license, indent: 2)).to eq(compound_license.indent(2))
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not add indent if already indented" do
|
|
||||||
expect(described_class.stanza_text(:revision, " revision 1", indent: 2)).to eq(" revision 1")
|
|
||||||
expect(
|
|
||||||
described_class.stanza_text(:license, compound_license.indent(2), indent: 2),
|
|
||||||
).to eq(compound_license.indent(2))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#add_bottle_block" do
|
describe "#add_bottle_block" do
|
||||||
let(:bottle_output) do
|
let(:bottle_output) do
|
||||||
<<~RUBY.chomp.indent(2)
|
<<~RUBY.chomp.indent(2)
|
||||||
@ -9,18 +9,79 @@ module Utils
|
|||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
module AST
|
module AST
|
||||||
|
extend T::Sig
|
||||||
|
|
||||||
Node = RuboCop::AST::Node
|
Node = RuboCop::AST::Node
|
||||||
SendNode = RuboCop::AST::SendNode
|
SendNode = RuboCop::AST::SendNode
|
||||||
BlockNode = RuboCop::AST::BlockNode
|
BlockNode = RuboCop::AST::BlockNode
|
||||||
ProcessedSource = RuboCop::AST::ProcessedSource
|
ProcessedSource = RuboCop::AST::ProcessedSource
|
||||||
TreeRewriter = Parser::Source::TreeRewriter
|
TreeRewriter = Parser::Source::TreeRewriter
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
sig { params(body_node: Node).returns(T::Array[Node]) }
|
||||||
|
def body_children(body_node)
|
||||||
|
if body_node.blank?
|
||||||
|
[]
|
||||||
|
elsif body_node.begin_type?
|
||||||
|
body_node.children.compact
|
||||||
|
else
|
||||||
|
[body_node]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), indent: T.nilable(Integer)).returns(String) }
|
||||||
|
def stanza_text(name, value, indent: nil)
|
||||||
|
text = if value.is_a?(String)
|
||||||
|
_, node = process_source(value)
|
||||||
|
value if (node.is_a?(SendNode) || node.is_a?(BlockNode)) && node.method_name == name
|
||||||
|
end
|
||||||
|
text ||= "#{name} #{value.inspect}"
|
||||||
|
text = text.indent(indent) if indent && !text.match?(/\A\n* +/)
|
||||||
|
text
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(source: String).returns([ProcessedSource, Node]) }
|
||||||
|
def process_source(source)
|
||||||
|
ruby_version = Version.new(HOMEBREW_REQUIRED_RUBY_VERSION).major_minor.to_f
|
||||||
|
processed_source = ProcessedSource.new(source, ruby_version)
|
||||||
|
root_node = processed_source.ast
|
||||||
|
[processed_source, root_node]
|
||||||
|
end
|
||||||
|
|
||||||
|
sig do
|
||||||
|
params(
|
||||||
|
component_name: Symbol,
|
||||||
|
component_type: Symbol,
|
||||||
|
target_name: Symbol,
|
||||||
|
target_type: T.nilable(Symbol),
|
||||||
|
).returns(T::Boolean)
|
||||||
|
end
|
||||||
|
def component_match?(component_name:, component_type:, target_name:, target_type: nil)
|
||||||
|
component_name == target_name && (target_type.nil? || component_type == target_type)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(node: Node, name: Symbol, type: T.nilable(Symbol)).returns(T::Boolean) }
|
||||||
|
def call_node_match?(node, name:, type: nil)
|
||||||
|
node_type = case node
|
||||||
|
when SendNode then :method_call
|
||||||
|
when BlockNode then :block_call
|
||||||
|
else return false
|
||||||
|
end
|
||||||
|
|
||||||
|
component_match?(component_name: node.method_name,
|
||||||
|
component_type: node_type,
|
||||||
|
target_name: name,
|
||||||
|
target_type: type)
|
||||||
|
end
|
||||||
|
|
||||||
# Helper class for editing formulae.
|
# Helper class for editing formulae.
|
||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
class FormulaAST
|
class FormulaAST
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
include AST
|
||||||
|
|
||||||
delegate process: :tree_rewriter
|
delegate process: :tree_rewriter
|
||||||
|
|
||||||
@ -33,17 +94,6 @@ module Utils
|
|||||||
@tree_rewriter = T.let(TreeRewriter.new(processed_source.buffer), TreeRewriter)
|
@tree_rewriter = T.let(TreeRewriter.new(processed_source.buffer), TreeRewriter)
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { params(body_node: Node).returns(T::Array[Node]) }
|
|
||||||
def self.body_children(body_node)
|
|
||||||
if body_node.nil?
|
|
||||||
[]
|
|
||||||
elsif body_node.begin_type?
|
|
||||||
body_node.children.compact
|
|
||||||
else
|
|
||||||
[body_node]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { returns(T.nilable(Node)) }
|
sig { returns(T.nilable(Node)) }
|
||||||
def bottle_block
|
def bottle_block
|
||||||
stanza(:bottle, type: :block_call)
|
stanza(:bottle, type: :block_call)
|
||||||
@ -66,10 +116,10 @@ module Utils
|
|||||||
|
|
||||||
sig { params(name: Symbol, replacement: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void }
|
sig { params(name: Symbol, replacement: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void }
|
||||||
def replace_stanza(name, replacement, type: nil)
|
def replace_stanza(name, replacement, type: nil)
|
||||||
stanza_node = children.find { |child| call_node_match?(child, name: name, type: type) }
|
stanza_node = stanza(name, type: type)
|
||||||
raise "Could not find #{name} stanza!" if stanza_node.nil?
|
raise "Could not find `#{name}` stanza!" if stanza_node.blank?
|
||||||
|
|
||||||
tree_rewriter.replace(stanza_node.source_range, self.class.stanza_text(name, replacement, indent: 2).lstrip)
|
tree_rewriter.replace(stanza_node.source_range, stanza_text(name, replacement, indent: 2).lstrip)
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void }
|
sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void }
|
||||||
@ -104,26 +154,7 @@ module Utils
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
tree_rewriter.insert_after(preceding_expr, "\n#{self.class.stanza_text(name, value, indent: 2)}")
|
tree_rewriter.insert_after(preceding_expr, "\n#{stanza_text(name, value, indent: 2)}")
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), indent: T.nilable(Integer)).returns(String) }
|
|
||||||
def self.stanza_text(name, value, indent: nil)
|
|
||||||
text = if value.is_a?(String)
|
|
||||||
_, node = process_source(value)
|
|
||||||
value if (node.is_a?(SendNode) || node.is_a?(BlockNode)) && node.method_name == name
|
|
||||||
end
|
|
||||||
text ||= "#{name} #{value.inspect}"
|
|
||||||
text = text.indent(indent) if indent && !text.match?(/\A\n* +/)
|
|
||||||
text
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(source: String).returns([ProcessedSource, Node]) }
|
|
||||||
def self.process_source(source)
|
|
||||||
ruby_version = Version.new(HOMEBREW_REQUIRED_RUBY_VERSION).major_minor.to_f
|
|
||||||
processed_source = ProcessedSource.new(source, ruby_version)
|
|
||||||
root_node = processed_source.ast
|
|
||||||
[processed_source, root_node]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -142,7 +173,7 @@ module Utils
|
|||||||
|
|
||||||
sig { returns([ProcessedSource, T::Array[Node]]) }
|
sig { returns([ProcessedSource, T::Array[Node]]) }
|
||||||
def process_formula
|
def process_formula
|
||||||
processed_source, root_node = self.class.process_source(formula_contents)
|
processed_source, root_node = process_source(formula_contents)
|
||||||
|
|
||||||
class_node = root_node if root_node.class_type?
|
class_node = root_node if root_node.class_type?
|
||||||
if root_node.begin_type?
|
if root_node.begin_type?
|
||||||
@ -156,7 +187,7 @@ module Utils
|
|||||||
|
|
||||||
raise "Could not find formula class!" if class_node.nil?
|
raise "Could not find formula class!" if class_node.nil?
|
||||||
|
|
||||||
children = self.class.body_children(class_node.body)
|
children = body_children(class_node.body)
|
||||||
raise "Formula class is empty!" if children.empty?
|
raise "Formula class is empty!" if children.empty?
|
||||||
|
|
||||||
[processed_source, children]
|
[processed_source, children]
|
||||||
@ -178,32 +209,6 @@ module Utils
|
|||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
sig do
|
|
||||||
params(
|
|
||||||
component_name: Symbol,
|
|
||||||
component_type: Symbol,
|
|
||||||
target_name: Symbol,
|
|
||||||
target_type: T.nilable(Symbol),
|
|
||||||
).returns(T::Boolean)
|
|
||||||
end
|
|
||||||
def component_match?(component_name:, component_type:, target_name:, target_type: nil)
|
|
||||||
component_name == target_name && (target_type.nil? || component_type == target_type)
|
|
||||||
end
|
|
||||||
|
|
||||||
sig { params(node: Node, name: Symbol, type: T.nilable(Symbol)).returns(T::Boolean) }
|
|
||||||
def call_node_match?(node, name:, type: nil)
|
|
||||||
node_type = case node
|
|
||||||
when SendNode then :method_call
|
|
||||||
when BlockNode then :block_call
|
|
||||||
else return false
|
|
||||||
end
|
|
||||||
|
|
||||||
component_match?(component_name: node.method_name,
|
|
||||||
component_type: node_type,
|
|
||||||
target_name: name,
|
|
||||||
target_type: type)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user