rubocop-vicenzo 0.3.0 → 0.4.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: 685f19680a9d5721ed006772467155092d9d441c95c035191e3fa9dc449eac65
4
- data.tar.gz: 22fa73c161855ced3561903c9a901ca19beec2904fe326402f426ec93040dd78
3
+ metadata.gz: 2caba14e7971e328ef6fc5c5fb203fe11a26cbd1f78f534f223f01f9d99c240f
4
+ data.tar.gz: 2eb689487018dfa035aff365831cc219cf71dfce583885639656f314fbcffc82
5
5
  SHA512:
6
- metadata.gz: 8ea51ee079b7a796cb1f76c8630f9e78a8c8e4fe191f06e7096d4813744a8623a8b35b43288c7b24a671383d65b1ec4a76db72601bc32484ad5945009bb6a1d0
7
- data.tar.gz: 795995a84e448e543fbc5d68a6c0434fd0f7ad62dc19e57727d6da251bcfc0bc9624c7eaa78e40a802d5e08d924dc85a2a3512b3d7a868388bb62e22df7447ce
6
+ metadata.gz: e1c048efe4a3c8b7f7de35d8fcbe76908efcceb50bf053df761e59d53ea8fa9d74f779f223bb0d6e6ff1274e4ec2c83daf9a561084f970cdd5a48c8b7d4f7dce
7
+ data.tar.gz: f8c2f91bd54d5845ae68928a5ff783f6b161a47ca0e2e1d0afdc177473761416487d2e0d5c984f9b6d064a98ef3cc64db98baf3454f3989be19bb146172a3620
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-03-28
4
+
5
+ - Add RuboCop::Cop::Vicenzo::RSpec::ConditionalInSpec #19;
6
+ - Add RuboCop::Cop::Vicenzo::RSpec::DynamicExampleGeneration #19;
7
+ - Add RuboCop::Cop::Vicenzo::RSpec::IterationInsideExample #19;
8
+
3
9
  ## [0.3.0] - 2025-12-17
4
10
 
