Skip to content

Commit

Permalink
Introduce Sorbet/ForbidSigWithoutRuntime
Browse files Browse the repository at this point in the history
This allows us to forbid usages of `T::Sig::WithoutRuntime.sig`.

By default, this cop is disabled, we only enable it in the `rbi.yml`
configuration.

Signed-off-by: Alexandre Terrasa <[email protected]>
  • Loading branch information
Morriar committed Feb 25, 2025
1 parent 005a485 commit ca4ede7
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 11 deletions.
7 changes: 6 additions & 1 deletion config/default.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Sorbet/Refinement:
Description: >-
Description: >-
Checks for the use of Ruby Refinements library. Refinements add
complexity and incur a performance penalty that can be significant
for large code bases. They are also not supported by Sorbet.
Expand Down Expand Up @@ -102,6 +102,11 @@ Sorbet/ForbidTypeAliasedShapes:
Enabled: false
VersionAdded: 0.7.6

Sorbet/ForbidSigWithoutRuntime:
Description: 'Forbid usage of T::Sig::WithoutRuntime.sig'
Enabled: false
VersionAdded: <<next>>

Sorbet/ForbidSuperclassConstLiteral:
Description: 'Forbid superclasses which are non-literal constants.'
Enabled: false
Expand Down
3 changes: 3 additions & 0 deletions config/rbi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ Lint/Syntax:

## Sorbet

Sorbet/ForbidSigWithoutRuntime:
Enabled: true

Sorbet/EnforceSigilOrder:
Enabled: true

Expand Down
27 changes: 20 additions & 7 deletions lib/rubocop/cop/sorbet/mixin/signature_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,34 @@ module SignatureHelp

