rubocop-elegant 0.2.0 → 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: d208aee894da49bf76dac6b393ecd907d137ce125f445903078f34def9435955
4
- data.tar.gz: 2bb699562cc741372c3ab5f708a815cc2e046476d3a7646b267c72eb4c49ca98
3
+ metadata.gz: 5a4408943315c5a59e194dcb473bc2b48e7d1b2c07182a155a27ce613bc7bef5
4
+ data.tar.gz: a4f9028d7cd0b076315f65b9e00a0ee0c204f395a821957a036795bcf14234ff
5
5
  SHA512:
6
- metadata.gz: 2c74ed34a71c7fc3b89799f80dc97a77afecf8c2d3d1220b27454b829b27ffd452937a9779a0b17bb562308ef023e9a93ecda0fb6a87c6d10413ef78abf69ca5
7
- data.tar.gz: 472da807a9a7530dcdf5cb5147115b060a379a149d05adf53762134b07314d51d70fe57b855fdf8355a1c0190855a8f1ce59b663a7073ca7f0765aa24f716450
6
+ metadata.gz: 1489006295818667d80b86717b323f179e739cc7a5cf5ff4716d8c07cac67aa6f1ab3d8236a35636f290ea147315f1e19461a1cb17f50461a48d5e48e6b1e098
7
+ data.tar.gz: 0c42fda07e77a2ac808325c596e57a207d1cdc19ac05d7807d2318d69d1ca91822144c75c97c14d4fcbb6656bb92fe36c3d7ea0616480c5daf388393a254b8f7
data/README.md CHANGED
@@ -18,6 +18,19 @@ Default RuboCop configuration is too permissive, allowing coding practices
18
18
  cryptic variable names, and so on.
19
19
  This plugin configures existing RuboCop cops with stricter settings
20
20
  and adds custom cops for rules that RuboCop doesn't provide out of the box.
21
+ The custom cops add the following restrictions:
22
+
23
+ * Classes must live inside a module; top-level classes are forbidden.
24
+ * A class nested in a module must use compact namespace syntax.
25
+ * Method names must be single lowercase verbs.
26
+ * Variable names must be single lowercase nouns.
27
+ * Each indentation step must add exactly two spaces.
28
+ * Comments are forbidden, except SPDX, magic, RuboCop directives, and class docblocks.
29
+ * Empty lines inside block bodies are forbidden.
30
+ * Empty lines inside method bodies are forbidden.
31
+ * A method cannot return `nil` explicitly.
32
+ * Local variables that are assigned once and read once must be inlined.
33
+ * Brackets must be paired on the same line, or start/end their own line.
21
34
 
22
35
  First, install it:
23
36
 
data/config/default.yml CHANGED
@@ -21,6 +21,10 @@ Elegant/NoNilReturn:
21
21
  Description: 'Forbids a method or function from returning nil on any code path'
22
22
  Enabled: true
23
23
  VersionAdded: '0.1.0'
24
+ Elegant/NoRedundantVariable:
25
+ Description: 'Forbids local variables that are assigned once and read once; such variables must be inlined'
26
+ Enabled: true
27
+ VersionAdded: '0.3.0'
24
28
  Elegant/PairedBrackets:
25
29
  Description: 'Enforces the paired brackets notation: a bracket must pair on the same line, or start/end its line'
26
30
  Enabled: true
@@ -21,8 +21,7 @@ class RuboCop::Cop::Elegant::ClassInModule < RuboCop::Cop::Base
21
21
  private
22
22
 
23
23
  def namespaced?(node)
24
- const = node.children[0]
25
- scope = const.children[0]
24
+ scope = node.children[0].children[0]
26
25
  !scope.nil? && scope.type != :cbase
27
26
  end
28
27
 
@@ -44,7 +44,6 @@ class RuboCop::Cop::Elegant::IndentationLadder < RuboCop::Cop::Base
44
44
  end
45
45
 
46
46
  def register(num, step)
47
- target = processed_source.buffer.line_range(num)
48
- add_offense(target, message: format(MSG, step: step))
47
+ add_offense(processed_source.buffer.line_range(num), message: format(MSG, step: step))
49
48
  end
50
49
  end
