rubocop-sorbet 0.3.3 → 0.3.4

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 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