rubocop-sorbet 0.5.1 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/release.yml +23 -0
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +25 -41
- data/README.md +22 -0
- data/Rakefile +12 -16
- data/bin/console +3 -3
- data/bin/rspec +6 -6
- data/bin/rubocop +29 -0
- data/config/default.yml +94 -10
- data/config/rbi.yml +262 -0
- data/dev.yml +1 -1
- data/lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb +4 -4
- data/lib/rubocop/cop/sorbet/callback_conditionals_binding.rb +142 -0
- data/lib/rubocop/cop/sorbet/constants_from_strings.rb +1 -1
- data/lib/rubocop/cop/sorbet/forbid_include_const_literal.rb +11 -2
- data/lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb +2 -2
- data/lib/rubocop/cop/sorbet/forbid_t_unsafe.rb +26 -0
- data/lib/rubocop/cop/sorbet/forbid_untyped_struct_props.rb +2 -2
- 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_allowed_paths.rb +47 -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 +6 -6
- data/lib/rubocop/cop/sorbet/sigils/enforce_single_sigil.rb +63 -0
- data/lib/rubocop/cop/sorbet/sigils/false_sigil.rb +3 -3
- data/lib/rubocop/cop/sorbet/sigils/has_sigil.rb +2 -2
- data/lib/rubocop/cop/sorbet/sigils/ignore_sigil.rb +3 -3
- data/lib/rubocop/cop/sorbet/sigils/strict_sigil.rb +3 -3
- data/lib/rubocop/cop/sorbet/sigils/strong_sigil.rb +3 -3
- data/lib/rubocop/cop/sorbet/sigils/true_sigil.rb +3 -3
- data/lib/rubocop/cop/sorbet/sigils/valid_sigil.rb +10 -8
- data/lib/rubocop/cop/sorbet/signatures/allow_incompatible_override.rb +3 -3
- data/lib/rubocop/cop/sorbet/signatures/checked_true_in_signature.rb +6 -6
- data/lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb +30 -21
- data/lib/rubocop/cop/sorbet/signatures/keyword_argument_ordering.rb +3 -3
- data/lib/rubocop/cop/sorbet/signatures/signature_build_order.rb +24 -15
- data/lib/rubocop/cop/sorbet/signatures/signature_cop.rb +17 -2
- data/lib/rubocop/cop/sorbet_cops.rb +26 -19
- data/lib/rubocop/sorbet/version.rb +1 -1
- data/lib/rubocop/sorbet.rb +1 -1
- data/lib/rubocop-sorbet.rb +5 -5
- data/manual/cops.md +7 -1
- data/manual/cops_sorbet.md +247 -14
- data/rubocop-sorbet.gemspec +2 -2
- data/service.yml +0 -5
- data/tasks/cops_documentation.rake +60 -62
- metadata +20 -12
- data/.shopify-build/VERSION +0 -1
- data/.shopify-build/rubocop-sorbet.yml +0 -16
- data/lib/rubocop/cop/sorbet/signatures/parameters_ordering_in_signature.rb +0 -70
@@ -0,0 +1,47 @@
|
|
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 the defined allowed paths.
|
7
|
+
#
|
8
|
+
# Options:
|
9
|
+
#
|
10
|
+
# * `AllowedPaths`: A list of the paths where RBI files are allowed (default: ["sorbet/rbi/**"])
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# # lib/some_file.rbi
|
15
|
+
# # other_file.rbi
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# # sorbet/rbi/some_file.rbi
|
19
|
+
# # sorbet/rbi/any/path/for/file.rbi
|
20
|
+
class ForbidRBIOutsideOfAllowedPaths < RuboCop::Cop::Cop
|
21
|
+
include RangeHelp
|
22
|
+
|
23
|
+
def investigate(processed_source)
|
24
|
+
add_offense(
|
25
|
+
nil,
|
26
|
+
location: source_range(processed_source.buffer, 1, 0),
|
27
|
+
message: message
|
28
|
+
) if allowed_paths.none? { |pattern| File.fnmatch(pattern, processed_source.file_path) }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def allowed_paths
|
34
|
+
cop_config["AllowedPaths"]&.compact || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def message
|
38
|
+
if allowed_paths.empty?
|
39
|
+
"RBI files should be located in an allowed path, but AllowedPaths is empty or nil"
|
40
|
+
else
|
41
|
+
"RBI file path should match one of: #{allowed_paths.join(", ")}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rubocop"
|
4
4
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
@@ -69,10 +69,10 @@ module RuboCop
|
|
69
69
|
FROZEN_REGEX = /#\s+frozen_string_literal:(?:\s+([\w]+))?/
|
70
70
|
|
71
71
|
PREFERRED_ORDER = {
|
72
|
-
CODING_REGEX =>
|
73
|
-
SIGIL_REGEX =>
|
74
|
-
INDENT_REGEX =>
|
75
|
-
FROZEN_REGEX =>
|
72
|
+
CODING_REGEX => "encoding",
|
73
|
+
SIGIL_REGEX => "typed",
|
74
|
+
INDENT_REGEX => "warn_indent",
|
75
|
+
FROZEN_REGEX => "frozen_string_literal",
|
76
76
|
}.freeze
|
77
77
|
|
78
78
|
MAGIC_REGEX = Regexp.union(*PREFERRED_ORDER.keys)
|
@@ -104,7 +104,7 @@ module RuboCop
|
|
104
104
|
add_offense(
|
105
105
|
token,
|
106
106
|
location: token.pos,
|
107
|
-
message: "Magic comments should be in the following order: #{PREFERRED_ORDER.values.join(
|
107
|
+
message: "Magic comments should be in the following order: #{PREFERRED_ORDER.values.join(", ")}."
|
108
108
|
)
|
109
109
|
end
|
110
110
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Sorbet
|
6
|
+
# This cop checks that there is only one Sorbet sigil in a given file
|
7
|
+
#
|
8
|
+
# For example, the following class with two sigils
|
9
|
+
#
|
10
|
+
# ```ruby
|
11
|
+
# # typed: true
|
12
|
+
# # typed: true
|
13
|
+
# # frozen_string_literal: true
|
14
|
+
# class Foo; end
|
15
|
+
# ```
|
16
|
+
#
|
17
|
+
# Will be corrected as:
|
18
|
+
#
|
19
|
+
# ```ruby
|
20
|
+
# # typed: true
|
21
|
+
# # frozen_string_literal: true
|
22
|
+
# class Foo; end
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# Other comments or magic comments are left in place.
|
26
|
+
class EnforceSingleSigil < ValidSigil
|
27
|
+
include RangeHelp
|
28
|
+
|
29
|
+
def investigate(processed_source)
|
30
|
+
return if processed_source.tokens.empty?
|
31
|
+
sigils = extract_all_sigils(processed_source)
|
32
|
+
return unless sigils.size > 1
|
33
|
+
|
34
|
+
sigils[1..sigils.size].each do |token|
|
35
|
+
add_offense(token, location: token.pos, message: "Files must only contain one sigil")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def autocorrect(_node)
|
40
|
+
-> (corrector) do
|
41
|
+
sigils = extract_all_sigils(processed_source)
|
42
|
+
return unless sigils.size > 1
|
43
|
+
|
44
|
+
# The first sigil encountered represents the "real" strictness so remove any below
|
45
|
+
sigils[1..sigils.size].each do |token|
|
46
|
+
corrector.remove(
|
47
|
+
source_range(processed_source.buffer, token.line, (0..token.pos.last_column))
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def extract_all_sigils(processed_source)
|
56
|
+
processed_source.tokens
|
57
|
+
.take_while { |token| token.type == :tCOMMENT }
|
58
|
+
.find_all { |token| SIGIL_REGEX.match?(token.text) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "has_sigil"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
9
9
|
# This cop makes the Sorbet `false` sigil mandatory in all files.
|
10
10
|
class FalseSigil < HasSigil
|
11
11
|
def minimum_strictness
|
12
|
-
|
12
|
+
"false"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "has_sigil"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
9
9
|
# This cop makes the Sorbet `ignore` sigil mandatory in all files.
|
10
10
|
class IgnoreSigil < HasSigil
|
11
11
|
def minimum_strictness
|
12
|
-
|
12
|
+
"ignore"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "has_sigil"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
9
9
|
# This cop makes the Sorbet `strict` sigil mandatory in all files.
|
10
10
|
class StrictSigil < HasSigil
|
11
11
|
def minimum_strictness
|
12
|
-
|
12
|
+
"strict"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "has_sigil"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
9
9
|
# This cop makes the Sorbet `strong` sigil mandatory in all files.
|
10
10
|
class StrongSigil < HasSigil
|
11
11
|
def minimum_strictness
|
12
|
-
|
12
|
+
"strong"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "has_sigil"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
9
9
|
# This cop makes the Sorbet `true` sigil mandatory in all files.
|
10
10
|
class TrueSigil < HasSigil
|
11
11
|
def minimum_strictness
|
12
|
-
|
12
|
+
"true"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rubocop"
|
4
4
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
|
49
49
|
protected
|
50
50
|
|
51
|
-
STRICTNESS_LEVELS =
|
51
|
+
STRICTNESS_LEVELS = ["ignore", "false", "true", "strict", "strong"]
|
52
52
|
SIGIL_REGEX = /#\s+typed:(?:\s+([\w]+))?/
|
53
53
|
|
54
54
|
# extraction
|
@@ -74,7 +74,7 @@ module RuboCop
|
|
74
74
|
add_offense(
|
75
75
|
token,
|
76
76
|
location: token.pos,
|
77
|
-
message:
|
77
|
+
message: "No Sorbet sigil found in file. " \
|
78
78
|
"Try a `typed: #{strictness}` to start (you can also use `rubocop -a` to automatically add this)."
|
79
79
|
)
|
80
80
|
end
|
@@ -87,7 +87,7 @@ module RuboCop
|
|
87
87
|
return suggested_strictness unless minimum_strictness
|
88
88
|
|
89
89
|
# special case: if you're using Sorbet/IgnoreSigil without config, we should recommend `ignore`
|
90
|
-
return "ignore" if minimum_strictness == "ignore" && cop_config[
|
90
|
+
return "ignore" if minimum_strictness == "ignore" && cop_config["SuggestedStrictness"].nil?
|
91
91
|
|
92
92
|
# if a minimum strictness is set (eg. you're using Sorbet/FalseSigil)
|
93
93
|
# we want to compare the minimum strictness and suggested strictness. this is because
|
@@ -106,7 +106,7 @@ module RuboCop
|
|
106
106
|
add_offense(
|
107
107
|
sigil,
|
108
108
|
location: sigil.pos,
|
109
|
-
message:
|
109
|
+
message: "Sorbet sigil should not be empty."
|
110
110
|
)
|
111
111
|
false
|
112
112
|
end
|
@@ -142,17 +142,19 @@ module RuboCop
|
|
142
142
|
|
143
143
|
# Default is `false`
|
144
144
|
def require_sigil_on_all_files?
|
145
|
-
!!cop_config[
|
145
|
+
!!cop_config["RequireSigilOnAllFiles"]
|
146
146
|
end
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rubocop"
|
4
4
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
@@ -49,8 +49,8 @@ module RuboCop
|
|
49
49
|
return unless allow_incompatible_override?(node)
|
50
50
|
add_offense(
|
51
51
|
node.children[2],
|
52
|
-
message:
|
53
|
-
|
52
|
+
message: "Usage of `allow_incompatible` suggests a violation of the Liskov Substitution Principle. "\
|
53
|
+
"Instead, strive to write interfaces which respect subtyping principles and remove `allow_incompatible`",
|
54
54
|
)
|
55
55
|
end
|
56
56
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "signature_cop"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -27,10 +27,10 @@ module RuboCop
|
|
27
27
|
PATTERN
|
28
28
|
|
29
29
|
MESSAGE =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
"Using `checked(true)` in a method signature definition is not allowed. " \
|
31
|
+
"`checked(true)` is the default behavior for modules/classes with runtime checks enabled. " \
|
32
|
+
"To enable typechecking at runtime for this module, regardless of global settings, " \
|
33
|
+
"`include(WaffleCone::RuntimeChecks)` to this module and set other methods to `checked(false)`."
|
34
34
|
private_constant(:MESSAGE)
|
35
35
|
|
36
36
|
def on_signature(node)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require "stringio"
|
5
|
+
require_relative "signature_cop"
|
6
6
|
|
7
7
|
module RuboCop
|
8
8
|
module Cop
|
@@ -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_signature(node)
|
52
|
+
@last_sig_for_scope[scope(node)] = node
|
45
53
|
end
|
46
54
|
|
47
55
|
def autocorrect(node)
|
@@ -56,37 +64,38 @@ module RuboCop
|
|
56
64
|
method = node.children[1]
|
57
65
|
symbol = node.children[2]
|
58
66
|
suggest.params << symbol.value if symbol && (method == :attr_writer || method == :attr_accessor)
|
59
|
-
suggest.returns =
|
67
|
+
suggest.returns = "void" if method == :attr_writer
|
60
68
|
end
|
61
69
|
|
62
70
|
corrector.insert_before(node.loc.expression, suggest.to_autocorrect)
|
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
|
85
|
-
cop_config[
|
94
|
+
cop_config["ParameterTypePlaceholder"] || "T.untyped"
|
86
95
|
end
|
87
96
|
|
88
97
|
def return_type_placeholder
|
89
|
-
cop_config[
|
98
|
+
cop_config["ReturnTypePlaceholder"] || "T.untyped"
|
90
99
|
end
|
91
100
|
|
92
101
|
class SigSuggestion
|
@@ -102,11 +111,11 @@ module RuboCop
|
|
102
111
|
|
103
112
|
def to_autocorrect
|
104
113
|
out = StringIO.new
|
105
|
-
out <<
|
114
|
+
out << "sig { "
|
106
115
|
out << generate_params
|
107
116
|
out << generate_return
|
108
117
|
out << " }\n"
|
109
|
-
out <<
|
118
|
+
out << " " * @indent # preserve indent for the next line
|
110
119
|
out.string
|
111
120
|
end
|
112
121
|
|
@@ -115,17 +124,17 @@ module RuboCop
|
|
115
124
|
def generate_params
|
116
125
|
return if @params.empty?
|
117
126
|
out = StringIO.new
|
118
|
-
out <<
|
127
|
+
out << "params("
|
119
128
|
out << @params.map do |param|
|
120
129
|
"#{param}: #{@param_placeholder}"
|
121
130
|
end.join(", ")
|
122
|
-
out <<
|
131
|
+
out << ")."
|
123
132
|
out.string
|
124
133
|
end
|
125
134
|
|
126
135
|
def generate_return
|
127
136
|
return "returns(#{@return_placeholder})" if @returns.nil?
|
128
|
-
return @returns if @returns ==
|
137
|
+
return @returns if @returns == "void"
|
129
138
|
"returns(#{@returns})"
|
130
139
|
end
|
131
140
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "signature_cop"
|
5
5
|
|
6
6
|
module RuboCop
|
7
7
|
module Cop
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
41
41
|
|
42
42
|
add_offense(
|
43
43
|
param,
|
44
|
-
message:
|
44
|
+
message: "Optional keyword arguments must be at the end of the parameter list."
|
45
45
|
)
|
46
46
|
end
|
47
47
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require_relative
|
3
|
+
require "rubocop"
|
4
|
+
require_relative "signature_cop"
|
5
5
|
|
6
6
|
begin
|
7
|
-
require
|
7
|
+
require "unparser"
|
8
8
|
rescue LoadError
|
9
9
|
nil
|
10
10
|
end
|
@@ -28,7 +28,7 @@ module RuboCop
|
|
28
28
|
].each_with_index.to_h.freeze
|
29
29
|
|
30
30
|
def_node_search(:root_call, <<~PATTERN)
|
31
|
-
(send nil? {#{ORDER.keys.map(&:inspect).join(
|
31
|
+
(send nil? {#{ORDER.keys.map(&:inspect).join(" ")}} ...)
|
32
32
|
PATTERN
|
33
33
|
|
34
34
|
def on_signature(node)
|
@@ -38,10 +38,10 @@ module RuboCop
|
|
38
38
|
expected_order = calls.sort_by { |call| ORDER[call] }
|
39
39
|
return if expected_order == calls
|
40
40
|
|
41
|
-
message = "Sig builders must be invoked in the following order: #{expected_order.join(
|
41
|
+
message = "Sig builders must be invoked in the following order: #{expected_order.join(", ")}."
|
42
42
|
|
43
43
|
unless can_autocorrect?
|
44
|
-
message +=
|
44
|
+
message += " For autocorrection, add the `unparser` gem to your project."
|
45
45
|
end
|
46
46
|
|
47
47
|
add_offense(
|
@@ -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?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rubocop"
|
4
4
|
|
5
5
|
module RuboCop
|
6
6
|
module Cop
|
@@ -12,9 +12,24 @@ module RuboCop
|
|
12
12
|
@registry = Cop.registry # So we can properly subclass this cop
|
13
13
|
|
14
14
|
def_node_matcher(:signature?, <<~PATTERN)
|
15
|
-
(block (send
|
15
|
+
(block (send #allowed_recv :sig) (args) ...)
|
16
16
|
PATTERN
|
17
17
|
|
18
|
+
def_node_matcher(:with_runtime?, <<~PATTERN)
|
19
|
+
(const (const nil? :T) :Sig)
|
20
|
+
PATTERN
|
21
|
+
|
22
|
+
def_node_matcher(:without_runtime?, <<~PATTERN)
|
23
|
+
(const (const (const nil? :T) :Sig) :WithoutRuntime)
|
24
|
+
PATTERN
|
25
|
+
|
26
|
+
def allowed_recv(recv)
|
27
|
+
return true unless recv
|
28
|
+
return true if with_runtime?(recv)
|
29
|
+
return true if without_runtime?(recv)
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
18
33
|
def on_block(node)
|
19
34
|
on_signature(node) if signature?(node)
|
20
35
|
end
|