rubocop-canon 0.5.0 → 0.6.0

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: f8b4a8966824947c873a90109b21e96a5d2c3fa1d01c71c3423ffe45afa7864e
4
- data.tar.gz: eea9755c6e9ceab22ab0a316c5ec3eb52e3f290baa9cfe1a546adaaa25dc1bb3
3
+ metadata.gz: 899e2d0ddeff784568a001baef67ad0d3e42cf79178a89be152a3daaea76211d
4
+ data.tar.gz: a2e3f4f4e9cb206feb78327e911762bcbd42a9f082454da51251cfbfc20dd21d
5
5
  SHA512:
6
- metadata.gz: fd398ceccc79ca4d5d8f575dcf9141e84bd0648e7ca673edb166955c56898b65f61efcd4988435cb21831a83485c2972b7e12c97defeb9f5ab7610eead5ad805
7
- data.tar.gz: 65d340eb6eec5fba2b6d95c592abc238d869dd047135059089583596558191633f227a7a0e9aaa125383c241fd433214a435a252f03ca511e808f067a432adca
6
+ metadata.gz: c1b5e6bd6c16a23b28e8ab256dadad94566e0e3ed7780273072d09736758ed804bc42cc15928e60898a06e64199fa0af166c9f9d86e875aa2fad9cbd74b84352
7
+ data.tar.gz: 29236733c6b92470a145bb9ca1d1fd4182c365f5bbdd14aba418bd9d57898314c6721bba2c64da8c0089270f4f33f02ed3c725c476053c6a22d097397fc67d74
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Canon
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
@@ -7,17 +7,23 @@ module RuboCop
7
7
  #
8
8
  # A cop mixing this in supplies the decision — how many blank lines a
9
9
  # pair of statements takes — and leaves the counting, the comment guard,
10
- # the source ranges and the corrections here.
10
+ # the source ranges and the corrections here. A statement ends where its
11
+ # last heredoc ends, so a heredoc is part of the statement, not blank
12
+ # lines after it.
11
13
  module BlankLineHelp
12
14
  include RangeHelp
13
15
 
14
16
  BLOCK_TYPES = %i[block numblock].freeze
17
+ HEREDOC_TYPES = %i[str dstr xstr].freeze
15
18
  MSG_MULTILINE = 'Add a blank line around the multiline statement.'
16
19
 
17
20
  private
18
21
 
19
22
  def body_statements(node)
20
- body = node.body
23
+ statements(node.body)
24
+ end
25
+
26
+ def statements(body)
21
27
  return [] if body.nil?
22
28
  return body.children if body.begin_type?
23
29
 
@@ -25,11 +31,11 @@ module RuboCop
25
31
  end
26
32
 
27
33
  def blank_lines_between(previous, following)
28
- following.first_line - previous.last_line - 1
34
+ following.first_line - end_line(previous) - 1
29
35
  end
30
36
 
31
37
  def multiline?(node)
32
- node.first_line != node.last_line
38
+ node.first_line != end_line(node)
33
39
  end
34
40
 
35
41
  def multiline_pair?(previous, following)
@@ -38,6 +44,14 @@ module RuboCop
38
44
  multiline?(following)
39
45
  end
40
46
 
47
+ def end_line(node)
48
+ [node.last_line, *heredoc_end_lines(node)].max
49
+ end
50
+
51
+ def heredoc_end_lines(node)
52
+ node.each_node(*HEREDOC_TYPES).select(&:heredoc?).map { |heredoc| heredoc.loc.heredoc_end.line }
53
+ end
54
+
41
55
  def require_blank_line(previous, following, message)
42
56
  return if comments_between?(previous, following)
43
57
  return unless blank_lines_between(previous, following).zero?
@@ -54,7 +68,7 @@ module RuboCop
54
68
 
55
69
  def comments_between?(previous, following)
56
70
  processed_source.comments.any? do |comment|
57
- comment.loc.line >= previous.last_line && comment.loc.line < following.first_line
71
+ comment.loc.line >= end_line(previous) && comment.loc.line < following.first_line
58
72
  end
59
73
  end
60
74
 
@@ -66,7 +80,7 @@ module RuboCop
66
80
 
67
81
  def register_missing_blank_line(previous, following, message)
68
82
  add_offense(first_line_range(following), message:) do |corrector|
69
- corrector.insert_after(line_range(previous.last_line), "\n")
83
+ corrector.insert_after(line_range(end_line(previous)), "\n")
70
84
  end
71
85
  end
72
86
 
@@ -94,11 +108,11 @@ module RuboCop
94
108
  end
95
109
 
96
110
  def blank_line_range(previous)
97
- line_range(previous.last_line + 1)
111
+ line_range(end_line(previous) + 1)
98
112
  end
99
113
 
