Migrate from Sorbet DSL plugins to Parlour

This commit is contained in:
Bo Anderson 2021-07-27 04:44:24 +01:00
parent 0d8caf722b
commit 07239d267c
No known key found for this signature in database
GPG Key ID: 3DB94E204E137D65
18 changed files with 355 additions and 137 deletions

10
Library/Homebrew/.parlour Normal file
View File

@ -0,0 +1,10 @@
parser: false
output_file:
rbi: sorbet/rbi/parlour.rbi
relative_requires:
- sorbet/parlour.rb
plugins:
Attr: {}

View File

@ -24,6 +24,7 @@ gem "simplecov-cobertura", require: false
gem "warning", require: false
group :sorbet, optional: true do
gem "parlour", require: false
gem "rspec-sorbet", require: false
gem "sorbet", require: false
gem "sorbet-runtime", require: false

View File

@ -188,6 +188,7 @@ DEPENDENCIES
mechanize
minitest
parallel_tests
parlour
patchelf
plist
ronn

View File

@ -61,6 +61,7 @@ module Homebrew
ohai "Updating Tapioca RBI files..."
system "bundle", "exec", "tapioca", "sync", "--exclude", *excluded_gems
system "bundle", "exec", "parlour"
system "bundle", "exec", "srb", "rbi", "hidden-definitions"
system "bundle", "exec", "srb", "rbi", "todo"

View File

@ -0,0 +1,6 @@
# typed: strict
class Hash
sig { params(valid_keys: T.untyped).void }
def assert_valid_keys!(*valid_keys); end
end

View File

@ -0,0 +1,9 @@
# typed: strict
class Time
sig { returns(T.any(Integer, Float)) }
def remaining; end
sig { returns(T.any(Integer, Float)) }
def remaining!; end
end

View File

@ -6,6 +6,3 @@
--ignore
/test/.gem
--dsl-plugins
sorbet/triggers.yml

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
# typed: true
require_relative "../warnings"
Warnings.ignore :parser_syntax do
require "parser/current"
end
module Homebrew
module Parlour
extend T::Sig
ROOT_DIR = T.let(Pathname(__dir__).parent.realpath.freeze, Pathname)
sig { returns(T::Array[Parser::AST::Node]) }
def self.ast_list
@@ast_list ||= begin
ast_list = []
parser = Parser::CurrentRuby.new
ROOT_DIR.find do |path|
Find.prune if path.directory? && %w[sorbet shims test vendor].any? { |subdir| path == ROOT_DIR/subdir }
Find.prune if path.file? && path.extname != ".rb"
next unless path.file?
buffer = Parser::Source::Buffer.new(path, source: path.read)
parser.reset
ast = parser.parse(buffer)
ast_list << ast if ast
end
ast_list
end
end
end
end
require "parlour"
require_relative "parlour/attr"

View File

@ -0,0 +1,110 @@
# frozen_string_literal: true
# typed: strict
class Attr < Parlour::Plugin
sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
def generate(root)
tree = T.let([], T::Array[T.untyped])
Homebrew::Parlour.ast_list.each do |node|
tree += find_custom_attr(node)
end
process_custom_attr(tree, root)
end
sig { override.returns(T.nilable(String)) }
def strictness
return "strict"
end
private
sig { params(node: Parser::AST::Node, list: T::Array[String]).returns(T::Array[String]) }
def traverse_module_name(node, list = [])
parent, name = node.children
list = traverse_module_name(parent, list) if parent
list << name.to_s
list
end
sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
def extract_module_name(node)
return if node.nil?
traverse_module_name(node).join("::")
end
sig { params(node: Parser::AST::Node).returns(T::Array[T.untyped]) }
def find_custom_attr(node)
tree = T.let([], T::Array[T.untyped])
children = node.children.dup
if node.type == :begin
children.each do |child|
subtree = find_custom_attr(child)
tree += subtree unless subtree.empty?
end
elsif node.type == :sclass
subtree = find_custom_attr(node.children[1])
return tree if subtree.empty?
tree << [:sclass, subtree]
elsif node.type == :class || node.type == :module
element = []
if node.type == :class
element << :class
element << extract_module_name(children.shift)
element << extract_module_name(children.shift)
elsif node.type == :module
element << :module
element << extract_module_name(children.shift)
end
body = children.shift
return tree if body.nil?
subtree = find_custom_attr(body)
return tree if subtree.empty?
element << subtree
tree << element
elsif node.type == :send && children.shift.nil?
method_name = children.shift
if method_name == :attr_rw || method_name == :attr_predicate
children.each do |name_node|
tree << [method_name, name_node.children.first.to_s]
end
end
end
tree
end
sig { params(tree: T::Array[T.untyped], namespace: Parlour::RbiGenerator::Namespace, sclass: T::Boolean).void }
def process_custom_attr(tree, namespace, sclass: false)
tree.each do |node|
type = node.shift
case type
when :sclass
process_custom_attr(node.shift, namespace, sclass: true)
when :class
class_namespace = namespace.create_class(node.shift, superclass: node.shift)
process_custom_attr(node.shift, class_namespace)
when :module
module_namespace = namespace.create_module(node.shift)
process_custom_attr(node.shift, module_namespace)
when :attr_rw
name = node.shift
name = "self.#{name}" if sclass
namespace.create_method(name, parameters: [
Parlour::RbiGenerator::Parameter.new("arg", type: "T.untyped", default: "T.unsafe(nil)")
], return_type: "T.untyped")
when :attr_predicate
name = node.shift
name = "self.#{name}" if sclass
namespace.create_method(name, return_type: "T::Boolean")
else
raise "Malformed tree."
end
end
end
end