5
11
  - Add RuboCop::Cop::Vicenzo::Layout::MultilineMethodCallLineBreaks #12;
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [![Ruby](https://github.com/bvicenzo/rubocop-vicenzo/actions/workflows/main.yml/badge.svg)](https://github.com/bvicenzo/rubocop-vicenzo/actions/workflows/main.yml)
4
4
 
5
+ 📖 **[Documentation](https://bvicenzo.github.io/rubocop-vicenzo)**
6
+
5
7
  ## Installation
6
8
 
7
9
  Install the gem and add to the application's Gemfile by executing:
@@ -55,6 +57,25 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
55
57
 
56
58
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
57
59
 
60
+ ### Documentation
61
+
62
+ The documentation site is built with [Antora](https://antora.org) and published automatically to GitHub Pages on every new release.
63
+
64
+ To build it locally, you will need [Node.js](https://nodejs.org) (v20+) installed. Then install Antora:
65
+
66
+ ```bash
67
+ npm install -g @antora/cli @antora/site-generator
68
+ ```
69
+
70
+ Generate the AsciiDoc pages from the cop sources and build the site:
71
+
72
+ ```bash
73
+ bundle exec rake docs:generate
74
+ antora antora-playbook.yml
75
+ ```
76
+
77
+ The site will be available at `build/site/index.html`.
78
+
58
79
  ### Generate binstubs
59
80
 
60
81
  If you want is possible change the command `bundle exec something` by `bin/something` generating binstubs
@@ -0,0 +1,15 @@
1
+ site:
2
+ title: RuboCop Vicenzo
3
+ url: https://bvicenzo.github.io/rubocop-vicenzo
4
+
5
+ content:
6
+ sources:
7
+ - url: .
8
+ branches: HEAD
9
+ start_path: docs
10
+
11
+ ui:
12
+ bundle:
13
+ url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable
14
+ snapshot: true
15
+ supplemental_files: ./docs/supplemental-ui
data/config/default.yml CHANGED
@@ -11,12 +11,35 @@ Vicenzo/Rails/EnumInclusionOfValidation:
11
11
  Severity: convention
12
12
  VersionAdded: '0.1.0'
13
13
 
14
+ Vicenzo/RSpec/ConditionalInSpec:
15
+ Description: 'Do not use conditional logic in specs. Extract each branch into an explicit context instead.'
16
+ Enabled: true
17
+ Severity: warning
18
+ Include:
19
+ - '**/spec/**/*_spec.rb'
20
+ Exclude:
21
+ - '**/spec/support/**/*'
22
+ - '**/spec/factories/**/*'
23
+ VersionAdded: '0.4.0'
24
+
25
+ Vicenzo/RSpec/DynamicExampleGeneration:
26
+ Description: 'Do not use iteration to dynamically generate example groups or examples.'
27
+ Enabled: true
28
+ Severity: warning
29
+ VersionAdded: '0.4.0'
30
+
14
31
  Vicenzo/RSpec/InconsistentSiblingStructure:
15
32
  Description: 'Enforces strict structural consistency (e.g. prevents mixing describe with context or examples with groups).'
16
33
  Enabled: true
17
34
  Severity: warning
18
35
  VersionAdded: '0.2.0'
19
36
 
37
+ Vicenzo/RSpec/IterationInsideExample:
38
+ Description: 'Do not call `expect` inside an iteration within an example.'
39
+ Enabled: true
40
+ Severity: warning
41
+ VersionAdded: '0.4.0'
42
+
20
43
  Vicenzo/RSpec/NestedContextImproperStart:
21
44
  Description: 'Check if the nested context does not start as a root one.'
22
45
  Enabled: true
data/docs/antora.yml ADDED
@@ -0,0 +1,5 @@
1
+ name: rubocop-vicenzo
2
+ version: ~
3
+ title: RuboCop Vicenzo
4
+ nav:
5
+ - modules/ROOT/nav.adoc
@@ -0,0 +1,14 @@
1
+ <header class="header">
2
+ <nav class="navbar">
3
+ <div class="navbar-brand">
4
+ <a class="navbar-item" href="{{{or site.url siteRootPath}}}">{{site.title}}</a>
5
+ {{#if env.SITE_SEARCH_PROVIDER}}
6
+ <div class="navbar-item search hide-for-print">
7
+ <div id="search-field" class="field">
8
+ <input id="search-input" type="text" placeholder="Search the docs"{{#if page.home}} autofocus{{/if}}>
9
+ </div>
10
+ </div>
11
+ {{/if}}
12
+ </div>
13
+ </nav>
14
+ </header>
@@ -7,27 +7,20 @@ module RuboCop
7
7
  # Ensures that enums using the new syntax include the
8
8
  # `validate: { allow_nil: true }` option.
9
9
  #
10
- # ## Bad usage
10
+ # Old-style enums (keyword argument syntax) are ignored.
11
11
  #
12
- # ```ruby
13
- # enum :status, { active: 1, inactive: 0 }, suffix: true
14
- # ```
12
+ # @example
13
+ # # bad missing validate option
14
+ # enum :status, { active: 1, inactive: 0 }, suffix: true
15
15
  #
16
- # ```ruby
17
- # enum :status, { active: 1, inactive: 0 }, validate: true, suffix: true
18
- # ```
16
+ # # bad — validate option present but incorrect
17
+ # enum :status, { active: 1, inactive: 0 }, validate: true, suffix: true
19
18
  #
20
- # ## Good usage
19
+ # # good
20
+ # enum :status, { active: 1, inactive: 0 }, validate: { allow_nil: true }, suffix: true
21
21
  #
22
- # ```ruby
23
- # enum :status, { active: 1, inactive: 0 }, validate: { allow_nil: true }, suffix: true
24
- # ```
25
- #
26
- # This cop does not enforce validation on enums using the old syntax:
27
- #
28
- # ```ruby
29
- # enum status: { active: 1, inactive: 0 }
30
- # ```
22
+ # # ignored — old-style enum syntax
23
+ # enum status: { active: 1, inactive: 0 }
31
24
  class EnumInclusionOfValidation < Base
32
25
  extend AutoCorrector
33
26
 
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Vicenzo
6
+ module RSpec
7
+ # Do not use conditional logic in spec files.
8
+ #
9
+ # Any `if`, `unless`, or ternary expression in a spec represents a hidden
10
+ # context. Each branch should be an explicit `context` block so that the
11
+ # conditions and expectations are always clear and unconditional.
12
+ #
13
+ # @example
14
+ # # bad — hidden context inside an example
15
+ #
16
+ # it 'grants or denies access' do
17
+ # if user.admin?
18
+ # expect(result).to eq(:granted)
19
+ # else
20
+ # expect(result).to eq(:denied)
21
+ # end
22
+ # end
23
+ #
24
+ # # bad — hidden context inside a let
25
+ #
26
+ # let(:user) { admin? ? create(:admin) : create(:client) }
27
+ #
28
+ # # bad — hidden context inside a before hook
29
+ #
30
+ # before { setup_thing if feature_enabled? }
31
+ #
32
+ # # bad — hidden context at the example group level using unless
33
+ #
34
+ # unless legacy_mode?
35
+ # it 'uses the new behaviour' do
36
+ # ...
37
+ # end
38
+ # end
39
+ #
40
+ # # good
41
+ #
42
+ # context 'when user is admin' do
43
+ # let(:user) { create(:admin) }
44
+ #
45
+ # it 'grants access' do
46
+ # expect(result).to eq(:granted)
47
+ # end
48
+ # end
49
+ #
50
+ # context 'when user is not admin' do
51
+ # let(:user) { create(:client) }
52
+ #
53
+ # it 'denies access' do
54
+ # expect(result).to eq(:denied)
55
+ # end
56
+ # end
57
+ class ConditionalInSpec < RuboCop::Cop::RSpec::Base
58
+ MSG = 'Do not use conditional logic in specs. ' \
59
+ 'Extract each branch into an explicit context instead.'
60
+
61
+ # Both `if` and `unless` are represented as `if` nodes in the AST,
62
+ # so this single hook covers all conditional forms: `if`, `unless`,
63
+ # modifier `if`/`unless`, and ternary `?:`.
64
+ def on_if(node)
65
+ offense_location = node.ternary? ? node : node.loc.keyword
66
+ add_offense(offense_location)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Vicenzo
6
+ module RSpec
7
+ # Do not use iteration to dynamically generate example groups or examples.
8
+ #
9
+ # Dynamic generation makes tests hard to find, hard to read, and creates
10
+ # pressure to add conditional logic (e.g. `if variable == :x`) when not
11
+ # all iterations share the same conditions. Write explicit, static contexts
12
+ # instead — one context per case.
13
+ #
14
+ # @example
15
+ # # bad
16
+ #
17
+ # [:admin, :driver].each do |role|
18
+ # context "when role is #{role}" do
19
+ # it 'does something' do
20
+ # ...
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # # good
26
+ #
27
+ # context 'when role is admin' do
28
+ # let(:role) { :admin }
29
+ #
30
+ # it 'does something' do
31
+ # ...
32
+ # end
33
+ # end
34
+ #
35
+ # context 'when role is driver' do
36
+ # let(:role) { :driver }
37
+ #
38
+ # it 'does something' do
39
+ # ...
40
+ # end
41
+ # end
42
+ class DynamicExampleGeneration < RuboCop::Cop::RSpec::Base
43
+ MSG = 'Do not use iteration to dynamically generate example groups or examples. ' \
44
+ 'Write explicit, static contexts instead.'
45
+
46
+ ENUMERATION_METHODS = %i[each each_with_index each_with_object map flat_map].freeze
47
+
48
+ EXAMPLE_GROUP_DSL = %i[
49
+ context describe feature experiment
50
+ it specify example scenario focus
51
+ let let! subject subject! before after around
52
+ shared_examples shared_context shared_examples_for
53
+ ].freeze
54
+
55
+ # @!method enumeration_block?(node)
56
+ def_node_matcher :enumeration_block?, <<~PATTERN
57
+ (block
58
+ (send _ {#{ENUMERATION_METHODS.map(&:inspect).join(' ')}} ...)
59
+ ...)
60
+ PATTERN
61
+
62
+ # @!method example_group_dsl_call?(node)
63
+ def_node_matcher :example_group_dsl_call?, <<~PATTERN
64
+ (block (send nil? {#{EXAMPLE_GROUP_DSL.map(&:inspect).join(' ')}} ...) ...)
65
+ PATTERN
66
+
67
+ def on_block(node)
68
+ return unless enumeration_block?(node)
69
+ return unless contains_example_group_dsl?(node)
70
+
71
+ add_offense(node.send_node)
72
+ end
73
+
74
+ alias on_numblock on_block
75
+
76
+ private
77
+
78
+ def contains_example_group_dsl?(node)
79
+ node.body&.each_node(:block) do |child|
80
+ return true if example_group_dsl_call?(child)
81
+ end
82
+
83
+ false
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Vicenzo
6
+ module RSpec
7
+ # Do not use `expect` inside an iteration within an example.
8
+ #
9
+ # Placing `expect` calls inside an iteration block (e.g. `each`) makes
10
+ # tests implicit and hard to debug: when the assertion fails it is unclear
11
+ # which element caused the failure, and not all elements may represent the
12
+ # same condition. Using iteration to build or transform data before calling
13
+ # `expect` is fine; the problem is calling `expect` inside the iteration.
14
+ # Write explicit assertions for each relevant case instead.
15
+ #
16
+ # @example
17
+ # # bad — expect is called inside the iteration
18
+ #
19
+ # it 'returns vehicle costs general values' do
20
+ # response_body[:vehicle_costs].first.each do |attribute, value|
21
+ # expect(value).to eq(vehicle_cost.send(attribute).to_s)
22
+ # end
23
+ # end
24
+ #
25
+ # # good — iteration builds data, expect is called once outside
26
+ #
27
+ # it 'returns the expected column names' do
28
+ # columns = VehicleCost.column_names.map { |column| column.gsub('_centavos', '') }
29
+ # expect(response_body[:vehicle_costs].first.keys).to match_array(columns.map(&:to_sym))
30
+ # end
31
+ #
32
+ # # good — each attribute has an explicit example
33
+ #
34
+ # it 'returns the correct name' do
35
+ # expect(response_body[:vehicle_costs].first[:name]).to eq(vehicle_cost.name)
36
+ # end
37
+ #
38
+ # it 'returns the correct value' do
39
+ # expect(response_body[:vehicle_costs].first[:value]).to eq(vehicle_cost.value.to_s)
40
+ # end
41
+ class IterationInsideExample < RuboCop::Cop::RSpec::Base
42
+ MSG = 'Do not call `expect` inside an iteration. ' \
43
+ 'Write explicit assertions instead.'
44
+
45
+ ENUMERATION_METHODS = %i[each each_with_index each_with_object].freeze
46
+
47
+ # @!method enumeration_block?(node)
48
+ def_node_matcher :enumeration_block?, <<~PATTERN
49
+ (block
50
+ (send _ {#{ENUMERATION_METHODS.map(&:inspect).join(' ')}} ...)
51
+ ...)
52
+ PATTERN
53
+
54
+ def on_block(node)
55
+ return unless example?(node)
56
+
57
+ find_iterations_with_assertions(node.body)
58
+ end
59
+
60
+ alias on_numblock on_block
61
+
62
+ private
63
+
64
+ def find_iterations_with_assertions(body)
65
+ return unless body
66
+
67
+ body.each_node(:block) do |iteration|
68
+ next unless enumeration_block?(iteration)
69
+ next unless contains_expectation?(iteration.body)
70
+
71
+ add_offense(iteration.send_node)
72
+ end
73
+ end
74
+
75
+ def contains_expectation?(node)
76
+ return false unless node
77
+
78
+ node.each_node(:send).any? { |send_node| send_node.method?(:expect) }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'vicenzo/rspec/conditional_in_spec'
4
+ require_relative 'vicenzo/rspec/dynamic_example_generation'
3
5
  require_relative 'vicenzo/rspec/inconsistent_sibling_structure'
6
+ require_relative 'vicenzo/rspec/iteration_inside_example'
4
7
  require_relative 'vicenzo/rspec/nested_context_improper_start'
5
8
  require_relative 'vicenzo/rspec/nested_let_redefinition'
6
9
  require_relative 'vicenzo/rspec/nested_subject_redefinition'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Vicenzo
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
data/rakelib/docs.rake ADDED
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ DOCS_PAGES_DIR = 'docs/modules/ROOT/pages'
7
+ DOCS_NAV_FILE = 'docs/modules/ROOT/nav.adoc'
8
+
9
+ CAMEL_BOUNDARIES_UPPER_PATTERN = /(?<leading_caps>[A-Z]+)(?<next_word>[A-Z][a-z])/
10
+ CAMEL_BOUNDARIES_LOWER_PATTERN = /(?<word_end>[a-z\d])(?<word_start>[A-Z])/
11
+ CLASS_DECLARATION_PATTERN = /^\s+class\s/
12
+ COMMENT_LINE_PATTERN = /^\s+#/
13
+ EXAMPLE_TAG_PATTERN = /^\s+#\s+@example(.*)/
14
+ EXAMPLE_CODE_INDENT_PATTERN = /^\s+#\s{0,3}/
15
+ ANCHOR_INVALID_CHARS_PATTERN = /[^a-z0-9-]/
16
+
17
+ namespace :docs do
18
+ desc 'Generate AsciiDoc documentation for all Vicenzo cops'
19
+ task :generate do
20
+ FileUtils.mkdir_p(DOCS_PAGES_DIR)
21
+
22
+ default_config = YAML.load_file('config/default.yml')
23
+ cop_data = default_config.keys.map { |name| build_cop_data(name, default_config) }
24
+
25
+ cops_by_dept = cop_data.group_by { |cop| cop[:department] }
26
+
27
+ cops_by_dept.sort.each do |department, cops|
28
+ content = render_department_page(department, cops.sort_by { |cop| cop[:name] })
29
+ filename = "cops_#{department.downcase}.adoc"
30
+ File.write(File.join(DOCS_PAGES_DIR, filename), content)
31
+ puts " Generated: #{filename} (#{cops.size} cop#{'s' if cops.size != 1})"
32
+ end
33
+
34
+ write_index(cop_data)
35
+ write_nav(cops_by_dept)
36
+ puts "\nDone. #{cop_data.size} cops documented across #{cops_by_dept.size} departments."
37
+ end
38
+ end
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Data extraction
42
+ # ---------------------------------------------------------------------------
43
+
44
+ def build_cop_data(cop_name, default_config)
45
+ config = default_config[cop_name] || {}
46
+ source = read_cop_source(cop_name)
47
+ cop_data_hash(cop_name, config, source)
48
+ end
49
+
50
+ def cop_data_hash(cop_name, config, source)
51
+ {
52
+ name: cop_name,
53
+ department: cop_name.split('/')[1],
54
+ description: config['Description'] || '',
55
+ version: config['VersionAdded'] || '-',
56
+ enabled: config.fetch('Enabled', true),
57
+ autocorrect: source ? autocorrect?(source) : false,
58
+ examples: source ? extract_examples(source) : [],
59
+ config_keys: extra_config_keys(config)
60
+ }
61
+ end
62
+
63
+ def read_cop_source(cop_name)
64
+ path = cop_name_to_path(cop_name)
65
+ File.exist?(path) ? File.read(path) : nil
66
+ end
67
+
68
+ def cop_name_to_path(cop_name)
69
+ parts = cop_name.split('/')
70
+ dept = parts[1].downcase
71
+ klass = underscore(parts[2])
72
+ "lib/rubocop/cop/vicenzo/#{dept}/#{klass}.rb"
73
+ end
74
+
75
+ def underscore(str)
76
+ str
77
+ .gsub(CAMEL_BOUNDARIES_UPPER_PATTERN, '\k<leading_caps>_\k<next_word>')
78
+ .gsub(CAMEL_BOUNDARIES_LOWER_PATTERN, '\k<word_end>_\k<word_start>')
79
+ .downcase
80
+ end
81
+
82
+ def autocorrect?(source)
83
+ source.include?('AutoCorrector')
84
+ end
85
+
86
+ def extract_examples(source)
87
+ docstring = extract_docstring(source)
88
+ parse_examples(docstring)
89
+ end
90
+
91
+ def extract_docstring(source)
92
+ lines = []
93
+ source.each_line do |line|
94
+ break if CLASS_DECLARATION_PATTERN.match?(line)
95
+
96
+ lines << line if COMMENT_LINE_PATTERN.match?(line)
97
+ end
98
+ lines
99
+ end
100
+
101
+ def parse_examples(docstring_lines)
102
+ examples = []
103
+ current = nil
104
+ docstring_lines.each do |line|
105
+ examples, current = update_examples(line, examples, current)
106
+ end
107
+ finalize_examples(examples, current)
108
+ end
109
+
110
+ def update_examples(line, examples, current)
111
+ if line =~ EXAMPLE_TAG_PATTERN
112
+ examples << current if current
113
+ [examples, { title: Regexp.last_match(1).strip, code: [] }]
114
+ elsif current
115
+ current[:code] << line.sub(EXAMPLE_CODE_INDENT_PATTERN, '').rstrip
116
+ [examples, current]
117
+ else
118
+ [examples, current]
119
+ end
120
+ end
121
+
122
+ def finalize_examples(examples, current)
123
+ examples << current if current
124
+ examples.each { |example| example[:code].pop while example[:code].last&.empty? }
125
+ examples
126
+ end
127
+
128
+ def extra_config_keys(config)
129
+ skip = %w[Description Enabled Severity VersionAdded Include Exclude Safe]
130
+ config.except(*skip)
131
+ end
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # AsciiDoc rendering
135
+ # ---------------------------------------------------------------------------
136
+
137
+ def render_department_page(department, cops)
138
+ lines = ["= Vicenzo/#{department}", ':toc: left', ':toc-title: Cops', ':toclevels: 1', '']
139
+ cops.each { |cop| lines.concat(render_cop(cop)) }
140
+ lines.join("\n")
141
+ end
142
+
143
+ def render_cop(cop)
144
+ lines = ["== #{cop[:name]}", '', cop[:description], '']
145
+ lines.concat(render_metadata_table(cop))
146
+ lines.concat(render_all_examples(cop[:examples]))
147
+ lines.concat(render_config_keys(cop[:config_keys])) unless cop[:config_keys].empty?
148
+ lines.push("'''", '')
149
+ end
150
+
151
+ def render_all_examples(examples)
152
+ examples.each_with_index.flat_map { |example, index| render_example(example, index) }
153
+ end
154
+
155
+ def render_metadata_table(cop)
156
+ enabled = cop[:enabled] ? 'Enabled' : 'Disabled'
157
+ autocorrect = cop[:autocorrect] ? 'Yes' : 'No'
158
+ ['[cols="1,1,1,1"]', '|===', '| Enabled by default | Safe | Supports autocorrection | Version Added',
159
+ '', "| #{enabled}", '| Yes', "| #{autocorrect}", "| #{cop[:version]}", '|===', '']
160
+ end
161
+
162
+ def render_example(example, index)
163
+ title = if example[:title].empty?
164
+ index.zero? ? 'Example' : "Example #{index + 1}"
165
+ else
166
+ example[:title]
167
+ end
168
+ ["=== #{title}", '', '[source,ruby]', '----', *example[:code], '----', '']
169
+ end
170
+
171
+ def render_config_keys(config_keys)
172
+ lines = ['=== Configurable attributes', '', '[cols="1,1"]', '|===', '| Name | Default value', '']
173
+ config_keys.each { |key, value| lines.push("| #{key}", "| `#{value.inspect}`", '') }
174
+ lines.push('|===', '')
175
+ end
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # Index and navigation
179
+ # ---------------------------------------------------------------------------
180
+
181
+ def write_index(cop_data)
182
+ lines = index_header_lines + index_cops_table_lines(cop_data)
183
+ File.write(File.join(DOCS_PAGES_DIR, 'index.adoc'), lines.join("\n"))
184
+ puts ' Generated: index.adoc'
185
+ end
186
+
187
+ def index_header_lines
188
+ ['= RuboCop Vicenzo', ':toc: left', ''] +
189
+ ['Custom RuboCop cops for enforcing conventions adopted by Vicenzo projects.', ''] +
190
+ installation_section_lines +
191
+ ['== Cops', '', '[cols="2,1,1"]', '|===', '| Cop | Department | Version Added', '']
192
+ end
193
+
194
+ def installation_section_lines
195
+ ['== Installation', ''] +
196
+ gemfile_installation_lines +
197
+ rubocop_yml_installation_lines
198
+ end
199
+
200
+ def gemfile_installation_lines
201
+ ['Add to your `Gemfile`:', '', '[source,ruby]', '----',
202
+ "gem 'rubocop-vicenzo', require: false", '----', '']
203
+ end
204
+
205
+ def rubocop_yml_installation_lines
206
+ ['Then add to your `.rubocop.yml`:', '', '[source,yaml]', '----',
207
+ 'plugins:', ' - rubocop-vicenzo', '----', '']
208
+ end
209
+
210
+ def index_cops_table_lines(cop_data)
211
+ lines = cop_data.sort_by { |cop| cop[:name] }.map do |cop|
212
+ dept = cop[:department]
213
+ filename = "cops_#{dept.downcase}.adoc"
214
+ anchor = cop[:name].downcase.tr('/', '-').gsub(ANCHOR_INVALID_CHARS_PATTERN, '')
215
+ "| xref:#{filename}##{anchor}[#{cop[:name]}] | #{dept} | #{cop[:version]}"
216
+ end
217
+ lines.push('|===', '')
218
+ end
219
+
220
+ def write_nav(cops_by_dept)
221
+ lines = ['* xref:index.adoc[Home]', '* Cops']
222
+ cops_by_dept
223
+ .sort
224
+ .each { |department, _cops| lines << "** xref:cops_#{department.downcase}.adoc[#{department}]" }
225
+ File.write(DOCS_NAV_FILE, "#{lines.join("\n")}\n")
226
+ puts ' Generated: nav.adoc'
227
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-vicenzo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Vicenzo
@@ -66,11 +66,17 @@ files:
66
66
  - LICENSE.txt
67
67
  - README.md
68
68
  - Rakefile
69
+ - antora-playbook.yml
69
70
  - config/default.yml
71
+ - docs/antora.yml
72
+ - docs/supplemental-ui/partials/header-content.hbs
70
73
  - lib/rubocop-vicenzo.rb
71
74
  - lib/rubocop/cop/vicenzo/layout/multiline_method_call_line_breaks.rb
72
75
  - lib/rubocop/cop/vicenzo/rails/enum_inclusion_of_validation.rb
76
+ - lib/rubocop/cop/vicenzo/rspec/conditional_in_spec.rb
77
+ - lib/rubocop/cop/vicenzo/rspec/dynamic_example_generation.rb
73
78
  - lib/rubocop/cop/vicenzo/rspec/inconsistent_sibling_structure.rb
79
+ - lib/rubocop/cop/vicenzo/rspec/iteration_inside_example.rb
74
80
  - lib/rubocop/cop/vicenzo/rspec/leaky_definition.rb
75
81
  - lib/rubocop/cop/vicenzo/rspec/nested_context_improper_start.rb
76
82
  - lib/rubocop/cop/vicenzo/rspec/nested_let_redefinition.rb
@@ -80,6 +86,7 @@ files:
80
86
  - lib/rubocop/vicenzo.rb
81
87
  - lib/rubocop/vicenzo/plugin.rb
82
88
  - lib/rubocop/vicenzo/version.rb
89
+ - rakelib/docs.rake
83
90
  - rakelib/release.rake
84
91
  - sig/rubocop/vicenzo.rbs
85
92
  homepage: https://github.com/bvicenzo/rubocop-vicenzo
@@ -106,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
113
  - !ruby/object:Gem::Version
107
114
  version: '0'
108
115
  requirements: []
109
- rubygems_version: 3.7.2
116
+ rubygems_version: 4.0.3
110
117
  specification_version: 4
111
118
  summary: Cops of Bruno Vicenzo
112
119
  test_files: []