Skip to content

Commit 7fb928c

Browse files
authored
Merge pull request #20325 from Homebrew/dug/typed-cask-audit
Enable strict typing in Cask::Audit
2 parents ff4ee5d + 157992b commit 7fb928c

File tree

3 files changed

+69
-31
lines changed

3 files changed

+69
-31
lines changed

Library/Homebrew/cask/audit.rb

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "cask/denylist"
@@ -19,6 +19,14 @@ class Audit
1919
include SystemCommand::Mixin
2020
include ::Utils::Curl
2121

22+
Error = T.type_alias do
23+
{
24+
message: T.nilable(String),
25+
location: T.nilable(Homebrew::SourceLocation),
26+
corrected: T::Boolean,
27+
}
28+
end
29+
2230
sig { returns(Cask) }
2331
attr_reader :cask
2432

@@ -47,6 +55,7 @@ def initialize(
4755
download ||= online || signing
4856

4957
@cask = cask
58+
@download = T.let(nil, T.nilable(Download))
5059
@download = Download.new(cask, quarantine:) if download
5160
@online = online
5261
@strict = strict
@@ -88,8 +97,9 @@ def run!
8897
self
8998
end
9099

100+
sig { returns(T::Array[Error]) }
91101
def errors
92-
@errors ||= []
102+
@errors ||= T.let([], T.nilable(T::Array[Error]))
93103
end
94104

95105
sig { returns(T::Boolean) }
@@ -113,9 +123,10 @@ def add_error(message, location: nil, strict_only: false)
113123
# Only raise non-critical audits if the user specified `--strict`.
114124
return if strict_only && !@strict
115125

116-
errors << ({ message:, location:, corrected: false })
126+
errors << { message:, location:, corrected: false }
117127
end
118128

129+
sig { returns(T.nilable(String)) }
119130
def result
120131
Formatter.error("failed") if errors?
121132
end
@@ -346,6 +357,7 @@ def audit_download_url_format
346357
location: url.location
347358
end
348359

360+
sig { void }
349361
def audit_download_url_is_osdn
350362
return if (url = cask.url).nil?
351363
return if block_url_offline?
@@ -541,8 +553,15 @@ def audit_signing
541553
end
542554
end
543555

544-
sig { void }
545-
def extract_artifacts
556+
sig {
557+
params(
558+
_block: T.nilable(T.proc.params(
559+
arg0: T::Array[T.any(Artifact::Pkg, Artifact::Relocated)],
560+
arg1: Pathname,
561+
).void),
562+
).void
563+
}
564+
def extract_artifacts(&_block)
546565
return unless online?
547566
return if (download = self.download).nil?
548567

@@ -557,7 +576,7 @@ def extract_artifacts
557576

558577
return if artifacts.empty?
559578

560-
@tmpdir ||= Pathname(Dir.mktmpdir("cask-audit", HOMEBREW_TEMP))
579+
@tmpdir ||= T.let(Pathname(Dir.mktmpdir("cask-audit", HOMEBREW_TEMP)), T.nilable(Pathname))
561580

562581
# Clean up tmp dir when @tmpdir object is destroyed
563582
ObjectSpace.define_finalizer(
@@ -606,7 +625,8 @@ def extract_artifacts
606625
.extract_nestedly(to: @tmpdir, verbose: false)
607626
end
608627

609-
@artifacts_extracted = true # Set the flag to indicate that extraction has occurred.
628+
# Set the flag to indicate that extraction has occurred.
629+
@artifacts_extracted = T.let(true, T.nilable(TrueClass))
610630

611631
# Yield the artifacts and temp directory to the block if provided.
612632
yield artifacts, @tmpdir if block_given?
@@ -626,8 +646,8 @@ def audit_rosetta
626646
extract_artifacts do |artifacts, tmpdir|
627647
is_container = artifacts.any? { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Pkg) }
628648

629-
artifacts.filter { |a| a.is_a?(Artifact::App) || a.is_a?(Artifact::Binary) }
630-
.each do |artifact|
649+
artifacts.each do |artifact|
650+
next if !artifact.is_a?(Artifact::App) && !artifact.is_a?(Artifact::Binary)
631651
next if artifact.is_a?(Artifact::Binary) && is_container
632652

633653
path = tmpdir/artifact.source.relative_path_from(cask.staged_path)
@@ -644,10 +664,10 @@ def audit_rosetta
644664

645665
system_command("lipo", args: ["-archs", main_binary], print_stderr: false)
646666
when Artifact::Binary
647-
binary_path = path.to_s.gsub(cask.appdir, tmpdir)
667+
binary_path = path.to_s.gsub(cask.appdir, tmpdir.to_s)
648668
system_command("lipo", args: ["-archs", binary_path], print_stderr: true)
649669
else
650-
add_error "Unknown artifact type: #{artifact.class}", location: url.location
670+
T.absurd(artifact)
651671
end
652672

653673
# binary stanza can contain shell scripts, so we just continue if lipo fails.
@@ -795,7 +815,7 @@ def cask_bundle_min_os
795815
return unless online?
796816

797817
min_os = T.let(nil, T.untyped)
798-
@staged_path ||= cask.staged_path
818+
@staged_path ||= T.let(cask.staged_path, T.nilable(Pathname))
799819

800820
extract_artifacts do |artifacts, tmpdir|
801821
artifacts.each do |artifact|
@@ -1104,15 +1124,15 @@ def bad_sourceforge_url?
11041124

11051125
sig { returns(T::Boolean) }
11061126
def bad_osdn_url?
1107-
domain.match?(%r{^(?:\w+\.)*osdn\.jp(?=/|$)})
1127+
T.must(domain).match?(%r{^(?:\w+\.)*osdn\.jp(?=/|$)})
11081128
end
11091129

1110-
# sig { returns(String) }
1130+
sig { returns(T.nilable(String)) }
11111131
def homepage
11121132
URI(cask.homepage.to_s).host
11131133
end
11141134

1115-
# sig { returns(String) }
1135+
sig { returns(T.nilable(String)) }
11161136
def domain
11171137
URI(cask.url.to_s).host
11181138
end
@@ -1127,24 +1147,25 @@ def url_match_homepage?
11271147
host_uri.host
11281148
end
11291149

1130-
return false if homepage.blank?
1150+
home = homepage
1151+
return false if home.blank?
11311152

1132-
home = homepage.downcase
1153+
home.downcase!
11331154
if (split_host = T.must(host).split(".")).length >= 3
11341155
host = T.must(split_host[-2..]).join(".")
11351156
end
1136-
if (split_home = homepage.split(".")).length >= 3
1137-
home = split_home[-2..].join(".")
1157+
if (split_home = home.split(".")).length >= 3
1158+
home = T.must(split_home[-2..]).join(".")
11381159
end
11391160
host == home
11401161
end
11411162

1142-
# sig { params(url: String).returns(String) }
1163+
sig { params(url: String).returns(String) }
11431164
def strip_url_scheme(url)
11441165
url.sub(%r{^[^:/]+://(www\.)?}, "")
11451166
end
11461167

1147-
# sig { returns(String) }
1168+
sig { returns(String) }
11481169
def url_from_verified
11491170
strip_url_scheme(T.must(cask.url).verified)
11501171
end
@@ -1154,8 +1175,10 @@ def verified_matches_url?
11541175
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
11551176
verified_domain, verified_path = url_from_verified.split("/", 2)
11561177

1157-
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
1158-
(!verified_path || url_path&.start_with?(verified_path))
1178+
domains_match = (url_domain == verified_domain) ||
1179+
(verified_domain && url_domain&.end_with?(".#{verified_domain}"))
1180+
paths_match = !verified_path || url_path&.start_with?(verified_path)
1181+
(domains_match && paths_match) || false
11591182
end
11601183

11611184
sig { returns(T::Boolean) }
@@ -1177,10 +1200,10 @@ def block_url_offline?
11771200

11781201
sig { returns(Tap) }
11791202
def core_tap
1180-
@core_tap ||= CoreTap.instance
1203+
@core_tap ||= T.let(CoreTap.instance, T.nilable(Tap))
11811204
end
11821205

1183-
# sig { returns(T::Array[String]) }
1206+
sig { returns(T::Array[String]) }
11841207
def core_formula_names
11851208
core_tap.formula_names
11861209
end

Library/Homebrew/cli/parser.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ def generate_help_text
445445
.gsub(/`(.*?)`/m, "#{Tty.bold}\\1#{Tty.reset}")
446446
.gsub(%r{<([^\s]+?://[^\s]+?)>}) { |url| Formatter.url(url) }
447447
.gsub(/\*(.*?)\*|<(.*?)>/m) do |underlined|
448-
underlined[1...-1].gsub(/^(\s*)(.*?)$/, "\\1#{Tty.underline}\\2#{Tty.reset}")
448+
T.must(underlined[1...-1]).gsub(/^(\s*)(.*?)$/, "\\1#{Tty.underline}\\2#{Tty.reset}")
449449
end
450450
end
451451

Library/Homebrew/utils/formatter.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# typed: true # rubocop:todo Sorbet/StrictSigil
1+
# typed: strict
22
# frozen_string_literal: true
33

44
require "utils/tty"
@@ -10,46 +10,54 @@ module Formatter
1010
COMMAND_DESC_WIDTH = 80
1111
OPTION_DESC_WIDTH = 45
1212

13+
sig { params(string: String, color: T.nilable(Symbol)).returns(String) }
1314
def self.arrow(string, color: nil)
1415
prefix("==>", string, color)
1516
end
1617

1718
# Format a string as headline.
1819
#
1920
# @api internal
21+
sig { params(string: String, color: T.nilable(Symbol)).returns(String) }
2022
def self.headline(string, color: nil)
2123
arrow("#{Tty.bold}#{string}#{Tty.reset}", color:)
2224
end
2325

26+
sig { params(string: Object).returns(String) }
2427
def self.identifier(string)
2528
"#{Tty.green}#{string}#{Tty.default}"
2629
end
2730

31+
sig { params(string: String).returns(String) }
2832
def self.bold(string)
2933
"#{Tty.bold}#{string}#{Tty.reset}"
3034
end
3135

36+
sig { params(string: String).returns(String) }
3237
def self.option(string)
3338
bold(string)
3439
end
3540

3641
# Format a string as success, with an optional label.
3742
#
3843
# @api internal
44+
sig { params(string: String, label: T.nilable(String)).returns(String) }
3945
def self.success(string, label: nil)
4046
label(label, string, :green)
4147
end
4248

4349
# Format a string as warning, with an optional label.
4450
#
4551
# @api internal
52+
sig { params(string: T.any(String, Exception), label: T.nilable(String)).returns(String) }
4653
def self.warning(string, label: nil)
4754
label(label, string, :yellow)
4855
end
4956

5057
# Format a string as error, with an optional label.
5158
#
5259
# @api internal
60+
sig { params(string: T.any(String, Exception), label: T.nilable(String)).returns(String) }
5361
def self.error(string, label: nil)
5462
label(label, string, :red)
5563
end
@@ -80,6 +88,7 @@ def self.truncate(string, max: 30, omission: "...")
8088
# so we always wrap one word before an option.
8189
# @see https://github.com/Homebrew/brew/pull/12672
8290
# @see https://macromates.com/blog/2006/wrapping-text-with-regular-expressions/
91+
sig { params(string: String, width: Integer).returns(String) }
8392
def self.format_help_text(string, width: 172)
8493
desc = OPTION_DESC_WIDTH
8594
indent = width - desc
@@ -90,21 +99,26 @@ def self.format_help_text(string, width: 172)
9099
.gsub(/(.{1,#{width}})( +|$)(?!-)\n?/, "\\1\n")
91100
end
92101

102+
sig { params(string: T.any(NilClass, String, URI::Generic)).returns(String) }
93103
def self.url(string)
94104
"#{Tty.underline}#{string}#{Tty.no_underline}"
95105
end
96106

107+
sig { params(label: T.nilable(String), string: T.any(String, Exception), color: Symbol).returns(String) }
97108
def self.label(label, string, color)
98109
label = "#{label}:" unless label.nil?
99110
prefix(label, string, color)
100111
end
101112
private_class_method :label
102113

114+
sig {
115+
params(prefix: T.nilable(String), string: T.any(String, Exception), color: T.nilable(Symbol)).returns(String)
116+
}
103117
def self.prefix(prefix, string, color)
104118
if prefix.nil? && color.nil?
105-
string
119+
string.to_s
106120
elsif prefix.nil?
107-
"#{Tty.send(color)}#{string}#{Tty.reset}"
121+
"#{Tty.send(T.must(color))}#{string}#{Tty.reset}"
108122
elsif color.nil?
109123
"#{prefix} #{string}"
110124
else
@@ -116,7 +130,8 @@ def self.prefix(prefix, string, color)
116130
# Layout objects in columns that fit the current terminal width.
117131
#
118132
# @api internal
119-
def self.columns(*objects, gap_size: 2)
133+
sig { params(objects: T::Array[String], gap_size: Integer).returns(String) }
134+
def self.columns(objects, gap_size: 2)
120135
objects = objects.flatten.map(&:to_s)
121136

122137
fallback = proc do
@@ -145,7 +160,7 @@ def self.columns(*objects, gap_size: 2)
145160
item_indices_for_row = T.cast(row_index.step(objects.size - 1, rows).to_a, T::Array[Integer])
146161

147162
first_n = T.must(item_indices_for_row[0...-1]).map do |index|
148-
objects[index] + "".rjust(col_width - object_lengths.fetch(index))
163+
objects.fetch(index) + "".rjust(col_width - object_lengths.fetch(index))
149164
end
150165

151166
# don't add trailing whitespace to last column

0 commit comments

Comments
 (0)