Skip to content

Commit 2a48e23

Browse files
authored
Merge pull request #21486 from Homebrew/cask-artifact-strict-typing
cask/artifact: enable Sorbet `typed: strict` for abstract classes
2 parents c8fbbb7 + 2d06396 commit 2a48e23

File tree

13 files changed

+187
-80
lines changed

13 files changed

+187
-80
lines changed

Library/Homebrew/cask/artifact/abstract_artifact.rb

Lines changed: 82 additions & 54 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 "extend/object/deep_dup"
@@ -16,25 +16,35 @@ class AbstractArtifact
1616
include Comparable
1717
include ::Utils::Output::Mixin
1818

19+
# T.anything or the union of all possible argument types would be better choice, but it's convenient to be
20+
# able to invoke `.inspect`, `.to_s`, etc. without the overhead of type guards.
21+
DirectivesType = T.type_alias { Object }
22+
23+
sig { overridable.returns(String) }
1924
def self.english_name
20-
@english_name ||= T.must(name).sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')
25+
@english_name ||= T.let(T.must(name).sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2'), T.nilable(String))
2126
end
2227

28+
sig { returns(String) }
2329
def self.english_article
24-
@english_article ||= /^[aeiou]/i.match?(english_name) ? "an" : "a"
30+
@english_article ||= T.let(/^[aeiou]/i.match?(english_name) ? "an" : "a", T.nilable(String))
2531
end
2632

33+
sig { overridable.returns(Symbol) }
2734
def self.dsl_key
28-
@dsl_key ||= T.must(name).sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym
35+
@dsl_key ||= T.let(T.must(name).sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym,
36+
T.nilable(Symbol))
2937
end
3038

39+
sig { overridable.returns(Symbol) }
3140
def self.dirmethod
32-
@dirmethod ||= :"#{dsl_key}dir"
41+
@dirmethod ||= T.let(:"#{dsl_key}dir", T.nilable(Symbol))
3342
end
3443

3544
sig { abstract.returns(String) }
3645
def summarize; end
3746

47+
sig { params(path: T.any(String, Pathname)).returns(Pathname) }
3848
def staged_path_join_executable(path)
3949
path = Pathname(path)
4050
path = path.expand_path if path.to_s.start_with?("~")
@@ -54,64 +64,78 @@ def staged_path_join_executable(path)
5464
end
5565
end
5666

67+
sig { returns(T::Hash[T.class_of(AbstractArtifact), Integer]) }
5768
def sort_order
58-
@sort_order ||= [
59-
PreflightBlock,
60-
# The `uninstall` stanza should be run first, as it may
61-
# depend on other artifacts still being installed.
62-
Uninstall,
63-
Installer,
64-
# `pkg` should be run before `binary`, so
65-
# targets are created prior to linking.
66-
# `pkg` should be run before `app`, since an `app` could
67-
# contain a nested installer (e.g. `wireshark`).
68-
Pkg,
69+
@sort_order ||= T.let(
6970
[
70-
App,
71-
Suite,
72-
Artifact,
73-
Colorpicker,
74-
Prefpane,
75-
Qlplugin,
76-
Mdimporter,
77-
Dictionary,
78-
Font,
79-
Service,
80-
InputMethod,
81-
InternetPlugin,
82-
KeyboardLayout,
83-
AudioUnitPlugin,
84-
VstPlugin,
85-
Vst3Plugin,
86-
ScreenSaver,
87-
],
88-
Binary,
89-
Manpage,
90-
PostflightBlock,
91-
Zap,
92-
].each_with_index.flat_map { |classes, i| Array(classes).map { |c| [c, i] } }.to_h
71+
PreflightBlock,
72+
# The `uninstall` stanza should be run first, as it may
73+
# depend on other artifacts still being installed.
74+
Uninstall,
75+
Installer,
76+
# `pkg` should be run before `binary`, so
77+
# targets are created prior to linking.
78+
# `pkg` should be run before `app`, since an `app` could
79+
# contain a nested installer (e.g. `wireshark`).
80+
Pkg,
81+
[
82+
App,
83+
Suite,
84+
Artifact,
85+
Colorpicker,
86+
Prefpane,
87+
Qlplugin,
88+
Mdimporter,
89+
Dictionary,
90+
Font,
91+
Service,
92+
InputMethod,
93+
InternetPlugin,
94+
KeyboardLayout,
95+
AudioUnitPlugin,
96+
VstPlugin,
97+
Vst3Plugin,
98+
ScreenSaver,
99+
],
100+
Binary,
101+
Manpage,
102+
PostflightBlock,
103+
Zap,
104+
].each_with_index.flat_map { |classes, i| Array(classes).map { |c| [c, i] } }.to_h,
105+
T.nilable(T::Hash[T.class_of(AbstractArtifact), Integer]),
106+
)
93107
end
94108

109+
sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
95110
def <=>(other)
96-
return unless other.class < AbstractArtifact
97-
return 0 if instance_of?(other.class)
111+
case other
112+
when AbstractArtifact
113+
return 0 if instance_of?(other.class)
98114

