rubocop-elegant 0.7.0 → 0.8.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 +4 -4
- data/README.md +1 -0
- data/Rakefile +2 -2
- data/config/default.yml +26 -0
- data/lib/rubocop/cop/elegant/class_in_module.rb +14 -6
- data/lib/rubocop/cop/elegant/good_method_name.rb +5 -0
- data/lib/rubocop/cop/elegant/no_redundant_variable.rb +20 -2
- data/lib/rubocop/cop/elegant/paired_brackets.rb +27 -7
- data/lib/rubocop/cop/elegant/spdx_on_top.rb +41 -0
- data/lib/rubocop/cop/elegant_cops.rb +1 -0
- data/rubocop-elegant.gemspec +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25c022ecd592d9b71642d845716275362e0175a24c1703046fa987dd5fac977b
|
|
4
|
+
data.tar.gz: 7e8df712bdc9326743e2baa33db7117742759f8bb685e42403ffce8b537b7b31
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80858ff6896e4509151c4e6df75df9dc7559fa08154cd5b4ef890a57bcf6b3a9b33bb64ec59f98d8ec45dad7bcfd02a94402cd3b9d0943ac581d66e5e94be718
|
|
7
|
+
data.tar.gz: e7555aa1fb5a013dd33ed84959ff6e5bee69df1dd433625a48ab9a148159c9b525a3b9244807c5f769c6fa2ac68d705b085c85a1f4a91a6371618752a1e507fe
|
data/README.md
CHANGED
|
@@ -32,6 +32,7 @@ The custom cops add the following restrictions:
|
|
|
32
32
|
* A method cannot return `nil` explicitly.
|
|
33
33
|
* Local variables that are assigned once and read once must be inlined.
|
|
34
34
|
* Brackets must be paired on the same line, or start/end their own line.
|
|
35
|
+
* The SPDX header must sit above the code, with only comments before it.
|
|
35
36
|
|
|
36
37
|
First, install it:
|
|
37
38
|
|
data/Rakefile
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'rake'
|
|
4
|
-
require 'rake/clean'
|
|
5
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
6
4
|
# SPDX-License-Identifier: MIT
|
|
7
5
|
|
|
6
|
+
require 'rake'
|
|
7
|
+
require 'rake/clean'
|
|
8
8
|
require 'rubygems'
|
|
9
9
|
|
|
10
10
|
CLEAN.include('coverage')
|
data/config/default.yml
CHANGED
|
@@ -36,6 +36,10 @@ Elegant/ClassInModule:
|
|
|
36
36
|
Exclude:
|
|
37
37
|
- '**/*Test.rb'
|
|
38
38
|
- '**/test_*.rb'
|
|
39
|
+
- '**/*_test.rb'
|
|
40
|
+
- '**/*_spec.rb'
|
|
41
|
+
- 'test/**/*'
|
|
42
|
+
- 'spec/**/*'
|
|
39
43
|
Elegant/NoClassInModule:
|
|
40
44
|
Description: 'Forbids declaring a class inside a module declaration; use compact namespace syntax instead'
|
|
41
45
|
Enabled: true
|
|
@@ -43,6 +47,10 @@ Elegant/NoClassInModule:
|
|
|
43
47
|
Exclude:
|
|
44
48
|
- '**/*Test.rb'
|
|
45
49
|
- '**/test_*.rb'
|
|
50
|
+
- '**/*_test.rb'
|
|
51
|
+
- '**/*_spec.rb'
|
|
52
|
+
- 'test/**/*'
|
|
53
|
+
- 'spec/**/*'
|
|
46
54
|
Elegant/OneClassPerFile:
|
|
47
55
|
Description: 'Allows only one non-empty top-level class per file; empty forward declarations are treated as namespace scaffolding'
|
|
48
56
|
Enabled: true
|
|
@@ -50,6 +58,10 @@ Elegant/OneClassPerFile:
|
|
|
50
58
|
Exclude:
|
|
51
59
|
- '**/*Test.rb'
|
|
52
60
|
- '**/test_*.rb'
|
|
61
|
+
- '**/*_test.rb'
|
|
62
|
+
- '**/*_spec.rb'
|
|
63
|
+
- 'test/**/*'
|
|
64
|
+
- 'spec/**/*'
|
|
53
65
|
Elegant/MonotonicIndents:
|
|
54
66
|
Description: 'Requires the indentation step to be exactly two spaces when a line indents to the right of the previous one'
|
|
55
67
|
Enabled: true
|
|
@@ -66,6 +78,10 @@ Elegant/GoodMethodName:
|
|
|
66
78
|
VersionAdded: '0.0.3'
|
|
67
79
|
Pattern: '^(?=.{1,48}$)(((to|fake|the|with|without)_)?[a-z]{1,16}[!?]?|(on|test)_[a-z_]+)$'
|
|
68
80
|
AllowedNames: []
|
|
81
|
+
Elegant/SpdxOnTop:
|
|
82
|
+
Description: 'Requires the SPDX header to sit above the code, with only comments and blank lines before it'
|
|
83
|
+
Enabled: true
|
|
84
|
+
VersionAdded: '0.8.0'
|
|
69
85
|
|
|
70
86
|
Style/RedundantException:
|
|
71
87
|
Enabled: false
|
|
@@ -78,11 +94,19 @@ Metrics/ModuleLength:
|
|
|
78
94
|
Exclude:
|
|
79
95
|
- '**/*Test.rb'
|
|
80
96
|
- '**/test_*.rb'
|
|
97
|
+
- '**/*_test.rb'
|
|
98
|
+
- '**/*_spec.rb'
|
|
99
|
+
- 'test/**/*'
|
|
100
|
+
- 'spec/**/*'
|
|
81
101
|
Metrics/ClassLength:
|
|
82
102
|
Max: 300
|
|
83
103
|
Exclude:
|
|
84
104
|
- '**/*Test.rb'
|
|
85
105
|
- '**/test_*.rb'
|
|
106
|
+
- '**/*_test.rb'
|
|
107
|
+
- '**/*_spec.rb'
|
|
108
|
+
- 'test/**/*'
|
|
109
|
+
- 'spec/**/*'
|
|
86
110
|
Metrics/MethodLength:
|
|
87
111
|
Max: 100
|
|
88
112
|
Metrics/BlockLength:
|
|
@@ -103,6 +127,8 @@ Layout/EndOfLine:
|
|
|
103
127
|
EnforcedStyle: lf
|
|
104
128
|
Layout/ParameterAlignment:
|
|
105
129
|
Enabled: false
|
|
130
|
+
Layout/ArrayAlignment:
|
|
131
|
+
EnforcedStyle: with_fixed_indentation
|
|
106
132
|
Layout/LineLength:
|
|
107
133
|
Max: 120
|
|
108
134
|
Style/MultilineBlockChain:
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
6
|
# Enforces that every class be defined inside a module. A class declared
|
|
7
|
-
# at the top level
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
7
|
+
# at the top level pollutes the global namespace and breaks modular
|
|
8
|
+
# design. The compact namespaced form +class Foo::Bar+ is allowed
|
|
9
|
+
# because its name already resolves into an enclosing namespace, and so
|
|
10
|
+
# is a class nested inside another class, since the enclosing class
|
|
11
|
+
# names it too: +class Foo::Bar; class Baz+ defines +Foo::Bar::Baz+.
|
|
12
|
+
# A class declared with an explicit root scope, +class ::Baz+, is global
|
|
13
|
+
# whatever encloses it lexically, so it is reported wherever it sits.
|
|
11
14
|
class RuboCop::Cop::Elegant::ClassInModule < RuboCop::Cop::Base
|
|
12
15
|
MSG = 'Class %<name>s must be defined inside a module, not globally'
|
|
13
16
|
public_constant :MSG
|
|
@@ -21,12 +24,17 @@ class RuboCop::Cop::Elegant::ClassInModule < RuboCop::Cop::Base
|
|
|
21
24
|
private
|
|
22
25
|
|
|
23
26
|
def namespaced?(node)
|
|
24
|
-
scope = node
|
|
27
|
+
scope = outer(node)
|
|
25
28
|
!scope.nil? && scope.type != :cbase
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def scoped?(node)
|
|
29
|
-
node
|
|
32
|
+
return false unless outer(node).nil?
|
|
33
|
+
node.each_ancestor(:module, :class).any?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def outer(node)
|
|
37
|
+
node.children[0].children[0]
|
|
30
38
|
end
|
|
31
39
|
|
|
32
40
|
def label(node)
|
|
@@ -18,11 +18,16 @@ class RuboCop::Cop::Elegant::GoodMethodName < RuboCop::Cop::Base
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
20
|
def check(node, name)
|
|
21
|
+
return unless identifier?(name)
|
|
21
22
|
return if allowed?(name)
|
|
22
23
|
return if match?(name)
|
|
23
24
|
add_offense(node, message: format(MSG, name: name))
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
def identifier?(name)
|
|
28
|
+
/\A[a-zA-Z_]/.match?(name)
|
|
29
|
+
end
|
|
30
|
+
|
|
26
31
|
def match?(name)
|
|
27
32
|
pattern.match?(name)
|
|
28
33
|
end
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
# Auto-correct inlines the redundant assignment: it replaces the
|
|
24
24
|
# single +lvar+ read with the source of the assignment's right-hand
|
|
25
25
|
# side, then removes the whole assignment line including its leading
|
|
26
|
-
# indent and trailing newline.
|
|
26
|
+
# indent and trailing newline. When the read is the value of a Ruby
|
|
27
|
+
# 3.1 shorthand pair, such as the +ids+ of +call(ids:)+, key and
|
|
28
|
+
# value share one range, so the whole pair is rewritten into its
|
|
29
|
+
# long form +ids: <rhs>+ instead. The right-hand side is wrapped in
|
|
27
30
|
# parentheses unless it is already a primary expression (literal,
|
|
28
31
|
# variable, parenthesized expression, or method call with parentheses
|
|
29
32
|
# or no arguments), so that operator precedence at the read site is
|
|
@@ -81,11 +84,26 @@ class RuboCop::Cop::Elegant::NoRedundantVariable < RuboCop::Cop::Base
|
|
|
81
84
|
def register(assign, read, name)
|
|
82
85
|
return add_offense(assign, message: format(MSG, name: name)) unless solo?(assign)
|
|
83
86
|
add_offense(assign, message: format(MSG, name: name)) do |corrector|
|
|
84
|
-
corrector.replace(
|
|
87
|
+
corrector.replace(*rewrite(assign.children.last, read))
|
|
85
88
|
corrector.remove(range_by_whole_lines(assign.source_range, include_final_newline: true))
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
|
|
92
|
+
def rewrite(rhs, read)
|
|
93
|
+
pair = shorthand(read)
|
|
94
|
+
return [read.source_range, inlined(rhs, read)] if pair.nil?
|
|
95
|
+
[pair.source_range, "#{pair.children.first.source}: #{inlined(rhs, read)}"]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def shorthand(read)
|
|
99
|
+
parent = read.parent
|
|
100
|
+
return if parent.nil? || !parent.pair_type?
|
|
101
|
+
key, value = parent.children
|
|
102
|
+
return unless value.equal?(read)
|
|
103
|
+
return unless key.source_range == value.source_range
|
|
104
|
+
parent
|
|
105
|
+
end
|
|
106
|
+
|
|
89
107
|
def solo?(assign)
|
|
90
108
|
range = assign.source_range
|
|
91
109
|
return false unless range.first_line == range.last_line
|
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
# are forbidden because they hide the structure of the code.
|
|
11
11
|
#
|
|
12
12
|
# Auto-correct relocates the offending bracket onto its own line: an
|
|
13
|
-
# opener that is not at end-of-line
|
|
14
|
-
# indent plus two spaces
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
13
|
+
# opener that is not at end-of-line is followed by a newline and the
|
|
14
|
+
# opener-line indent plus two spaces; a closer that is not at
|
|
15
|
+
# start-of-line is preceded by a newline and the opener-line indent.
|
|
16
|
+
# The whitespace already sitting next to the bracket is consumed by
|
|
17
|
+
# the rewrite rather than left in place, so the moved code lands on
|
|
18
|
+
# the intended column instead of drifting right by that much.
|
|
19
|
+
# Surrounding indentation may still need a follow-up layout pass, but
|
|
20
|
+
# the brackets themselves end up paired.
|
|
18
21
|
#
|
|
19
22
|
# See https://www.yegor256.com/2014/10/23/paired-brackets-notation.html
|
|
20
23
|
class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
|
|
@@ -29,6 +32,9 @@ class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
|
|
|
29
32
|
CLOSERS = %i[tRPAREN tRBRACK tRCURLY].freeze
|
|
30
33
|
private_constant :CLOSERS
|
|
31
34
|
|
|
35
|
+
BLANKS = [' ', "\t"].freeze
|
|
36
|
+
private_constant :BLANKS
|
|
37
|
+
|
|
32
38
|
def on_new_investigation
|
|
33
39
|
super
|
|
34
40
|
pair.each { |duo| check(duo) }
|
|
@@ -53,8 +59,22 @@ class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
|
|
|
53
59
|
opener, closer = duo
|
|
54
60
|
return if opener.line == closer.line
|
|
55
61
|
indent = leading(opener)
|
|
56
|
-
register(opener) { |corrector| corrector.
|
|
57
|
-
register(closer) { |corrector| corrector.
|
|
62
|
+
register(opener) { |corrector| corrector.replace(trailing(opener), "\n#{indent} ") } unless ends?(opener)
|
|
63
|
+
register(closer) { |corrector| corrector.replace(preceding(closer), "\n#{indent}") } unless starts?(closer)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def trailing(tok)
|
|
67
|
+
text = processed_source.buffer.source
|
|
68
|
+
stop = tok.pos.end_pos
|
|
69
|
+
stop += 1 while BLANKS.include?(text[stop])
|
|
70
|
+
tok.pos.end.resize(stop - tok.pos.end_pos)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def preceding(tok)
|
|
74
|
+
text = processed_source.buffer.source
|
|
75
|
+
from = tok.pos.begin_pos
|
|
76
|
+
from -= 1 while from.positive? && BLANKS.include?(text[from - 1])
|
|
77
|
+
tok.pos.begin.adjust(begin_pos: from - tok.pos.begin_pos)
|
|
58
78
|
end
|
|
59
79
|
|
|
60
80
|
def starts?(tok)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Requires the SPDX header to sit above the code. The header is the
|
|
7
|
+
# license declaration of the file, and REUSE keeps it in the first
|
|
8
|
+
# few lines so that scanners, editors and reviewers read the license
|
|
9
|
+
# without parsing past unrelated code. Only blank lines and comments
|
|
10
|
+
# may precede it: a shebang, a magic comment, a RuboCop directive.
|
|
11
|
+
# A file that carries no SPDX comment at all is left alone, because
|
|
12
|
+
# checking that the tags exist is the job of +reuse lint+ and not of
|
|
13
|
+
# this plugin. Only the first SPDX comment counts, so the tags of a
|
|
14
|
+
# multi-line header may be split by nothing but comments.
|
|
15
|
+
class RuboCop::Cop::Elegant::SpdxOnTop < RuboCop::Cop::Base
|
|
16
|
+
MSG = 'SPDX header must be above the code, but this line comes before it'
|
|
17
|
+
public_constant :MSG
|
|
18
|
+
|
|
19
|
+
def on_new_investigation
|
|
20
|
+
header = spdx
|
|
21
|
+
return if header.nil?
|
|
22
|
+
line = intruder(header.location.line)
|
|
23
|
+
return if line.nil?
|
|
24
|
+
add_offense(processed_source.buffer.line_range(line))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def spdx
|
|
30
|
+
processed_source.comments.find { |comment| comment.text.match?(/^#\s*SPDX-/) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def intruder(stop)
|
|
34
|
+
(1...stop).find { |idx| code?(processed_source.lines[idx - 1]) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def code?(line)
|
|
38
|
+
text = line.strip
|
|
39
|
+
!text.empty? && !text.start_with?('#')
|
|
40
|
+
end
|
|
41
|
+
end
|
data/rubocop-elegant.gemspec
CHANGED
|
@@ -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.
|
|
12
|
+
s.version = '0.8.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.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -65,6 +65,7 @@ files:
|
|
|
65
65
|
- lib/rubocop/cop/elegant/no_redundant_variable.rb
|
|
66
66
|
- lib/rubocop/cop/elegant/one_class_per_file.rb
|
|
67
67
|
- lib/rubocop/cop/elegant/paired_brackets.rb
|
|
68
|
+
- lib/rubocop/cop/elegant/spdx_on_top.rb
|
|
68
69
|
- lib/rubocop/cop/elegant_cops.rb
|
|
69
70
|
- lib/rubocop/elegant/plugin.rb
|
|
70
71
|
- lib/rubocop/elegant/version.rb
|