View File

@ -1,13 +0,0 @@
# typed: strict
# frozen_string_literal: true
source = ARGV[5]
source.scan(/:([^?]+\?)/).flatten.each do |method|
puts <<~RUBY
# typed: strict
sig { returns(T::Boolean) }
def #{method}; end
RUBY
end

View File

@ -1,13 +0,0 @@
# typed: strict
# frozen_string_literal: true
source = ARGV[5]
source.scan(/:([^\s,]+)/).flatten.each do |method|
puts <<~RUBY
# typed: strict
sig { params(arg: T.untyped).returns(T.untyped) }
def #{method}(arg = T.unsafe(nil)); end
RUBY
end

View File

@ -1,17 +0,0 @@
# typed: strict
# frozen_string_literal: true
source = ARGV[5]
match = source.match(/\s*def_delegator\s+.*:(?<method>[^:]+)\s*\Z/m)
raise if match.nil?
method = match[:method]
puts <<~RUBY
# typed: strict
sig {params(arg0: T.untyped, blk: T.untyped).returns(T.untyped)}
def #{method}(*arg0, &blk); end
RUBY

View File

@ -1,17 +0,0 @@
# typed: strict
# frozen_string_literal: true
source = ARGV[5]
symbols = source.scan(/:[^\s,]+/)
_, *methods = symbols.map { |s| s.delete_prefix(":") }
methods.each do |method|
puts <<~RUBY
# typed: strict
sig {params(arg0: T.untyped, blk: T.untyped).returns(T.untyped)}
def #{method}(*arg0, &blk); end
RUBY
end

View File

