rubocop-fourshark 0.8.6 → 0.9.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: 1bdc31121f34ef40cca25af1bc804caafecc8b8f58fd2996457cd01d82b98502
4
- data.tar.gz: 26bbc30201fe22eef3477a20ce1eaf7016ee020a1ed93f53134429654da4a11d
3
+ metadata.gz: f809e807d128c7ab3f1bd4f3f32217bfde29b24c45c3c3e610531fa08fff38c2
4
+ data.tar.gz: cf503705817938741d2b144fbb38d1887f72a47bbd0ac0b1e761c46cc3299f70
5
5
  SHA512:
6
- metadata.gz: ce23cde85c85d07932802b8bc89aa7c48f047cd274f59ff7d8c32b5e5e490e4bc46f118bb0e6216796cc774f9dc7c687bc464d33ac59361885a8bab2dd865edb
7
- data.tar.gz: 616ed09b7f636b5cc4931894d86063a62b9e7357cf35802dee67f6d4a2c77ab8523131519ced29806cb6e1bd862ce8771931401411b3b1e0d59ebe6fabc5b575
6
+ metadata.gz: a37d20936f3bc14e9a5d7c92777a222fb2b364840501775d5ea301d27de15ba69863a5959aca707187d10053495d3753c5012e790fbba5d914500eacea0ea481
7
+ data.tar.gz: 85896dc4843fdb5cb2f86ac8366f3fd3284e63928c0bd95779d280fb9807bb61866d7aa68cdc8645f6bdb3543c3b0886e1b2518246b68331ccc02fb8f108f6c4
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 4.0.5
1
+ 4.0.6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [0.9.0] - 2026-08-31
2
+
3
+ ### Added
4
+
5
+ - `Layout/EmptyLineBeforeGuardClause` — a guard clause that follows ordinary code must be preceded by a blank line
6
+ - Automated dependency-update runs
7
+ - Minimum-age verification on pull requests, re-evaluated daily
8
+ - `Layout/SingleLineStatementSpacing` — a blank line between two consecutive single-line statements is removed
9
+ - `Style/OrderedConstants` — a run of consecutive constant assignments must be sorted alphabetically
10
+
11
+ ### Changed
12
+
13
+ - Development Ruby version to 4.0.6
14
+
15
+ ### Fixed
16
+
17
+ - Weekly transitive dependency updates
18
+ - Transitive dependency pull request now covers every lockfile the update touched
19
+ - Lockfile discovery no longer publishes a short set when the search itself fails
20
+ - Transitive resolution no longer runs on a short lockfile set when the search itself fails
21
+ - Lockfile discovery skips the bundles vendored under node_modules
22
+ - Held-back gem list pinned to the collation that sorted it
23
+ - Daily dependency age re-check leaving pull requests without the status they need to merge
24
+ - Dependency age checks running with no configured minimum
25
+ - Minimum release age applied to the weekly transitive dependency update
26
+ - Ruby setup action pinned like every other action in the workflows
27
+ - Minimum-age check no longer errors on the gem's own unreleased version
28
+
1
29
  ## [0.8.6] - 2026-08-08
2
30
 
3
31
  ### Fixed
data/README.md CHANGED
@@ -80,12 +80,15 @@ Where a 4Shark cop supersedes or contradicts a stock cop, `config/default.yml` t
80
80
  | `Style/DisallowSafeNavigation` | Flags safe navigation (`&.`). 4Shark rejects it — a `&.` chain silently swallows a `nil` that usually signals a real bug. Use an explicit conditional so the `nil` case is handled on purpose. |
81
81
  | `Style/DisallowTernary` | Flags the ternary conditional (`cond ? a : b`). `?` and `:` are punctuation that already mean other things in Ruby (`valid?`, `:symbol`), and the ternary hides a branch inside an expression where skimming misses it. Use an explicit `if`/`else`. |
82
82
  | `Style/DisallowTry` | Flags `try` / `try!`. Same rationale — it hides the `nil`/missing-method case instead of handling it explicitly. |
