rubocop-sorbet 0.4.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)