rubocop-elegant 0.3.0 → 0.5.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: 5a4408943315c5a59e194dcb473bc2b48e7d1b2c07182a155a27ce613bc7bef5
4
- data.tar.gz: a4f9028d7cd0b076315f65b9e00a0ee0c204f395a821957a036795bcf14234ff
3
+ metadata.gz: 07050b077e5266437a527369312875e178be1df47c24f2792fd5945060fe0e26
4
+ data.tar.gz: 04ad13719f726d00a61a5cd287573813ce6d2780ef4ef7e8ea4092ed02a24af2
5
5
  SHA512:
6
- metadata.gz: 1489006295818667d80b86717b323f179e739cc7a5cf5ff4716d8c07cac67aa6f1ab3d8236a35636f290ea147315f1e19461a1cb17f50461a48d5e48e6b1e098
7
- data.tar.gz: 0c42fda07e77a2ac808325c596e57a207d1cdc19ac05d7807d2318d69d1ca91822144c75c97c14d4fcbb6656bb92fe36c3d7ea0616480c5daf388393a254b8f7
6
+ metadata.gz: 384fa71d95456e0515d5bb4a518a65af285f6e9774289163595e3f1a1885473dab1f85f931ce731e5376d6fa73029991f7cd5624771736e5650dec8e06b845ab
7
+ data.tar.gz: d9d7248056d93b0c1c0d81afd0e7c96c709e314e944eeb95365ef1affe3d4f1342607f84b6da874a2e3a68ae3eb21fa0064e9163449935f1a695906f2da5fae4
data/README.md CHANGED
@@ -22,6 +22,7 @@ The custom cops add the following restrictions:
22
22
 
23
23
  * Classes must live inside a module; top-level classes are forbidden.
24
24
  * A class nested in a module must use compact namespace syntax.
25
+ * Only one non-empty top-level class per file; empty forward declarations are allowed.
25
26
  * Method names must be single lowercase verbs.
26
27
  * Variable names must be single lowercase nouns.
27
28
  * Each indentation step must add exactly two spaces.
data/config/default.yml CHANGED
@@ -43,6 +43,13 @@ Elegant/NoClassInModule:
43
43
  Exclude:
44
44
  - '**/*Test.rb'
45
45
  - '**/test_*.rb'
46
+ Elegant/OneClassPerFile:
47
+ Description: 'Allows only one non-empty top-level class per file; empty forward declarations are treated as namespace scaffolding'
48
+ Enabled: true
49
+ VersionAdded: '0.4.0'
50
+ Exclude:
51
+ - '**/*Test.rb'
52
+ - '**/test_*.rb'
46
53
  Elegant/IndentationLadder:
47
54
  Description: 'Requires the indentation step to be exactly two spaces when a line indents to the right of the previous one'
48
55
  Enabled: true
@@ -102,6 +109,10 @@ Style/MultilineBlockChain:
102
109
  Enabled: false
103
110
  Style/ClassAndModuleChildren:
104
111
  Enabled: false
112
+ Style/OneClassPerFile:
113
+ Enabled: false
114
+ Lint/EmptyClass:
115
+ Enabled: false
105
116
  Lint/NestedMethodDefinition:
106
117
  Enabled: false
107
118
  Metrics/ParameterLists:
@@ -19,7 +19,19 @@
19
19
  # whether or when the right-hand side is evaluated. A variable that
20
20
  # is reassigned, read more than once, or never read is left alone
21
21
  # too.
22
+ #
23
+ # Auto-correct inlines the redundant assignment: it replaces the
24
+ # single +lvar+ read with the source of the assignment's right-hand
25
+ # side, then removes the whole assignment line including its leading
26
+ # indent and trailing newline. The right-hand side is wrapped in
27
+ # parentheses unless it is already a primary expression (literal,
28
+ # variable, parenthesized expression, or method call with parentheses
29
+ # or no arguments), so that operator precedence at the read site is
30
+ # preserved.
22
31
  class RuboCop::Cop::Elegant::NoRedundantVariable < RuboCop::Cop::Base