100
114
  def newline_range(previous)
101
- range_between(line_range(previous.last_line).end_pos, line_range(previous.last_line + 1).end_pos)
115
+ range_between(line_range(end_line(previous)).end_pos, line_range(end_line(previous) + 1).end_pos)
102
116
  end
103
117
 
104
118
  def first_line_range(node)
@@ -3,19 +3,30 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Canon
6
- # Enforces blank lines between declaration groups in a class, module or
7
- # DSL block body.
6
+ # Enforces blank lines between declaration groups in a class, module,
7
+ # singleton class or DSL block body.
8
8
  #
9
- # `GroupedMethods` is the whole specification: two declarations calling
10
- # methods listed in the same group take no blank line between them, and
11
- # two calling methods listed in different groups take exactly one. The
12
- # cop says nothing about a method name it has not been given, so a DSL
13
- # it does not know stays as its author wrote it.
9
+ # Every method name is its own group: two declarations calling the same
10
+ # method take no blank line between them, and two calling different
11
+ # methods take exactly one. `GroupedMethods` merges names into one
12
+ # group `belongs_to` with `has_many`, `validate` with `validates`.
13
+ # Constants are a group of their own, and so are the two Ruby families
14
+ # RuboCop's layout cops already treat as one: `attr`, `attr_accessor`,
15
+ # `attr_reader` and `attr_writer`; `extend`, `include` and `prepend`.
14
16
  #
15
- # A declaration spanning several lines, and any block, is a group of one
16
- # whatever it calls, so it always stands apart from its neighbours.
17
- # Everything else the cop has not been told about an unlisted method
18
- # name on one line, a constant it leaves alone.
17
+ # A declaration is a call without a receiver or on `self`, or a constant
18
+ # assignment. A declaration spanning several lines, a block and a method
19
+ # definition are each a group of one whatever they call, so they always
20
+ # stand apart from their neighbours. Two definitions in a row belong to
21
+ # `Layout/EmptyLineBetweenDefs` and the lines around an access modifier
22
+ # to `Layout/EmptyLinesAroundAccessModifier`, so this cop leaves those
23
+ # alone. Anything else — an assignment, a conditional — it has no
24
+ # opinion about.
25
+ #
26
+ # A DSL block counts when it has no receiver and is a statement of a
27
+ # class, module or singleton class body, or of such a block: `included
28
+ # do`, `with_options ... do`, `action :index do`. A block passed as an
29
+ # argument — the lambda given to `scope` — is left alone.
19
30
  #
20
31
  # @example GroupedMethods: [[belongs_to, has_many], [validate, validates]]
21
32
  # # bad
@@ -24,6 +35,9 @@ module RuboCop
24
35
  #
25
36
  # has_many :shift_assignments, dependent: :destroy
26
37
  # validates :starts_at, presence: true
38
+ # scope :upcoming, -> { where(starts_at: Time.current..) }
39
+ #
40
+ # scope :past, -> { where(starts_at: ...Time.current) }
27
41
  # end
28
42
  #
29
43
  # # good
@@ -32,12 +46,9 @@ module RuboCop
32
46
  # has_many :shift_assignments, dependent: :destroy
33
47
  #
34
48
  # validates :starts_at, presence: true
35
- # end
36
49
  #
37
- # # good — neither name is listed, so the cop leaves the body alone
38
- # class Filtering < Capability::Base
39
- # request_transformer RequestTransformer
40
- # api_builder APIBuilder
50
+ # scope :upcoming, -> { where(starts_at: Time.current..) }
51
+ # scope :past, -> { where(starts_at: ...Time.current) }
41
52
  # end
42
53
  class DeclarationGroups < Base
43
54
  extend AutoCorrector
@@ -46,9 +57,9 @@ module RuboCop
46
57
  MSG_EXTRA = 'Remove the blank line between declarations of the same group.'
47
58
  MSG_MISSING = 'Add a blank line between declaration groups.'
48
59
  ACCESS_MODIFIERS = %i[private protected public module_function private_class_method].freeze
60
+ BODY_TYPES = %i[class module sclass].freeze
61
+ BUILT_IN_GROUPS = [%w[attr attr_accessor attr_reader attr_writer], %w[extend include prepend]].freeze
49
62
  DEFINITION_TYPES = %i[def defs].freeze
50
- BLOCK_TYPES = %i[block numblock].freeze
51
- STANDALONE_TYPES = %i[block numblock def defs].freeze
52
63
 
53
64
  def on_class(node)
54
65
  check_declarations(node)
@@ -58,8 +69,12 @@ module RuboCop
58
69
  check_declarations(node)
59
70
  end
60
71
 
72
+ def on_sclass(node)
73
+ check_declarations(node)
74
+ end
75
+
61
76
  def on_block(node)
