utils/ast: add FormulaAST class
This commit is contained in:
parent
d75e9c99b3
commit
2ebfb4221c
@ -493,22 +493,22 @@ module Homebrew
|
||||
require "utils/ast"
|
||||
|
||||
path = Pathname.new((HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]).to_s)
|
||||
checksums = old_checksums(path, bottle_hash, args: args)
|
||||
formula = Formulary.factory(path)
|
||||
formula_ast = Utils::AST::FormulaAST.new(path.read)
|
||||
checksums = old_checksums(formula, formula_ast, bottle_hash, args: args)
|
||||
update_or_add = checksums.nil? ? "add" : "update"
|
||||
|
||||
checksums&.each(&bottle.method(:sha256))
|
||||
output = bottle_output(bottle)
|
||||
puts output
|
||||
|
||||
Utils::Inreplace.inreplace(path) do |s|
|
||||
formula_contents = s.inreplace_string
|
||||
case update_or_add
|
||||
when "update"
|
||||
Utils::AST.replace_bottle_stanza!(formula_contents, output)
|
||||
when "add"
|
||||
Utils::AST.add_bottle_stanza!(formula_contents, output)
|
||||
end
|
||||
case update_or_add
|
||||
when "update"
|
||||
formula_ast.replace_bottle_block(output)
|
||||
when "add"
|
||||
formula_ast.add_bottle_block(output)
|
||||
end
|
||||
path.atomic_write(formula_ast.process)
|
||||
|
||||
unless args.no_commit?
|
||||
Utils::Git.set_name_email!
|
||||
@ -566,16 +566,16 @@ module Homebrew
|
||||
[mismatches, checksums]
|
||||
end
|
||||
|
||||
def old_checksums(formula_path, bottle_hash, args:)
|
||||
bottle_node = Utils::AST.bottle_block(formula_path.read)
|
||||
def old_checksums(formula, formula_ast, bottle_hash, args:)
|
||||
bottle_node = formula_ast.bottle_block
|
||||
if bottle_node.nil?
|
||||
odie "--keep-old was passed but there was no existing bottle block!" if args.keep_old?
|
||||
return
|
||||
end
|
||||
return [] unless args.keep_old?
|
||||
|
||||
old_keys = Utils::AST.body_children(bottle_node.body).map(&:method_name)
|
||||
old_bottle_spec = Formulary.factory(formula_path).bottle_specification
|
||||
old_keys = Utils::AST::FormulaAST.body_children(bottle_node.body).map(&:method_name)
|
||||
old_bottle_spec = formula.bottle_specification
|
||||
mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"])
|
||||
if mismatches.present?
|
||||
odie <<~EOS
|
||||
|
||||
@ -52,14 +52,13 @@ module Homebrew
|
||||
Homebrew.install_bundler_gems!
|
||||
require "utils/ast"
|
||||
|
||||
Utils::Inreplace.inreplace(formula.path) do |s|
|
||||
s = s.inreplace_string
|
||||
if current_revision.zero?
|
||||
Utils::AST.add_formula_stanza!(s, :revision, new_revision)
|
||||
else
|
||||
Utils::AST.replace_formula_stanza!(s, :revision, new_revision)
|
||||
end
|
||||
formula_ast = Utils::AST::FormulaAST.new(formula.path.read)
|
||||
if current_revision.zero?
|
||||
formula_ast.add_stanza(:revision, new_revision)
|
||||
else
|
||||
formula_ast.replace_stanza(:revision, new_revision)
|
||||
end
|
||||
formula.path.atomic_write(formula_ast.process)
|
||||
end
|
||||
|
||||
message = "#{formula.name}: revision bump #{args.message}"
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
require "utils/ast"
|
||||
|
||||
describe Utils::AST do
|
||||
let(:initial_formula) do
|
||||
<<~RUBY
|
||||
describe Utils::AST::FormulaAST do
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
license all_of: [
|
||||
@ -17,11 +17,10 @@ describe Utils::AST do
|
||||
RUBY
|
||||
end
|
||||
|
||||
describe ".replace_formula_stanza!" do
|
||||
describe "#replace_stanza" do
|
||||
it "replaces the specified stanza in a formula" do
|
||||
contents = initial_formula.dup
|
||||
described_class.replace_formula_stanza!(contents, :license, :public_domain)
|
||||
expect(contents).to eq <<~RUBY
|
||||
formula_ast.replace_stanza(:license, :public_domain)
|
||||
expect(formula_ast.process).to eq <<~RUBY
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
license :public_domain
|
||||
@ -30,11 +29,10 @@ describe Utils::AST do
|
||||
end
|
||||
end
|
||||
|
||||
describe ".add_formula_stanza!" do
|
||||
describe "#add_stanza" do
|
||||
it "adds the specified stanza to a formula" do
|
||||
contents = initial_formula.dup
|
||||
described_class.add_formula_stanza!(contents, :revision, 1)
|
||||
expect(contents).to eq <<~RUBY
|
||||
formula_ast.add_stanza(:revision, 1)
|
||||
expect(formula_ast.process).to eq <<~RUBY
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
license all_of: [
|
||||
@ -92,7 +90,7 @@ describe Utils::AST do
|
||||
end
|
||||
end
|
||||
|
||||
describe ".add_bottle_stanza!" do
|
||||
describe "#add_bottle_block" do
|
||||
let(:bottle_output) do
|
||||
<<~RUBY.chomp.indent(2)
|
||||
bottle do
|
||||
@ -102,8 +100,8 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
context "when `license` is a string" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
license "MIT"
|
||||
@ -125,14 +123,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` after `license`" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when `license` is a symbol" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
license :cannot_represent
|
||||
@ -154,14 +152,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` after `license`" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when `license` is multiline" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
license all_of: [
|
||||
@ -191,14 +189,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` after `license`" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when `head` is a string" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
head "https://brew.sh/foo.git"
|
||||
@ -220,14 +218,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` after `head`" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when `head` is a block" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
|
||||
@ -255,14 +253,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` before `head`" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is a comment on the same line" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz" # comment
|
||||
end
|
||||
@ -282,14 +280,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` after the comment" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the next line is a comment" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
# comment
|
||||
@ -311,14 +309,14 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` after the comment" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the next line is blank and the one after it is a comment" do
|
||||
let(:formula_contents) do
|
||||
<<~RUBY.chomp
|
||||
subject(:formula_ast) do
|
||||
described_class.new <<~RUBY.chomp
|
||||
class Foo < Formula
|
||||
url "https://brew.sh/foo-1.0.tar.gz"
|
||||
|
||||
@ -342,8 +340,8 @@ describe Utils::AST do
|
||||
end
|
||||
|
||||
it "adds `bottle` before the comment" do
|
||||
described_class.add_bottle_stanza!(formula_contents, bottle_output)
|
||||
expect(formula_contents).to eq(new_contents)
|
||||
formula_ast.add_bottle_block(bottle_output)
|
||||
expect(formula_ast.process).to eq(new_contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -13,12 +13,28 @@ module Utils
|
||||
SendNode = RuboCop::AST::SendNode
|
||||
BlockNode = RuboCop::AST::BlockNode
|
||||
ProcessedSource = RuboCop::AST::ProcessedSource
|
||||
TreeRewriter = Parser::Source::TreeRewriter
|
||||
|
||||
class << self
|
||||
# Helper class for editing formulae.
|
||||
#
|
||||
# @api private
|
||||
class FormulaAST
|
||||
extend T::Sig
|
||||
extend Forwardable
|
||||
|
||||
delegate process: :tree_rewriter
|
||||
|
||||
sig { params(formula_contents: String).void }
|
||||
def initialize(formula_contents)
|
||||
@formula_contents = formula_contents
|
||||
processed_source, children = process_formula
|
||||
@processed_source = T.let(processed_source, ProcessedSource)
|
||||
@children = T.let(children, T::Array[Node])
|
||||
@tree_rewriter = T.let(TreeRewriter.new(processed_source.buffer), TreeRewriter)
|
||||
end
|
||||
|
||||
sig { params(body_node: Node).returns(T::Array[Node]) }
|
||||
def body_children(body_node)
|
||||
def self.body_children(body_node)
|
||||
if body_node.nil?
|
||||
[]
|
||||
elsif body_node.begin_type?
|
||||
@ -28,56 +44,36 @@ module Utils
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(formula_contents: String).returns(T.nilable(Node)) }
|
||||
def bottle_block(formula_contents)
|
||||
formula_stanza(formula_contents, :bottle, type: :block_call)
|
||||
sig { returns(T.nilable(Node)) }
|
||||
def bottle_block
|
||||
stanza(:bottle, type: :block_call)
|
||||
end
|
||||
|
||||
sig { params(formula_contents: String, name: Symbol, type: T.nilable(Symbol)).returns(T.nilable(Node)) }
|
||||
def formula_stanza(formula_contents, name, type: nil)
|
||||
_, children = process_formula(formula_contents)
|
||||
sig { params(name: Symbol, type: T.nilable(Symbol)).returns(T.nilable(Node)) }
|
||||
def stanza(name, type: nil)
|
||||
children.find { |child| call_node_match?(child, name: name, type: type) }
|
||||
end
|
||||
|
||||
sig { params(formula_contents: String, bottle_output: String).void }
|
||||
def replace_bottle_stanza!(formula_contents, bottle_output)
|
||||
replace_formula_stanza!(formula_contents, :bottle, bottle_output.chomp, type: :block_call)
|
||||
sig { params(bottle_output: String).void }
|
||||
def replace_bottle_block(bottle_output)
|
||||
replace_stanza(:bottle, bottle_output.chomp, type: :block_call)
|
||||
end
|
||||
|
||||
sig { params(formula_contents: String, bottle_output: String).void }
|
||||
def add_bottle_stanza!(formula_contents, bottle_output)
|
||||
add_formula_stanza!(formula_contents, :bottle, "\n#{bottle_output.chomp}", type: :block_call)
|
||||
sig { params(bottle_output: String).void }
|
||||
def add_bottle_block(bottle_output)
|
||||
add_stanza(:bottle, "\n#{bottle_output.chomp}", type: :block_call)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
formula_contents: String,
|
||||
name: Symbol,
|
||||
replacement: T.any(Numeric, String, Symbol),
|
||||
type: T.nilable(Symbol),
|
||||
).void
|
||||
end
|
||||
def replace_formula_stanza!(formula_contents, name, replacement, type: nil)
|
||||
processed_source, children = process_formula(formula_contents)
|
||||
sig { params(name: Symbol, replacement: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void }
|
||||
def replace_stanza(name, replacement, type: nil)
|
||||
stanza_node = children.find { |child| call_node_match?(child, name: name, type: type) }
|
||||
raise "Could not find #{name} stanza!" if stanza_node.nil?
|
||||
|
||||
tree_rewriter = Parser::Source::TreeRewriter.new(processed_source.buffer)
|
||||
tree_rewriter.replace(stanza_node.source_range, stanza_text(name, replacement, indent: 2).lstrip)
|
||||
formula_contents.replace(tree_rewriter.process)
|
||||
tree_rewriter.replace(stanza_node.source_range, self.class.stanza_text(name, replacement, indent: 2).lstrip)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
formula_contents: String,
|
||||
name: Symbol,
|
||||
value: T.any(Numeric, String, Symbol),
|
||||
type: T.nilable(Symbol),
|
||||
).void
|
||||
end
|
||||
def add_formula_stanza!(formula_contents, name, value, type: nil)
|
||||
processed_source, children = process_formula(formula_contents)
|
||||
|
||||
sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void }
|
||||
def add_stanza(name, value, type: nil)
|
||||
preceding_component = if children.length > 1
|
||||
children.reduce do |previous_child, current_child|
|
||||
if formula_component_before_target?(current_child,
|
||||
@ -108,13 +104,11 @@ module Utils
|
||||
end
|
||||
end
|
||||
|
||||
tree_rewriter = Parser::Source::TreeRewriter.new(processed_source.buffer)
|
||||
tree_rewriter.insert_after(preceding_expr, "\n#{stanza_text(name, value, indent: 2)}")
|
||||
formula_contents.replace(tree_rewriter.process)
|
||||
tree_rewriter.insert_after(preceding_expr, "\n#{self.class.stanza_text(name, value, indent: 2)}")
|
||||
end
|
||||
|
||||
sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), indent: T.nilable(Integer)).returns(String) }
|
||||
def stanza_text(name, value, indent: nil)
|
||||
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
|
||||
@ -124,19 +118,31 @@ module Utils
|
||||
text
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig { params(source: String).returns([ProcessedSource, Node]) }
|
||||
def process_source(source)
|
||||
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
|
||||
|
||||
sig { params(formula_contents: String).returns([ProcessedSource, T::Array[Node]]) }
|
||||
def process_formula(formula_contents)
|
||||
processed_source, root_node = process_source(formula_contents)
|
||||
private
|
||||
|
||||
sig { returns(String) }
|
||||
attr_reader :formula_contents
|
||||
|
||||
sig { returns(ProcessedSource) }
|
||||
attr_reader :processed_source
|
||||
|
||||
sig { returns(T::Array[Node]) }
|
||||
attr_reader :children
|
||||
|
||||
sig { returns(TreeRewriter) }
|
||||
attr_reader :tree_rewriter
|
||||
|
||||
sig { returns([ProcessedSource, T::Array[Node]]) }
|
||||
def process_formula
|
||||
processed_source, root_node = self.class.process_source(formula_contents)
|
||||
|
||||
class_node = if root_node.class_type?
|
||||
root_node
|
||||
@ -146,7 +152,7 @@ module Utils
|
||||
|
||||
raise "Could not find formula class!" if class_node.nil?
|
||||
|
||||
children = body_children(class_node.body)
|
||||
children = self.class.body_children(class_node.body)
|
||||
raise "Formula class is empty!" if children.empty?
|
||||
|
||||
[processed_source, children]
|
||||
|
||||
7
Library/Homebrew/utils/ast.rbi
Normal file
7
Library/Homebrew/utils/ast.rbi
Normal file
@ -0,0 +1,7 @@
|
||||
# typed: strict
|
||||
|
||||
module Utils
|
||||
module AST
|
||||
include ::Kernel
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user