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 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
@@ -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
@@ -0,0 +1,2 @@
1
+ require 'rubocop'
2
+ require_relative 'rubocop/cop/aaa/pattern'
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: []