rubocop-sorbet 0.4.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,13 +12,6 @@ require 'rubocop'
12
12
  # class Foo < send_expr; end
13
13
  # ```
14
14
  #
15
- # This cop replaces them by:
16
- #
17
- # ```ruby
18
- # FooParent = send_expr
19
- # class Foo < FooParent; end
20
- # ```
21
- #
22
15
  # Multiple occurences of this can be found in Shopify's code base like:
23
16
  #
24
17
  # ```ruby
@@ -46,16 +39,6 @@ module RuboCop
46
39
  return unless not_lit_const_superclass?(node)
47
40
  add_offense(node.child_nodes[1])
48
41
  end
49
-
50
- def autocorrect(node)
51
- lambda do |corrector|
52
- class_name = node.parent.child_nodes.first.const_name
53
- parent_name = "#{class_name}Parent"
54
- indent = ' ' * node.parent.loc.column
55
- corrector.insert_before(node.parent.loc.expression, "#{parent_name} = #{node.source}\n#{indent}")
56
- corrector.replace(node.loc.expression, parent_name)
57
- end
58
- end
59
42
  end
60
43
  end
61
44
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Sorbet
8
+ # This cop disallows using `T.unsafe` anywhere.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # T.unsafe(foo)
14
+ #
15
+ # # good
16
+ # foo
17
+ class ForbidTUnsafe < RuboCop::Cop::Cop
18
+ def_node_matcher(:t_unsafe?, '(send (const nil? :T) :unsafe _)')
19
+
20
+ def on_send(node)
21
+ add_offense(node, message: "Do not use `T.unsafe`.") if t_unsafe?(node)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'rubocop'
5
+
6
+ module RuboCop
7
+ module Cop
8
+ module Sorbet
9
+ # This cop ensures one ancestor per requires_ancestor line
10
+ # rather than chaining them as a comma-separated list.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # module SomeModule
16
+ # requires_ancestor Kernel, Minitest::Assertions
17
+ # end
18
+ #
19
+ # # good
20
+ # module SomeModule
21
+ # requires_ancestor Kernel
22
+ # requires_ancestor Minitest::Assertions
23
+ # end
24
+ class OneAncestorPerLine < RuboCop::Cop::Cop
25
+ MSG = 'Cannot require more than one ancestor per line'
26
+
27
+ def_node_search :requires_ancestors, <<~PATTERN
28
+ (send nil? :requires_ancestor ...)
29
+ PATTERN
30
+
31
+ def_node_matcher :more_than_one_ancestor, <<~PATTERN
32
+ (send nil? :requires_ancestor const const+)
33
+ PATTERN
34
+
35
+ def_node_search :abstract?, <<~PATTERN
36
+ (send nil? :abstract!)
37
+ PATTERN
38
+
39
+ def on_module(node)
40
+ return unless node.body
41
+ return unless requires_ancestors(node)
42
+ process_node(node)
43
+ end
44
+
45
+ def on_class(node)
46
+ return unless abstract?(node)
47
+ return unless requires_ancestors(node)
48
+ process_node(node)
49
+ end
50
+
51
+ def autocorrect(node)
52
+ -> (corrector) do
53
+ ra_call = node.parent
54
+ split_ra_calls = ra_call.source.gsub(/,\s+/, new_ra_line(ra_call.loc.column))
55
+ corrector.replace(ra_call, split_ra_calls)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def process_node(node)
62
+ requires_ancestors(node).each do |ra|
63
+ add_offense(ra.child_nodes[1]) if more_than_one_ancestor(ra)
64
+ end
65
+ end
66
+
67
+ def new_ra_line(indent_count)
68
+ indents = " " * indent_count
69
+ indented_ra_call = "#{indents}requires_ancestor "
70
+ "\n#{indented_ra_call}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # This cop ensures RBI shims do not include a call to extend T::Sig
7
+ # or to extend T::Helpers
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # module SomeModule
13
+ # extend T::Sig
14
+ # extend T::Helpers
15
+ #
16
+ # sig { returns(String) }
17
+ # def foo; end
18
+ # end
19
+ #
20
+ # # good
21
+ # module SomeModule
22
+ # sig { returns(String) }
23
+ # def foo; end
24
+ # end
25
+ class ForbidExtendTSigHelpersInShims < RuboCop::Cop::Cop
26
+ include RangeHelp
27
+
28
+ MSG = 'Extending T::Sig or T::Helpers in a shim is unnecessary'
29
+ RESTRICT_ON_SEND = [:extend]
30
+
31
+ def_node_matcher :extend_t_sig?, <<~PATTERN
32
+ (send nil? :extend (const (const nil? :T) :Sig))
33
+ PATTERN
34
+
35
+ def_node_matcher :extend_t_helpers?, <<~PATTERN
36
+ (send nil? :extend (const (const nil? :T) :Helpers))
37
+ PATTERN
38
+
39
+ def autocorrect(node)
40
+ -> (corrector) do
41
+ corrector.remove(
42
+ range_by_whole_lines(node.source_range, include_final_newline: true)
43
+ )
44
+ end
45
+ end
46
+
47
+ def on_send(node)
48
+ add_offense(node) if extend_t_helpers?(node) || extend_t_sig?(node)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # This cop makes sure that RBI files are always located under sorbet/rbi/.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # lib/some_file.rbi
11
+ # other_file.rbi
12
+ #
13
+ # # good
14
+ # sorbet/rbi/some_file.rbi
15
+ # sorbet/rbi/any/path/for/file.rbi
16
+ class ForbidRBIOutsideOfSorbetDir < RuboCop::Cop::Cop
17
+ include RangeHelp
18
+
19
+ PATH_REGEXP = %r{sorbet/rbi}
20
+
21
+ def investigate(processed_source)
22
+ add_offense(
23
+ nil,
24
+ location: source_range(processed_source.buffer, 1, 0),
25
+ message: "RBI files are only accepted in the sorbet/rbi/ directory."
26
+ ) unless processed_source.file_path =~ PATH_REGEXP
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Sorbet
6
+ # This cop ensures empty class/module definitions in RBI files are
7
+ # done on a single line rather than being split across multiple lines.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # module SomeModule
13
+ # end
14
+ #
15
+ # # good
16
+ # module SomeModule; end
17
+ class SingleLineRbiClassModuleDefinitions < RuboCop::Cop::Cop
18
+ MSG = 'Empty class/module definitions in RBI files should be on a single line.'
19
+
20
+ def on_module(node)
21
+ process_node(node)
22
+ end
23
+
24
+ def on_class(node)
25
+ process_node(node)
26
+ end
27
+
28
+ def autocorrect(node)
29
+ -> (corrector) { corrector.replace(node, convert_newlines(node.source)) }
30
+ end
31
+
32
+ protected
33
+
34
+ def convert_newlines(source)
35
+ source.sub(/[\r\n]+\s*[\r\n]*/, "; ")
36
+ end
37
+
38
+ def process_node(node)
39
+ return if node.body
40
+ return if node.single_line?
41
+ add_offense(node)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -28,6 +28,8 @@ module RuboCop
28
28
  # Only `typed`, `(en)?coding`, `warn_indent` and `frozen_string_literal` magic comments are considered,
