rubocop-elegant 0.0.20 → 0.1.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 -1
- data/config/default.yml +24 -1
- data/lib/rubocop/cop/elegant/class_in_module.rb +36 -0
- data/lib/rubocop/cop/elegant/good_method_name.rb +30 -36
- data/lib/rubocop/cop/elegant/good_variable_name.rb +38 -44
- data/lib/rubocop/cop/elegant/indentation_ladder.rb +50 -0
- data/lib/rubocop/cop/elegant/no_class_in_module.rb +27 -0
- data/lib/rubocop/cop/elegant/no_comments.rb +98 -104
- data/lib/rubocop/cop/elegant/no_empty_lines_in_blocks.rb +101 -107
- data/lib/rubocop/cop/elegant/no_empty_lines_in_methods.rb +49 -55
- data/lib/rubocop/cop/elegant/paired_brackets.rb +64 -0
- data/lib/rubocop/cop/elegant_cops.rb +8 -2
- data/lib/rubocop/elegant/plugin.rb +18 -22
- data/lib/rubocop-elegant.rb +3 -0
- data/rubocop-elegant.gemspec +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8341c1ab1fa0a7363197452f41a6146ecff051b7f6f094386612cf3f27b5427
|
|
4
|
+
data.tar.gz: 229010b24b1fbafe803804f3b6615f9295727fe1f8e87bf79902dc9c2a0675d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f20ed17a83b4263d2c1336a9d5b1e1d44299c13014fba2a32220df5dd326794cbeda32e15fa1b68474661545b94744d58ed6e8400d894405329a51ad6ca0b965
|
|
7
|
+
data.tar.gz: 79975886caca7e40ac61a08a5a2737b9654c4dd4f43d6633a8a2a0a6047d01c1517094906b2d3920ebccd0e386d5a8afadaa784fc7cf5a5c87d5952f54ab9193
|
data/README.md
CHANGED
|
@@ -38,7 +38,7 @@ plugins:
|
|
|
38
38
|
- rubocop-elegant # must be the last one
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
The `rubocop-elegant` not only provides its own cops, but also configures
|
|
41
|
+
The `rubocop-elegant` plugin not only provides its own cops, but also configures
|
|
42
42
|
default ones the "right" way.
|
|
43
43
|
|
|
44
44
|
## How to contribute
|
data/config/default.yml
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
---
|
|
4
|
+
# yamllint disable rule:line-length
|
|
4
5
|
Elegant:
|
|
5
6
|
Enabled: true
|
|
6
7
|
DocumentationBaseURL: https://github.com/yegor256/rubocop-elegant
|
|
@@ -16,6 +17,28 @@ Elegant/NoEmptyLinesInBlocks:
|
|
|
16
17
|
Description: 'Disallows empty lines inside blocks (do/end, if/end, while, case, begin, etc.)'
|
|
17
18
|
Enabled: true
|
|
18
19
|
VersionAdded: '0.0.19'
|
|
20
|
+
Elegant/PairedBrackets:
|
|
21
|
+
Description: 'Enforces the paired brackets notation: a bracket must pair on the same line, or start/end its line'
|
|
22
|
+
Enabled: true
|
|
23
|
+
VersionAdded: '0.0.20'
|
|
24
|
+
Elegant/ClassInModule:
|
|
25
|
+
Description: 'Requires every class to be defined inside a module, not globally'
|
|
26
|
+
Enabled: true
|
|
27
|
+
VersionAdded: '0.1.0'
|
|
28
|
+
Exclude:
|
|
29
|
+
- '**/*Test.rb'
|
|
30
|
+
- '**/test_*.rb'
|
|
31
|
+
Elegant/NoClassInModule:
|
|
32
|
+
Description: 'Forbids declaring a class inside a module declaration; use compact namespace syntax instead'
|
|
33
|
+
Enabled: true
|
|
34
|
+
VersionAdded: '0.1.0'
|
|
35
|
+
Exclude:
|
|
36
|
+
- '**/*Test.rb'
|
|
37
|
+
- '**/test_*.rb'
|
|
38
|
+
Elegant/IndentationLadder:
|
|
39
|
+
Description: 'Requires the indentation step to be exactly two spaces when a line indents to the right of the previous one'
|
|
40
|
+
Enabled: true
|
|
41
|
+
VersionAdded: '0.1.0'
|
|
19
42
|
Elegant/GoodVariableName:
|
|
20
43
|
Description: 'Checks that variable names match the configured pattern'
|
|
21
44
|
Enabled: true
|
|
@@ -26,7 +49,7 @@ Elegant/GoodMethodName:
|
|
|
26
49
|
Description: 'Checks that method names match the configured pattern'
|
|
27
50
|
Enabled: true
|
|
28
51
|
VersionAdded: '0.0.3'
|
|
29
|
-
Pattern: '^(((to|fake|the|with|without)_)?[a-z]{1,16}[!?]?|(on|test)_[a-z_]+)$'
|
|
52
|
+
Pattern: '^(?=.{1,48}$)(((to|fake|the|with|without)_)?[a-z]{1,16}[!?]?|(on|test)_[a-z_]+)$'
|
|
30
53
|
AllowedNames: []
|
|
31
54
|
|
|
32
55
|
Style/RedundantException:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Enforces that every class be defined inside a module. A class declared
|
|
7
|
+
# at the top level, or nested only inside another class, pollutes the
|
|
8
|
+
# global namespace and breaks modular design. The compact namespaced
|
|
9
|
+
# form +class Foo::Bar+ is allowed because its name already resolves
|
|
10
|
+
# into an enclosing namespace.
|
|
11
|
+
class RuboCop::Cop::Elegant::ClassInModule < RuboCop::Cop::Base
|
|
12
|
+
MSG = 'Class %<name>s must be defined inside a module, not globally'
|
|
13
|
+
public_constant :MSG
|
|
14
|
+
|
|
15
|
+
def on_class(node)
|
|
16
|
+
return if namespaced?(node)
|
|
17
|
+
return if scoped?(node)
|
|
18
|
+
add_offense(node, message: format(MSG, name: label(node)))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def namespaced?(node)
|
|
24
|
+
const = node.children[0]
|
|
25
|
+
scope = const.children[0]
|
|
26
|
+
!scope.nil? && scope.type != :cbase
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def scoped?(node)
|
|
30
|
+
node.each_ancestor(:module).any?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def label(node)
|
|
34
|
+
node.children[0].source
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -3,41 +3,35 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def pattern
|
|
38
|
-
@pattern ||= Regexp.new(cop_config['Pattern'] || '^[a-z]+[!?]?$')
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
6
|
+
class RuboCop::Cop::Elegant::GoodMethodName < RuboCop::Cop::Base
|
|
7
|
+
MSG = 'Method name "%<name>s" does not match the required pattern'
|
|
8
|
+
public_constant :MSG
|
|
9
|
+
|
|
10
|
+
def on_def(node)
|
|
11
|
+
check(node, node.method_name.to_s)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def on_defs(node)
|
|
15
|
+
check(node, node.method_name.to_s)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def check(node, name)
|
|
21
|
+
return if allowed?(name)
|
|
22
|
+
return if match?(name)
|
|
23
|
+
add_offense(node, message: format(MSG, name: name))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def match?(name)
|
|
27
|
+
pattern.match?(name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def allowed?(name)
|
|
31
|
+
Array(cop_config['AllowedNames']).map(&:to_s).include?(name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pattern
|
|
35
|
+
@pattern ||= Regexp.new(cop_config['Pattern'] || '^[a-z]+[!?]?$')
|
|
42
36
|
end
|
|
43
37
|
end
|
|
@@ -3,49 +3,43 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def pattern
|
|
46
|
-
@pattern ||= Regexp.new(cop_config['Pattern'] || '^[a-z]+$')
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
6
|
+
class RuboCop::Cop::Elegant::GoodVariableName < RuboCop::Cop::Base
|
|
7
|
+
MSG = 'Variable name "%<name>s" does not match the required pattern'
|
|
8
|
+
public_constant :MSG
|
|
9
|
+
|
|
10
|
+
def on_lvasgn(node)
|
|
11
|
+
check(node, node.children.first.to_s)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def on_ivasgn(node)
|
|
15
|
+
check(node, node.children.first.to_s)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_cvasgn(node)
|
|
19
|
+
check(node, node.children.first.to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_gvasgn(node)
|
|
23
|
+
check(node, node.children.first.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def check(node, name)
|
|
29
|
+
return if allowed?(name)
|
|
30
|
+
return if match?(name)
|
|
31
|
+
add_offense(node, message: format(MSG, name: name))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def match?(name)
|
|
35
|
+
pattern.match?(name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def allowed?(name)
|
|
39
|
+
Array(cop_config['AllowedNames']).map(&:to_s).include?(name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def pattern
|
|
43
|
+
@pattern ||= Regexp.new(cop_config['Pattern'] || '^[a-z]+$')
|
|
50
44
|
end
|
|
51
45
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Enforces the "indentation ladder" rule: when a line is indented further
|
|
7
|
+
# to the right than the previous non-empty line, the extra indentation
|
|
8
|
+
# must be exactly two spaces. Larger jumps (or odd ones, such as a single
|
|
9
|
+
# space or three spaces) break the visual rhythm of the code and make
|
|
10
|
+
# nesting harder to follow. Lines that match the previous indentation, or
|
|
11
|
+
# de-indent by any amount, are not affected. Lines that belong to the
|
|
12
|
+
# body of a heredoc are ignored, because their whitespace is part of the
|
|
13
|
+
# literal value rather than program structure.
|
|
14
|
+
class RuboCop::Cop::Elegant::IndentationLadder < RuboCop::Cop::Base
|
|
15
|
+
MSG = 'Indentation step of %<step>d spaces is not allowed; use 2 spaces'
|
|
16
|
+
public_constant :MSG
|
|
17
|
+
|
|
18
|
+
def on_new_investigation
|
|
19
|
+
super
|
|
20
|
+
skip = heredocs
|
|
21
|
+
prev = nil
|
|
22
|
+
processed_source.lines.each_with_index do |line, idx|
|
|
23
|
+
num = idx + 1
|
|
24
|
+
next if skip.include?(num)
|
|
25
|
+
next if line.strip.empty?
|
|
26
|
+
indent = line[/\A */].length
|
|
27
|
+
register(num, indent - prev) if prev && indent > prev && (indent - prev) != 2
|
|
28
|
+
prev = indent
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def heredocs
|
|
35
|
+
result = []
|
|
36
|
+
ast = processed_source.ast
|
|
37
|
+
return result if ast.nil?
|
|
38
|
+
ast.each_node(:str, :dstr, :xstr) do |node|
|
|
39
|
+
next unless node.heredoc?
|
|
40
|
+
body = node.loc.heredoc_body
|
|
41
|
+
(body.first_line..body.last_line).each { |num| result << num }
|
|
42
|
+
end
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def register(num, step)
|
|
47
|
+
target = processed_source.buffer.line_range(num)
|
|
48
|
+
add_offense(target, message: format(MSG, step: step))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Forbids declaring a class inside a module declaration. Instead, the
|
|
7
|
+
# class must be declared with the compact namespace syntax, like
|
|
8
|
+
# +class Foo::Bar+. An empty module +module Foo; end+, a module that
|
|
9
|
+
# contains only other modules, methods, or constants, and a class
|
|
10
|
+
# nested inside another class are all allowed; only a class whose
|
|
11
|
+
# nearest enclosing scope is a module is rejected.
|
|
12
|
+
class RuboCop::Cop::Elegant::NoClassInModule < RuboCop::Cop::Base
|
|
13
|
+
MSG = 'Class %<name>s must use compact namespace syntax, not be nested inside a module'
|
|
14
|
+
public_constant :MSG
|
|
15
|
+
|
|
16
|
+
def on_class(node)
|
|
17
|
+
owner = node.each_ancestor(:module, :class).first
|
|
18
|
+
return if owner.nil? || !owner.module_type?
|
|
19
|
+
add_offense(node, message: format(MSG, name: label(node)))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def label(node)
|
|
25
|
+
node.children[0].source
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -3,110 +3,104 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def register(comment)
|
|
82
|
-
add_offense(comment) do |corrector|
|
|
83
|
-
corrector.remove(removal(comment))
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def removal(comment)
|
|
88
|
-
target = comment.source_range
|
|
89
|
-
prefix = target.source_line[0, target.column]
|
|
90
|
-
return fullrange(target) if prefix.strip.empty?
|
|
91
|
-
prefixed(target, prefix)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def fullrange(target)
|
|
95
|
-
start = target.begin_pos - target.column
|
|
96
|
-
ending = target.end_pos
|
|
97
|
-
ending += 1 if newline?(ending)
|
|
98
|
-
target.with(begin_pos: start, end_pos: ending)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def prefixed(target, prefix)
|
|
102
|
-
spaces = prefix.match(/\s*$/)[0]
|
|
103
|
-
target.with(begin_pos: target.begin_pos - spaces.length, end_pos: target.end_pos)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def newline?(pos)
|
|
107
|
-
processed_source.buffer.source[pos] == "\n"
|
|
108
|
-
end
|
|
109
|
-
end
|
|
6
|
+
class RuboCop::Cop::Elegant::NoComments < RuboCop::Cop::Base
|
|
7
|
+
extend RuboCop::Cop::AutoCorrector
|
|
8
|
+
|
|
9
|
+
MSG = 'Comment is not allowed, unless it is SPDX, magic, rubocop directive, or docblock'
|
|
10
|
+
public_constant :MSG
|
|
11
|
+
|
|
12
|
+
def on_new_investigation
|
|
13
|
+
processed_source.comments.each do |comment|
|
|
14
|
+
register(comment) unless allowed?(comment)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def allowed?(comment)
|
|
21
|
+
spdx?(comment) || magic?(comment) || rubocop?(comment) || (gemspec? && docblock?(comment))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def spdx?(comment)
|
|
25
|
+
comment.text.match?(/^#\s*SPDX-/)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def magic?(comment)
|
|
29
|
+
comment.text.match?(/^#\s*(frozen_string_literal|encoding|coding|warn_indent):/)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def rubocop?(comment)
|
|
33
|
+
comment.text.match?(/^#\s*rubocop:(disable|enable|todo)\s/)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def gemspec?
|
|
37
|
+
return @gemspec if defined?(@gemspec)
|
|
38
|
+
path = processed_source.path
|
|
39
|
+
return @gemspec = false if path.nil?
|
|
40
|
+
@gemspec = root(File.dirname(path))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def root(dir)
|
|
44
|
+
return true if Dir.glob(File.join(dir, '*.gemspec')).any?
|
|
45
|
+
parent = File.dirname(dir)
|
|
46
|
+
return false if parent == dir
|
|
47
|
+
root(parent)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def docblock?(comment)
|
|
51
|
+
line = comment.location.line
|
|
52
|
+
successor = codeline(line)
|
|
53
|
+
return false if successor.nil?
|
|
54
|
+
definition?(successor)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def codeline(start)
|
|
58
|
+
lines = processed_source.lines
|
|
59
|
+
(start...lines.size).each do |idx|
|
|
60
|
+
content = lines[idx]
|
|
61
|
+
next if content.nil?
|
|
62
|
+
stripped = content.strip
|
|
63
|
+
next if stripped.empty? || stripped.start_with?('#')
|
|
64
|
+
return idx + 1
|
|
65
|
+
end
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def definition?(line)
|
|
70
|
+
ast = processed_source.ast
|
|
71
|
+
return false if ast.nil?
|
|
72
|
+
ast.each_node(:class, :module, :def, :defs) do |node|
|
|
73
|
+
return true if node.location.line == line
|
|
74
|
+
end
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def register(comment)
|
|
79
|
+
add_offense(comment) do |corrector|
|
|
80
|
+
corrector.remove(removal(comment))
|
|
110
81
|
end
|
|
111
82
|
end
|
|
83
|
+
|
|
84
|
+
def removal(comment)
|
|
85
|
+
target = comment.source_range
|
|
86
|
+
prefix = target.source_line[0, target.column]
|
|
87
|
+
return fullrange(target) if prefix.strip.empty?
|
|
88
|
+
prefixed(target, prefix)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fullrange(target)
|
|
92
|
+
start = target.begin_pos - target.column
|
|
93
|
+
ending = target.end_pos
|
|
94
|
+
ending += 1 if newline?(ending)
|
|
95
|
+
target.with(begin_pos: start, end_pos: ending)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def prefixed(target, prefix)
|
|
99
|
+
spaces = prefix.match(/\s*$/)[0]
|
|
100
|
+
target.with(begin_pos: target.begin_pos - spaces.length, end_pos: target.end_pos)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def newline?(pos)
|
|
104
|
+
processed_source.buffer.source[pos] == "\n"
|
|
105
|
+
end
|
|
112
106
|
end
|
|
@@ -3,113 +3,107 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
next if @gaps.include?(num)
|
|
76
|
-
line = processed_source.lines[num - 1]
|
|
77
|
-
result << num if line.strip.empty?
|
|
78
|
-
end
|
|
79
|
-
result
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def scan
|
|
83
|
-
gaps = []
|
|
84
|
-
ast = processed_source.ast
|
|
85
|
-
return gaps if ast.nil?
|
|
86
|
-
ast.each_node(:def, :defs) do |node|
|
|
87
|
-
nxt = node.right_sibling
|
|
88
|
-
next unless nxt.is_a?(RuboCop::AST::Node)
|
|
89
|
-
next unless %i[def defs].include?(nxt.type)
|
|
90
|
-
first = node.last_line + 1
|
|
91
|
-
last = nxt.first_line - 1
|
|
92
|
-
next if first > last
|
|
93
|
-
(first..last).each { |n| gaps << n }
|
|
94
|
-
end
|
|
95
|
-
gaps
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def register(num)
|
|
99
|
-
return if @reported.include?(num)
|
|
100
|
-
@reported << num
|
|
101
|
-
target = processed_source.buffer.line_range(num)
|
|
102
|
-
add_offense(target) do |corrector|
|
|
103
|
-
corrector.remove(fullrange(target))
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def fullrange(target)
|
|
108
|
-
ending = target.end_pos
|
|
109
|
-
ending += 1 if processed_source.buffer.source[ending] == "\n"
|
|
110
|
-
target.with(end_pos: ending)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
6
|
+
class RuboCop::Cop::Elegant::NoEmptyLinesInBlocks < RuboCop::Cop::Base
|
|
7
|
+
extend RuboCop::Cop::AutoCorrector
|
|
8
|
+
|
|
9
|
+
MSG = 'Empty line inside block body is not allowed'
|
|
10
|
+
public_constant :MSG
|
|
11
|
+
|
|
12
|
+
def on_new_investigation
|
|
13
|
+
super
|
|
14
|
+
@reported = []
|
|
15
|
+
@gaps = scan
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_block(node)
|
|
19
|
+
check(node)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_numblock(node)
|
|
23
|
+
check(node)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_if(node)
|
|
27
|
+
check(node)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_while(node)
|
|
31
|
+
check(node)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_until(node)
|
|
35
|
+
check(node)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def on_for(node)
|
|
39
|
+
check(node)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def on_case(node)
|
|
43
|
+
check(node)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def on_case_match(node)
|
|
47
|
+
check(node)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def on_kwbegin(node)
|
|
51
|
+
check(node)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def check(node)
|
|
57
|
+
lines = range(node)
|
|
58
|
+
return if lines.nil?
|
|
59
|
+
empty(lines).each { |num| register(num) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def range(node)
|
|
63
|
+
first = node.first_line + 1
|
|
64
|
+
last = node.last_line - 1
|
|
65
|
+
return if first > last
|
|
66
|
+
(first..last)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def empty(lines)
|
|
70
|
+
result = []
|
|
71
|
+
lines.each do |num|
|
|
72
|
+
next if @gaps.include?(num)
|
|
73
|
+
line = processed_source.lines[num - 1]
|
|
74
|
+
result << num if line.strip.empty?
|
|
113
75
|
end
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def scan
|
|
80
|
+
gaps = []
|
|
81
|
+
ast = processed_source.ast
|
|
82
|
+
return gaps if ast.nil?
|
|
83
|
+
ast.each_node(:def, :defs) do |node|
|
|
84
|
+
nxt = node.right_sibling
|
|
85
|
+
next unless nxt.is_a?(RuboCop::AST::Node)
|
|
86
|
+
next unless %i[def defs].include?(nxt.type)
|
|
87
|
+
first = node.last_line + 1
|
|
88
|
+
last = nxt.first_line - 1
|
|
89
|
+
next if first > last
|
|
90
|
+
(first..last).each { |n| gaps << n }
|
|
91
|
+
end
|
|
92
|
+
gaps
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def register(num)
|
|
96
|
+
return if @reported.include?(num)
|
|
97
|
+
@reported << num
|
|
98
|
+
target = processed_source.buffer.line_range(num)
|
|
99
|
+
add_offense(target) do |corrector|
|
|
100
|
+
corrector.remove(fullrange(target))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def fullrange(target)
|
|
105
|
+
ending = target.end_pos
|
|
106
|
+
ending += 1 if processed_source.buffer.source[ending] == "\n"
|
|
107
|
+
target.with(end_pos: ending)
|
|
114
108
|
end
|
|
115
109
|
end
|
|
@@ -3,61 +3,55 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
target = processed_source.buffer.line_range(num)
|
|
50
|
-
add_offense(target) do |corrector|
|
|
51
|
-
corrector.remove(fullrange(target))
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def fullrange(target)
|
|
56
|
-
ending = target.end_pos
|
|
57
|
-
ending += 1 if processed_source.buffer.source[ending] == "\n"
|
|
58
|
-
target.with(end_pos: ending)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
6
|
+
class RuboCop::Cop::Elegant::NoEmptyLinesInMethods < RuboCop::Cop::Base
|
|
7
|
+
extend RuboCop::Cop::AutoCorrector
|
|
8
|
+
|
|
9
|
+
MSG = 'Empty line inside method body is not allowed'
|
|
10
|
+
public_constant :MSG
|
|
11
|
+
|
|
12
|
+
def on_def(node)
|
|
13
|
+
check(node)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def on_defs(node)
|
|
17
|
+
check(node)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def check(node)
|
|
23
|
+
return if node.body.nil?
|
|
24
|
+
lines = range(node)
|
|
25
|
+
return if lines.nil?
|
|
26
|
+
empty(lines).each { |num| register(num) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def range(node)
|
|
30
|
+
first = node.body.first_line
|
|
31
|
+
last = node.body.last_line
|
|
32
|
+
return if first == last
|
|
33
|
+
(first..last)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def empty(lines)
|
|
37
|
+
result = []
|
|
38
|
+
lines.each do |num|
|
|
39
|
+
line = processed_source.lines[num - 1]
|
|
40
|
+
result << num if line.strip.empty?
|
|
41
|
+
end
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def register(num)
|
|
46
|
+
target = processed_source.buffer.line_range(num)
|
|
47
|
+
add_offense(target) do |corrector|
|
|
48
|
+
corrector.remove(fullrange(target))
|
|
61
49
|
end
|
|
62
50
|
end
|
|
51
|
+
|
|
52
|
+
def fullrange(target)
|
|
53
|
+
ending = target.end_pos
|
|
54
|
+
ending += 1 if processed_source.buffer.source[ending] == "\n"
|
|
55
|
+
target.with(end_pos: ending)
|
|
56
|
+
end
|
|
63
57
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
# Enforces the "paired brackets" notation: every round, square, or curly
|
|
7
|
+
# bracket must either be paired with its matching counterpart on the same
|
|
8
|
+
# line, or end its own line (when opening) or start its own line (when
|
|
9
|
+
# closing). Brackets stranded in the middle of a multi-line expression
|
|
10
|
+
# are forbidden because they hide the structure of the code.
|
|
11
|
+
#
|
|
12
|
+
# See https://www.yegor256.com/2014/10/23/paired-brackets-notation.html
|
|
13
|
+
class RuboCop::Cop::Elegant::PairedBrackets < RuboCop::Cop::Base
|
|
14
|
+
MSG = 'Bracket %<text>s must be paired on the same line, or start/end its line'
|
|
15
|
+
public_constant :MSG
|
|
16
|
+
|
|
17
|
+
OPENERS = %i[tLPAREN tLPAREN2 tLPAREN_ARG tLBRACK tLBRACK2 tLCURLY tLBRACE tLBRACE_ARG].freeze
|
|
18
|
+
private_constant :OPENERS
|
|
19
|
+
|
|
20
|
+
CLOSERS = %i[tRPAREN tRBRACK tRCURLY].freeze
|
|
21
|
+
private_constant :CLOSERS
|
|
22
|
+
|
|
23
|
+
def on_new_investigation
|
|
24
|
+
super
|
|
25
|
+
pair.each { |duo| check(duo) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def pair
|
|
31
|
+
stack = []
|
|
32
|
+
result = []
|
|
33
|
+
processed_source.tokens.each do |tok|
|
|
34
|
+
if OPENERS.include?(tok.type)
|
|
35
|
+
stack << tok
|
|
36
|
+
elsif CLOSERS.include?(tok.type) && !stack.empty?
|
|
37
|
+
result << [stack.pop, tok]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def check(duo)
|
|
44
|
+
opener, closer = duo
|
|
45
|
+
return if opener.line == closer.line
|
|
46
|
+
register(opener) unless ends?(opener)
|
|
47
|
+
register(closer) unless starts?(closer)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def starts?(tok)
|
|
51
|
+
line = processed_source.lines[tok.line - 1]
|
|
52
|
+
line[0...tok.column].strip.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ends?(tok)
|
|
56
|
+
line = processed_source.lines[tok.line - 1]
|
|
57
|
+
after = line[(tok.column + tok.text.length)..-1].to_s.strip
|
|
58
|
+
after.empty? || after.start_with?('#')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def register(tok)
|
|
62
|
+
add_offense(tok.pos, message: format(MSG, text: tok.text))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'elegant/good_method_name'
|
|
4
|
-
require_relative 'elegant/good_variable_name'
|
|
5
3
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
6
4
|
# SPDX-License-Identifier: MIT
|
|
7
5
|
|
|
6
|
+
module RuboCop::Cop::Elegant; end
|
|
7
|
+
|
|
8
|
+
require_relative 'elegant/class_in_module'
|
|
9
|
+
require_relative 'elegant/good_method_name'
|
|
10
|
+
require_relative 'elegant/good_variable_name'
|
|
11
|
+
require_relative 'elegant/indentation_ladder'
|
|
12
|
+
require_relative 'elegant/no_class_in_module'
|
|
8
13
|
require_relative 'elegant/no_comments'
|
|
9
14
|
require_relative 'elegant/no_empty_lines_in_blocks'
|
|
10
15
|
require_relative 'elegant/no_empty_lines_in_methods'
|
|
16
|
+
require_relative 'elegant/paired_brackets'
|
|
@@ -5,29 +5,25 @@
|
|
|
5
5
|
|
|
6
6
|
require 'lint_roller'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
18
|
-
end
|
|
8
|
+
class RuboCop::Elegant::Plugin < LintRoller::Plugin
|
|
9
|
+
def about
|
|
10
|
+
LintRoller::About.new(
|
|
11
|
+
name: 'rubocop-elegant',
|
|
12
|
+
version: RuboCop::Elegant::VERSION,
|
|
13
|
+
homepage: 'https://github.com/yegor256/rubocop-elegant',
|
|
14
|
+
description: 'Set of custom RuboCop cops for elegant Ruby coding'
|
|
15
|
+
)
|
|
16
|
+
end
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
def supported?(context)
|
|
19
|
+
context.engine == :rubocop
|
|
20
|
+
end
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
end
|
|
22
|
+
def rules(_context)
|
|
23
|
+
LintRoller::Rules.new(
|
|
24
|
+
type: :path,
|
|
25
|
+
config_format: :rubocop,
|
|
26
|
+
value: Pathname.new(__dir__).join('../../../config/default.yml')
|
|
27
|
+
)
|
|
32
28
|
end
|
|
33
29
|
end
|
data/lib/rubocop-elegant.rb
CHANGED
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.0
|
|
12
|
+
s.version = '0.1.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.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -53,11 +53,15 @@ files:
|
|
|
53
53
|
- Rakefile
|
|
54
54
|
- config/default.yml
|
|
55
55
|
- lib/rubocop-elegant.rb
|
|
56
|
+
- lib/rubocop/cop/elegant/class_in_module.rb
|
|
56
57
|
- lib/rubocop/cop/elegant/good_method_name.rb
|
|
57
58
|
- lib/rubocop/cop/elegant/good_variable_name.rb
|
|
59
|
+
- lib/rubocop/cop/elegant/indentation_ladder.rb
|
|
60
|
+
- lib/rubocop/cop/elegant/no_class_in_module.rb
|
|
58
61
|
- lib/rubocop/cop/elegant/no_comments.rb
|
|
59
62
|
- lib/rubocop/cop/elegant/no_empty_lines_in_blocks.rb
|
|
60
63
|
- lib/rubocop/cop/elegant/no_empty_lines_in_methods.rb
|
|
64
|
+
- lib/rubocop/cop/elegant/paired_brackets.rb
|
|
61
65
|
- lib/rubocop/cop/elegant_cops.rb
|
|
62
66
|
- lib/rubocop/elegant/plugin.rb
|
|
63
67
|
- lib/rubocop/elegant/version.rb
|