83
+ | `Style/OrderedConstants` | A run of consecutive constant assignments must be sorted alphabetically by name. A lone constant is never flagged, and any non-constant statement breaks the run. Flags without autocorrecting — a constant can reference one declared above it, so a human decides the order. Experimental. |
83
84
 
84
85
  ### Layout
85
86
 
86
87
  | Cop | Intent |
87
88
  |---|---|
89
+ | `Layout/EmptyLineBeforeGuardClause` | Requires a blank line before a guard clause that follows ordinary code, so a clause that breaks the flow is findable at a glance instead of read for. Consecutive guard clauses are one block and stay glued together — only the first of a run needs the blank line, and a clause that opens a body needs none. Mirrors the stock `Layout/EmptyLineAfterGuardClause`, which sets the run off below; enable both. |
88
90
  | `Layout/MultilineStatementSpacing` | Requires a blank line between two consecutive statements when either spans multiple lines, so multi-line statements read as distinct units. |
91
+ | `Layout/SingleLineStatementSpacing` | Forbids a blank line between two consecutive single-line statements — they are one run, so the blank reveals nothing. The complement of `Layout/MultilineStatementSpacing`; the two never fire on the same pair. Autocorrects by removing the blank. |
89
92
 
90
93
  ### Naming
91
94
 
data/Rakefile CHANGED
@@ -9,9 +9,7 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
9
9
  end
10
10
 
11
11
  RuboCop::RakeTask.new
12
-
13
12
  task default: %i[spec rubocop]
14
-
15
13
  desc 'Generate a new cop with a template'
16
14
 
17
15
  task :new_cop, [:cop] do |_task, args|
@@ -23,11 +21,9 @@ task :new_cop, [:cop] do |_task, args|
23
21
  end
24
22
 
25
23
  generator = RuboCop::Cop::Generator.new(cop_name)
26
-
27
24
  generator.write_source
28
25
  generator.write_spec
29
26
  generator.inject_require(root_file_path: 'lib/rubocop/cop/fourshark_cops.rb')
30
27
  generator.inject_config(config_file_path: 'config/default.yml')
31
-
32
28
  puts generator.todo
33
29
  end
data/config/default.yml CHANGED
@@ -3,11 +3,25 @@
3
3
  # so the names read like any other cop. Where a 4Shark cop supersedes or
4
4
  # contradicts a stock cop, the stock cop is disabled here (see the bottom block).
5
5
 
6
+ # Mirrors the stock `Layout/EmptyLineAfterGuardClause` rather than replacing it:
7
+ # that one sets the run off below, this one above, and both are wanted.
8
+ Layout/EmptyLineBeforeGuardClause:
9
+ Description: 'Add a blank line before a guard clause that follows ordinary code.'
10
+ Enabled: true
11
+ VersionAdded: '0.9.0'
12
+
6
13
  Layout/MultilineStatementSpacing:
7
14
  Description: 'Add a blank line between consecutive statements when either spans multiple lines.'
8
15
  Enabled: true
9
16
  VersionAdded: '0.2.0'
10
17
 
18
+ # The complement of Layout/MultilineStatementSpacing: no blank line between two
19
+ # single-line statements. Autocorrects (removing a blank has no placement ambiguity).
20
+ Layout/SingleLineStatementSpacing:
21
+ Description: 'Do not put a blank line between two consecutive single-line statements.'
22
+ Enabled: true
23
+ VersionAdded: '0.9.0'
24
+
11
25
  # Stock cop configured (not a 4Shark cop): enforce the `indented` style for
12
26
  # multiline method-call chains — continuation calls are indented one step from
13
27
  # the receiver instead of aligning with the first call.
@@ -42,6 +56,11 @@ Style/DisallowTry:
42
56
  Enabled: true
43
57
  VersionAdded: '0.1.0'
44
58
 
