rubocop-canon 0.2.1 → 0.3.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: 24d224c2ecf5e361f702c300a79778baf8ad63247fb726cb8c39b89534d7dd3f
4
- data.tar.gz: 8e4e0ca31f844a61bdba984c2efd2ccadb2d0ddea11693f38dc452c9b356e40c
3
+ metadata.gz: 26e0b8c697ca4788f1bf6836c36cffa1d3eb6ddbf2e6422416a2a20e3fb85c41
4
+ data.tar.gz: f07d041294a92e97cff5e83c8bfa05ec9f83cca4c00ac0c1ee3a626d0a2da75a
5
5
  SHA512:
6
- metadata.gz: b1eea01a2c5652d7513ccfe08b048cd61817f4f20c3dcb757c5e7b0e96ac99c92d0b52f1840d40b69a9c3c5b2eced42444c22ae29ed3461eedc83c1dbd4b5081
7
- data.tar.gz: 98f0a6237ae0b71995af7bf03e8d8b46bcf83ebd92f3fa0ddf81f607a503ab71061b365f2e19a05279702791998be00d7cd1d621224dcdd965ae79d2d3fc2d84
6
+ metadata.gz: d50e6ec2327f88dcca6a886c87e164a592c13bea20e84c95b081f31299f701c97690b62332bfca9ffe8278260d7129cc28ca658db747220783dfe9706657e25c
7
+ data.tar.gz: 939c0422bbb87f0a328ae04ccab63c9e816046e8dbbe422faf4879f32d1bd00ceac92e0d840d660ae96e3ead1a13bf5960a3cd7ae2d60c64fb07852d2c61807b
data/README.md CHANGED
@@ -40,7 +40,6 @@ Canon/BlockPhases:
40
40
  - it
41
41
  TrailingMethods: # calls that make up the trailing phase (required)
42
42
  - expect
43
- MaxPhases: 3 # phases allowed before the trailing one
44
43
 
45
44
  Canon/DeclarationGroups:
46
45
  GroupedMethods: # method names that form one group
data/config/default.yml CHANGED
@@ -3,7 +3,6 @@ Canon/BlockPhases:
3
3
  Enabled: false
4
4
  VersionAdded: '0.2'
5
5
  Blocks: []
6
- MaxPhases: 3
7
6
  TrailingMethods: []
8
7
 
9
8
  Canon/DeclarationGroups:
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Canon
5
- VERSION = '0.2.1'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -27,6 +27,10 @@ module RuboCop
27
27
  following.first_line - previous.last_line - 1
28
28
  end
29
29
 
30
+ def multiline?(node)
31
+ node.first_line != node.last_line
32
+ end
33
+
30
34
  def comments_between?(previous, following)
31
35
  processed_source.comments.any? do |comment|
32
36
  comment.loc.line >= previous.last_line && comment.loc.line < following.first_line
@@ -7,18 +7,22 @@ module RuboCop
7
7
  #
8
8
  # A body checked by this cop ends in a *trailing phase*: the run of
9
9
  # statements that call one of `TrailingMethods`. Exactly one blank line
10
- # opens that run, no blank line splits it, and the statements before it
11
- # form at most `MaxPhases` - 1 further phases.
10
+ # opens that run. Before it come an arrange phase and, when the last
11
+ # leading statement is a call rather than an assignment, an act phase
12
+ # opened by one blank line. Inside a phase no blank line is allowed.
13
+ #
14
+ # An assignment cannot end the act phase. When the last statement before
15
+ # the trailing phase assigns, there is no act: every leading statement
16
+ # belongs to one arrange phase.
17
+ #
18
+ # A statement spanning several lines is always set apart by a blank line
19
+ # on each side, whatever phase it is in.
12
20
  #
13
21
  # `Blocks` names the methods whose blocks are checked and
14
22
  # `TrailingMethods` the calls that make up the trailing phase, matched
15
23
  # against the leftmost call in a chain. Both are empty by default, so
16
24
  # the cop does nothing until it is configured.
17
25
  #
18
- # A body carrying more phases than `MaxPhases` allows keeps the last
19
- # separator before the trailing phase and loses the earlier ones —
20
- # everything ahead of the last leading statement is one phase.
21
- #
22
26
  # @example Blocks: ['step'], TrailingMethods: ['report']
23
27
  # # bad — no blank line opens the trailing phase
24
28
  # step 'totals the order' do
@@ -26,21 +30,30 @@ module RuboCop
26
30
  # report order.total
27
31
  # end
28
32
  #
29
- # # bad — a blank line splits the trailing phase
33
+ # # bad — an assignment is arrange, not act
30
34
  # step 'totals the order' do
31
35
  # order = build_order
32
36
  #
33
- # report order.total
37
+ # total = order.total
34
38
  #
35
- # report order.currency
39
+ # report total
36
40
  # end
37
41
  #
38
42
  # # good
39
43
  # step 'totals the order' do
40
44
  # order = build_order
45
+ # total = order.total
46
+ #
47
+ # report total
48
+ # end
49
+ #
50
+ # # good — the call is the act
51
+ # step 'totals the order' do
52
+ # order = build_order
53
+ #
54
+ # settle(order)
41
55
  #
42
56
  # report order.total
43
- # report order.currency
44
57
  # end
45
58
  class BlockPhases < Base
46
59
  extend AutoCorrector
@@ -48,7 +61,10 @@ module RuboCop
48
61
 