@ -1,21 +0,0 @@
# typed: strict
# frozen_string_literal: true
source = ARGV[5]
methods = if (single = source[/delegate\s+([^:]+):\s+/, 1])
[single]
else
multiple = source[/delegate\s+\[(.*?)\]\s+=>\s+/m, 1]
non_comments = multiple.gsub(/\#.*$/, "")
non_comments.scan(/:([^:,\s]+)/).flatten
end
methods.each do |method|
puts <<~RUBY
# typed: strict
sig {params(arg0: T.untyped, blk: T.untyped).returns(T.untyped)}
def #{method}(*arg0, &blk); end
RUBY
end

View File

@ -1,43 +0,0 @@
# typed: strict
# frozen_string_literal: true
source = ARGV[5]
case source[/\Ausing\s+(.*)\Z/, 1]
when "Magic"
puts <<-RUBY
# typed: strict
class ::Pathname
sig { returns(String) }
def magic_number; end
sig { returns(String) }
def file_type; end
sig { returns(T::Array[String]) }
def zipinfo; end
end
RUBY
when "HashValidator"
puts <<-RUBY
# typed: strict
class ::Hash
sig { params(valid_keys: T.untyped).void }
def assert_valid_keys!(*valid_keys); end
end
RUBY
when "TimeRemaining"
puts <<-RUBY
# typed: strict
class ::Time
sig { returns(T.any(Integer, Float)) }
def remaining; end
sig { returns(T.any(Integer, Float)) }
def remaining!; end
end
RUBY
end

View File

@ -0,0 +1,164 @@
# typed: strict
module Homebrew
class Cleanup
sig { returns(T::Boolean) }
def dry_run?; end
sig { returns(T::Boolean) }
def scrub?; end
sig { returns(T::Boolean) }
def prune?; end
end
end
module Debrew
sig { returns(T::Boolean) }
def self.active?; end
end
class Formula
sig { params(arg: T.untyped).returns(T.untyped) }
def self.desc(arg = T.unsafe(nil)); end
sig { params(arg: T.untyped).returns(T.untyped) }
def self.homepage(arg = T.unsafe(nil)); end
sig { params(arg: T.untyped).returns(T.untyped) }
def self.revision(arg = T.unsafe(nil)); end
sig { params(arg: T.untyped).returns(T.untyped) }
def self.version_scheme(arg = T.unsafe(nil)); end
end
class FormulaInstaller
sig { returns(T::Boolean) }
def installed_as_dependency?; end
sig { returns(T::Boolean) }
def installed_on_request?; end
sig { returns(T::Boolean) }
def show_summary_heading?; end
sig { returns(T::Boolean) }
def show_header?; end
sig { returns(T::Boolean) }
def force_bottle?; end
sig { returns(T::Boolean) }
def ignore_deps?; end
sig { returns(T::Boolean) }
def only_deps?; end
sig { returns(T::Boolean) }
def interactive?; end
sig { returns(T::Boolean) }
def git?; end
sig { returns(T::Boolean) }
def force?; end
sig { returns(T::Boolean) }
def keep_tmp?; end
sig { returns(T::Boolean) }
def verbose?; end
sig { returns(T::Boolean) }
def debug?; end
sig { returns(T::Boolean) }
def quiet?; end
sig { returns(T::Boolean) }
def hold_locks?; end
end
class Requirement
sig { params(arg: T.untyped).returns(T.untyped) }
def self.fatal(arg = T.unsafe(nil)); end
sig { params(arg: T.untyped).returns(T.untyped) }
def self.cask(arg = T.unsafe(nil)); end
sig { params(arg: T.untyped).returns(T.untyped) }
def self.download(arg = T.unsafe(nil)); end
end
class BottleSpecification
sig { params(arg: T.untyped).returns(T.untyped) }
def rebuild(arg = T.unsafe(nil)); end
end
class SystemCommand
sig { returns(T::Boolean) }
def sudo?; end
sig { returns(T::Boolean) }
def print_stdout?; end
sig { returns(T::Boolean) }
def print_stderr?; end
sig { returns(T::Boolean) }
def must_succeed?; end
end
module Cask
class Audit
sig { returns(T::Boolean) }
def appcast?; end
sig { returns(T::Boolean) }
def new_cask?; end
sig { returns(T::Boolean) }
def strict?; end
sig { returns(T::Boolean) }
def online?; end
sig { returns(T::Boolean) }
def token_conflicts?; end
end
class DSL
class Caveats < Base
sig { returns(T::Boolean) }
def discontinued?; end
end
end
class Installer
sig { returns(T::Boolean) }
def binaries?; end
sig { returns(T::Boolean) }
def force?; end
sig { returns(T::Boolean) }
def skip_cask_deps?; end
sig { returns(T::Boolean) }
def require_sha?; end
sig { returns(T::Boolean) }
def reinstall?; end
sig { returns(T::Boolean) }
def upgrade?; end
sig { returns(T::Boolean) }
def verbose?; end
sig { returns(T::Boolean) }
def installed_as_dependency?; end
sig { returns(T::Boolean) }
def quarantine?; end
end
end

View File

@ -1,10 +0,0 @@
ruby_extra_args:
- --disable-gems
triggers:
using: sorbet/plugins/using.rb
attr_predicate: sorbet/plugins/attr_predicate.rb
attr_rw: sorbet/plugins/attr_rw.rb
def_delegator: sorbet/plugins/def_delegator.rb
def_delegators: sorbet/plugins/def_delegators.rb
delegate: sorbet/plugins/delegate.rb

View File

@ -3,3 +3,14 @@
module UnpackStrategy
include Kernel
end
class Pathname
sig { returns(String) }
def magic_number; end
sig { returns(String) }
def file_type; end
sig { returns(T::Array[String]) }
def zipinfo; end
end