@@ -48,8 +48,7 @@ class RuboCop::Cop::Elegant::NoComments < RuboCop::Cop::Base
48
48
  end
49
49
 
50
50
  def docblock?(comment)
51
- line = comment.location.line
52
- successor = codeline(line)
51
+ successor = codeline(comment.location.line)
53
52
  successor.positive? && definition?(successor)
54
53
  end
55
54
 
@@ -88,15 +87,13 @@ class RuboCop::Cop::Elegant::NoComments < RuboCop::Cop::Base
88
87
  end
89
88
 
90
89
  def fullrange(target)
91
- start = target.begin_pos - target.column
92
90
  ending = target.end_pos
93
91
  ending += 1 if newline?(ending)
94
- target.with(begin_pos: start, end_pos: ending)
92
+ target.with(begin_pos: target.begin_pos - target.column, end_pos: ending)
95
93
  end
96
94
 
97
95
  def prefixed(target, prefix)
98
- spaces = prefix.match(/\s*$/)[0]
99
- target.with(begin_pos: target.begin_pos - spaces.length, end_pos: target.end_pos)
96
+ target.with(begin_pos: target.begin_pos - prefix.match(/\s*$/)[0].length, end_pos: target.end_pos)
100
97
  end
101
98
 
102
99
  def newline?(pos)
@@ -70,8 +70,7 @@ class RuboCop::Cop::Elegant::NoEmptyLinesInBlocks < RuboCop::Cop::Base
70
70
  result = []
71
71
  lines.each do |num|
72
72
  next if @gaps.include?(num)
73
- line = processed_source.lines[num - 1]
74
- result << num if line.strip.empty?
73
+ result << num if processed_source.lines[num - 1].strip.empty?
75
74
  end
76
75
  result
77
76
  end
@@ -36,8 +36,7 @@ class RuboCop::Cop::Elegant::NoEmptyLinesInMethods < RuboCop::Cop::Base
36
36
  def empty(lines)
37
37
  result = []
38
38
  lines.each do |num|
39
- line = processed_source.lines[num - 1]
40
- result << num if line.strip.empty?
39
+ result << num if processed_source.lines[num - 1].strip.empty?
41
40
  end
42
41
  result
