From 14b4916ddabe5b3349643f847fc140a00b2f8179 Mon Sep 17 00:00:00 2001 From: Seeker Date: Mon, 11 Jan 2021 20:11:08 -0800 Subject: [PATCH] utils/ast: move helper functions from `FormulaAST` to `AST` --- Library/Homebrew/dev-cmd/bottle.rb | 2 +- Library/Homebrew/test/utils/ast/ast_spec.rb | 50 +++++++ .../{ast_spec.rb => ast/formula_ast_spec.rb} | 44 ------ Library/Homebrew/utils/ast.rb | 129 +++++++++--------- 4 files changed, 118 insertions(+), 107 deletions(-) create mode 100644 Library/Homebrew/test/utils/ast/ast_spec.rb rename Library/Homebrew/test/utils/{ast_spec.rb => ast/formula_ast_spec.rb} (81%) diff --git a/Library/Homebrew/dev-cmd/bottle.rb b/Library/Homebrew/dev-cmd/bottle.rb index bd782f6ab1..f406ce4344 100644 --- a/Library/Homebrew/dev-cmd/bottle.rb +++ b/Library/Homebrew/dev-cmd/bottle.rb @@ -574,7 +574,7 @@ module Homebrew end 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 mismatches, checksums = merge_bottle_spec(old_keys, old_bottle_spec, bottle_hash["bottle"]) if mismatches.present? diff --git a/Library/Homebrew/test/utils/ast/ast_spec.rb b/Library/Homebrew/test/utils/ast/ast_spec.rb new file mode 100644 index 0000000000..1f9dc30ffc --- /dev/null +++ b/Library/Homebrew/test/utils/ast/ast_spec.rb @@ -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 diff --git a/Library/Homebrew/test/utils/ast_spec.rb b/Library/Homebrew/test/utils/ast/formula_ast_spec.rb similarity index 81% rename from Library/Homebrew/test/utils/ast_spec.rb rename to Library/Homebrew/test/utils/ast/formula_ast_spec.rb index c1f4fe32c1..abff5d07c4 100644 --- a/Library/Homebrew/test/utils/ast_spec.rb +++ b/Library/Homebrew/test/utils/ast/formula_ast_spec.rb @@ -46,50 +46,6 @@ describe Utils::AST::FormulaAST do 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 let(:bottle_output) do <<~RUBY.chomp.indent(2) diff --git a/Library/Homebrew/utils/ast.rb b/Library/Homebrew/utils/ast.rb index 90aeb47a4c..8452ef512f 100644 --- a/Library/Homebrew/utils/ast.rb +++ b/Library/Homebrew/utils/ast.rb @@ -9,18 +9,79 @@ module Utils # # @api private module AST + extend T::Sig + Node = RuboCop::AST::Node SendNode = RuboCop::AST::SendNode BlockNode = RuboCop::AST::BlockNode ProcessedSource = RuboCop::AST::ProcessedSource 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. # # @api private class FormulaAST extend T::Sig extend Forwardable + include AST delegate process: :tree_rewriter @@ -33,17 +94,6 @@ module Utils @tree_rewriter = T.let(TreeRewriter.new(processed_source.buffer), TreeRewriter) 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)) } def bottle_block 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 } 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? + stanza_node = stanza(name, type: type) + 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 sig { params(name: Symbol, value: T.any(Numeric, String, Symbol), type: T.nilable(Symbol)).void } @@ -104,26 +154,7 @@ module Utils end end - 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 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] + tree_rewriter.insert_after(preceding_expr, "\n#{stanza_text(name, value, indent: 2)}") end private @@ -142,7 +173,7 @@ module Utils sig { returns([ProcessedSource, T::Array[Node]]) } 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? if root_node.begin_type? @@ -156,7 +187,7 @@ module Utils 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? [processed_source, children] @@ -178,32 +209,6 @@ module Utils false 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