Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion lib/tapioca/helpers/rbi_files_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [],
res = sorbet(
"--no-config",
"--error-url-base=#{error_url_base}",
"--stop-after namer",
"--stop-after resolver",
dsl_dir,
gem_dir,
)
Expand Down Expand Up @@ -132,6 +132,11 @@ def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [],
if auto_strictness
redef_errors = errors.select { |error| error.code == 4010 }
update_gem_rbis_strictnesses(redef_errors, gem_dir)

payload_superclass_errors = errors.select do |error|
error.more.any? { |line| line.include?(SUPPRESS_PAYLOAD_SUPERCLASS_REDEFINITION_FLAG) }
end
update_sorbet_config_for_payload_superclass_redefinitions(payload_superclass_errors)
end

Kernel.raise Tapioca::Error, error_messages.join("\n") if parse_errors.any?
Expand Down Expand Up @@ -258,6 +263,57 @@ def extract_methods_and_attrs(nodes)
)
end

SUPPRESS_PAYLOAD_SUPERCLASS_REDEFINITION_FLAG =
"--suppress-payload-superclass-redefinition-for" #: String

#: (Array[Spoom::Sorbet::Errors::Error] errors) -> void
def update_sorbet_config_for_payload_superclass_redefinitions(errors)
errors
.filter_map { |error| payload_superclass_constant_from_error(error) }
.uniq
.each { |constant| add_payload_superclass_suppression_to_config(constant) }
end

#: (Spoom::Sorbet::Errors::Error error) -> String?
def payload_superclass_constant_from_error(error)
error.more.each do |line|
if line =~ /--suppress-payload-superclass-redefinition-for=([^\s`]+)/
return T.must(Regexp.last_match(1))
end
end

nil
end

#: (String constant) -> void
def add_payload_superclass_suppression_to_config(constant)
flag = "#{SUPPRESS_PAYLOAD_SUPERCLASS_REDEFINITION_FLAG}=#{constant}"
config_path = Tapioca::SORBET_CONFIG_FILE
config = File.exist?(config_path) ? File.read(config_path) : ""
flag_already_present = config.lines(chomp: true).include?(flag)

if flag_already_present
say(
"\n Payload superclass of `#{constant}` was redefined; `#{flag}` is already in sorbet/config",
[:yellow, :bold],
)
else
FileUtils.mkdir_p(File.dirname(config_path))
if config.empty?
File.write(config_path, "#{flag}\n")
else
suffix = config.end_with?("\n") ? "" : "\n"
File.write(config_path, "#{config}#{suffix}#{flag}\n")
end
say(
"\n Added `#{flag}` to sorbet/config (payload superclass of `#{constant}` was redefined)",
[:yellow, :bold],
)
end

say("\n")
end

#: (Array[Spoom::Sorbet::Errors::Error] errors, String gem_dir) -> void
def update_gem_rbis_strictnesses(errors, gem_dir)
files = []
Expand Down
81 changes: 81 additions & 0 deletions spec/tapioca/cli/gem_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,87 @@ def foo; end

@project.remove!("sorbet/rbi/shims/foo.rbi")
end

it "must add a payload superclass redefinition suppression to sorbet/config" do
config = @project.read("sorbet/config")
@project.write!(
"sorbet/config",
"#{config.rstrip}\n--suppress-payload-superclass-redefinition-for=Net::IMAP::CommandData\n",
)

@project.write!("sorbet/rbi/gems/bar@0.3.0.rbi", <<~RBI)
# typed: true

module Bar
end

class Net::IMAP::Literal < ::String
end
RBI

result = @project.tapioca("gem foo")

assert_stdout_includes(result, <<~OUT)
Checking generated RBI files... Done


Added `--suppress-payload-superclass-redefinition-for=Net::IMAP::Literal` to sorbet/config (payload superclass of `Net::IMAP::Literal` was redefined)
OUT

assert_empty_stderr(result)
assert_success_status(result)

config = @project.read("sorbet/config")
assert_equal(
1,
config.lines(chomp: true).count("--suppress-payload-superclass-redefinition-for=Net::IMAP::Literal"),
)
assert_equal(
1,
config.lines(chomp: true).count("--suppress-payload-superclass-redefinition-for=Net::IMAP::CommandData"),
)

result = @project.tapioca("gem foo")

assert_stdout_includes(result, <<~OUT)
Payload superclass of `Net::IMAP::Literal` was redefined; `--suppress-payload-superclass-redefinition-for=Net::IMAP::Literal` is already in sorbet/config
OUT

config = @project.read("sorbet/config")
assert_equal(
1,
config.lines(chomp: true).count("--suppress-payload-superclass-redefinition-for=Net::IMAP::Literal"),
)

assert_empty_stderr(result)
assert_success_status(result)
end

it "must not add a payload suppression for non-payload superclass redefinitions" do
@project.write!("sorbet/rbi/dsl/non_payload_superclass_conflict.rbi", <<~RBI)
# typed: true

class NonPayloadSuperclassConflict < ::Object
end
RBI

@project.write!("sorbet/rbi/gems/bar@0.3.0.rbi", <<~RBI)
# typed: true

class NonPayloadSuperclassConflict < ::String
end
RBI

result = @project.tapioca("gem foo")

refute_includes(
@project.read("sorbet/config"),
"--suppress-payload-superclass-redefinition-for=NonPayloadSuperclassConflict",
)

assert_empty_stderr(result)
assert_success_status(result)
end
end

describe "sanity" do
Expand Down
Loading