# @!method signature?(node)
def_node_matcher(:signature?, <<~PATTERN)
{#bare_sig? #sig_with_runtime? #sig_without_runtime?}
PATTERN

# @!method bare_sig?(node)
def_node_matcher(:bare_sig?, <<~PATTERN)
(block (send
{nil? #with_runtime? #without_runtime?}
nil?
:sig
(sym :final)?
) (args) ...)
PATTERN

# @!method with_runtime?(node)
def_node_matcher(:with_runtime?, <<~PATTERN)
(const (const {nil? cbase} :T) :Sig)
# @!method sig_with_runtime?(node)
def_node_matcher(:sig_with_runtime?, <<~PATTERN)
(block (send
(const (const {nil? cbase} :T) :Sig)
:sig
(sym :final)?
) (args) ...)
PATTERN

# @!method without_runtime?(node)
def_node_matcher(:without_runtime?, <<~PATTERN)
(const (const (const {nil? cbase} :T) :Sig) :WithoutRuntime)
# @!method sig_without_runtime?(node)
def_node_matcher(:sig_without_runtime?, <<~PATTERN)
(block (send
(const (const (const {nil? cbase} :T) :Sig) :WithoutRuntime)
:sig
(sym :final)?
) (args) ...)
PATTERN

def on_block(node)
Expand Down
43 changes: 43 additions & 0 deletions lib/rubocop/cop/sorbet/signatures/forbid_sig_without_runtime.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require "stringio"

module RuboCop
module Cop
module Sorbet
# Check that `sig` is used instead of `T::Sig::WithoutRuntime.sig`.
#
# Good:
#
# ```
# sig { void }
# def foo; end
# ```
#
# Bad:
#
# ```
# T::Sig::WithoutRuntime.sig { void }
# def foo; end
# ```
class ForbidSigWithoutRuntime < ::RuboCop::Cop::Base
include SignatureHelp
extend AutoCorrector

MSG = "Do not use `T::Sig::WithoutRuntime.sig`. Use `sig` instead."

def on_signature(node)
return unless sig_without_runtime?(node)

sig = node.children[0]
# puts "source: #{sig.children.inspect}"
# puts "sig: #{sig.source}"

add_offense(sig) do |corrector|
corrector.replace(sig, sig.source.gsub(/T\s*::\s*Sig\s*::\s*WithoutRuntime\s*\.\s*/m, ""))
end
end
end
end
end
end
7 changes: 4 additions & 3 deletions lib/rubocop/cop/sorbet_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@

require_relative "sorbet/signatures/allow_incompatible_override"
require_relative "sorbet/signatures/checked_true_in_signature"
require_relative "sorbet/signatures/void_checked_tests"
require_relative "sorbet/signatures/empty_line_after_sig"
require_relative "sorbet/signatures/enforce_signatures"
require_relative "sorbet/signatures/forbid_sig_without_runtime"
require_relative "sorbet/signatures/keyword_argument_ordering"
require_relative "sorbet/signatures/signature_build_order"
require_relative "sorbet/signatures/enforce_signatures"
require_relative "sorbet/signatures/empty_line_after_sig"
require_relative "sorbet/signatures/void_checked_tests"

require_relative "sorbet/sigils/valid_sigil"
require_relative "sorbet/sigils/has_sigil"
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ In the following section you find all available cops:
* [Sorbet/ForbidExtendTSigHelpersInShims](cops_sorbet.md#sorbetforbidextendtsighelpersinshims)
* [Sorbet/ForbidIncludeConstLiteral](cops_sorbet.md#sorbetforbidincludeconstliteral)
* [Sorbet/ForbidRBIOutsideOfAllowedPaths](cops_sorbet.md#sorbetforbidrbioutsideofallowedpaths)
* [Sorbet/ForbidSigWithoutRuntime](cops_sorbet.md#sorbetforbidsigwithoutruntime)
* [Sorbet/ForbidSuperclassConstLiteral](cops_sorbet.md#sorbetforbidsuperclassconstliteral)
* [Sorbet/ForbidTEnum](cops_sorbet.md#sorbetforbidtenum)
* [Sorbet/ForbidTStruct](cops_sorbet.md#sorbetforbidtstruct)
Expand Down
22 changes: 22 additions & 0 deletions manual/cops_sorbet.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,28 @@ Name | Default value | Configurable values
AllowedPaths | `rbi/**`, `sorbet/rbi/**` | Array
Include | `**/*.rbi` | Array

## Sorbet/ForbidSigWithoutRuntime

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
--- | --- | --- | --- | ---
Disabled | Yes | Yes | <<next>> | -

Check that `sig` is used instead of `T::Sig::WithoutRuntime.sig`.

Good:

```
sig { void }
def foo; end
```

Bad:

```
T::Sig::WithoutRuntime.sig { void }
def foo; end
```

## Sorbet/ForbidSuperclassConstLiteral

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe(RuboCop::Cop::Sorbet::ForbidSigWithoutRuntime, :config) do
def message
RuboCop::Cop::Sorbet::ForbidSigWithoutRuntime::MSG
end

describe("offenses") do
it("allows using sig") do
expect_no_offenses(<<~RUBY)
sig { void }
def foo; end
RUBY
end

it("disallows using T::Sig::WithoutRuntime.sig { ... }") do
expect_offense(<<~RUBY)
T::Sig::WithoutRuntime.sig { void }
^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message}
def foo; end
RUBY

expect_correction(<<~RUBY)
sig { void }
def foo; end
RUBY
end

it("disallows using T::Sig::WithoutRuntime.sig do ... end") do
expect_offense(<<~RUBY)
T::Sig::WithoutRuntime.sig do
^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message}
void
end
def self.foo(x); end
RUBY

expect_correction(<<~RUBY)
sig do
void
end
def self.foo(x); end
RUBY
end

it("autocorrects with the correct parameters and block") do
expect_offense(<<~RUBY)
T::Sig::WithoutRuntime.sig(:final) do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message}
params(
x: A,
y: B,
).returns(C)
end
def self.foo(x, y); end
RUBY

expect_correction(<<~RUBY)
sig(:final) do
params(
x: A,
y: B,
).returns(C)
end
def self.foo(x, y); end
RUBY
end

it("autocorrects with the correct parameters and block in a multiline sig") do
expect_offense(<<~RUBY)
T::
^^^ #{message}
Sig::
WithoutRuntime
.sig(:final) do
params(
x: A,
y: B,
).returns(C)
end
def self.foo(x, y); end
RUBY

expect_correction(<<~RUBY)
sig(:final) do
params(
x: A,
y: B,
).returns(C)
end
def self.foo(x, y); end
RUBY
end
end
end

0 comments on commit ca4ede7

Please sign in to comment.