diff --git a/config/default.yml b/config/default.yml index 35e50559..e0dc4658 100644 --- a/config/default.yml +++ b/config/default.yml @@ -89,6 +89,11 @@ Sorbet/ForbidIncludeConstLiteral: VersionAdded: 0.2.0 VersionChanged: 0.5.0 +Sorbet/ForbidTypeAliasedShapes: + Description: 'Forbids defining type aliases that contain shapes' + Enabled: false + VersionAdded: 0.7.6 + Sorbet/ForbidSuperclassConstLiteral: Description: 'Forbid superclasses which are non-literal constants.' Enabled: false diff --git a/lib/rubocop/cop/sorbet/forbid_type_aliased_shapes.rb b/lib/rubocop/cop/sorbet/forbid_type_aliased_shapes.rb new file mode 100644 index 00000000..51383d00 --- /dev/null +++ b/lib/rubocop/cop/sorbet/forbid_type_aliased_shapes.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "rubocop" + +module RuboCop + module Cop + module Sorbet + # Disallows defining type aliases that contain shapes + # + # @example + # + # # bad + # Foo = T.type_alias { { foo: Integer } } + # + # # good + # class Foo + # extend T::Sig + # + # sig { params(foo: Integer).void } + # def initialize(foo) + # @foo = foo + # end + # end + class ForbidTypeAliasedShapes < RuboCop::Cop::Base + MSG = "Type aliases shouldn't contain shapes because of significant performance overhead" + + # @!method shape_type_alias?(node) + def_node_matcher(:shape_type_alias?, <<-PATTERN) + (block + (send (const {nil? cbase} :T) :type_alias) + (args) + `hash + ) + PATTERN + + def on_block(node) + add_offense(node) if shape_type_alias?(node) + end + + alias_method :on_numblock, :on_block + end + end + end +end diff --git a/lib/rubocop/cop/sorbet_cops.rb b/lib/rubocop/cop/sorbet_cops.rb index c22a3d14..4eb3dfee 100644 --- a/lib/rubocop/cop/sorbet_cops.rb +++ b/lib/rubocop/cop/sorbet_cops.rb @@ -7,6 +7,7 @@ require_relative "sorbet/constants_from_strings" require_relative "sorbet/forbid_superclass_const_literal" require_relative "sorbet/forbid_include_const_literal" +require_relative "sorbet/forbid_type_aliased_shapes" require_relative "sorbet/forbid_untyped_struct_props" require_relative "sorbet/implicit_conversion_method" require_relative "sorbet/one_ancestor_per_line" diff --git a/manual/cops.md b/manual/cops.md index 40704880..33e996b9 100644 --- a/manual/cops.md +++ b/manual/cops.md @@ -23,6 +23,7 @@ In the following section you find all available cops: * [Sorbet/ForbidTStruct](cops_sorbet.md#sorbetforbidtstruct) * [Sorbet/ForbidTUnsafe](cops_sorbet.md#sorbetforbidtunsafe) * [Sorbet/ForbidTUntyped](cops_sorbet.md#sorbetforbidtuntyped) +* [Sorbet/ForbidTypeAliasedShapes](cops_sorbet.md#sorbetforbidtypealiasedshapes) * [Sorbet/ForbidUntypedStructProps](cops_sorbet.md#sorbetforbiduntypedstructprops) * [Sorbet/HasSigil](cops_sorbet.md#sorbethassigil) * [Sorbet/IgnoreSigil](cops_sorbet.md#sorbetignoresigil) diff --git a/manual/cops_sorbet.md b/manual/cops_sorbet.md index 4989af05..07fa9ab6 100644 --- a/manual/cops_sorbet.md +++ b/manual/cops_sorbet.md @@ -496,6 +496,31 @@ sig { params(my_argument: String).void } def foo(my_argument); end ``` +## Sorbet/ForbidTypeAliasedShapes + +Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged +--- | --- | --- | --- | --- +Disabled | Yes | No | 0.7.6 | - + +Disallows defining type aliases that contain shapes + +### Examples + +```ruby +# bad +Foo = T.type_alias { { foo: Integer } } + +# good +class Foo + extend T::Sig + + sig { params(foo: Integer).void } + def initialize(foo) + @foo = foo + end +end +``` + ## Sorbet/ForbidUntypedStructProps Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged diff --git a/spec/rubocop/cop/sorbet/forbid_type_aliased_shapes_spec.rb b/spec/rubocop/cop/sorbet/forbid_type_aliased_shapes_spec.rb new file mode 100644 index 00000000..09508971 --- /dev/null +++ b/spec/rubocop/cop/sorbet/forbid_type_aliased_shapes_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe(RuboCop::Cop::Sorbet::ForbidTypeAliasedShapes, :config) do + def message + "Type aliases shouldn't contain shapes because of significant performance overhead" + end + + it("allows defining type aliases that don't contain shapes") do + expect_no_offenses(<<~RUBY) + Foo = T.type_alias { Integer } + RUBY + end + + it("disallows defining type aliases that contain shapes") do + expect_offense(<<~RUBY) + Foo = T.type_alias { { foo: Integer } } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message} + RUBY + end + + it("disallows defining type aliases that contain nested shapes") do + expect_offense(<<~RUBY) + A = T.type_alias { [{ foo: Integer }] } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message} + B = T.type_alias { T.nilable({ foo: Integer }) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message} + C = T.type_alias { T::Hash[Symbol, { foo: Integer }] } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message} + D = T.type_alias { T::Hash[Symbol, T::Array[T.any(String, { foo: Integer })]] } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{message} + RUBY + end +end