49
62
  MSG_EXTRA = 'Remove the blank line inside the trailing phase.'
50
63
  MSG_MISSING = 'Add a blank line before the trailing phase.'
51
- MSG_TOO_MANY_PHASES = 'Use at most %<max_phases>d phases in a block body.'
64
+ MSG_ARRANGE_SPLIT = 'Remove the blank line inside the arrange phase.'
65
+ MSG_ACT_UNSEPARATED = 'Add a blank line before the act.'
66
+ MSG_MULTILINE = 'Add a blank line around the multiline statement.'
67
+ ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn masgn op_asgn or_asgn and_asgn].freeze
52
68
 
53
69
  def on_block(node)
54
70
  return unless configured_block?(node)
@@ -73,44 +89,59 @@ module RuboCop
73
89
 
74
90
  def check_trailing_phase(statements, trailing_start)
75
91
  statements[trailing_start..].each_cons(2) do |previous, following|
76
- next if comments_between?(previous, following)
77
- next if blank_lines_between(previous, following) != 1
78
-
79
- register_extra_blank_line(previous, MSG_EXTRA)
92
+ if multiline_pair?(previous, following)
93
+ require_blank_line(previous, following, MSG_MULTILINE)
94
+ else
95
+ forbid_blank_line(previous, following, MSG_EXTRA)
96
+ end
80
97
  end
81
98
  end
82
99
 
83
100
  def check_leading_phases(statements, trailing_start)
84
101
  return if trailing_start.zero?
85
102
 
86
- check_separator(statements[trailing_start - 1], statements[trailing_start])
87
- check_phase_count(statements[..(trailing_start - 1)])
103
+ require_blank_line(statements[trailing_start - 1], statements[trailing_start], MSG_MISSING)
104
+ leading = statements[..(trailing_start - 1)]
105
+
106
+ leading.each_cons(2).with_index do |(previous, following), index|
107
+ check_leading_gap(leading, previous, following, index)
108
+ end
109
+ end
110
+
111
+ def check_leading_gap(leading, previous, following, index)
112
+ if multiline_pair?(previous, following)
113
+ require_blank_line(previous, following, MSG_MULTILINE)
114
+ elsif assignment?(leading.last)
115
+ forbid_blank_line(previous, following, MSG_ARRANGE_SPLIT)
116
+ elsif index == leading.size - 2
117
+ require_blank_line(previous, following, MSG_ACT_UNSEPARATED)
118
+ else
119
+ forbid_blank_line(previous, following, MSG_ARRANGE_SPLIT)
120
+ end
88
121
  end
89
122
 
90
- def check_separator(previous, following)
123
+ def require_blank_line(previous, following, message)
91
124
  return if comments_between?(previous, following)
92
- return if blank_lines_between(previous, following) == 1
125
+ return unless blank_lines_between(previous, following).zero?
93
126
 
94
- register_missing_blank_line(previous, following, MSG_MISSING)
127
+ register_missing_blank_line(previous, following, message)
95
128
  end
96
129
 
97
- def check_phase_count(statements)
98
- separators = leading_separators(statements)
99
- return if separators.size < max_phases - 1
100
-
101
- surplus = separators[0..-(max_phases - 1)]
130
+ def forbid_blank_line(previous, following, message)
131
+ return if comments_between?(previous, following)
132
+ return unless blank_lines_between(previous, following) == 1
102
133
 
103
- surplus.each do |previous|
104
- register_extra_blank_line(previous, format(MSG_TOO_MANY_PHASES, max_phases:))
105
- end
134
+ register_extra_blank_line(previous, message)
106
135
  end
107
136
 
108
- def leading_separators(statements)
109
- statements.each_cons(2).filter_map do |previous, following|
110
- next if comments_between?(previous, following)
137
+ def multiline_pair?(previous, following)
138
+ return true if multiline?(previous)
111
139
 
112
- previous if blank_lines_between(previous, following) == 1
113
- end
140
+ multiline?(following)
141
+ end
142
+
143
+ def assignment?(node)
144
+ ASSIGNMENT_TYPES.include?(node.type)
114
145
  end
115
146
 
116
147
  def detect_trailing_start(statements)
@@ -145,10 +176,6 @@ module RuboCop
145
176
  def trailing_methods
146
177
  @trailing_methods ||= Array(cop_config['TrailingMethods']).map(&:to_s)
147
178
  end
148
-
149
- def max_phases
150
- cop_config['MaxPhases']
151
- end
152
179
  end
153
180
  end
154
181
  end
@@ -12,10 +12,10 @@ module RuboCop
12
12
  # cop says nothing about a method name it has not been given, so a DSL
13
13
  # it does not know stays as its author wrote it.
14
14
  #
15
- # A block is a group of one whatever it calls, so a DSL block always
16
- # stands apart from the declarations around it. Everything else the cop
17
- # has not been told about — an unlisted method name, a constant — it
18
- # leaves alone.
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.
19
19
  #
20
20
  # @example GroupedMethods: [[belongs_to, has_many], [validate, validates]]
21
21
  # # bad
@@ -112,7 +112,9 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def standalone?(node)
115
- STANDALONE_TYPES.include?(node.type)
115
+ return true if STANDALONE_TYPES.include?(node.type)
116
+
117
+ multiline?(node)
116
118
  end
117
119
 
118
120
  def group_key(node)
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - skiftle