59
+ Style/OrderedConstants:
60
+ Description: 'Sort a run of consecutive constant assignments alphabetically by name.'
61
+ Enabled: true
62
+ VersionAdded: '0.9.0'
63
+
45
64
  Rails/MandatoryInverseOf:
46
65
  Description: 'Every association must declare `inverse_of` (stricter than stock `Rails/InverseOf`).'
47
66
  Enabled: true
@@ -23,16 +23,16 @@ module RuboCop
23
23
  # end
24
24
  #
25
25
  class AssociationInFactory < ::RuboCop::Cop::Base
26
- MSG = 'Do not declare associations in factories — set them manually in the spec.'
27
-
28
26
  CONTAINER_METHODS = %i[factory trait].freeze
29
27
  # FactoryBot DSL methods that are bare calls but are NOT associations.
30
28
  DSL_KEYWORDS = %i[skip_create initialize_with].freeze
29
+ MSG = 'Do not declare associations in factories — set them manually in the spec.'
31
30
 
32
31
  def on_block(node)
33
32
  return unless container_block?(node)
34
33
 
35
34
  body = node.body
35
+
36
36
  return unless body
37
37
 
38
38
  statements = if body.begin_type?
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'factory_bot/association_in_factory'
4
+ require_relative 'layout/empty_line_before_guard_clause'
4
5
  require_relative 'layout/multiline_statement_spacing'
6
+ require_relative 'layout/single_line_statement_spacing'
5
7
  require_relative 'rails/bidirectional_association'
6
8
  require_relative 'rails/mandatory_inverse_of'
7
9
  require_relative 'rails/optional_belongs_to'
@@ -14,3 +16,4 @@ require_relative 'style/disallow_delegate'
14
16
  require_relative 'style/disallow_safe_navigation'
15
17
  require_relative 'style/disallow_ternary'
16
18
  require_relative 'style/disallow_try'
