Exclude sorbet assignments from Style/MutableConstant cop
This commit is contained in:
		
							parent
							
								
									45ae9f44d4
								
							
						
					
					
						commit
						c354377f3e
					
				@ -5,6 +5,7 @@ require_relative "../extend/array"
 | 
			
		||||
require_relative "../extend/blank"
 | 
			
		||||
require_relative "blank"
 | 
			
		||||
require_relative "compact_blank"
 | 
			
		||||
require_relative "extend/mutable_constant_exclude_unfreezable"
 | 
			
		||||
require_relative "io_read"
 | 
			
		||||
require_relative "move_to_extend_os"
 | 
			
		||||
require_relative "negate_include"
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "rubocop/cop/style/mutable_constant"
 | 
			
		||||
 | 
			
		||||
module RuboCop
 | 
			
		||||
  module Cop
 | 
			
		||||
    module Sorbet
 | 
			
		||||
      # TODO: delete this file when https://github.com/Shopify/rubocop-sorbet/pull/256 is available
 | 
			
		||||
      module MutableConstantExcludeUnfreezable
 | 
			
		||||
        class << self
 | 
			
		||||
          sig { params(base: RuboCop::AST::NodePattern::Macros).void }
 | 
			
		||||
          def prepended(base)
 | 
			
		||||
            base.def_node_matcher(:t_let, <<~PATTERN)
 | 
			
		||||
              (send (const nil? :T) :let $_constant _type)
 | 
			
		||||
            PATTERN
 | 
			
		||||
 | 
			
		||||
            base.def_node_matcher(:t_type_alias?, <<~PATTERN)
 | 
			
		||||
              (block (send (const {nil? cbase} :T) :type_alias ...) ...)
 | 
			
		||||
            PATTERN
 | 
			
		||||
 | 
			
		||||
            base.def_node_matcher(:type_member?, <<~PATTERN)
 | 
			
		||||
              (block (send nil? :type_member ...) ...)
 | 
			
		||||
            PATTERN
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        sig { params(value: RuboCop::AST::Node).void }
 | 
			
		||||
        def on_assignment(value)
 | 
			
		||||
          T.unsafe(self).t_let(value) do |constant|
 | 
			
		||||
            value = T.let(constant, RuboCop::AST::Node)
 | 
			
		||||
          end
 | 
			
		||||
          return if T.unsafe(self).t_type_alias?(value)
 | 
			
		||||
          return if T.unsafe(self).type_member?(value)
 | 
			
		||||
 | 
			
		||||
          super
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
RuboCop::Cop::Style::MutableConstant.prepend(
 | 
			
		||||
  RuboCop::Cop::Sorbet::MutableConstantExcludeUnfreezable,
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,630 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "rubocops/extend/mutable_constant_exclude_unfreezable"
 | 
			
		||||
 | 
			
		||||
RSpec.describe(RuboCop::Cop::Style::MutableConstant, :config) do
 | 
			
		||||
  let(:prefix) { nil }
 | 
			
		||||
 | 
			
		||||
  shared_examples "mutable objects" do |o|
 | 
			
		||||
    context "when using T.let" do
 | 
			
		||||
      context "when assigning with =" do
 | 
			
		||||
        it "registers an offense for #{o} assigned to a constant " \
 | 
			
		||||
           "and corrects by adding .freeze" do
 | 
			
		||||
          expect_offense([prefix, <<~RUBY].compact.join("\n"), o:)
 | 
			
		||||
            CONST = T.let(%<o>s, Object)
 | 
			
		||||
                          ^{o} Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
          expect_correction([prefix, <<~RUBY].compact.join("\n"))
 | 
			
		||||
            CONST = T.let(#{o}.freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when assigning with ||=" do
 | 
			
		||||
        it "registers an offense for #{o} assigned to a constant " \
 | 
			
		||||
           "and corrects by adding .freeze" do
 | 
			
		||||
          expect_offense([prefix, <<~RUBY].compact.join("\n"), o:)
 | 
			
		||||
            CONST ||= T.let(%<o>s, Object)
 | 
			
		||||
                            ^{o} Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
          expect_correction([prefix, <<~RUBY].compact.join("\n"))
 | 
			
		||||
            CONST ||= T.let(#{o}.freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when not using T.let" do
 | 
			
		||||
      context "when assigning with =" do
 | 
			
		||||
        it "registers an offense for #{o} assigned to a constant " \
 | 
			
		||||
           "and corrects by adding .freeze" do
 | 
			
		||||
          expect_offense([prefix, <<~RUBY].compact.join("\n"), o:)
 | 
			
		||||
            CONST = %<o>s
 | 
			
		||||
                    ^{o} Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
          expect_correction([prefix, <<~RUBY].compact.join("\n"))
 | 
			
		||||
            CONST = #{o}.freeze
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when assigning with ||=" do
 | 
			
		||||
        it "registers an offense for #{o} assigned to a constant " \
 | 
			
		||||
           "and corrects by adding .freeze" do
 | 
			
		||||
          expect_offense([prefix, <<~RUBY].compact.join("\n"), o:)
 | 
			
		||||
            CONST ||= %<o>s
 | 
			
		||||
                      ^{o} Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
          expect_correction([prefix, <<~RUBY].compact.join("\n"))
 | 
			
		||||
            CONST ||= #{o}.freeze
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  shared_examples "immutable objects" do |o|
 | 
			
		||||
    context "when using T.let" do
 | 
			
		||||
      it "allows #{o} to be assigned to a constant" do
 | 
			
		||||
        const = if o.start_with?("<<~HERE")
 | 
			
		||||
          heredoc = o.delete_prefix("<<~HERE")
 | 
			
		||||
          "CONST = T.let(<<~HERE, Object)#{heredoc}"
 | 
			
		||||
        else
 | 
			
		||||
          "CONST = T.let(#{o.chomp}, Object)"
 | 
			
		||||
        end
 | 
			
		||||
        source = [prefix, const].compact.join("\n")
 | 
			
		||||
        expect_no_offenses(source)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "allows #{o} to be ||= to a constant" do
 | 
			
		||||
        const = if o.start_with?("<<~HERE")
 | 
			
		||||
          heredoc = o.delete_prefix("<<~HERE")
 | 
			
		||||
          "CONST ||= T.let(<<~HERE, Object)#{heredoc}"
 | 
			
		||||
        else
 | 
			
		||||
          "CONST ||= T.let(#{o.chomp}, Object)"
 | 
			
		||||
        end
 | 
			
		||||
        source = [prefix, const].compact.join("\n")
 | 
			
		||||
        expect_no_offenses(source)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when not using T.let" do
 | 
			
		||||
      it "allows #{o} to be assigned to a constant" do
 | 
			
		||||
        source = [prefix, "CONST = #{o}"].compact.join("\n")
 | 
			
		||||
        expect_no_offenses(source)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "allows #{o} to be ||= to a constant" do
 | 
			
		||||
        source = [prefix, "CONST ||= #{o}"].compact.join("\n")
 | 
			
		||||
        expect_no_offenses(source)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  shared_examples "literals that are frozen" do |o|
 | 
			
		||||
    let(:prefix) { o }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "immutable objects", "[1, 2, 3]"
 | 
			
		||||
    it_behaves_like "immutable objects", "%w(a b c)"
 | 
			
		||||
    it_behaves_like "immutable objects", "{ a: 1, b: 2 }"
 | 
			
		||||
    it_behaves_like "immutable objects", "'str'"
 | 
			
		||||
    it_behaves_like "immutable objects", %Q("top#{1 + 2}")
 | 
			
		||||
    it_behaves_like "immutable objects", "1"
 | 
			
		||||
    it_behaves_like "immutable objects", "1.5"
 | 
			
		||||
    it_behaves_like "immutable objects", ":sym"
 | 
			
		||||
    it_behaves_like "immutable objects", "FOO + BAR"
 | 
			
		||||
    it_behaves_like "immutable objects", "FOO - BAR"
 | 
			
		||||
    it_behaves_like "immutable objects", "'foo' + 'bar'"
 | 
			
		||||
    it_behaves_like "immutable objects", "ENV['foo']"
 | 
			
		||||
    it_behaves_like "immutable objects", "::ENV['foo']"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  shared_examples "literals that are not frozen" do |o|
 | 
			
		||||
    let(:prefix) { o }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "mutable objects", "[1, 2, 3]"
 | 
			
		||||
    it_behaves_like "mutable objects", "%w(a b c)"
 | 
			
		||||
    it_behaves_like "mutable objects", "{ a: 1, b: 2 }"
 | 
			
		||||
    it_behaves_like "mutable objects", "'str'"
 | 
			
		||||
    it_behaves_like "mutable objects", %Q("top#{1 + 2}")
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "immutable objects", "1"
 | 
			
		||||
    it_behaves_like "immutable objects", "1.5"
 | 
			
		||||
    it_behaves_like "immutable objects", ":sym"
 | 
			
		||||
    it_behaves_like "immutable objects", "FOO + BAR"
 | 
			
		||||
    it_behaves_like "immutable objects", "FOO - BAR"
 | 
			
		||||
    it_behaves_like "immutable objects", "'foo' + 'bar'"
 | 
			
		||||
    it_behaves_like "immutable objects", "ENV['foo']"
 | 
			
		||||
    it_behaves_like "immutable objects", "::ENV['foo']"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  shared_examples "string literal" do
 | 
			
		||||
    # TODO : It is not yet decided when frozen string will be the default.
 | 
			
		||||
    # It has been abandoned in the Ruby 3.0 period, but may default in
 | 
			
		||||
    # the long run. So these tests are left with a provisional value of 4.0.
 | 
			
		||||
    if RuboCop::TargetRuby.supported_versions.include?(4.0)
 | 
			
		||||
      context "when the target ruby version >= 4.0" do
 | 
			
		||||
        let(:ruby_version) { 4.0 }
 | 
			
		||||
 | 
			
		||||
        context "when the frozen string literal comment is missing" do
 | 
			
		||||
          it_behaves_like "immutable objects", %Q("#{a}")
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        context "when the frozen string literal comment is true" do
 | 
			
		||||
          let(:prefix) { "# frozen_string_literal: true" }
 | 
			
		||||
 | 
			
		||||
          it_behaves_like "immutable objects", %Q("#{a}")
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        context "when the frozen string literal comment is false" do
 | 
			
		||||
          let(:prefix) { "# frozen_string_literal: false" }
 | 
			
		||||
 | 
			
		||||
          it_behaves_like "immutable objects", %Q("#{a}")
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with Ruby 3.0 or higher", :ruby30 do
 | 
			
		||||
      context "when the frozen string literal comment is missing" do
 | 
			
		||||
        it_behaves_like "mutable objects", %Q("#{a}")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when the frozen string literal comment is true" do
 | 
			
		||||
        let(:prefix) { "# frozen_string_literal: true" }
 | 
			
		||||
 | 
			
		||||
        it_behaves_like "mutable objects", %Q("#{a}")
 | 
			
		||||
        it_behaves_like "immutable objects", <<~RUBY
 | 
			
		||||
          <<~HERE
 | 
			
		||||
            foo
 | 
			
		||||
            bar
 | 
			
		||||
          HERE
 | 
			
		||||
        RUBY
 | 
			
		||||
        it "registers an offense when using interpolated heredoc constant" do
 | 
			
		||||
          expect_offense(<<~'RUBY')
 | 
			
		||||
            # frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
            CONST = T.let(<<~HERE, Object)
 | 
			
		||||
                          ^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
              foo #{use_interpolation}
 | 
			
		||||
              bar
 | 
			
		||||
            HERE
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "does not register an offense when using a multiline string" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            # frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
            CONST = T.let('foo' \
 | 
			
		||||
                    'bar', Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "registers an offense when using a multiline string with interpolation" do
 | 
			
		||||
          expect_offense(<<~'RUBY')
 | 
			
		||||
            # frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
            CONST = T.let("#{foo}" \
 | 
			
		||||
                          ^^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
                    'bar', Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when the frozen string literal comment is false" do
 | 
			
		||||
        let(:prefix) { "# frozen_string_literal: false" }
 | 
			
		||||
 | 
			
		||||
        it_behaves_like "mutable objects", %Q("#{a}")
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with Ruby 2.7 or lower", :ruby27 do
 | 
			
		||||
      context "when the frozen string literal comment is missing" do
 | 
			
		||||
        it_behaves_like "mutable objects", %Q("#{a}")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when the frozen string literal comment is true" do
 | 
			
		||||
        let(:prefix) { "# frozen_string_literal: true" }
 | 
			
		||||
 | 
			
		||||
        it_behaves_like "immutable objects", %Q("#{a}")
 | 
			
		||||
        it_behaves_like "immutable objects", <<~RUBY
 | 
			
		||||
          <<~HERE
 | 
			
		||||
            foo
 | 
			
		||||
            bar
 | 
			
		||||
          HERE
 | 
			
		||||
        RUBY
 | 
			
		||||
        it "does not register an offense when using interpolated heredoc constant" do
 | 
			
		||||
          expect_no_offenses(<<~'RUBY')
 | 
			
		||||
            # frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
            CONST = T.let(<<~HERE, Object)
 | 
			
		||||
              foo #{use_interpolation}
 | 
			
		||||
              bar
 | 
			
		||||
            HERE
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "does not register an offense when using a multiline string" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            # frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
            CONST = T.let('foo' \
 | 
			
		||||
                    'bar', Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when the frozen string literal comment is false" do
 | 
			
		||||
        let(:prefix) { "# frozen_string_literal: false" }
 | 
			
		||||
 | 
			
		||||
        it_behaves_like "mutable objects", %Q("#{a}")
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context "with Strict: false" do
 | 
			
		||||
    let(:cop_config) { { "EnforcedStyle" => "literals" } }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "mutable objects", "[1, 2, 3]"
 | 
			
		||||
    it_behaves_like "mutable objects", "%w(a b c)"
 | 
			
		||||
    it_behaves_like "mutable objects", "{ a: 1, b: 2 }"
 | 
			
		||||
    it_behaves_like "mutable objects", "'str'"
 | 
			
		||||
    it_behaves_like "mutable objects", %Q("top#{1 + 2}")
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "immutable objects", "1"
 | 
			
		||||
    it_behaves_like "immutable objects", "1.5"
 | 
			
		||||
    it_behaves_like "immutable objects", ":sym"
 | 
			
		||||
    it_behaves_like "immutable objects", "FOO + BAR"
 | 
			
		||||
    it_behaves_like "immutable objects", "FOO - BAR"
 | 
			
		||||
    it_behaves_like "immutable objects", "'foo' + 'bar'"
 | 
			
		||||
    it_behaves_like "immutable objects", "ENV['foo']"
 | 
			
		||||
    it_behaves_like "immutable objects", "::ENV['foo']"
 | 
			
		||||
 | 
			
		||||
    it "allows method call assignments" do
 | 
			
		||||
      expect_no_offenses("TOP_TEST = Something.new")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when assigning an array without brackets" do
 | 
			
		||||
      it "does not insert brackets for %w() arrays" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          XXX = T.let(%w(YYY ZZZ), Object)
 | 
			
		||||
                      ^^^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
        RUBY
 | 
			
		||||
 | 
			
		||||
        expect_correction(<<~RUBY)
 | 
			
		||||
          XXX = T.let(%w(YYY ZZZ).freeze, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Ruby 3.0's Regexp and Range literals are frozen.
 | 
			
		||||
    #
 | 
			
		||||
    # https://bugs.ruby-lang.org/issues/15504
 | 
			
		||||
    # https://bugs.ruby-lang.org/issues/16377
 | 
			
		||||
    context "with Ruby 3.0 or higher", :ruby30 do
 | 
			
		||||
      context "when assigning a regexp" do
 | 
			
		||||
        it "does not register an offense" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            XXX = T.let(/regexp/, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when assigning a range (irange)" do
 | 
			
		||||
        it "does not register an offense when without parenthesis" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            XXX = T.let(1..99, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "does not register an offense when with parenthesis" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1..99), Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when assigning a range (erange)" do
 | 
			
		||||
        it "does not register an offense when without parenthesis" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            XXX = T.let(1...99, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "does not register an offense when with parenthesis" do
 | 
			
		||||
          expect_no_offenses(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1...99), Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when using shareable_constant_value" do
 | 
			
		||||
        it_behaves_like "literals that are frozen", "# shareable_constant_value: literal"
 | 
			
		||||
        it_behaves_like "literals that are frozen", "# shareable_constant_value: experimental_everything"
 | 
			
		||||
        it_behaves_like "literals that are frozen", "# shareable_constant_value: experimental_copy"
 | 
			
		||||
        it_behaves_like "literals that are not frozen", "# shareable_constant_value: none"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "raises offense when shareable_constant_value is specified as an inline comment" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          X = T.let([1, 2, 3], Object) # shareable_constant_value: literal
 | 
			
		||||
                    ^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          Y = T.let([4, 5, 6], Object)
 | 
			
		||||
                    ^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "raises offense only for shareable_constant_value as none when set in the order of: " \
 | 
			
		||||
         "literal, none and experimental_everything" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          # shareable_constant_value: literal
 | 
			
		||||
          X = T.let([1, 2, 3], Object)
 | 
			
		||||
          # shareable_constant_value: none
 | 
			
		||||
          Y = T.let([4, 5, 6], Object)
 | 
			
		||||
                    ^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          # shareable_constant_value: experimental_everything
 | 
			
		||||
          Z = T.let([7, 8, 9], Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with Ruby 2.7 or lower", :ruby27 do
 | 
			
		||||
      context "when assigning a regexp" do
 | 
			
		||||
        it "registers an offense" do
 | 
			
		||||
          expect_offense(<<~RUBY)
 | 
			
		||||
            XXX = T.let(/regexp/, Object)
 | 
			
		||||
                        ^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
 | 
			
		||||
          expect_correction(<<~RUBY)
 | 
			
		||||
            XXX = T.let(/regexp/.freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when assigning a range (irange) without parenthesis" do
 | 
			
		||||
        it "adds parenthesis when auto-correcting" do
 | 
			
		||||
          expect_offense(<<~RUBY)
 | 
			
		||||
            XXX = T.let(1..99, Object)
 | 
			
		||||
                        ^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
 | 
			
		||||
          expect_correction(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1..99).freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "does not insert parenthesis to range enclosed in parentheses" do
 | 
			
		||||
          expect_offense(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1..99), Object)
 | 
			
		||||
                        ^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
 | 
			
		||||
          expect_correction(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1..99).freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when assigning a range (erange) without parenthesis" do
 | 
			
		||||
        it "adds parenthesis when auto-correcting" do
 | 
			
		||||
          expect_offense(<<~RUBY)
 | 
			
		||||
            XXX = T.let(1...99, Object)
 | 
			
		||||
                        ^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
 | 
			
		||||
          expect_correction(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1...99).freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "does not insert parenthesis to range enclosed in parentheses" do
 | 
			
		||||
          expect_offense(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1...99), Object)
 | 
			
		||||
                        ^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
 | 
			
		||||
          expect_correction(<<~RUBY)
 | 
			
		||||
            XXX = T.let((1...99).freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when using shareable_constant_values" do
 | 
			
		||||
        it_behaves_like "literals that are not frozen", "# shareable_constant_value: literal"
 | 
			
		||||
        it_behaves_like "literals that are not frozen", "# shareable_constant_value: experimental_everything"
 | 
			
		||||
        it_behaves_like "literals that are not frozen", "# shareable_constant_value: experimental_copy"
 | 
			
		||||
        it_behaves_like "literals that are not frozen", "# shareable_constant_value: none"
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "string literal"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context "with Strict: true" do
 | 
			
		||||
    let(:cop_config) { { "EnforcedStyle" => "strict" } }
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "mutable objects", "[1, 2, 3]"
 | 
			
		||||
    it_behaves_like "mutable objects", "%w(a b c)"
 | 
			
		||||
    it_behaves_like "mutable objects", "{ a: 1, b: 2 }"
 | 
			
		||||
    it_behaves_like "mutable objects", "'str'"
 | 
			
		||||
    it_behaves_like "mutable objects", %Q("top#{1 + 2}")
 | 
			
		||||
    it_behaves_like "mutable objects", "Something.new"
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "immutable objects", "1"
 | 
			
		||||
    it_behaves_like "immutable objects", "1.5"
 | 
			
		||||
    it_behaves_like "immutable objects", ":sym"
 | 
			
		||||
    it_behaves_like "immutable objects", "ENV['foo']"
 | 
			
		||||
    it_behaves_like "immutable objects", "::ENV['foo']"
 | 
			
		||||
    it_behaves_like "immutable objects", "OTHER_CONST"
 | 
			
		||||
    it_behaves_like "immutable objects", "::OTHER_CONST"
 | 
			
		||||
    it_behaves_like "immutable objects", "Namespace::OTHER_CONST"
 | 
			
		||||
    it_behaves_like "immutable objects", "::Namespace::OTHER_CONST"
 | 
			
		||||
    it_behaves_like "immutable objects", "Struct.new"
 | 
			
		||||
    it_behaves_like "immutable objects", "::Struct.new"
 | 
			
		||||
    it_behaves_like "immutable objects", "Struct.new(:a, :b)"
 | 
			
		||||
    it_behaves_like "immutable objects", "T.type_alias { T.nilable(T.any(Pathname, String)) }"
 | 
			
		||||
    it_behaves_like "immutable objects", "::T.type_alias { T.nilable(T.any(Pathname, String)) }"
 | 
			
		||||
    it_behaves_like "immutable objects", "type_member { { fixed: Module } }"
 | 
			
		||||
    it_behaves_like "immutable objects", <<~RUBY
 | 
			
		||||
      Struct.new(:node) do
 | 
			
		||||
        def assignment?
 | 
			
		||||
          true
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    RUBY
 | 
			
		||||
 | 
			
		||||
    it "allows calls to freeze" do
 | 
			
		||||
      expect_no_offenses(<<~RUBY)
 | 
			
		||||
        CONST = T.let([1].freeze, Object)
 | 
			
		||||
      RUBY
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when assigning with an operator" do
 | 
			
		||||
      shared_examples "operator methods" do |o|
 | 
			
		||||
        it "registers an offense and corrects with parens and freeze" do
 | 
			
		||||
          expect_offense(<<~RUBY, o:)
 | 
			
		||||
            CONST = T.let(FOO %<o>s BAR, Object)
 | 
			
		||||
                          ^^^^^{o}^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          RUBY
 | 
			
		||||
 | 
			
		||||
          expect_correction(<<~RUBY)
 | 
			
		||||
            CONST = T.let((FOO #{o} BAR).freeze, Object)
 | 
			
		||||
          RUBY
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it_behaves_like "operator methods", "+"
 | 
			
		||||
      it_behaves_like "operator methods", "-"
 | 
			
		||||
      it_behaves_like "operator methods", "*"
 | 
			
		||||
      it_behaves_like "operator methods", "/"
 | 
			
		||||
      it_behaves_like "operator methods", "%"
 | 
			
		||||
      it_behaves_like "operator methods", "**"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when assigning with multiple operator calls" do
 | 
			
		||||
      it "registers an offense and corrects with parens and freeze" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          FOO = [1].freeze
 | 
			
		||||
          BAR = [2].freeze
 | 
			
		||||
          BAZ = [3].freeze
 | 
			
		||||
          CONST = T.let(FOO + BAR + BAZ, Object)
 | 
			
		||||
                        ^^^^^^^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
        RUBY
 | 
			
		||||
 | 
			
		||||
        expect_correction(<<~RUBY)
 | 
			
		||||
          FOO = [1].freeze
 | 
			
		||||
          BAR = [2].freeze
 | 
			
		||||
          BAZ = [3].freeze
 | 
			
		||||
          CONST = T.let((FOO + BAR + BAZ).freeze, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with methods and operators that produce frozen objects" do
 | 
			
		||||
      it "accepts assigning to an environment variable with a fallback" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(ENV['foo'] || 'foo', Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(::ENV['foo'] || 'foo', Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "accepts operating on a constant and an interger" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(FOO + 2, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "accepts operating on multiple integers" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(1 + 2, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "accepts operating on a constant and a float" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(FOO + 2.1, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "accepts operating on multiple floats" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(1.2 + 2.1, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "accepts comparison operators" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let(FOO == BAR, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "accepts checking fixed size" do
 | 
			
		||||
        expect_no_offenses(<<~RUBY)
 | 
			
		||||
          CONST = T.let('foo'.count, Object)
 | 
			
		||||
          CONST = T.let('foo'.count('f'), Object)
 | 
			
		||||
          CONST = T.let([1, 2, 3].count { |n| n > 2 }, Object)
 | 
			
		||||
          CONST = T.let([1, 2, 3].count(2) { |n| n > 2 }, Object)
 | 
			
		||||
          CONST = T.let('foo'.length, Object)
 | 
			
		||||
          CONST = T.let('foo'.size, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "with operators that produce unfrozen objects" do
 | 
			
		||||
      it "registers an offense when operating on a constant and a string" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          CONST = T.let(FOO + 'bar', Object)
 | 
			
		||||
                        ^^^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
        RUBY
 | 
			
		||||
 | 
			
		||||
        expect_correction(<<~RUBY)
 | 
			
		||||
          CONST = T.let((FOO + 'bar').freeze, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "registers an offense when operating on multiple strings" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          CONST = T.let('foo' + 'bar' + 'baz', Object)
 | 
			
		||||
                        ^^^^^^^^^^^^^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
        RUBY
 | 
			
		||||
 | 
			
		||||
        expect_correction(<<~RUBY)
 | 
			
		||||
          CONST = T.let(('foo' + 'bar' + 'baz').freeze, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when assigning an array without brackets" do
 | 
			
		||||
      it "does not insert brackets for %w() arrays" do
 | 
			
		||||
        expect_offense(<<~RUBY)
 | 
			
		||||
          XXX = T.let(%w(YYY ZZZ), Object)
 | 
			
		||||
                      ^^^^^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
        RUBY
 | 
			
		||||
 | 
			
		||||
        expect_correction(<<~RUBY)
 | 
			
		||||
          XXX = T.let(%w(YYY ZZZ).freeze, Object)
 | 
			
		||||
        RUBY
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "freezes a heredoc" do
 | 
			
		||||
      expect_offense(<<~RUBY)
 | 
			
		||||
        FOO = T.let(<<-HERE, Object)
 | 
			
		||||
                    ^^^^^^^ Freeze mutable objects assigned to constants.
 | 
			
		||||
          SOMETHING
 | 
			
		||||
        HERE
 | 
			
		||||
      RUBY
 | 
			
		||||
 | 
			
		||||
      expect_correction(<<~RUBY)
 | 
			
		||||
        FOO = T.let(<<-HERE.freeze, Object)
 | 
			
		||||
          SOMETHING
 | 
			
		||||
        HERE
 | 
			
		||||
      RUBY
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it_behaves_like "string literal"
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user