32
+ extend RuboCop::Cop::AutoCorrector
33
+ include RuboCop::Cop::RangeHelp
34
+
23
35
  MSG = 'Variable "%<name>s" is redundant and must be inlined: it is read only once'
24
36
  public_constant :MSG
25
37
 
@@ -35,6 +47,12 @@ class RuboCop::Cop::Elegant::NoRedundantVariable < RuboCop::Cop::Base
35
47
  ALWAYS_HOIST_TYPES = %i[rescue resbody ensure].freeze
36
48
  public_constant :ALWAYS_HOIST_TYPES
37
49
 
50
+ PRIMARY_TYPES = %i[
51
+ int float str sym dstr dsym xstr true false nil array hash regexp
52
+ lvar ivar cvar gvar const self nth_ref back_ref
53
+ ].freeze
54
+ public_constant :PRIMARY_TYPES
55
+
38
56
  def on_def(node)
39
57
  check(node.body)
40
58
  end
@@ -56,10 +74,52 @@ class RuboCop::Cop::Elegant::NoRedundantVariable < RuboCop::Cop::Base
56
74
  next unless nodes.size == 1
57
75
  next unless reads[name].size == 1
58
76
  next if hoisted?(reads[name].first, nodes.first)
59
- add_offense(nodes.first, message: format(MSG, name: name))
77
+ register(nodes.first, reads[name].first, name)
78
+ end
79
+ end
80
+
81
+ def register(assign, read, name)
82
+ return add_offense(assign, message: format(MSG, name: name)) unless solo?(assign)
83
+ add_offense(assign, message: format(MSG, name: name)) do |corrector|
84
+ corrector.replace(read.source_range, inlined(assign.children.last, read))
85
+ corrector.remove(range_by_whole_lines(assign.source_range, include_final_newline: true))
60
86
  end
61
87
  end
62
88
 
89
+ def solo?(assign)
90
+ range = assign.source_range
91
+ return false unless range.first_line == range.last_line
92
+ range.source_buffer.source_line(range.first_line).strip == range.source.strip
93
+ end
94
+
95
+ def inlined(rhs, read)
96
+ return "(#{braced(rhs)})" if wrap?(rhs, read)
97
+ braced(rhs)
98
+ end
99
+
100
+ def braced(rhs)
101
+ return "{ #{rhs.source} }" if rhs.hash_type? && rhs.loc.begin.nil?
102
+ rhs.source
103
+ end
104
+
105
+ def wrap?(rhs, read)
106
+ return true unless primary?(rhs)
107
+ rhs.hash_type? && bare?(read)
108
+ end
109
+
110
+ def primary?(node)
111
+ return true if PRIMARY_TYPES.include?(node.type)
112
+ return !node.loc.begin.nil? || node.arguments.empty? if node.send_type? || node.csend_type?
113
+ node.begin_type? && node.children.size == 1
114
+ end
115
+
116
+ def bare?(read)
117
+ parent = read.parent
118
+ return false if parent.nil?
119
+ return false unless parent.send_type? || parent.csend_type?
120
+ parent.loc.begin.nil? && parent.arguments.include?(read)
121
+ end
122
+
63
123
  def walk(node, assigns, reads, tainted)
64
124
  return unless node.is_a?(RuboCop::AST::Node)
65
125
  return if node.def_type? || node.defs_type?
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ # Enforces one top-level class per file, treating empty class bodies as
7
+ # namespace scaffolding rather than real classes. An empty top-level
8
+ # +class Foo::Bar; end+ resolves a parent namespace so that the actual
9
+ # class in the same file can use the compact-namespaced form required
10
+ # by +Elegant/NoClassInModule+ without a circular require; it is not a
11
+ # class definition in its own right. Only when two or more top-level
12
+ # class bodies are non-empty does the cop register an offense, on every
13
+ # such class after the first.
14
+ class RuboCop::Cop::Elegant::OneClassPerFile < RuboCop::Cop::Base
15
+ MSG = 'Only one non-empty class per file is allowed; %<name>s is the extra one'
16
+ public_constant :MSG
17
+
18
+ def on_new_investigation
19
+ real = tops.reject { |node| node.body.nil? }
20
+ real.drop(1).each do |node|
21
+ add_offense(node, message: format(MSG, name: label(node)))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def tops
28
+ ast = processed_source.ast
29
+ return [] if ast.nil?
30
+ return [ast] if ast.class_type?
31
+ return [] unless ast.begin_type?
32
+ ast.children.select(&:class_type?)
33
+ end
34
+
35
+ def label(node)
36
+ node.children[0].source
37
+ end
38
+ end
@@ -9,8 +9,17 @@
9
9
  # closing). Brackets stranded in the middle of a multi-line expression