29
29
  # other comments or magic comments are left in the same place.
30
30
  class EnforceSigilOrder < ValidSigil
31
+ include RangeHelp
32
+
31
33
  def investigate(processed_source)
32
34
  return if processed_source.tokens.empty?
33
35
 
@@ -49,6 +51,14 @@ module RuboCop
49
51
  tokens.each_with_index do |token, index|
50
52
  corrector.replace(token.pos, expected[index].text)
51
53
  end
54
+
55
+ # Remove blank lines between the magic comments
56
+ lines = tokens.map(&:line).to_set
57
+ (lines.min...lines.max).each do |line|
58
+ next if lines.include?(line)
59
+ next unless processed_source[line - 1].empty?
60
+ corrector.remove(source_range(processed_source.buffer, line, 0))
61
+ end
52
62
  end
53
63
  end
54
64
 
@@ -147,12 +147,14 @@ module RuboCop
147
147
 
148
148
  # Default is `'false'`
149
149
  def suggested_strictness
150
- STRICTNESS_LEVELS.include?(cop_config['SuggestedStrictness']) ? cop_config['SuggestedStrictness'] : 'false'
150
+ config = cop_config['SuggestedStrictness'].to_s
151
+ STRICTNESS_LEVELS.include?(config) ? config : 'false'
151
152
  end
152
153
 
153
154
  # Default is `nil`
154
155
  def minimum_strictness
