utils/ast: add Sorbet method signatures

This commit is contained in:
Seeker 2021-01-06 09:11:34 -08:00
parent 4e79550b03
commit 332778025a
7 changed files with 105 additions and 61 deletions

View File

@ -15,6 +15,7 @@ gem "rspec-retry", require: false
gem "rspec-sorbet", require: false gem "rspec-sorbet", require: false
gem "rspec-wait", require: false gem "rspec-wait", require: false
gem "rubocop", require: false gem "rubocop", require: false
gem "rubocop-ast", require: false
gem "simplecov", require: false gem "simplecov", require: false
gem "sorbet", require: false gem "sorbet", require: false
gem "sorbet-runtime", require: false gem "sorbet-runtime", require: false

View File

@ -183,6 +183,7 @@ DEPENDENCIES
rspec-sorbet rspec-sorbet
rspec-wait rspec-wait
rubocop rubocop
rubocop-ast
rubocop-performance rubocop-performance
rubocop-rails rubocop-rails
rubocop-rspec rubocop-rspec

View File

@ -0,0 +1,44 @@
# typed: true
# frozen_string_literal: true
FORMULA_COMPONENT_PRECEDENCE_LIST = [
[{ name: :include, type: :method_call }],
[{ name: :desc, type: :method_call }],
[{ name: :homepage, type: :method_call }],
[{ name: :url, type: :method_call }],
[{ name: :mirror, type: :method_call }],
[{ name: :version, type: :method_call }],
[{ name: :sha256, type: :method_call }],
[{ name: :license, type: :method_call }],
[{ name: :revision, type: :method_call }],
[{ name: :version_scheme, type: :method_call }],
[{ name: :head, type: :method_call }],
[{ name: :stable, type: :block_call }],
[{ name: :livecheck, type: :block_call }],
[{ name: :bottle, type: :block_call }],
[{ name: :pour_bottle?, type: :block_call }],
[{ name: :head, type: :block_call }],
[{ name: :bottle, type: :method_call }],
[{ name: :keg_only, type: :method_call }],
[{ name: :option, type: :method_call }],
[{ name: :deprecated_option, type: :method_call }],
[{ name: :disable!, type: :method_call }],
[{ name: :deprecate!, type: :method_call }],
[{ name: :depends_on, type: :method_call }],
[{ name: :uses_from_macos, type: :method_call }],
[{ name: :on_macos, type: :block_call }],
[{ name: :on_linux, type: :block_call }],
[{ name: :conflicts_with, type: :method_call }],
[{ name: :skip_clean, type: :method_call }],
[{ name: :cxxstdlib_check, type: :method_call }],
[{ name: :link_overwrite, type: :method_call }],
[{ name: :fails_with, type: :method_call }, { name: :fails_with, type: :block_call }],
[{ name: :go_resource, type: :block_call }, { name: :resource, type: :block_call }],
[{ name: :patch, type: :method_call }, { name: :patch, type: :block_call }],
[{ name: :needs, type: :method_call }],
[{ name: :install, type: :method_definition }],
[{ name: :post_install, type: :method_definition }],
[{ name: :caveats, type: :method_definition }],
[{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }],
[{ name: :test, type: :block_call }],
].freeze

View File