19
+ require_relative 'style/ordered_constants'
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Layout
8
+ # Requires a blank line before a guard clause that follows ordinary code.
9
+ # A guard clause breaks the flow, so it has to be findable at a glance
10
+ # rather than read for — the same reason a multi-line statement is set off
11
+ # from its neighbours.
12
+ #
13
+ # Consecutive guard clauses are one block and stay glued together: the run
14
+ # is what the reader scans, and blank lines inside it would break the shape
15
+ # instead of revealing it. Only the first clause of a run needs the blank
16
+ # line above, and a guard that opens a body needs none because the `def`
17
+ # or `do` already delimits it.
18
+ #
19
+ # What counts as a guard clause is `guard_clause?` from `rubocop-ast`,
20
+ # which covers `return`, `break`, `next`, `raise` and `fail`.
21
+ #
22
+ # The blank line *after* the run is the stock
23
+ # `Layout/EmptyLineAfterGuardClause`, which this cop mirrors rather than
24
+ # replaces — enable both to get a run that is set off on both sides.
25
+ #
26
+ # Detection-only (no autocorrect), matching the sibling
27
+ # `Layout/MultilineStatementSpacing`: where a comment sits above the
28
+ # clause the blank line belongs above the comment, and that placement
29
+ # needs validation against the real repos before it is applied for anyone.
30
+ #
31
+ # @example
32
+ # # bad
33
+ # def foo
34
+ # bar
35
+ # return if baz?
36
+ #
37
+ # qux
38
+ # end
39
+ #
40
+ # # good
41
+ # def foo
42
+ # bar
43
+ #
44
+ # return if baz?
45
+ #
46
+ # qux
47
+ # end
48
+ #
49
+ # # good — a run of guard clauses stays together
50
+ # def foo
51
+ # return if baz?
52
+ # return if qux?
53
+ #
54
+ # bar
55
+ # end
56
+ #
57
+ # # good — nothing above it to separate from
58
+ # def foo
59
+ # return if baz?
60
+ #
61
+ # bar
62
+ # end
63
+ #
64
+ class EmptyLineBeforeGuardClause < ::RuboCop::Cop::Base
65
+ MSG = 'Add a blank line before the guard clause.'
66
+
67
+ def on_begin(node)
68
+ node.children.each_cons(2) do |previous, current|
69
+ next unless guard_clause?(current)
70
+ next if guard_clause?(previous)
71
+ next if blank_line_between?(previous, current)
72
+
73
+ add_offense(current)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def guard_clause?(node)
80
+ return false unless node.is_a?(::RuboCop::AST::Node)
81
+ return false unless node.if_type?
82
+
83
+ branch = node.if_branch
84
+
85
+ return false if branch.nil?
86
+
87
+ branch.guard_clause?
88
+ end
89
+
90
+ def blank_line_between?(previous, current)
91
+ return false if current.first_line - previous.last_line < 2
92
+
93
+ ((previous.last_line + 1)...current.first_line).any? do |line|
94
+ processed_source.lines[line - 1].strip.empty?
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Layout
8
+ # Forbids a blank line between two consecutive single-line statements —
9
+ # they are one run, so the blank reveals nothing. A blank that separates
10
+ # code from an adjacent comment is kept, as is one owned by another cop
11
+ # (a guard clause, a flow-control statement, an access modifier) or one
12
+ # beside a multi-line or heredoc neighbour. Autocorrects by removing it.
13
+ class SingleLineStatementSpacing < ::RuboCop::Cop::Base
14
+ extend ::RuboCop::Cop::AutoCorrector
15
+ ACCESS_MODIFIERS = %i[private protected public module_function].freeze
16
+ FLOW_CONTROL_METHODS = %i[raise fail throw].freeze
17
+ FLOW_CONTROL_TYPES = %i[return next break redo retry].freeze
18
+ MSG = 'Remove the blank line between consecutive single-line statements.'
19
+
20
+ def on_begin(node)
21
+ node.children.each_cons(2) do |first, second|
22
+ next unless first.is_a?(::RuboCop::AST::Node) && second.is_a?(::RuboCop::AST::Node)
23
+ next if structural?(first) || structural?(second)
24
+
25
+ blanks = removable_blank_lines(first, second)
26
+
27
+ next if blanks.empty?
28
+
29
+ add_offense(second) do |corrector|
30
+ blanks.each { |line| corrector.remove(line_range_with_newline(line)) }
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # A statement whose surrounding blank another cop owns, or which reads as
38
+ # a block. Its blank is preserved: this cop only glues ordinary runs.
39
+ def structural?(node)
40
+ multiline_or_heredoc?(node) || flow_control?(node) || guard_clause?(node) || access_modifier?(node)
41
+ end
42
+
43
+ def multiline_or_heredoc?(node)
44
+ node.multiline? || node.each_node(:any_str).any?(&:heredoc?)
45
+ end
46
+
47
+ def flow_control?(node)
48
+ return true if FLOW_CONTROL_TYPES.include?(node.type)
49
+
50
+ node.send_type? && node.receiver.nil? && FLOW_CONTROL_METHODS.include?(node.method_name)
51
+ end
52
+
53
+ def guard_clause?(node)
54
+ return false unless node.if_type?
55
+
56
+ branch = node.if_branch
57
+
58
+ return false if branch.nil?
59
+
60
+ branch.guard_clause?
61
+ end
62
+
63
+ def access_modifier?(node)
64
+ node.send_type? && node.receiver.nil? && ACCESS_MODIFIERS.include?(node.method_name)
65
+ end
66
+
67
+ # A blank whose nearest non-blank line on each side is the same kind
68
+ # (code-to-code or comment-to-comment). A blank on a code/comment border
69
+ # separates the two deliberately and is left alone.
70
+ def removable_blank_lines(first, second)
71
+ gap = (first.last_line + 1)...second.first_line
72
+
73
+ gap.select do |line|
74
+ blank_line?(line) && same_kind_neighbours?(line, first.last_line, second.first_line)
75
+ end
76
+ end
77
+
78
+ def same_kind_neighbours?(line, floor, ceiling)
79
+ above = nearest_non_blank(line - 1, floor, -1)
80
+ below = nearest_non_blank(line + 1, ceiling, 1)
81
+ line_kind(above) == line_kind(below)
82
+ end
83
+
84
+ def nearest_non_blank(start_line, boundary, step)
85
+ line = start_line
86
+ line += step while line != boundary && blank_line?(line)
87
+ line
88
+ end
89
+
90
+ def line_kind(line)
91
+ return :comment if processed_source.lines[line - 1].strip.start_with?('#')
92
+
93
+ :code
94
+ end
95
+
96
+ def blank_line?(line)
97
+ processed_source.lines[line - 1].strip.empty?
98
+ end
99
+
100
+ def line_range_with_newline(line)
101
+ line_range = processed_source.buffer.line_range(line)
102
+ line_range.resize(line_range.length + 1)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -43,6 +43,7 @@ module RuboCop
43
43
  next if model_name.nil? || target_class.nil?