155
- cop_config['MinimumStrictness'] if STRICTNESS_LEVELS.include?(cop_config['MinimumStrictness'])
156
+ config = cop_config['MinimumStrictness'].to_s
157
+ config if STRICTNESS_LEVELS.include?(config)
156
158
  end
157
159
  end
158
160
  end
@@ -27,6 +27,11 @@ module RuboCop
27
27
  # * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
28
28
  # * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
29
29
  class EnforceSignatures < SignatureCop
30
+ def initialize(config = nil, options = nil)
31
+ super(config, options)
32
+ @last_sig_for_scope = {}
33
+ end
34
+
30
35
  def_node_matcher(:accessor?, <<-PATTERN)
31
36
  (send nil? {:attr_reader :attr_writer :attr_accessor} ...)
32
37
  PATTERN
@@ -40,8 +45,11 @@ module RuboCop
40
45
  end
41
46
 
42
47
  def on_send(node)
43
- return unless accessor?(node)
44
- check_node(node)
48
+ check_node(node) if accessor?(node)
49
+ end
50
+
51
+ def on_block(node)
52
+ @last_sig_for_scope[scope(node)] = node if signature?(node)
45
53
  end
46
54
 
47
55
  def autocorrect(node)
@@ -63,22 +71,23 @@ module RuboCop
63
71
  end
64
72
  end
65
73
 
74
+ def scope(node)
75
+ return nil unless node.parent
76
+ return node.parent if [:begin, :block, :class, :module].include?(node.parent.type)
77
+ scope(node.parent)
78
+ end
79
+
66
80
  private
67
81
 
68
82
  def check_node(node)
69
- prev = previous_node(node)
70
- unless signature?(prev)
83
+ scope = self.scope(node)
84
+ unless @last_sig_for_scope[scope]
71
85
  add_offense(
72
86
  node,
73
87
  message: "Each method is required to have a signature."
74
88
  )
75
89
  end
76
- end
77
-
78
- def previous_node(node)
79
- parent = node.parent
80
- return nil unless parent
81
- parent.children[node.sibling_index - 1]
90
+ @last_sig_for_scope[scope] = nil
82
91
  end
83
92
 
84
93
  def param_type_placeholder
@@ -55,7 +55,7 @@ module RuboCop
55
55
  return nil unless can_autocorrect?
56
56
 
57
57
  lambda do |corrector|
58
- tree = call_chain(node_with_index_sends(node))
58
+ tree = call_chain(node_reparsed_with_modern_features(node))
59
59
  .sort_by { |call| ORDER[call.method_name] }
60
60
  .reduce(nil) do |receiver, caller|
61
61
  caller.updated(nil, [receiver] + caller.children.drop(1))
@@ -68,16 +68,25 @@ module RuboCop
68
68
  end
69
69
  end
70
70
 
71
+ # Create a subclass of AST Builder that has modern features turned on
72
+ class ModernBuilder < RuboCop::AST::Builder
73
+ modernize
74
+ end
75
+ private_constant :ModernBuilder
76
+
71
77
  private
72
78
 
73
- def node_with_index_sends(node)
74
- # This is really dirty hack to reparse the current node with index send
75
- # emitting enabled, which is necessary to unparse them back as index accessors.
76
- emit_index_value = RuboCop::AST::Builder.emit_index
77
- RuboCop::AST::Builder.emit_index = true
78
- RuboCop::AST::ProcessedSource.new(node.source, target_ruby_version, processed_source.path).ast
79
- ensure
80
- RuboCop::AST::Builder.emit_index = emit_index_value
79
+ # This method exists to reparse the current node with modern features enabled.
80
+ # Modern features include "index send" emitting, which is necessary to unparse
81
+ # "index sends" (i.e. `[]` calls) back to index accessors (i.e. as `foo[bar]``).
82
+ # Otherwise, we would get the unparsed node as `foo.[](bar)`.
83
+ def node_reparsed_with_modern_features(node)
84
+ # Create a new parser with a modern builder class instance
85
+ parser = Parser::CurrentRuby.new(ModernBuilder.new)
86
+ # Create a new source buffer with the node source
87
+ buffer = Parser::Source::Buffer.new(processed_source.path, source: node.source)
88
+ # Re-parse the buffer
89
+ parser.parse(buffer)
81
90
  end
82
91
 
83
92
  def can_autocorrect?
