rubocop-aaa 0.0.1
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 +7 -0
- data/README.md +88 -0
- data/config/default.yml +21 -0
- data/lib/rubocop/cop/aaa/pattern.rb +123 -0
- data/lib/rubocop-aaa.rb +2 -0
- metadata +80 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 88bb8909e976646d38fa8d819807e978ad8e2db91592ee69fe1e8f848dc13692
|
|
4
|
+
data.tar.gz: 84ae0416f47272ccdcf38fc48b55c060106b96cff1b5828075da5aaf4886140a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5eea4bdadc3cbc5755f3c40d300dc188df672d20032c2781fd832d65c5fe795f020e1834c288acf271492450933d7f5ff9938b2bcbce28ab9e51749a60a979a3
|
|
7
|
+
data.tar.gz: 6a8e83b998e8e385064ee34311bd280ea9c6ae251a2ed4a9ad081790e564233283052ae4221a4d163340a04894d14001af6d8ef82cfb1d517b166802f9be2b80
|
data/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# rubocop-aaa
|
|
2
|
+
|
|
3
|
+
RuboCop cop that enforces the **Arrange-Act-Assert** pattern in Ruby test code.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Gemfile
|
|
9
|
+
group :development, :test do
|
|
10
|
+
gem 'rubocop-aaa', require: false
|
|
11
|
+
end
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Enable the cop in your `.rubocop.yml`:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
require:
|
|
20
|
+
- rubocop-aaa
|
|
21
|
+
|
|
22
|
+
AAA/Pattern:
|
|
23
|
+
Enabled: true
|
|
24
|
+
Include:
|
|
25
|
+
- 'spec/**/*.rb'
|
|
26
|
+
- 'test/**/*.rb'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What it checks
|
|
30
|
+
|
|
31
|
+
Every test block (`it`, `test`, `specify`, `example` by default) must contain three marker comments in order:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
it 'adds' do
|
|
35
|
+
# arrange
|
|
36
|
+
a = 1
|
|
37
|
+
b = 2
|
|
38
|
+
|
|
39
|
+
# act
|
|
40
|
+
sum = a + b
|
|
41
|
+
|
|
42
|
+
# assert
|
|
43
|
+
expect(sum).to eq(3)
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
AAA/Pattern:
|
|
51
|
+
TestFunctions:
|
|
52
|
+
- it
|
|
53
|
+
- test
|
|
54
|
+
- specify
|
|
55
|
+
- example
|
|
56
|
+
Labels:
|
|
57
|
+
arrange: [arrange]
|
|
58
|
+
act: [act]
|
|
59
|
+
assert: [assert]
|
|
60
|
+
CaseSensitive: false
|
|
61
|
+
AllowEmptySection: true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Given / When / Then
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
AAA/Pattern:
|
|
68
|
+
Labels:
|
|
69
|
+
arrange: [given]
|
|
70
|
+
act: [when]
|
|
71
|
+
assert: [then]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Japanese
|
|
75
|
+
|
|
76
|
+
Japanese phrasing varies, so no preset ships — configure with the wording your team uses:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
AAA/Pattern:
|
|
80
|
+
Labels:
|
|
81
|
+
arrange: [準備, 前準備]
|
|
82
|
+
act: [実行]
|
|
83
|
+
assert: [検証, 確認]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
data/config/default.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
AAA/Pattern:
|
|
2
|
+
Description: 'Enforce Arrange-Act-Assert comments in test blocks.'
|
|
3
|
+
Enabled: true
|
|
4
|
+
VersionAdded: '0.0.1'
|
|
5
|
+
Include:
|
|
6
|
+
- 'spec/**/*.rb'
|
|
7
|
+
- 'test/**/*.rb'
|
|
8
|
+
TestFunctions:
|
|
9
|
+
- it
|
|
10
|
+
- test
|
|
11
|
+
- specify
|
|
12
|
+
- example
|
|
13
|
+
Labels:
|
|
14
|
+
arrange:
|
|
15
|
+
- arrange
|
|
16
|
+
act:
|
|
17
|
+
- act
|
|
18
|
+
assert:
|
|
19
|
+
- assert
|
|
20
|
+
CaseSensitive: false
|
|
21
|
+
AllowEmptySection: true
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module AAA
|
|
4
|
+
class Pattern < Base
|
|
5
|
+
SECTIONS = %i[arrange act assert].freeze
|
|
6
|
+
|
|
7
|
+
MSG_MISSING = 'Missing "%<section>s" comment in test block.'.freeze
|
|
8
|
+
MSG_ORDER = 'AAA comments must appear in order: arrange -> act -> assert.'.freeze
|
|
9
|
+
MSG_EMPTY = 'Section "%<section>s" has no statements.'.freeze
|
|
10
|
+
|
|
11
|
+
DEFAULT_LABELS = {
|
|
12
|
+
'arrange' => %w[arrange],
|
|
13
|
+
'act' => %w[act],
|
|
14
|
+
'assert' => %w[assert]
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
DEFAULT_TEST_FUNCTIONS = %w[it test specify example].freeze
|
|
18
|
+
|
|
19
|
+
def on_block(node)
|
|
20
|
+
return unless test_method?(node.method_name)
|
|
21
|
+
|
|
22
|
+
found = collect_sections(node)
|
|
23
|
+
seen = {}
|
|
24
|
+
found.each { |section, comment| seen[section] ||= comment }
|
|
25
|
+
|
|
26
|
+
SECTIONS.each do |section|
|
|
27
|
+
unless seen[section]
|
|
28
|
+
add_offense(node, message: format(MSG_MISSING, section: section))
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
ordered = SECTIONS.map { |s| seen[s] }
|
|
34
|
+
(1...ordered.size).each do |i|
|
|
35
|
+
if ordered[i].loc.expression.begin_pos < ordered[i - 1].loc.expression.begin_pos
|
|
36
|
+
add_offense(ordered[i], message: MSG_ORDER)
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
return if allow_empty_section?
|
|
42
|
+
|
|
43
|
+
check_empty_sections(node, seen)
|
|
44
|
+
end
|
|
45
|
+
alias on_numblock on_block
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def collect_sections(node)
|
|
50
|
+
block_range = node.loc.expression
|
|
51
|
+
processed_source.comments.each_with_object([]) do |comment, acc|
|
|
52
|
+
pos = comment.loc.expression.begin_pos
|
|
53
|
+
next unless pos > block_range.begin_pos && pos < block_range.end_pos
|
|
54
|
+
|
|
55
|
+
section = match_section(comment_body(comment))
|
|
56
|
+
acc << [section, comment] if section
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def check_empty_sections(node, seen)
|
|
61
|
+
block_end = node.loc.end.begin_pos
|
|
62
|
+
boundaries = [
|
|
63
|
+
[:arrange, seen[:arrange].loc.expression.end_pos, seen[:act].loc.expression.begin_pos],
|
|
64
|
+
[:act, seen[:act].loc.expression.end_pos, seen[:assert].loc.expression.begin_pos],
|
|
65
|
+
[:assert, seen[:assert].loc.expression.end_pos, block_end]
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
boundaries.each do |section, start_pos, end_pos|
|
|
69
|
+
next if has_code_between?(start_pos, end_pos)
|
|
70
|
+
|
|
71
|
+
add_offense(seen[section], message: format(MSG_EMPTY, section: section))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def has_code_between?(start_pos, end_pos)
|
|
76
|
+
return false if start_pos >= end_pos
|
|
77
|
+
|
|
78
|
+
between = processed_source.raw_source[start_pos...end_pos].to_s
|
|
79
|
+
between.each_line.any? do |line|
|
|
80
|
+
stripped = line.strip
|
|
81
|
+
!stripped.empty? && !stripped.start_with?('#')
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def match_section(text)
|
|
86
|
+
normalized = case_sensitive? ? text : text.downcase
|
|
87
|
+
SECTIONS.each do |section|
|
|
88
|
+
candidates = labels[section.to_s] || []
|
|
89
|
+
candidates.each do |label|
|
|
90
|
+
target = case_sensitive? ? label : label.downcase
|
|
91
|
+
return section if normalized == target
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def comment_body(comment)
|
|
98
|
+
comment.text.sub(/\A#\s*/, '').strip
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_method?(name)
|
|
102
|
+
test_functions.include?(name.to_s)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_functions
|
|
106
|
+
cop_config.fetch('TestFunctions', DEFAULT_TEST_FUNCTIONS)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def labels
|
|
110
|
+
cop_config.fetch('Labels', DEFAULT_LABELS)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def case_sensitive?
|
|
114
|
+
cop_config.fetch('CaseSensitive', false)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def allow_empty_section?
|
|
118
|
+
cop_config.fetch('AllowEmptySection', true)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/rubocop-aaa.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubocop-aaa
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- babu-ch
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rubocop
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.12'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.12'
|
|
41
|
+
description: A custom RuboCop cop that checks test blocks contain arrange/act/assert
|
|
42
|
+
comments in order.
|
|
43
|
+
email:
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- README.md
|
|
49
|
+
- config/default.yml
|
|
50
|
+
- lib/rubocop-aaa.rb
|
|
51
|
+
- lib/rubocop/cop/aaa/pattern.rb
|
|
52
|
+
homepage: https://babu-ch.github.io/aaa-lint/guide/rubocop
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
homepage_uri: https://babu-ch.github.io/aaa-lint/guide/rubocop
|
|
57
|
+
source_code_uri: https://github.com/babu-ch/aaa-lint/tree/main/packages/rubocop-aaa
|
|
58
|
+
bug_tracker_uri: https://github.com/babu-ch/aaa-lint/issues
|
|
59
|
+
documentation_uri: https://babu-ch.github.io/aaa-lint/guide/rubocop
|
|
60
|
+
rubygems_mfa_required: 'true'
|
|
61
|
+
post_install_message:
|
|
62
|
+
rdoc_options: []
|
|
63
|
+
require_paths:
|
|
64
|
+
- lib
|
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '3.0'
|
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
requirements: []
|
|
76
|
+
rubygems_version: 3.4.10
|
|
77
|
+
signing_key:
|
|
78
|
+
specification_version: 4
|
|
79
|
+
summary: RuboCop cop enforcing the Arrange-Act-Assert pattern in test code.
|
|
80
|
+
test_files: []
|