44
44
 
45
45
  opposite_model_path = File.join(Dir.pwd, "app/models/#{camel_to_snake(target_class)}.rb")
46
+
46
47
  next unless File.exist?(opposite_model_path)
47
48
 
48
49
  opposite_content = File.read(opposite_model_path)
@@ -81,7 +82,6 @@ module RuboCop
81
82
  assoc_name = literal_value(node.first_argument)
82
83
  inverse_name = extract_inverse_of(node)
83
84
  target_class = extract_class_name(node, assoc_name)
84
-
85
85
  [model_name, assoc_name, inverse_name, target_class]
86
86
  end
87
87
  end
@@ -107,6 +107,7 @@ module RuboCop
107
107
  # plain symbol (string keys, double-splats) are skipped rather than read.
108
108
  def option_pair(node, key)
109
109
  kwargs = node.last_argument
110
+
110
111
  return nil unless kwargs.respond_to?(:hash_type?) && kwargs.hash_type?
111
112
 
112
113
  kwargs.pairs.find { |pair| pair.key.sym_type? && pair.key.value == key }
@@ -125,6 +126,7 @@ module RuboCop
125
126
 
126
127
  def class_name_from_ast
127
128
  class_node = processed_source.ast.each_descendant(:class).first
129
+
128
130
  return nil unless class_node
129
131
 
130
132
  const_node = class_node.children.first
@@ -38,6 +38,7 @@ module RuboCop
38
38
 
39
39
  def optional_true?(node)
40
40
  kwargs = node.last_argument
41
+
41
42
  return false if !kwargs || !kwargs.hash_type?
42
43
 
43
44
  kwargs.pairs.any? { |pair| pair.key.value == :optional && pair.value.true_type? }
@@ -27,9 +27,8 @@ module RuboCop
27
27
  # validates :name
28
28
  #
29
29
  class OrderedMacros < ::RuboCop::Cop::Base
30
- MSG = 'Sort `%<macro>s` declarations alphabetically (`%<name>s` should come before `%<previous>s`).'
31
-
32
30
  MACROS = %i[belongs_to has_one has_many has_and_belongs_to_many validates scope].freeze
31
+ MSG = 'Sort `%<macro>s` declarations alphabetically (`%<name>s` should come before `%<previous>s`).'
33
32
 
34
33
  def self.default_configuration
35
34
  super.merge('Include' => ['app/models/**/*.rb'])
@@ -37,6 +36,7 @@ module RuboCop
37
36
 
38
37
  def on_class(node)
39
38
  body = node.body
39
+
40
40
  return unless body
41
41
 
42
42
  statements = if body.begin_type?
@@ -68,6 +68,7 @@ module RuboCop
68
68
  calls.each_cons(2) do |previous, current|
69
69
  previous_name = macro_name(previous)
70
70
  current_name = macro_name(current)
71
+
71
72
  next if current_name >= previous_name
72
73
 