43
42
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ # Forbids local variables that are assigned exactly once and then
7
+ # referenced exactly once, since such variables can be inlined at
8
+ # the place of their single use without loss of clarity. The cop
9
+ # inspects each method body in isolation, ignores compound
10
+ # assignments (+=, ||=, &&=), multiple assignments, +for+ loops,
11
+ # rescue variables, and assignments embedded into expressions such
12
+ # as +if+ or +while+ conditions; only top-level statements of a
13
+ # sequence are considered. A variable whose single read sits inside
14
+ # a context that does not also enclose the assignment is left alone:
15
+ # loops or blocks (+block+, +numblock+, +while+, +until+, +for+) so
16
+ # that inlining does not move the right-hand side into a hot path,
17
+ # and conditional branches (+if+, +case+, +case_match+, +and+, +or+,
18
+ # +rescue+, +resbody+, +ensure+) so that inlining does not change
19
+ # whether or when the right-hand side is evaluated. A variable that
20
+ # is reassigned, read more than once, or never read is left alone
21
+ # too.
22
+ class RuboCop::Cop::Elegant::NoRedundantVariable < RuboCop::Cop::Base
23
+ MSG = 'Variable "%<name>s" is redundant and must be inlined: it is read only once'
24
+ public_constant :MSG
25
+
26
+ STATEMENT_PARENTS = %i[begin kwbegin def defs block numblock].freeze
27
+ public_constant :STATEMENT_PARENTS
28
+
29
+ LOOP_TYPES = %i[block numblock while until while_post until_post for].freeze
30
+ public_constant :LOOP_TYPES
31
+
32
+ ALWAYS_FIRST_TYPES = %i[if case case_match and or].freeze
33
+ public_constant :ALWAYS_FIRST_TYPES
34
+
35
+ ALWAYS_HOIST_TYPES = %i[rescue resbody ensure].freeze
36
+ public_constant :ALWAYS_HOIST_TYPES
37
+
38
+ def on_def(node)
39
+ check(node.body)
40
+ end
41
+
42
+ def on_defs(node)
43
+ check(node.body)
44
+ end
45
+
46
+ private
47
+
48
+ def check(body)
49
+ return if body.nil?
50
+ assigns = Hash.new { |h, k| h[k] = [] }
51
+ reads = Hash.new { |h, k| h[k] = [] }
52
+ tainted = []
53
+ walk(body, assigns, reads, tainted)
54
+ assigns.each do |name, nodes|
55
+ next if tainted.include?(name)
56
+ next unless nodes.size == 1
57
+ next unless reads[name].size == 1
58
+ next if hoisted?(reads[name].first, nodes.first)
59
+ add_offense(nodes.first, message: format(MSG, name: name))
60
+ end
61
+ end
62
+
63
+ def walk(node, assigns, reads, tainted)
64
+ return unless node.is_a?(RuboCop::AST::Node)
65
+ return if node.def_type? || node.defs_type?
66
+ record(node, assigns, reads, tainted)
67
+ node.each_child_node { |child| walk(child, assigns, reads, tainted) }
68
+ end
69
+
70
+ def record(node, assigns, reads, tainted)
71
+ if node.op_asgn_type? || node.or_asgn_type? || node.and_asgn_type?
72
+ taint(node.children.first, tainted)
73
+ elsif node.lvasgn_type? && statement?(node)
74
+ assigns[node.children.first] << node
75
+ elsif node.lvar_type?
76
+ reads[node.children.first] << node
77
+ end
78
+ end
79
+
80
+ def taint(target, tainted)
81
+ return unless target.is_a?(RuboCop::AST::Node)
82
+ return unless target.lvasgn_type?
83
+ tainted << target.children.first
84
+ end
85
+
86
+ def statement?(node)
87
+ return false unless node.children.size == 2
88
+ parent = node.parent
89
+ parent = parent.parent while !parent.nil? && parent.type == :begin && parent.children.size == 1
90
+ return false if parent.nil?
91
+ STATEMENT_PARENTS.include?(parent.type)
92
+ end
93
+
94
+ def hoisted?(read, assign)
95
+ boundary = assign.parent
96
+ return true if boundary.nil?
97
+ child = read
98
+ parent = read.parent
99
+ while !parent.nil? && !parent.equal?(boundary)
100
+ return true if LOOP_TYPES.include?(parent.type)
101
+ return true if ALWAYS_HOIST_TYPES.include?(parent.type)
102
+ return true if ALWAYS_FIRST_TYPES.include?(parent.type) && !parent.children.first.equal?(child)
103
+ child = parent
104
+ parent = parent.parent
105
+ end
106
+ parent.nil?
107
+ end
108
+ end
@@ -48,13 +48,11 @@ class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
48
48
  end
49
49
 
50
50
  def starts?(tok)
51
- line = processed_source.lines[tok.line - 1]
52
- line[0...tok.column].strip.empty?
51
+ processed_source.lines[tok.line - 1][0...tok.column].strip.empty?
53
52
  end
54
53
 
55
54
  def ends?(tok)
56
- line = processed_source.lines[tok.line - 1]
57
- after = line[(tok.column + tok.text.length)..-1].to_s.strip
55
+ after = processed_source.lines[tok.line - 1][(tok.column + tok.text.length)..-1].to_s.strip
58
56
  after.empty? || after.start_with?('#')
59
57
  end
60
58
 
@@ -14,4 +14,5 @@ require_relative 'elegant/no_comments'
14
14
  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
+ require_relative 'elegant/no_redundant_variable'
17
18
  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.2.0'
12
+ s.version = '0.3.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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -62,6 +62,7 @@ files:
62
62
  - lib/rubocop/cop/elegant/no_empty_lines_in_blocks.rb
63
63
  - lib/rubocop/cop/elegant/no_empty_lines_in_methods.rb
64
64
  - lib/rubocop/cop/elegant/no_nil_return.rb
65
+ - lib/rubocop/cop/elegant/no_redundant_variable.rb
65
66
  - lib/rubocop/cop/elegant/paired_brackets.rb
66
67
  - lib/rubocop/cop/elegant_cops.rb
67
68
  - lib/rubocop/elegant/plugin.rb