10
10
  # are forbidden because they hide the structure of the code.
11
11
  #
12
+ # Auto-correct relocates the offending bracket onto its own line: an
13
+ # opener that is not at end-of-line gets a newline and the opener-line
14
+ # indent plus two spaces inserted right after it; a closer that is not
15
+ # at start-of-line gets a newline and the opener-line indent inserted
16
+ # right before it. Surrounding indentation may still need a follow-up
17
+ # layout pass, but the brackets themselves end up paired.
18
+ #
12
19
  # See https://www.yegor256.com/2014/10/23/paired-brackets-notation.html
13
20
  class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
21
+ extend RuboCop::Cop::AutoCorrector
22
+
14
23
  MSG = 'Bracket %<text>s must be paired on the same line, or start/end its line'
15
24
  public_constant :MSG
16
25
 
@@ -43,8 +52,9 @@ class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
43
52
  def check(duo)
44
53
  opener, closer = duo
45
54
  return if opener.line == closer.line
46
- register(opener) unless ends?(opener)
47
- register(closer) unless starts?(closer)
55
+ indent = leading(opener)
56
+ register(opener) { |corrector| corrector.insert_after(opener.pos, "\n#{indent} ") } unless ends?(opener)
57
+ register(closer) { |corrector| corrector.insert_before(closer.pos, "\n#{indent}") } unless starts?(closer)
48
58
  end
49
59
 
50
60
  def starts?(tok)
@@ -56,7 +66,11 @@ class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
56
66
  after.empty? || after.start_with?('#')
57
67
  end
58
68
 
59
- def register(tok)
60
- add_offense(tok.pos, message: format(MSG, text: tok.text))
69
+ def leading(tok)
70
+ processed_source.lines[tok.line - 1][/\A[ \t]*/]
71
+ end
72
+
73
+ def register(tok, &block)
74
+ add_offense(tok.pos, message: format(MSG, text: tok.text), &block)
61
75
  end
62
76
  end
@@ -15,4 +15,5 @@ require_relative 'elegant/no_empty_lines_in_blocks'
15
15
  require_relative 'elegant/no_empty_lines_in_methods'
16
16
  require_relative 'elegant/no_nil_return'
17
17
  require_relative 'elegant/no_redundant_variable'
18
+ require_relative 'elegant/one_class_per_file'
18
19
  require_relative 'elegant/paired_brackets'
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to?(:required_rubygems_version=)
10
10
  s.required_ruby_version = '>=2.2'
11
11
  s.name = 'rubocop-elegant'
12
- s.version = '0.3.0'
12
+ s.version = '0.5.0'
13
13
  s.license = 'MIT'
14
14
  s.summary = 'Set of custom RuboCop cops for elegant Ruby coding'
15
15
  s.description =
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-elegant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -63,6 +63,7 @@ files:
63
63
  - lib/rubocop/cop/elegant/no_empty_lines_in_methods.rb
64
64
  - lib/rubocop/cop/elegant/no_nil_return.rb
65
65
  - lib/rubocop/cop/elegant/no_redundant_variable.rb
66
+ - lib/rubocop/cop/elegant/one_class_per_file.rb
66
67
  - lib/rubocop/cop/elegant/paired_brackets.rb
67
68
  - lib/rubocop/cop/elegant_cops.rb
68
69
  - lib/rubocop/elegant/plugin.rb