62
- return unless declaration_context?(node)
77
+ return unless declaration_block?(node)
63
78
 
64
79
  check_declarations(node)
65
80
  end
@@ -71,7 +86,6 @@ module RuboCop
71
86
  def check_declarations(node)
72
87
  statements = body_statements(node)
73
88
  return if statements.size < 2
74
- return unless statements.all? { |statement| declaration?(statement) }
75
89
 
76
90
  statements.each_cons(2) do |previous, following|
77
91
  next if definitions?(previous, following)
@@ -112,35 +126,54 @@ module RuboCop
112
126
  end
113
127
 
114
128
  def standalone?(node)
115
- return true if STANDALONE_TYPES.include?(node.type)
129
+ return true if BLOCK_TYPES.include?(node.type)
130
+ return true if definition?(node)
116
131
 
117
132
  multiline?(node)
118
133
  end
119
134
 
120
135
  def group_key(node)
121
- return unless node.send_type?
136
+ return :constant if node.casgn_type?
137
+ return unless declaration_call?(node)
122
138
 
123
- grouped_methods.index { |group| group.include?(node.method_name.to_s) }
139
+ name = node.method_name.to_s
140
+ group = groups.find { |members| members.include?(name) }
141
+ group.nil? ? name : group.first
124
142
  end
125
143
 
126
- def declaration_context?(node)
127
- enclosing = node.each_ancestor(:def, :defs, :class, :module).first
128
- return false if enclosing.nil?
129
- return true if enclosing.class_type?
144
+ def declaration_call?(node)
145
+ return false unless node.send_type?
146
+ return true if node.receiver.nil?
130
147
 
131
- enclosing.module_type?
148
+ node.receiver.self_type?
132
149
  end
133
150
 
134
- def declaration?(node)
135
- return true if node.casgn_type?
151
+ def declaration_block?(node)
152
+ return false unless node.send_node.receiver.nil?
153
+
154
+ declaration_body?(node.parent)
155
+ end
156
+
157
+ def declaration_body?(node)
158
+ return false if node.nil?
159
+ return declaration_body?(node.parent) if node.begin_type?
160
+ return true if BODY_TYPES.include?(node.type)
161
+ return false unless BLOCK_TYPES.include?(node.type)
162
+
163
+ declaration_block?(node)
164
+ end
165
+
166
+ def definition?(node)
136
167
  return true if DEFINITION_TYPES.include?(node.type)
137
- return node.send_node.receiver.nil? if BLOCK_TYPES.include?(node.type)
168
+ return false unless node.send_type?
138
169
 
139
- node.send_type? && node.receiver.nil?
170
+ node.arguments.any? { |argument| DEFINITION_TYPES.include?(argument.type) }
140
171
  end
141
172
 
142
173
  def definitions?(previous, following)
143
- DEFINITION_TYPES.include?(previous.type) && DEFINITION_TYPES.include?(following.type)
174
+ return false unless definition?(previous)
175
+
176
+ definition?(following)
144
177
  end
145
178
 
146
179
  def access_modifier?(node)
@@ -150,8 +183,8 @@ module RuboCop
150
183
  ACCESS_MODIFIERS.include?(node.method_name)
151
184
  end
152
185
 
153
- def grouped_methods
154
- @grouped_methods ||= Array(cop_config['GroupedMethods']).map { |group| Array(group).map(&:to_s) }
186
+ def groups
187
+ @groups ||= BUILT_IN_GROUPS + Array(cop_config['GroupedMethods']).map { |group| Array(group).map(&:to_s) }
155
188
  end
156
189
  end
157
190
  end
@@ -15,15 +15,22 @@ module RuboCop
15
15
  # The list is empty by default, so the cop only removes until it is
16
16
  # configured.
17
17
  #
18
- # Guard clauses group: two in a row take no blank line between them.
19
- # The position after the last guard belongs to
20
- # `Layout/EmptyLineAfterGuardClause`, so this cop leaves it alone and
21
- # recognises a guard by that cop's own definition: an `if` whose branch
22
- # is a single-line `return`, `break`, `next`, `raise` or `fail`. A bare
23
- # `raise` on its own line is a statement, not a guard.
18
+ # Guard clauses group by what they do: the guards that return sit
19
+ # together, the guards that raise sit together, and one blank line
20
+ # separates the two groups. The position after the last guard belongs
21
+ # to `Layout/EmptyLineAfterGuardClause`, so this cop leaves it alone
22
+ # and recognises a guard by that cop's own definition: an `if` whose
23
+ # branch is a single-line `return`, `break`, `next`, `raise` or `fail`.
24
+ # A bare `raise` on its own line is a statement, not a guard. A guard
25
+ # whose branch spans several lines is a multiline statement to both
26
+ # cops, so this cop sets it apart.
24
27
  #