99-
(sort_order[self.class] <=> sort_order[other.class]).to_i
115+
(sort_order[self.class] <=> sort_order[other.class]).to_i
116+
end
100117
end
101118

102119
# TODO: this sort of logic would make more sense in dsl.rb, or a
103120
# constructor called from dsl.rb, so long as that isn't slow.
121+
sig {
122+
params(
123+
arguments: DirectivesType,
124+
stanza: T.any(String, Symbol),
125+
default_arguments: T::Hash[Symbol, T.anything],
126+
override_arguments: T::Hash[Symbol, T.anything],
127+
key: T.nilable(Symbol),
128+
).returns([T.nilable(String), T::Hash[Symbol, T.untyped]])
129+
}
104130
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
105131
# TODO: when stanza names are harmonized with class names,
106132
# stanza may not be needed as an explicit argument
107133
description = key ? "#{stanza} #{key.inspect}" : stanza.to_s
108134

109-
# backward-compatible string value
110-
arguments = if arguments.is_a?(String)
111-
{ executable: arguments }
112-
else
113-
# Avoid mutating the original argument
114-
arguments.dup
135+
arguments = case arguments
136+
when String then { executable: arguments } # backward-compatible string value
137+
when Hash then arguments.dup # Avoid mutating the original argument
138+
else odie "Unsupported arguments type #{arguments.class}"
115139
end
116140

117141
# key sanity
@@ -140,18 +164,21 @@ def self.read_script_arguments(arguments, stanza, default_arguments = {}, overri
140164
[executable, arguments]
141165
end
142166

167+
sig { returns(Cask) }
143168
attr_reader :cask
144169

170+
sig { params(cask: Cask, dsl_args: T.anything).void }
145171
def initialize(cask, *dsl_args)
146172
@cask = cask
147-
@dirmethod = nil
148-
@dsl_args = dsl_args.deep_dup
149-
@dsl_key = nil
150-
@english_article = nil
151-
@english_name = nil
152-
@sort_order = nil
173+
@dirmethod = T.let(nil, T.nilable(Symbol))
174+
@dsl_args = T.let(dsl_args.deep_dup, T::Array[T.anything])
175+
@dsl_key = T.let(nil, T.nilable(Symbol))
176+
@english_article = T.let(nil, T.nilable(String))
177+
@english_name = T.let(nil, T.nilable(String))
178+
@sort_order = T.let(nil, T.nilable(T::Hash[T.class_of(AbstractArtifact), Integer]))
153179
end
154180

181+
sig { returns(Config) }
155182
def config
156183
cask.config
157184
end
@@ -161,6 +188,7 @@ def to_s
161188
"#{summarize} (#{self.class.english_name})"
162189
end
163190

191+
sig { returns(T::Array[T.anything]) }
164192
def to_args
165193
@dsl_args.compact_blank
166194
end

Library/Homebrew/cask/artifact/abstract_flight_block.rb

Lines changed: 13 additions & 4 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/artifact/abstract_artifact"
@@ -7,44 +7,53 @@ module Cask
77
module Artifact
88
# Abstract superclass for block artifacts.
99
class AbstractFlightBlock < AbstractArtifact
10+
sig { override.returns(Symbol) }
1011
def self.dsl_key
1112
super.to_s.sub(/_block$/, "").to_sym
1213
end
1314

15+
sig { returns(Symbol) }
1416
def self.uninstall_dsl_key
1517
:"uninstall_#{dsl_key}"
1618
end
1719

20+
sig { returns(T::Hash[Symbol, DirectivesType]) }
1821
attr_reader :directives
1922

23+
sig { params(cask: Cask, directives: DirectivesType).void }
2024
def initialize(cask, **directives)
2125
super(cask)
2226
@directives = directives
2327
end
2428

25-
def install_phase(**)
29+
sig { params(_options: T.anything).void }
30+
def install_phase(**_options)
2631
abstract_phase(self.class.dsl_key)
2732
end
2833

29-
def uninstall_phase(**)
34+
sig { params(_options: T.anything).void }
35+
def uninstall_phase(**_options)
3036
abstract_phase(self.class.uninstall_dsl_key)
3137
end
3238

39+
sig { override.returns(String) }
3340
def summarize
3441
directives.keys.map(&:to_s).join(", ")
3542
end
3643

3744
private
3845

46+
sig { params(dsl_key: Symbol).returns(T::Class[::Cask::DSL::Base]) }
3947
def class_for_dsl_key(dsl_key)
4048
namespace = self.class.name.to_s.sub(/::.*::.*$/, "")
4149
self.class.const_get("#{namespace}::DSL::#{dsl_key.to_s.split("_").map(&:capitalize).join}")
4250
end
4351

52+
sig { params(dsl_key: Symbol).void }
4453
def abstract_phase(dsl_key)
4554
return if (block = directives[dsl_key]).nil?
4655

47-
class_for_dsl_key(dsl_key).new(cask).instance_eval(&block)
56+
class_for_dsl_key(dsl_key).new(cask).instance_eval(&T.cast(block, T.proc.returns(T.anything)))
4857
end
4958
end
5059
end

0 commit comments

Comments
 (0)