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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -3
- data/README.md +7 -0
- data/bin/rubocop +29 -0
- data/config/default.yml +53 -3
- data/dev.yml +1 -1
- data/lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb +14 -1
- data/lib/rubocop/cop/sorbet/callback_conditionals_binding.rb +138 -0
- data/lib/rubocop/cop/sorbet/forbid_include_const_literal.rb +0 -40
- data/lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb +0 -17
- data/lib/rubocop/cop/sorbet/forbid_t_unsafe.rb +26 -0
- data/lib/rubocop/cop/sorbet/one_ancestor_per_line.rb +75 -0
- data/lib/rubocop/cop/sorbet/rbi/forbid_extend_t_sig_helpers_in_shims.rb +53 -0
- data/lib/rubocop/cop/sorbet/rbi/forbid_rbi_outside_of_sorbet_dir.rb +31 -0
- data/lib/rubocop/cop/sorbet/rbi/single_line_rbi_class_module_definitions.rb +46 -0
- data/lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb +10 -0
- data/lib/rubocop/cop/sorbet/sigils/valid_sigil.rb +4 -2
- data/lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb +19 -10
- data/lib/rubocop/cop/sorbet/signatures/signature_build_order.rb +18 -9
- data/lib/rubocop/cop/sorbet_cops.rb +7 -0
- data/lib/rubocop/sorbet/version.rb +1 -1
- data/manual/cops.md +6 -0
- data/manual/cops_sorbet.md +208 -4
- data/service.yml +1 -4
- metadata +11 -5
- data/.shopify-build/VERSION +0 -1
- data/.shopify-build/rubocop-sorbet.yml +0 -16
@@ -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
|
-
|
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
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
70
|
-
unless
|
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
|
-
|
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(
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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'
|
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)
|