Skip to content

Commit

Permalink
Merge pull request #165 from dduugg/implicit-conversion-method-cop
Browse files Browse the repository at this point in the history
Add new `Sorbet/ImplicitConversionMethod` cop
  • Loading branch information
Morriar authored Jul 14, 2023
2 parents 7d639be + 9887882 commit 0a4c5ee
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 0 deletions.
7 changes: 7 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ Sorbet/IgnoreSigil:
- db/**/*.rb
- script/**/*

Sorbet/ImplicitConversionMethod:
Description: >-
This cop disallows declaring implicit conversion methods, as sorbet does
not support implicit conversion.
Enabled: false
VersionAdded: '<<next>>'

Sorbet/KeywordArgumentOrdering:
Description: >-
Enforces a compatible keyword arguments with Sorbet.
Expand Down
56 changes: 56 additions & 0 deletions lib/rubocop/cop/sorbet/implicit_conversion_method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

require "rubocop"

module RuboCop
module Cop
module Sorbet
# This cop disallows declaring implicit conversion methods.
# Since Sorbet is a nominal (not structural) type system,
# implicit conversion is currently unsupported.
#
# @example
#
# # bad
# def to_str; end
#
# # good
# def to_str(x); end
#
# # bad
# def self.to_str; end
#
# # good
# def self.to_str(x); end
#
# # bad
# alias to_str to_s
#
# @see https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html
# @note Since the arity of aliased methods is not checked, false positives may result.
class ImplicitConversionMethod < RuboCop::Cop::Base
IMPLICIT_CONVERSION_METHODS = [:to_ary, :to_int, :to_hash, :to_str].freeze
MSG = "Avoid implicit conversion methods, as Sorbet does not support them. " \
"Explicity convert to the desired type instead."
RESTRICT_ON_SEND = [:alias_method].freeze

def on_alias(node)
new_id = node.new_identifier
add_offense(new_id) if IMPLICIT_CONVERSION_METHODS.include?(new_id.value)
end

def on_def(node)
return unless IMPLICIT_CONVERSION_METHODS.include?(node.method_name)
return unless node.arguments.empty?

add_offense(node)
end
alias_method :on_defs, :on_def

def on_send(node)
add_offense(node.first_argument) if IMPLICIT_CONVERSION_METHODS.include?(node.first_argument.value)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/sorbet_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative "sorbet/forbid_superclass_const_literal"
require_relative "sorbet/forbid_include_const_literal"
require_relative "sorbet/forbid_untyped_struct_props"
require_relative "sorbet/implicit_conversion_method"
require_relative "sorbet/one_ancestor_per_line"
require_relative "sorbet/callback_conditionals_binding"
require_relative "sorbet/forbid_t_unsafe"
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ In the following section you find all available cops:
* [Sorbet/ForbidUntypedStructProps](cops_sorbet.md#sorbetforbiduntypedstructprops)
* [Sorbet/HasSigil](cops_sorbet.md#sorbethassigil)
* [Sorbet/IgnoreSigil](cops_sorbet.md#sorbetignoresigil)
* [Sorbet/ImplicitConversionMethod](cops_sorbet.md#sorbetimplicitconversionmethod)
* [Sorbet/KeywordArgumentOrdering](cops_sorbet.md#sorbetkeywordargumentordering)
* [Sorbet/OneAncestorPerLine](cops_sorbet.md#sorbetoneancestorperline)
* [Sorbet/RedundantExtendTSig](cops_sorbet.md#sorbetredundantextendtsig)
Expand Down
29 changes: 29 additions & 0 deletions manual/cops_sorbet.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,35 @@ SuggestedStrictness | `ignore` | String
Include | `**/*.{rb,rbi,rake,ru}` | Array
Exclude | `bin/**/*`, `db/**/*.rb`, `script/**/*` | Array

## Sorbet/ImplicitConversionMethod

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

This cop disallows declaring implicit conversion methods.
Since Sorbet is a nominal (not structural) type system,
implicit conversion is currently unsupported.

### Examples

```ruby
# bad
def to_str; end

# good
def to_str(x); end

# bad
def self.to_str; end

# good
def self.to_str(x); end

# bad
alias to_str to_s
```
## Sorbet/KeywordArgumentOrdering
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down
47 changes: 47 additions & 0 deletions spec/rubocop/cop/sorbet/implicit_conversion_method_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe(RuboCop::Cop::Sorbet::ImplicitConversionMethod, :config) do
def message
"Avoid implicit conversion methods, as Sorbet does not support them. " \
"Explicity convert to the desired type instead."
end

it "adds offense when defining implicit conversion instance method" do
expect_offense(<<~RUBY)
def to_ary
^^^^^^^^^^ #{message}
end
RUBY
end

it "adds offense when defining implicit conversion class method" do
expect_offense(<<~RUBY)
def self.to_int
^^^^^^^^^^^^^^^ #{message}
end
RUBY
end

it "does not add offense when method arguments exist" do
expect_no_offenses(<<~RUBY)
def to_int(foo)
end
RUBY
end

it "adds offense when declaring an implicit conversion method via alias" do
expect_offense(<<~RUBY)
alias to_str to_s
^^^^^^ #{message}
RUBY
end

it "adds offense when declaring an implicit conversion method via alias_method" do
expect_offense(<<~RUBY)
alias_method :to_hash, :to_h
^^^^^^^^ #{message}
RUBY
end
end

0 comments on commit 0a4c5ee

Please sign in to comment.