diff --git a/Library/Homebrew/.parlour b/Library/Homebrew/.parlour new file mode 100644 index 0000000000..1fe16333dc --- /dev/null +++ b/Library/Homebrew/.parlour @@ -0,0 +1,10 @@ +parser: false + +output_file: + rbi: sorbet/rbi/parlour.rbi + +relative_requires: + - sorbet/parlour.rb + +plugins: + Attr: {} diff --git a/Library/Homebrew/Gemfile b/Library/Homebrew/Gemfile index a434b3a5c9..c83f920267 100644 --- a/Library/Homebrew/Gemfile +++ b/Library/Homebrew/Gemfile @@ -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 diff --git a/Library/Homebrew/Gemfile.lock b/Library/Homebrew/Gemfile.lock index 1bcb62f2b7..99689717d8 100644 --- a/Library/Homebrew/Gemfile.lock +++ b/Library/Homebrew/Gemfile.lock @@ -188,6 +188,7 @@ DEPENDENCIES mechanize minitest parallel_tests + parlour patchelf plist ronn diff --git a/Library/Homebrew/dev-cmd/typecheck.rb b/Library/Homebrew/dev-cmd/typecheck.rb index 07ee66aae3..73242d0a5b 100644 --- a/Library/Homebrew/dev-cmd/typecheck.rb +++ b/Library/Homebrew/dev-cmd/typecheck.rb @@ -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" diff --git a/Library/Homebrew/extend/hash_validator.rbi b/Library/Homebrew/extend/hash_validator.rbi new file mode 100644 index 0000000000..715b783813 --- /dev/null +++ b/Library/Homebrew/extend/hash_validator.rbi @@ -0,0 +1,6 @@ +# typed: strict + +class Hash + sig { params(valid_keys: T.untyped).void } + def assert_valid_keys!(*valid_keys); end +end diff --git a/Library/Homebrew/extend/time.rbi b/Library/Homebrew/extend/time.rbi new file mode 100644 index 0000000000..ab1c7254f9 --- /dev/null +++ b/Library/Homebrew/extend/time.rbi @@ -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 diff --git a/Library/Homebrew/sorbet/config b/Library/Homebrew/sorbet/config index 024ba458ec..02fbf5ab02 100644 --- a/Library/Homebrew/sorbet/config +++ b/Library/Homebrew/sorbet/config @@ -6,6 +6,3 @@ --ignore /test/.gem - ---dsl-plugins -sorbet/triggers.yml diff --git a/Library/Homebrew/sorbet/parlour.rb b/Library/Homebrew/sorbet/parlour.rb new file mode 100644 index 0000000000..c33a64dc8c --- /dev/null +++ b/Library/Homebrew/sorbet/parlour.rb @@ -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" diff --git a/Library/Homebrew/sorbet/parlour/attr.rb b/Library/Homebrew/sorbet/parlour/attr.rb new file mode 100644 index 0000000000..8f8c4bf8fd --- /dev/null +++ b/Library/Homebrew/sorbet/parlour/attr.rb @@ -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 diff --git a/Library/Homebrew/sorbet/plugins/attr_predicate.rb b/Library/Homebrew/sorbet/plugins/attr_predicate.rb deleted file mode 100644 index 2559705d5d..0000000000 --- a/Library/Homebrew/sorbet/plugins/attr_predicate.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/sorbet/plugins/attr_rw.rb b/Library/Homebrew/sorbet/plugins/attr_rw.rb deleted file mode 100644 index 2363d3bffa..0000000000 --- a/Library/Homebrew/sorbet/plugins/attr_rw.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/sorbet/plugins/def_delegator.rb b/Library/Homebrew/sorbet/plugins/def_delegator.rb deleted file mode 100644 index 7eea2d6bd0..0000000000 --- a/Library/Homebrew/sorbet/plugins/def_delegator.rb +++ /dev/null @@ -1,17 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -source = ARGV[5] - -match = source.match(/\s*def_delegator\s+.*:(?[^:]+)\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 diff --git a/Library/Homebrew/sorbet/plugins/def_delegators.rb b/Library/Homebrew/sorbet/plugins/def_delegators.rb deleted file mode 100644 index ac09880d3c..0000000000 --- a/Library/Homebrew/sorbet/plugins/def_delegators.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/sorbet/plugins/delegate.rb b/Library/Homebrew/sorbet/plugins/delegate.rb deleted file mode 100644 index 8a249d188c..0000000000 --- a/Library/Homebrew/sorbet/plugins/delegate.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/sorbet/plugins/using.rb b/Library/Homebrew/sorbet/plugins/using.rb deleted file mode 100644 index 288c4c6c44..0000000000 --- a/Library/Homebrew/sorbet/plugins/using.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/sorbet/rbi/parlour.rbi b/Library/Homebrew/sorbet/rbi/parlour.rbi new file mode 100644 index 0000000000..ed2a0e5fcd --- /dev/null +++ b/Library/Homebrew/sorbet/rbi/parlour.rbi @@ -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 diff --git a/Library/Homebrew/sorbet/triggers.yml b/Library/Homebrew/sorbet/triggers.yml deleted file mode 100644 index ccf1647ba4..0000000000 --- a/Library/Homebrew/sorbet/triggers.yml +++ /dev/null @@ -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 diff --git a/Library/Homebrew/unpack_strategy.rbi b/Library/Homebrew/unpack_strategy.rbi index d7550c56b2..55284f6668 100644 --- a/Library/Homebrew/unpack_strategy.rbi +++ b/Library/Homebrew/unpack_strategy.rbi @@ -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