73
74
  add_offense(
@@ -25,14 +25,14 @@ module RuboCop
25
25
  # end
26
26
  #
27
27
  class ConditionalInLet < ::RuboCop::Cop::Base
28
- MSG = 'Do not put conditional logic in a `let` — use separate contexts.'
29
-
30
28
  LET_METHODS = %i[let let!].freeze
29
+ MSG = 'Do not put conditional logic in a `let` — use separate contexts.'
31
30
 
32
31
  def on_block(node)
33
32
  return unless let_block?(node)
34
33
 
35
34
  body = node.body
35
+
36
36
  return unless body
37
37
 
38
38
  body.each_node(:if, :case) do |conditional|
@@ -17,9 +17,8 @@ module RuboCop
17
17
  # let(:user) { create(:user) }
18
18
  #
19
19
  class FactoryBotInBefore < ::RuboCop::Cop::Base
20
- MSG = 'Do not create objects in `before` — use `let` for object creation.'
21
-
22
20
  CREATION_METHODS = %i[create build create_list build_list].freeze
21
+ MSG = 'Do not create objects in `before` — use `let` for object creation.'
23
22
 
24
23
  def on_block(node)
25
24
  return unless before_block?(node)
@@ -20,8 +20,8 @@ module RuboCop
20
20
  # model class (a linter runs without the Rails app booted).
21
21
  #
22
22
  class InverseOfMatcher < ::RuboCop::Cop::Base
23
- MSG_MISSING_INVERSE = 'Root models must include `.inverse_of` in association specs.'
24
23
  MSG_FORBIDDEN_INVERSE = 'Subclasses must NOT include `.inverse_of` in specs (it belongs to the parent).'
24
+ MSG_MISSING_INVERSE = 'Root models must include `.inverse_of` in association specs.'
25
25
 
26
26
  def self.default_configuration
27
27
  super.merge(
@@ -33,12 +33,15 @@ module RuboCop
33
33
  return unless node.method?(:belong_to)
34
34
 
35
35
  model_name = model_class_from_spec
36
+
36
37
  return unless model_name
37
38
 
38
39
  association_name = association_name_from(node)
40
+
39
41
  return if association_name && polymorphic_in_model?(model_name, association_name)
40
42
 
41
43
  classification = classify_model(model_name)
44
+
42
45
  return if classification == :non_ar
43
46
 
44
47
  if classification == :root
@@ -66,6 +69,7 @@ module RuboCop
66
69
  # The association name passed to the matcher, e.g. belong_to(:user) -> :user
67
70
  def association_name_from(node)
68
71
  argument = node.first_argument
72
+
69
73
  return nil unless argument && argument.sym_type?
70
74
 
71
75
  argument.value
@@ -76,6 +80,7 @@ module RuboCop
76
80
  # and the declaration lives in the model, not in the spec matcher chain.
77
81
  def polymorphic_in_model?(model_name, association_name)
78
82
  content = model_source(model_name)
83
+
79
84
  return false unless content
80
85
 
81
86
  content.match?(/belongs_to\s+:#{Regexp.escape(association_name.to_s)}\b[^\n]*polymorphic:\s*true/)
@@ -84,12 +89,15 @@ module RuboCop
84
89
  # Convert spec file path into model class name
85
90
  def model_class_from_spec
86
91
  path = processed_source.file_path if processed_source && processed_source.buffer
92
+
87
93
  return nil unless path
88
94
 
89
95
  match = path.match(%r{spec/models/(.+)_spec\.rb})
96
+
90
97
  return nil unless match
91
98
 
92
99
  relative = match[1]
100
+
93
101
  return nil if relative.empty?
94
102
 
95
103
  relative.split('/').map { |part| part.split('_').map(&:capitalize).join }.join('::')
@@ -99,9 +107,11 @@ module RuboCop
99
107
  # model file statically (no class loading).
100
108
  def classify_model(model_name)
101
109
  content = model_source(model_name)
110
+
102
111
  return :non_ar unless content
103
112
 
104
113
  superclass = superclass_of(content, model_name)
114
+
105
115
  return :non_ar unless superclass
106
116
  return :root if superclass == 'ApplicationRecord'
107
117
  return :subclass if model_source(superclass)
@@ -113,6 +123,7 @@ module RuboCop
113
123
 
114
124
  def model_source(model_name)
115
125
  path = File.join(Dir.pwd, 'app/models', "#{camel_to_snake(model_name)}.rb")
126
+
116
127
  return File.read(path) if File.exist?(path)
117
128
 
118
129
  nil
@@ -32,18 +32,19 @@ module RuboCop
32
32
  # end
33
33
  #
34
34
  class OverwrittenLet < ::RuboCop::Cop::Base
35
- MSG = 'Do not override the outer `let` `%<name>s`; use a distinct name or set the value in `before`.'
36
-
37
- LET_METHODS = %i[let let!].freeze
38
35
  EXAMPLE_GROUP_METHODS = %i[describe context].freeze
36
+ LET_METHODS = %i[let let!].freeze
37
+ MSG = 'Do not override the outer `let` `%<name>s`; use a distinct name or set the value in `before`.'
39
38
 
40
39
  def on_send(node)
41
40
  return unless LET_METHODS.include?(node.method_name) && node.receiver.nil?
42
41
 
43
42
  name = let_name(node)
43
+
44
44
  return unless name
45
45
 
46
46
  immediate_group = node.each_ancestor(:block).find { |block| example_group?(block) }
47
+
47
48
  return unless immediate_group
48
49
  return unless outer_groups(immediate_group).any? { |group| defines_let?(group, name) }
49
50
 
@@ -63,6 +64,7 @@ module RuboCop
63
64
 
64
65
  def defines_let?(group, name)
65
66
  body = group.body
67
+
66
68
  return false unless body
67
69
 
68
70
  statements = if body.begin_type?
@@ -92,6 +94,7 @@ module RuboCop
92
94
 
93
95
  def let_name(node)
94
96
  first = node.first_argument
97
+
95
98
  return first.value if first && first.sym_type?
96
99
 
97
100
  nil
@@ -120,15 +120,13 @@ module RuboCop
120
120
  # end
121
121
  #
122
122
  class DisallowDelegate < ::RuboCop::Cop::Base
123
- MACRO_MSG = 'Do not use automatic delegation. Delete the forwarder and let the caller navigate.'
124
- FORWARDER_MSG = 'Do not forward a collaborator\'s message. Delete the forwarder and let the caller navigate.'
125
-
126
- RESTRICT_ON_SEND = %i[delegate delegate_missing_to def_delegator def_delegators DelegateClass].freeze
127
-
128
123
  # An argument reaches the body wrapped in one of these when the call site
129
124
  # spreads or blocks it, and the wrapper says nothing about whose state it
130
125
  # carries.
131
126
  ARGUMENT_WRAPPERS = %i[hash pair array splat kwsplat block_pass].freeze
127
+ FORWARDER_MSG = 'Do not forward a collaborator\'s message. Delete the forwarder and let the caller navigate.'
128
+ MACRO_MSG = 'Do not use automatic delegation. Delete the forwarder and let the caller navigate.'
129
+ RESTRICT_ON_SEND = %i[delegate delegate_missing_to def_delegator def_delegators DelegateClass].freeze
132
130
 
133
131
  # @!method mixes_in_enumerable?(node)
134
132
  def_node_matcher :mixes_in_enumerable?, <<~PATTERN
@@ -170,6 +168,7 @@ module RuboCop
170
168
  # same thing and both are read as one.
171
169
  def own_class_target?(node)
172
170
  target = to_option(node) || leading_target(node)
171
+
173
172
  return false if target.nil?
174
173
  return false unless target.type?(:sym, :str)
175
174
 
@@ -199,6 +198,7 @@ module RuboCop
199
198
  return false unless node.method?(:each)
200
199
 
201
200
  enclosing = node.each_ancestor(:class, :module, :sclass).first
201
+
202
202
  return false if enclosing.nil?
203
203
  return false if enclosing.sclass_type?
204
204
 
@@ -7,7 +7,6 @@ module RuboCop
7
7
  module Style
8
8
  class DisallowTry < ::RuboCop::Cop::Base
9
9
  MSG = 'Do not use `try` or `try!`. Use explicit conditionals instead.'
10
-
11
10
  RESTRICT_ON_SEND = %i[try try!].freeze
12
11
 
13
12
  def on_send(node)
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Style
8
+ # Requires a run of consecutive constant assignments to be sorted
9
+ # alphabetically by name. A lone constant is never flagged; a run is
10
+ # broken by any non-constant statement. Flags without autocorrecting — a
11
+ # constant may reference one declared above it, so a human decides the
12
+ # order. Experimental: if it flags more than it helps, drop it.
13
+ class OrderedConstants < ::RuboCop::Cop::Base
14
+ MSG = 'Sort constant assignments alphabetically (`%<name>s` should come before `%<previous>s`).'
15
+
16
+ def on_begin(node)
17
+ node.children.chunk { |child| constant_assignment?(child) }.each do |constants, run|
18
+ flag_unsorted(run) if constants
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def flag_unsorted(run)
25
+ run.each_cons(2) do |previous, current|
26
+ previous_name = constant_name(previous)
27
+ current_name = constant_name(current)
28
+
29
+ next if current_name >= previous_name
30
+
31
+ add_offense(current.loc.name, message: format(MSG, name: current_name, previous: previous_name))
32
+ end
33
+ end
34
+
35
+ def constant_assignment?(node)
36
+ node.is_a?(::RuboCop::AST::Node) && node.casgn_type?
37
+ end
38
+
39
+ def constant_name(node)
40
+ node.name.to_s
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Fourshark
5
- VERSION = '0.8.6'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rubocop'
4
-
5
4
  require_relative 'rubocop/fourshark'
6
5
  require_relative 'rubocop/fourshark/version'
7
6
  require_relative 'rubocop/fourshark/plugin'
8
-
9
7
  require_relative 'rubocop/cop/fourshark_cops'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-fourshark
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Ribeiro
@@ -138,7 +138,9 @@ files:
138
138
  - lib/rubocop-fourshark.rb
139
139
  - lib/rubocop/cop/factory_bot/association_in_factory.rb
140
140
  - lib/rubocop/cop/fourshark_cops.rb
141
+ - lib/rubocop/cop/layout/empty_line_before_guard_clause.rb
141
142
  - lib/rubocop/cop/layout/multiline_statement_spacing.rb
143
+ - lib/rubocop/cop/layout/single_line_statement_spacing.rb
142
144
  - lib/rubocop/cop/rails/bidirectional_association.rb
143
145
  - lib/rubocop/cop/rails/mandatory_inverse_of.rb
144
146
  - lib/rubocop/cop/rails/optional_belongs_to.rb
@@ -151,6 +153,7 @@ files:
151
153
  - lib/rubocop/cop/style/disallow_safe_navigation.rb
152
154
  - lib/rubocop/cop/style/disallow_ternary.rb
153
155
  - lib/rubocop/cop/style/disallow_try.rb
156
+ - lib/rubocop/cop/style/ordered_constants.rb
154
157
  - lib/rubocop/fourshark.rb
155
158
  - lib/rubocop/fourshark/plugin.rb
156
159
  - lib/rubocop/fourshark/version.rb
@@ -177,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
180
  - !ruby/object:Gem::Version
178
181
  version: '0'
179
182
  requirements: []
180
- rubygems_version: 4.0.10
183
+ rubygems_version: 4.0.16
181
184
  specification_version: 4
182
185
  summary: RuboCop extension enforcing 4Shark's Ruby, Rails, and RSpec conventions.
183
186
  test_files: []