25
- # A statement spanning several lines is always set apart by a blank
26
- # line on each side, guards included.
28
+ # A statement spanning several lines a heredoc counts is always set
29
+ # apart by a blank line on each side, guards included.
30
+ #
31
+ # A method that rescues has several bodies: the statements it protects,
32
+ # each `rescue` clause, the `else` and the `ensure`. The rules apply
33
+ # inside each of them.
27
34
  #
28
35
  # An entry matches a method name anywhere in the call chain. Write it
29
36
  # dotted — `errors.add` — to match a receiver and method together, so
@@ -61,12 +68,24 @@ module RuboCop
61
68
  #
62
69
  # order.items.sum(&:amount)
63
70
  # end
71
+ #
72
+ # # good — the guards that return and the guard that raises are two groups
73
+ # def resolve(name)
74
+ # return nil if name.nil?
75
+ # return name if name.is_a?(Array)
76
+ #
77
+ # raise ArgumentError, "#{name} not found" unless enums.key?(name)
78
+ #
79
+ # enums.fetch(name)
80
+ # end
64
81
  class MethodBodyBlankLines < Base
65
82
  extend AutoCorrector
66
83
  include BlankLineHelp
67
84
 
68
85
  MSG_EXTRA = 'Remove the blank line inside the method body.'
69
86
  MSG_MISSING = 'Add a blank line around the standalone call.'
87
+ MSG_MIXED_GUARDS = 'Add a blank line between a guard that returns and a guard that raises.'
88
+ CLAUSE_TYPES = %i[ensure rescue resbody].freeze
70
89
  JUMP_METHODS = %i[raise fail].freeze
71
90
 
72
91
  def on_def(node)
@@ -80,9 +99,24 @@ module RuboCop
80
99
  private
81
100
 
82
101
  def check_method_body(node)
83
- statements = body_statements(node)
84
- return if statements.size < 2
102
+ statement_lists(node.body).each { |statements| check_statements(statements) }
103
+ end
104
+
105
+ def statement_lists(node)
106
+ return [] if node.nil?
107
+ return [statements(node)] unless CLAUSE_TYPES.include?(node.type)
85
108
 
109
+ clause_bodies(node).flat_map { |body| statement_lists(body) }
110
+ end
111
+
112
+ def clause_bodies(node)
113
+ return [node.children.first, node.branch] if node.ensure_type?
114
+ return [node.body] if node.resbody_type?
115
+
116
+ [node.body, *node.resbody_branches, node.else_branch]
117
+ end
118
+
119
+ def check_statements(statements)
86
120
  statements.each_cons(2) do |previous, following|
87
121
  next if guard_boundary?(previous, following)
88
122
 
@@ -93,12 +127,14 @@ module RuboCop
93
127
  def guard_boundary?(previous, following)
94
128
  return false unless guard?(previous)
95
129
 
96
- !guard?(following)
130
+ !contains_guard?(following)
97
131
  end
98
132
 
99
133
  def check_gap(previous, following)
100
134
  if multiline_pair?(previous, following)
101
135
  require_blank_line(previous, following, MSG_MULTILINE)
136
+ elsif mixed_guards?(previous, following)
137
+ require_blank_line(previous, following, MSG_MIXED_GUARDS)
102
138
  elsif separated_pair?(previous, following)
103
139
  require_blank_line(previous, following, MSG_MISSING)
104
140
  else
@@ -125,9 +161,25 @@ module RuboCop
125
161
  chain.each_cons(2).any? { |pair| pair.join('.') == name }
126
162
  end
127
163
 
164
+ def mixed_guards?(previous, following)
165
+ return false unless guard?(previous)
166
+ return false unless guard?(following)
167
+
168
+ raising_guard?(previous) != raising_guard?(following)
169
+ end
170
+
128
171
  def guard?(node)
129
172
  return false unless node.if_type?
130
173
 
174
+ branch = node.if_branch
175
+ return false if branch.nil?
176
+
177
+ branch.guard_clause?
178
+ end
179
+
180
+ def contains_guard?(node)
181
+ return false unless node.if_type?
182
+
131
183
  branch = node.if_branch
132
184
  return false if branch.nil?
133
185
  return true if branch.guard_clause?
@@ -145,6 +197,13 @@ module RuboCop
145
197
  JUMP_METHODS.include?(node.method_name)
146
198
  end
147
199
 
200
+ def raising_guard?(node)
201
+ jump = node.if_branch
202
+ jump = jump.rhs if jump.operator_keyword?
203
+
204
+ jump.send_type?
205
+ end
206
+
148
207
  def separated_methods
149
208
  @separated_methods ||= Array(cop_config['SeparatedMethods']).map(&:to_s)
150
209
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - skiftle