Merge pull request #10298 from SeekingMeaning/ast

utils/ast: move helper functions from `FormulaAST` to `AST`
This commit is contained in:
Seeker 2021-01-12 22:32:23 -08:00 committed by GitHub
commit 82901036f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 107 deletions

View File

@ -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?

View 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

View File

@ -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)

View File

@ -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