rubocop-sorbet 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8e5d981eb3753f8e4a5599105ffca85878383e8c9babe36126529d13408c578
4
- data.tar.gz: 19162fbc227c9d40774bec276129f739ee885201bb688b74cd3a7901253770ac
3
+ metadata.gz: 680171335e42d9f614626c06b286af906fbd4a71473ad36a5133e343ca202376
4
+ data.tar.gz: c84b1f2a66e5f2406640d5c2543f5a936b61b25141a8136a1cdf94f36f6af2cf
5
5
  SHA512:
6
- metadata.gz: fd590134345de1400d2c653b263d2b0936aa1f02f6e199fc5d2f65eca50b37aef77df4d4463afe403ca7ba3f795dd389a72164d62c796dc943fb108e4eef76df
7
- data.tar.gz: 4b5caba384adc3a2b45e8532cd1dba357bf7404d577da3a24defb9279a903a7b1c02bb1efdd28571517cab33ba3fa01dde88b1319da8cd8d051ecb308cd9d8b5
6
+ metadata.gz: 6c93471fa6670ce5c257fa7604bf9755bd910baffcfed62348f22dcb9242868ec42c9738f2b507317340085b22ada30140e3ad618408a5563fd00d449d4a8e81
7
+ data.tar.gz: e105b484e8b8e75177e126e8dd3b682514a4deb4f9135ace3e97ca49d4f9dcd54dc23800676cb48386faabe7a779deca53335f01affdc981351d11647cbed47c
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubocop-sorbet (0.3.3)
4
+ rubocop-sorbet (0.3.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -1,3 +1,6 @@
1
1
  Sorbet/ValidSigil:
2
2
  Enabled: true
3
3
  EnforcedStyle: typed_files
4
+
5
+ Sorbet/EnforceSignatures:
6
+ Enabled: true
@@ -14,13 +14,22 @@ module RuboCop
14
14
  # FooOrBar = T.any(Foo, Bar)
15
15
  #
16
16
  # # good
17
- # FooOrBar = T.type_alias(T.any(Foo, Bar))
17
+ # FooOrBar = T.type_alias { T.any(Foo, Bar) }
18
18
  class BindingConstantWithoutTypeAlias < RuboCop::Cop::Cop
19
19
  def_node_matcher(:binding_unaliased_type?, <<-PATTERN)
20
20
  (casgn _ _ [#not_nil? #not_t_let? #method_needing_aliasing_on_t?])
21
21
  PATTERN
22
22
 
23
23
  def_node_matcher(:using_type_alias?, <<-PATTERN)
24
+ (block
25
+ (send
26
+ (const nil? :T) :type_alias)
27
+ _
28
+ _
29
+ )
30
+ PATTERN
31
+
32
+ def_node_matcher(:using_deprecated_type_alias_syntax?, <<-PATTERN)
24
33
  (
25
34
  send
26
35
  (const nil? :T)
@@ -58,6 +67,16 @@ module RuboCop
58
67
 
59
68
  def on_casgn(node)
60
69
  return unless binding_unaliased_type?(node) && !using_type_alias?(node.children[2])
70
+ if using_deprecated_type_alias_syntax?(node.children[2])
71
+ add_offense(
72
+ node.children[2],
73
+ message: "It looks like you're using the old `T.type_alias` syntax. " \
74
+ '`T.type_alias` now expects a block.' \
75
+ 'Run Sorbet with the options "--autocorrect --error-white-list=5043" ' \
76
+ 'to automatically upgrade to the new syntax.'
77
+ )
78
+ return
79
+ end
61
80
  add_offense(
62
81
  node.children[2],
63
82
  message: "It looks like you're trying to bind a type to a constant. " \
@@ -69,7 +88,7 @@ module RuboCop
69
88
  lambda do |corrector|
70
89
  corrector.replace(
71
90
  node.source_range,
72
- "T.type_alias(#{node.source})"
91
+ "T.type_alias { #{node.source} }"
73
92
  )
74
93
  end
75
94
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Sorbet
8
+ # This cop checks that the Sorbet sigil comes as the first magic comment in the file.
9
+ #
10
+ # The expected order for magic comments is: typed, (en)?coding, warn_indent then frozen_string_literal.
11
+ #
12
+ # For example, the following bad ordering:
13
+ #
14
+ # ```ruby
15
+ # # frozen_string_literal: true
16
+ # # typed: true
17
+ # class Foo; end
18
+ # ```
19
+ #
20
+ # Will be corrected as:
21
+ #
22
+ # ```ruby
23
+ # # typed: true
24
+ # # frozen_string_literal: true
25
+ # class Foo; end
26
+ # ```
27
+ #
28
+ # Only `typed`, `(en)?coding`, `warn_indent` and `frozen_string_literal` magic comments are considered,
29
+ # other comments or magic comments are left in the same place.
30
+ class EnforceSigilOrder < ValidSigil
31
+ def investigate(processed_source)
32
+ return if processed_source.tokens.empty?
33
+
34
+ tokens = extract_magic_comments(processed_source)
35
+ return if tokens.empty?
36
+
37
+ check_magic_comments_order(tokens)
38
+ end
39
+
40
+ def autocorrect(_node)
41
+ lambda do |corrector|
42
+ tokens = extract_magic_comments(processed_source)
43
+
44
+ # Get the magic comments tokens in their expected order
45
+ expected = PREFERRED_ORDER.keys.map do |re|
46
+ tokens.select { |token| re.match?(token.text) }
47
+ end.flatten
48
+
49
+ tokens.each_with_index do |token, index|
50
+ corrector.replace(token.pos, expected[index].text)
51
+ end
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ CODING_REGEX = /#\s+(en)?coding:(?:\s+([\w]+))?/
58
+ INDENT_REGEX = /#\s+warn_indent:(?:\s+([\w]+))?/
59
+ FROZEN_REGEX = /#\s+frozen_string_literal:(?:\s+([\w]+))?/
60
+
61
+ PREFERRED_ORDER = {
62
+ SIGIL_REGEX => 'typed',
63
+ CODING_REGEX => 'encoding',
64
+ INDENT_REGEX => 'warn_indent',
65
+ FROZEN_REGEX => 'frozen_string_literal',
66
+ }.freeze
67
+
68
+ MAGIC_REGEX = Regexp.union(*PREFERRED_ORDER.keys)
69
+
70
+ # extraction
71
+
72
+ # Get all the tokens in `processed_source` that match `MAGIC_REGEX`
73
+ def extract_magic_comments(processed_source)
74
+ processed_source.tokens
75
+ .take_while { |token| token.type == :tCOMMENT }
76
+ .select { |token| MAGIC_REGEX.match?(token.text) }
77
+ end
78
+
79
+ # checks
80
+
81
+ def check_magic_comments_order(tokens)
82
+ # Get the current magic comments order
83
+ order = tokens.map do |token|
84
+ PREFERRED_ORDER.keys.find { |re| re.match?(token.text) }
85
+ end.compact.uniq
86
+
87
+ # Get the expected magic comments order based on the one used in the actual source
88
+ expected = PREFERRED_ORDER.keys.select do |re|
89
+ tokens.any? { |token| re.match?(token.text) }
90
+ end.uniq
91
+
92
+ if order != expected
93
+ tokens.each do |token|
94
+ add_offense(
95
+ token,
96
+ location: token.pos,
97
+ message: "Magic comments should be in the following order: #{PREFERRED_ORDER.values.join(', ')}."
98
+ )
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rubocop'
4
+ require_relative 'signature_cop'
4
5
 
5
6
  module RuboCop
6
7
  module Cop
@@ -18,13 +19,9 @@ module RuboCop
18
19
  #
19
20
  # # good
20
21
  # sig { void }
21
- class CheckedTrueInSignature < RuboCop::Cop::Cop
22
+ class CheckedTrueInSignature < SignatureCop
22
23
  include(RuboCop::Cop::RangeHelp)
23
24
 
24
- def_node_matcher(:signature?, <<~PATTERN)
25
- (block (send nil? :sig) (args) ...)
26
- PATTERN
27
-
28
25
  def_node_search(:offending_node, <<~PATTERN)
29
26
  (send _ :checked (true))
30
27
  PATTERN
@@ -36,9 +33,7 @@ module RuboCop
36
33
  '`include(WaffleCone::RuntimeChecks)` to this module and set other methods to `checked(false)`.'
37
34
  private_constant(:MESSAGE)
38
35
 
39
- def on_block(node)
40
- return unless signature?(node)
41
-
36
+ def on_signature(node)
42
37
  error = offending_node(node).first
43
38
  return unless error
44
39
 
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+ require 'stringio'
5
+ require_relative 'signature_cop'
6
+
7
+ module RuboCop
8
+ module Cop
9
+ module Sorbet
10
+ # This cop checks that every method definition and attribute accessor has a Sorbet signature.
11
+ #
12
+ # It also suggest an autocorrect with placeholders so the following code:
13
+ #
14
+ # ```
15
+ # def foo(a, b, c); end
16
+ # ```
17
+ #
18
+ # Will be corrected as:
19
+ #
20
+ # ```
21
+ # sig { params(a: T.untyped, b: T.untyped, c: T.untyped).returns(T.untyped)
22
+ # def foo(a, b, c); end
23
+ # ```
24
+ #
25
+ # You can configure the placeholders used by changing the following options:
26
+ #
27
+ # * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
28
+ # * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
29
+ class EnforceSignatures < SignatureCop
30
+ def_node_matcher(:accessor?, <<-PATTERN)
31
+ (send nil? {:attr_reader :attr_writer :attr_accessor} ...)
32
+ PATTERN
33
+
34
+ def on_def(node)
35
+ check_node(node)
36
+ end
37
+
38
+ def on_defs(node)
39
+ check_node(node)
40
+ end
41
+
42
+ def on_send(node)
43
+ return unless accessor?(node)
44
+ check_node(node)
45
+ end
46
+
47
+ def autocorrect(node)
48
+ lambda do |corrector|
49
+ suggest = SigSuggestion.new(node.loc.column, param_type_placeholder, return_type_placeholder)
50
+
51
+ if node.is_a?(RuboCop::AST::DefNode) # def something
52
+ node.arguments.each do |arg|
53
+ suggest.params << arg.children.first
54
+ end
55
+ elsif accessor?(node) # attr reader, writer, accessor
56
+ method = node.children[1]
57
+ symbol = node.children[2]
58
+ suggest.params << symbol.value if symbol && (method == :attr_writer || method == :attr_accessor)
59
+ suggest.returns = 'void' if method == :attr_writer
60
+ end
61
+
62
+ corrector.insert_before(node.loc.expression, suggest.to_autocorrect)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def check_node(node)
69
+ prev = previous_node(node)
70
+ unless signature?(prev)
71
+ add_offense(
72
+ node,
73
+ message: "Each method is required to have a signature."
74
+ )
75
+ 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]
82
+ end
83
+
84
+ def param_type_placeholder
85
+ cop_config['ParameterTypePlaceholder'] || 'T.untyped'
86
+ end
87
+
88
+ def return_type_placeholder
89
+ cop_config['ReturnTypePlaceholder'] || 'T.untyped'
90
+ end
91
+
92
+ class SigSuggestion
93
+ attr_accessor :params, :returns
94
+
95
+ def initialize(indent, param_placeholder, return_placeholder)
96
+ @params = []
97
+ @returns = nil
98
+ @indent = indent
99
+ @param_placeholder = param_placeholder
100
+ @return_placeholder = return_placeholder
101
+ end
102
+
103
+ def to_autocorrect
104
+ out = StringIO.new
105
+ out << 'sig { '
106
+ out << generate_params
107
+ out << generate_return
108
+ out << " }\n"
109
+ out << ' ' * @indent # preserve indent for the next line
110
+ out.string
111
+ end
112
+
113
+ private
114
+
115
+ def generate_params
116
+ return if @params.empty?
117
+ out = StringIO.new
118
+ out << 'params('
119
+ out << @params.map do |param|
120
+ "#{param}: #{@param_placeholder}"
121
+ end.join(", ")
122
+ out << ').'
123
+ out.string
124
+ end
125
+
126
+ def generate_return
127
+ return "returns(#{@return_placeholder})" if @returns.nil?
128
+ return @returns if @returns == 'void'
129
+ "returns(#{@returns})"
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rubocop'
4
+ require_relative 'signature_cop'
4
5
 
5
6
  module RuboCop
6
7
  module Cop
@@ -19,14 +20,8 @@ module RuboCop
19
20
  # # good
20
21
  # sig { params(b: String, a: Integer).void }
21
22
  # def foo(b:, a: 1); end
22
- class KeywordArgumentOrdering < RuboCop::Cop::Cop
23
- def_node_matcher(:signature?, <<-PATTERN)
24
- (block (send nil? :sig) (args) ...)
25
- PATTERN
26
-
27
- def on_block(node)
28
- return unless signature?(node)
29
-
23
+ class KeywordArgumentOrdering < SignatureCop
24
+ def on_signature(node)
30
25
  method_node = node.parent.children[node.sibling_index + 1]
31
26
  return if method_node.nil?
32
27
  method_parameters = method_node.arguments
@@ -40,7 +35,7 @@ module RuboCop
40
35
  out_of_kwoptarg = false
41
36
 
42
37
  parameters.reverse.each do |param|
43
- out_of_kwoptarg = true unless param.type == :kwoptarg || param.type == :blockarg
38
+ out_of_kwoptarg = true unless param.type == :kwoptarg || param.type == :blockarg || param.type == :kwrestarg
44
39
 
45
40
  next unless param.type == :kwoptarg && out_of_kwoptarg
46
41
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rubocop'
4
+ require_relative 'signature_cop'
4
5
 
5
6
  module RuboCop
6
7
  module Cop
@@ -18,18 +19,12 @@ module RuboCop
18
19
  # # good
19
20
  # sig { params(a: Integer, b: String).void }
20
21
  # def foo(a:, b:); end
21
- class ParametersOrderingInSignature < RuboCop::Cop::Cop
22
- def_node_matcher(:signature?, <<-PATTERN)
23
- (block (send nil? :sig) (args) ...)
24
- PATTERN
25
-
22
+ class ParametersOrderingInSignature < SignatureCop
26
23
  def_node_search(:signature_params, <<-PATTERN)
27
24
  (send _ :params ...)
28
25
  PATTERN
29
26
 
30
- def on_block(node)
31
- return unless signature?(node)
32
-
27
+ def on_signature(node)
33
28
  sig_params = signature_params(node).first
34
29
  sig_params_order =
35
30
  if sig_params.nil?
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rubocop'
4
+ require_relative 'signature_cop'
4
5
 
5
6
  begin
6
7
  require 'unparser'
@@ -11,7 +12,7 @@ end
11
12
  module RuboCop
12
13
  module Cop
13
14
  module Sorbet
14
- class SignatureBuildOrder < RuboCop::Cop::Cop
15
+ class SignatureBuildOrder < SignatureCop
15
16
  ORDER =
16
17
  [
17
18
  :type_parameters,
@@ -27,17 +28,11 @@ module RuboCop
27
28
  :on_failure,
28
29
  ].each_with_index.to_h.freeze
29
30
 
30
- def_node_matcher(:signature?, <<~PATTERN)
31
- (block (send nil? :sig) (args) ...)
32
- PATTERN
33
-
34
31
  def_node_search(:root_call, <<~PATTERN)
35
32
  (send nil? {#{ORDER.keys.map(&:inspect).join(' ')}} ...)
36
33
  PATTERN
37
34
 
38
- def on_block(node)
39
- return unless signature?(node)
40
-
35
+ def on_signature(node)
41
36
  calls = call_chain(node.children[2]).map(&:method_name)
42
37
  return unless calls.any?
43
38
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Sorbet
8
+ # Abstract cop specific to Sorbet signatures
9
+ #
10
+ # You can subclass it to use the `on_signature` trigger and the `signature?` node matcher.
11
+ class SignatureCop < RuboCop::Cop::Cop
12
+ @registry = Cop.registry # So we can properly subclass this cop
13
+
14
+ def_node_matcher(:signature?, <<~PATTERN)
15
+ (block (send nil? :sig) (args) ...)
16
+ PATTERN
17
+
18
+ def on_block(node)
19
+ on_signature(node) if signature?(node)
20
+ end
21
+
22
+ def on_signature(_)
23
+ # To be defined in subclasses
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rubocop/cop/sorbet/allow_incompatible_override'
4
3
  require_relative 'rubocop/cop/sorbet/binding_constants_without_type_alias'
5
- require_relative 'rubocop/cop/sorbet/checked_true_in_signature'
6
4
  require_relative 'rubocop/cop/sorbet/constants_from_strings'
7
- require_relative 'rubocop/cop/sorbet/signature_build_order'
8
5
  require_relative 'rubocop/cop/sorbet/forbid_superclass_const_literal'
9
6
  require_relative 'rubocop/cop/sorbet/forbid_include_const_literal'
10
- require_relative 'rubocop/cop/sorbet/parameters_ordering_in_signature'
11
- require_relative 'rubocop/cop/sorbet/keyword_argument_ordering'
7
+
8
+ require_relative 'rubocop/cop/sorbet/signatures/allow_incompatible_override'
9
+ require_relative 'rubocop/cop/sorbet/signatures/checked_true_in_signature'
10
+ require_relative 'rubocop/cop/sorbet/signatures/keyword_argument_ordering'
11
+ require_relative 'rubocop/cop/sorbet/signatures/parameters_ordering_in_signature'
12
+ require_relative 'rubocop/cop/sorbet/signatures/signature_build_order'
13
+ require_relative 'rubocop/cop/sorbet/signatures/enforce_signatures'
12
14
 
13
15
  require_relative 'rubocop/cop/sorbet/sigils/valid_sigil'
14
16
  require_relative 'rubocop/cop/sorbet/sigils/has_sigil'
@@ -17,3 +19,4 @@ require_relative 'rubocop/cop/sorbet/sigils/false_sigil'
17
19
  require_relative 'rubocop/cop/sorbet/sigils/true_sigil'
18
20
  require_relative 'rubocop/cop/sorbet/sigils/strict_sigil'
19
21
  require_relative 'rubocop/cop/sorbet/sigils/strong_sigil'
22
+ require_relative 'rubocop/cop/sorbet/sigils/enforce_sigil_order'
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'rubocop-sorbet'
8
- spec.version = '0.3.3'
8
+ spec.version = '0.3.4'
9
9
  spec.authors = ['Ufuk Kayserilioglu', 'Alan Wu', 'Alexandre Terrasa', 'Peter Zhu']
10
10
  spec.email = ['ruby@shopify.com']
11
11
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-sorbet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2019-10-08 00:00:00.000000000 Z
14
+ date: 2019-12-02 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rspec
@@ -80,14 +80,11 @@ files:
80
80
  - config/default.yml
81
81
  - dev.yml
82
82
  - lib/rubocop-sorbet.rb
83
- - lib/rubocop/cop/sorbet/allow_incompatible_override.rb
84
83
  - lib/rubocop/cop/sorbet/binding_constants_without_type_alias.rb
85
- - lib/rubocop/cop/sorbet/checked_true_in_signature.rb
86
84
  - lib/rubocop/cop/sorbet/constants_from_strings.rb
87
85
  - lib/rubocop/cop/sorbet/forbid_include_const_literal.rb
88
86
  - lib/rubocop/cop/sorbet/forbid_superclass_const_literal.rb
89
- - lib/rubocop/cop/sorbet/keyword_argument_ordering.rb
90
- - lib/rubocop/cop/sorbet/parameters_ordering_in_signature.rb
87
+ - lib/rubocop/cop/sorbet/sigils/enforce_sigil_order.rb
91
88
  - lib/rubocop/cop/sorbet/sigils/false_sigil.rb
92
89
  - lib/rubocop/cop/sorbet/sigils/has_sigil.rb
93
90
  - lib/rubocop/cop/sorbet/sigils/ignore_sigil.rb
@@ -95,7 +92,13 @@ files:
95
92
  - lib/rubocop/cop/sorbet/sigils/strong_sigil.rb
96
93
  - lib/rubocop/cop/sorbet/sigils/true_sigil.rb
97
94
  - lib/rubocop/cop/sorbet/sigils/valid_sigil.rb
98
- - lib/rubocop/cop/sorbet/signature_build_order.rb
95
+ - lib/rubocop/cop/sorbet/signatures/allow_incompatible_override.rb
96
+ - lib/rubocop/cop/sorbet/signatures/checked_true_in_signature.rb
97
+ - lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb
98
+ - lib/rubocop/cop/sorbet/signatures/keyword_argument_ordering.rb
99
+ - lib/rubocop/cop/sorbet/signatures/parameters_ordering_in_signature.rb
100
+ - lib/rubocop/cop/sorbet/signatures/signature_build_order.rb
101
+ - lib/rubocop/cop/sorbet/signatures/signature_cop.rb
99
102
  - lib/rubocop_sorbet.rb
100
103
  - rubocop-sorbet.gemspec
101
104
  - service.yml