@@ -4,6 +4,13 @@ require_relative 'sorbet/constants_from_strings'
4
4
  require_relative 'sorbet/forbid_superclass_const_literal'
5
5
  require_relative 'sorbet/forbid_include_const_literal'
6
6
  require_relative 'sorbet/forbid_untyped_struct_props'
7
+ require_relative 'sorbet/one_ancestor_per_line'
8
+ require_relative 'sorbet/callback_conditionals_binding'
9
+ require_relative 'sorbet/forbid_t_unsafe'
10
+
11
+ require_relative 'sorbet/rbi/forbid_extend_t_sig_helpers_in_shims'
12
+ require_relative 'sorbet/rbi/forbid_rbi_outside_of_sorbet_dir'
13
+ require_relative 'sorbet/rbi/single_line_rbi_class_module_definitions'
7
14
 
8
15
  require_relative 'sorbet/signatures/allow_incompatible_override'
9
16
  require_relative 'sorbet/signatures/checked_true_in_signature'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module RuboCop
3
3
  module Sorbet
4
- VERSION = "0.4.1"
4
+ VERSION = "0.6.2"
5
5
  end
6
6
  end
data/manual/cops.md CHANGED
@@ -7,20 +7,26 @@ In the following section you find all available cops:
7
7
 
8
8
  * [Sorbet/AllowIncompatibleOverride](cops_sorbet.md#sorbetallowincompatibleoverride)
9
9
  * [Sorbet/BindingConstantWithoutTypeAlias](cops_sorbet.md#sorbetbindingconstantwithouttypealias)
10
+ * [Sorbet/CallbackConditionalsBinding](cops_sorbet.md#sorbetcallbackconditionalsbinding)
10
11
  * [Sorbet/CheckedTrueInSignature](cops_sorbet.md#sorbetcheckedtrueinsignature)
11
12
  * [Sorbet/ConstantsFromStrings](cops_sorbet.md#sorbetconstantsfromstrings)
12
13
  * [Sorbet/EnforceSigilOrder](cops_sorbet.md#sorbetenforcesigilorder)
13
14
  * [Sorbet/EnforceSignatures](cops_sorbet.md#sorbetenforcesignatures)
14
15
  * [Sorbet/FalseSigil](cops_sorbet.md#sorbetfalsesigil)
16
+ * [Sorbet/ForbidExtendTSigHelpersInShims](cops_sorbet.md#sorbetforbidextendtsighelpersinshims)
15
17
  * [Sorbet/ForbidIncludeConstLiteral](cops_sorbet.md#sorbetforbidincludeconstliteral)
18
+ * [Sorbet/ForbidRBIOutsideOfSorbetDir](cops_sorbet.md#sorbetforbidrbioutsideofsorbetdir)
16
19
  * [Sorbet/ForbidSuperclassConstLiteral](cops_sorbet.md#sorbetforbidsuperclassconstliteral)
20
+ * [Sorbet/ForbidTUnsafe](cops_sorbet.md#sorbetforbidtunsafe)
17
21
  * [Sorbet/ForbidUntypedStructProps](cops_sorbet.md#sorbetforbiduntypedstructprops)
18
22
  * [Sorbet/HasSigil](cops_sorbet.md#sorbethassigil)
19
23
  * [Sorbet/IgnoreSigil](cops_sorbet.md#sorbetignoresigil)
20
24
  * [Sorbet/KeywordArgumentOrdering](cops_sorbet.md#sorbetkeywordargumentordering)
25
+ * [Sorbet/OneAncestorPerLine](cops_sorbet.md#sorbetoneancestorperline)
21
26
  * [Sorbet/ParametersOrderingInSignature](cops_sorbet.md#sorbetparametersorderinginsignature)
22
27
  * [Sorbet/SignatureBuildOrder](cops_sorbet.md#sorbetsignaturebuildorder)
23
28
  * [Sorbet/SignatureCop](cops_sorbet.md#sorbetsignaturecop)
29
+ * [Sorbet/SingleLineRbiClassModuleDefinitions](cops_sorbet.md#sorbetsinglelinerbiclassmoduledefinitions)
24
30
  * [Sorbet/StrictSigil](cops_sorbet.md#sorbetstrictsigil)
25
31
  * [Sorbet/StrongSigil](cops_sorbet.md#sorbetstrongsigil)
26
32
  * [Sorbet/TrueSigil](cops_sorbet.md#sorbettruesigil)