@ -9,7 +9,6 @@ require "formula_versions"
require "cli/parser" require "cli/parser"
require "utils/inreplace" require "utils/inreplace"
require "erb" require "erb"
require "utils/ast"
BOTTLE_ERB = <<-EOS BOTTLE_ERB = <<-EOS
bottle do bottle do
@ -490,6 +489,9 @@ module Homebrew
end end
if args.write? if args.write?
Homebrew.install_bundler_gems!
require "utils/ast"
path = Pathname.new((HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]).to_s) path = Pathname.new((HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"]).to_s)
checksums = old_checksums(path, bottle_hash, args: args) checksums = old_checksums(path, bottle_hash, args: args)
update_or_add = checksums.nil? ? "add" : "update" update_or_add = checksums.nil? ? "add" : "update"

View File

@ -3,7 +3,6 @@
require "formula" require "formula"
require "cli/parser" require "cli/parser"
require "utils/ast"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -50,6 +49,9 @@ module Homebrew
end end
end end
else else
Homebrew.install_bundler_gems!
require "utils/ast"
Utils::Inreplace.inreplace(formula.path) do |s| Utils::Inreplace.inreplace(formula.path) do |s|
s = s.inreplace_string s = s.inreplace_string
if current_revision.zero? if current_revision.zero?

View File

@ -1,6 +1,7 @@
# typed: false # typed: false
# frozen_string_literal: true # frozen_string_literal: true
require "ast_constants"
require "rubocops/extend/formula" require "rubocops/extend/formula"
module RuboCop module RuboCop
@ -11,50 +12,8 @@ module RuboCop
# - `component_precedence_list` has component hierarchy in a nested list # - `component_precedence_list` has component hierarchy in a nested list
# where each sub array contains components' details which are at same precedence level # where each sub array contains components' details which are at same precedence level
class ComponentsOrder < FormulaCop class ComponentsOrder < FormulaCop
COMPONENT_PRECEDENCE_LIST = [
[{ name: :include, type: :method_call }],
[{ name: :desc, type: :method_call }],
[{ name: :homepage, type: :method_call }],
[{ name: :url, type: :method_call }],
[{ name: :mirror, type: :method_call }],
[{ name: :version, type: :method_call }],
[{ name: :sha256, type: :method_call }],
[{ name: :license, type: :method_call }],
[{ name: :revision, type: :method_call }],
[{ name: :version_scheme, type: :method_call }],
[{ name: :head, type: :method_call }],
[{ name: :stable, type: :block_call }],
[{ name: :livecheck, type: :block_call }],
[{ name: :bottle, type: :block_call }],
[{ name: :pour_bottle?, type: :block_call }],
[{ name: :head, type: :block_call }],
[{ name: :bottle, type: :method_call }],
[{ name: :keg_only, type: :method_call }],
[{ name: :option, type: :method_call }],
[{ name: :deprecated_option, type: :method_call }],
[{ name: :disable!, type: :method_call }],
[{ name: :deprecate!, type: :method_call }],
[{ name: :depends_on, type: :method_call }],
[{ name: :uses_from_macos, type: :method_call }],
[{ name: :on_macos, type: :block_call }],
[{ name: :on_linux, type: :block_call }],
[{ name: :conflicts_with, type: :method_call }],
[{ name: :skip_clean, type: :method_call }],
[{ name: :cxxstdlib_check, type: :method_call }],
[{ name: :link_overwrite, type: :method_call }],
[{ name: :fails_with, type: :method_call }, { name: :fails_with, type: :block_call }],
[{ name: :go_resource, type: :block_call }, { name: :resource, type: :block_call }],
[{ name: :patch, type: :method_call }, { name: :patch, type: :block_call }],
[{ name: :needs, type: :method_call }],
[{ name: :install, type: :method_definition }],
[{ name: :post_install, type: :method_definition }],
[{ name: :caveats, type: :method_definition }],
[{ name: :plist_options, type: :method_call }, { name: :plist, type: :method_definition }],
[{ name: :test, type: :block_call }],
].freeze
def audit_formula(_node, _class_node, _parent_class_node, body_node) def audit_formula(_node, _class_node, _parent_class_node, body_node)
@present_components, @offensive_nodes = check_order(COMPONENT_PRECEDENCE_LIST, body_node) @present_components, @offensive_nodes = check_order(FORMULA_COMPONENT_PRECEDENCE_LIST, body_node)
component_problem @offensive_nodes[0], @offensive_nodes[1] if @offensive_nodes component_problem @offensive_nodes[0], @offensive_nodes[1] if @offensive_nodes

View File

@ -1,14 +1,23 @@
# typed: true # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "ast_constants"
require "rubocop-ast"
module Utils module Utils
# Helper functions for editing Ruby files. # Helper functions for editing Ruby files.
# #
# @api private # @api private
module AST module AST
Node = RuboCop::AST::Node
SendNode = RuboCop::AST::SendNode
BlockNode = RuboCop::AST::BlockNode
ProcessedSource = RuboCop::AST::ProcessedSource
class << self class << self
extend T::Sig extend T::Sig
sig { params(body_node: Node).returns(T::Array[Node]) }
def body_children(body_node) def body_children(body_node)
if body_node.nil? if body_node.nil?
[] []
@ -19,23 +28,35 @@ module Utils
end end
end end
sig { params(formula_contents: String).returns(T.nilable(Node)) }
def bottle_block(formula_contents) def bottle_block(formula_contents)
formula_stanza(formula_contents, :bottle, type: :block_call) formula_stanza(formula_contents, :bottle, type: :block_call)
end end
sig { params(formula_contents: String, name: Symbol, type: T.nilable(Symbol)).returns(T.nilable(Node)) }
def formula_stanza(formula_contents, name, type: nil) def formula_stanza(formula_contents, name, type: nil)
_, children = process_formula(formula_contents) _, children = process_formula(formula_contents)
children.find { |child| call_node_match?(child, name: name, type: type) } children.find { |child| call_node_match?(child, name: name, type: type) }
end end
sig { params(formula_contents: String, bottle_output: String).void }
def replace_bottle_stanza!(formula_contents, bottle_output) def replace_bottle_stanza!(formula_contents, bottle_output)
replace_formula_stanza!(formula_contents, :bottle, bottle_output.chomp, type: :block_call) replace_formula_stanza!(formula_contents, :bottle, bottle_output.chomp, type: :block_call)
end end
sig { params(formula_contents: String, bottle_output: String).void }
def add_bottle_stanza!(formula_contents, bottle_output) def add_bottle_stanza!(formula_contents, bottle_output)
add_formula_stanza!(formula_contents, :bottle, "\n#{bottle_output.chomp}", type: :block_call) add_formula_stanza!(formula_contents, :bottle, "\n#{bottle_output.chomp}", type: :block_call)
end 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) def replace_formula_stanza!(formula_contents, name, replacement, type: nil)
processed_source, children = process_formula(formula_contents) processed_source, children = process_formula(formula_contents)
stanza_node = children.find { |child| call_node_match?(child, name: name, type: type) } stanza_node = children.find { |child| call_node_match?(child, name: name, type: type) }
@ -46,6 +67,14 @@ module Utils
formula_contents.replace(tree_rewriter.process) formula_contents.replace(tree_rewriter.process)
end 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) def add_formula_stanza!(formula_contents, name, value, type: nil)
processed_source, children = process_formula(formula_contents) processed_source, children = process_formula(formula_contents)
@ -62,7 +91,7 @@ module Utils
else else
children.first children.first
end end
preceding_component = preceding_component.last_argument if preceding_component.send_type? preceding_component = preceding_component.last_argument if preceding_component.is_a?(SendNode)
preceding_expr = preceding_component.location.expression preceding_expr = preceding_component.location.expression
processed_source.comments.each do |comment| processed_source.comments.each do |comment|
@ -88,7 +117,7 @@ module Utils
def stanza_text(name, value, indent: nil) def stanza_text(name, value, indent: nil)
text = if value.is_a?(String) text = if value.is_a?(String)
_, node = process_source(value) _, node = process_source(value)
value if (node.send_type? || node.block_type?) && node.method_name == name value if (node.is_a?(SendNode) || node.is_a?(BlockNode)) && node.method_name == name
end end
text ||= "#{name} #{value.inspect}" text ||= "#{name} #{value.inspect}"
text = text.indent(indent) if indent && !text.match?(/\A\n* +/) text = text.indent(indent) if indent && !text.match?(/\A\n* +/)
@ -97,16 +126,15 @@ module Utils
private private
sig { params(source: String).returns([ProcessedSource, Node]) }
def process_source(source) def process_source(source)
Homebrew.install_bundler_gems!
require "rubocop-ast"
ruby_version = Version.new(HOMEBREW_REQUIRED_RUBY_VERSION).major_minor.to_f ruby_version = Version.new(HOMEBREW_REQUIRED_RUBY_VERSION).major_minor.to_f
processed_source = RuboCop::AST::ProcessedSource.new(source, ruby_version) processed_source = ProcessedSource.new(source, ruby_version)
root_node = processed_source.ast root_node = processed_source.ast
[processed_source, root_node] [processed_source, root_node]
end end
sig { params(formula_contents: String).returns([ProcessedSource, T::Array[Node]]) }
def process_formula(formula_contents) def process_formula(formula_contents)
processed_source, root_node = process_source(formula_contents) processed_source, root_node = process_source(formula_contents)
@ -124,10 +152,9 @@ module Utils
[processed_source, children] [processed_source, children]
end end
sig { params(node: Node, target_name: Symbol, target_type: T.nilable(Symbol)).returns(T::Boolean) }
def formula_component_before_target?(node, target_name:, target_type: nil) def formula_component_before_target?(node, target_name:, target_type: nil)
require "rubocops/components_order" FORMULA_COMPONENT_PRECEDENCE_LIST.each do |components|
RuboCop::Cop::FormulaAudit::ComponentsOrder::COMPONENT_PRECEDENCE_LIST.each do |components|
return false if components.any? do |component| return false if components.any? do |component|
component_match?(component_name: component[:name], component_match?(component_name: component[:name],
component_type: component[:type], component_type: component[:type],
@ -142,19 +169,27 @@ 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) def component_match?(component_name:, component_type:, target_name:, target_type: nil)
component_name == target_name && (target_type.nil? || component_type == target_type) component_name == target_name && (target_type.nil? || component_type == target_type)
end end
sig { params(node: Node, name: Symbol, type: T.nilable(Symbol)).returns(T::Boolean) }
def call_node_match?(node, name:, type: nil) def call_node_match?(node, name:, type: nil)
node_type = if node.send_type? node_type = case node
:method_call when SendNode then :method_call
elsif node.block_type? when BlockNode then :block_call
:block_call else return false
end end
return false if node_type.nil?
component_match?(component_name: T.unsafe(node).method_name, component_match?(component_name: node.method_name,
component_type: node_type, component_type: node_type,
target_name: name, target_name: name,
target_type: type) target_type: type)