rubocop-view_component 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/.rubocop.yml +3 -0
- data/{CLAUDE.md → AGENTS.md} +3 -3
- data/README.md +42 -5
- data/config/default.yml +6 -2
- data/docs/rubydex.md +136 -0
- data/lib/rubocop/cop/view_component/base.rb +27 -10
- data/lib/rubocop/cop/view_component/prefer_composition.rb +42 -9
- data/lib/rubocop/cop/view_component/prefer_private_methods.rb +46 -13
- data/lib/rubocop/cop/view_component/rspec_scope.rb +98 -0
- data/lib/rubocop/cop/view_component/supplementary_index.rb +142 -0
- data/lib/rubocop/cop/view_component/test_rendered_output.rb +106 -11
- data/lib/rubocop/view_component/version.rb +1 -1
- data/script/verify +29 -18
- data/spec/expected_flowbite_failures.yml +64 -0
- data/spec/expected_govuk_failures.yml +9 -13
- data/spec/expected_polaris_failures.yml +2 -0
- data/spec/expected_primer_failures.yml +57 -2
- data/spec/rubocop/cop/view_component/base_spec.rb +16 -0
- data/spec/rubocop/cop/view_component/prefer_composition_spec.rb +104 -0
- data/spec/rubocop/cop/view_component/prefer_private_methods_spec.rb +124 -0
- data/spec/rubocop/cop/view_component/test_rendered_output_spec.rb +287 -4
- data/verification/flowbite_rubocop_config.yml +12 -0
- data/verification/libraries.yml +4 -0
- metadata +8 -3
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "rubydex"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module RuboCop
|
|
8
|
+
module Cop
|
|
9
|
+
module ViewComponent
|
|
10
|
+
# Extends the project index with `AdditionalSourcePaths`: source
|
|
11
|
+
# directories the cops index themselves, so a chain that passes through a
|
|
12
|
+
# gem RuboCop does not index is still resolvable. The motivating case is a
|
|
13
|
+
# Rails engine's `app/components`, which `AllCops/ProjectIndexIncludesGems`
|
|
14
|
+
# misses because it only indexes a gem's `lib`.
|
|
15
|
+
module SupplementaryIndex
|
|
16
|
+
# A minimal stand-in for the framework base class, indexed alongside the
|
|
17
|
+
# configured paths so ancestry there can still reach
|
|
18
|
+
# `ViewComponent::Base` without indexing the whole view_component gem.
|
|
19
|
+
BASE_URI = "rubydex:view_component_base"
|
|
20
|
+
BASE_SOURCE = <<~RUBY
|
|
21
|
+
module ViewComponent
|
|
22
|
+
class Base
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
RUBY
|
|
26
|
+
|
|
27
|
+
# RuboCop's cache key, extended with the supplementary files' mtimes:
|
|
28
|
+
# those files are not part of RuboCop's own index documents, so a change
|
|
29
|
+
# to one would otherwise not invalidate a cached result.
|
|
30
|
+
def external_dependency_checksum
|
|
31
|
+
base = super
|
|
32
|
+
extra = supplementary_files_checksum
|
|
33
|
+
return base if extra.nil?
|
|
34
|
+
return extra if base.nil?
|
|
35
|
+
|
|
36
|
+
Digest::SHA1.hexdigest("#{base}:#{extra}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Resolve through RuboCop's project index first, then the supplementary one.
|
|
42
|
+
def resolve_constant_in_index(const_node)
|
|
43
|
+
super || resolve_constant_in_graph(supplementary_index, const_node)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def resolve_constant_in_graph(graph, const_node)
|
|
47
|
+
return nil unless graph
|
|
48
|
+
|
|
49
|
+
segments = const_node.const_name.split("::")
|
|
50
|
+
# The inspected file's lexical scopes live in the project index, not
|
|
51
|
+
# in the supplementary one, so resolving through them can fail (a
|
|
52
|
+
# scope name like `Ui` is simply absent here). Try them first, then
|
|
53
|
+
# resolve from the top level.
|
|
54
|
+
nesting_candidates(const_node).each do |nesting|
|
|
55
|
+
declaration = resolve_segments(graph, segments, nesting)
|
|
56
|
+
return declaration if declaration
|
|
57
|
+
end
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def nesting_candidates(const_node)
|
|
62
|
+
return [[]] if const_node.absolute?
|
|
63
|
+
|
|
64
|
+
[lexical_nesting_of(const_node), []].uniq
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resolve_segments(graph, segments, nesting)
|
|
68
|
+
declaration = graph.resolve_constant(segments.first, nesting)
|
|
69
|
+
return nil unless declaration
|
|
70
|
+
|
|
71
|
+
segments.drop(1).each do |segment|
|
|
72
|
+
return nil unless declaration.is_a?(Rubydex::Namespace)
|
|
73
|
+
|
|
74
|
+
declaration = declaration.find_member(segment)
|
|
75
|
+
return nil unless declaration
|
|
76
|
+
end
|
|
77
|
+
declaration
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Built once per cop and only when the option is set.
|
|
81
|
+
def supplementary_index
|
|
82
|
+
return @supplementary_index if defined?(@supplementary_index)
|
|
83
|
+
|
|
84
|
+
@supplementary_index = build_supplementary_index
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def build_supplementary_index
|
|
88
|
+
files = supplementary_source_files
|
|
89
|
+
return nil if files.empty?
|
|
90
|
+
|
|
91
|
+
graph = Rubydex::Graph.new
|
|
92
|
+
graph.index_source(BASE_URI, BASE_SOURCE, "ruby")
|
|
93
|
+
index_files(graph, files)
|
|
94
|
+
graph.resolve
|
|
95
|
+
graph
|
|
96
|
+
rescue StandardError
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def index_files(graph, files)
|
|
101
|
+
# File by file, not `index_all`: in a forked RuboCop worker
|
|
102
|
+
# `index_all` crashes once the parent has built its own graph, while
|
|
103
|
+
# `index_source` is safe.
|
|
104
|
+
files.each do |file|
|
|
105
|
+
graph.index_source(file_uri(file), File.read(file), "ruby")
|
|
106
|
+
rescue StandardError
|
|
107
|
+
next
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def file_uri(file)
|
|
112
|
+
"file://#{URI::DEFAULT_PARSER.escape(file)}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def supplementary_source_files
|
|
116
|
+
configured_source_paths.flat_map { |path| Dir.glob(File.join(path, "**", "*.rb")) }.uniq.sort
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def configured_source_paths
|
|
120
|
+
Array(cop_config["AdditionalSourcePaths"]).filter_map do |path|
|
|
121
|
+
expanded = File.expand_path(path, config.base_dir_for_path_parameters)
|
|
122
|
+
expanded if File.directory?(expanded)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def supplementary_files_checksum
|
|
127
|
+
files = supplementary_source_files
|
|
128
|
+
return nil if files.empty?
|
|
129
|
+
|
|
130
|
+
Digest::SHA1.hexdigest(files.map { |file| file_signature(file) }.join("\n"))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def file_signature(file)
|
|
134
|
+
stat = File.stat(file)
|
|
135
|
+
"#{file}:#{stat.mtime.to_f}:#{stat.size}"
|
|
136
|
+
rescue StandardError
|
|
137
|
+
file
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "rspec_scope"
|
|
4
|
+
|
|
3
5
|
module RuboCop
|
|
4
6
|
module Cop
|
|
5
7
|
module ViewComponent
|
|
@@ -8,6 +10,11 @@ module RuboCop
|
|
|
8
10
|
#
|
|
9
11
|
# This cop is only enabled for test files by default (see config).
|
|
10
12
|
#
|
|
13
|
+
# A test is considered to instantiate a component when it builds one
|
|
14
|
+
# directly (`UserComponent.new`), through `described_class.new` inside a
|
|
15
|
+
# `describe UserComponent` block, or by referencing a `let`/`subject`
|
|
16
|
+
# helper whose body instantiates a component.
|
|
17
|
+
#
|
|
11
18
|
# @example
|
|
12
19
|
# # bad
|
|
13
20
|
# def test_formatted_title
|
|
@@ -21,10 +28,40 @@ module RuboCop
|
|
|
21
28
|
# assert_text "HELLO"
|
|
22
29
|
# end
|
|
23
30
|
#
|
|
31
|
+
# # bad
|
|
32
|
+
# describe TitleComponent do
|
|
33
|
+
# it "formats the title" do
|
|
34
|
+
# component = described_class.new("hello")
|
|
35
|
+
# expect(component.formatted_title).to eq("HELLO")
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# # good
|
|
40
|
+
# describe TitleComponent do
|
|
41
|
+
# it "formats the title" do
|
|
42
|
+
# render_inline described_class.new("hello")
|
|
43
|
+
# expect(page).to have_text("HELLO")
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# # bad
|
|
48
|
+
# let(:component) { TitleComponent.new("hello") }
|
|
49
|
+
#
|
|
50
|
+
# it "formats the title" do
|
|
51
|
+
# expect(component.formatted_title).to eq("HELLO")
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
24
54
|
class TestRenderedOutput < RuboCop::Cop::Base
|
|
55
|
+
include ViewComponent::Base
|
|
56
|
+
include RSpecScope
|
|
57
|
+
|
|
25
58
|
MSG = "Test instantiates a component but doesn't use `render_inline` or `render_preview`. " \
|
|
26
59
|
"Test the rendered output instead of component methods directly."
|
|
27
60
|
|
|
61
|
+
# `render_in_view_context` renders through a view context (it is what
|
|
62
|
+
# form-builder tests need), so it is a rendered-output test too.
|
|
63
|
+
RENDER_METHODS = %i[render_inline render_preview render_in_view_context].freeze
|
|
64
|
+
|
|
28
65
|
# Check Minitest-style test methods
|
|
29
66
|
def on_def(node)
|
|
30
67
|
return unless within_test_paths?
|
|
@@ -32,7 +69,7 @@ module RuboCop
|
|
|
32
69
|
method_name = node.method_name.to_s
|
|
33
70
|
return unless method_name.start_with?("test_")
|
|
34
71
|
return unless instantiates_component?(node)
|
|
35
|
-
return if
|
|
72
|
+
return if renders_output?(node)
|
|
36
73
|
|
|
37
74
|
add_offense(node)
|
|
38
75
|
end
|
|
@@ -40,9 +77,9 @@ module RuboCop
|
|
|
40
77
|
# Check RSpec-style it blocks
|
|
41
78
|
def on_block(node)
|
|
42
79
|
return unless within_test_paths?
|
|
43
|
-
return unless
|
|
80
|
+
return unless rspec_example_block?(node)
|
|
44
81
|
return unless instantiates_component?(node)
|
|
45
|
-
return if
|
|
82
|
+
return if renders_output?(node)
|
|
46
83
|
|
|
47
84
|
add_offense(node)
|
|
48
85
|
end
|
|
@@ -60,24 +97,82 @@ module RuboCop
|
|
|
60
97
|
end
|
|
61
98
|
|
|
62
99
|
def instantiates_component?(node)
|
|
63
|
-
node
|
|
100
|
+
component_new_call?(node) ||
|
|
101
|
+
referenced_helper?(node) { |body| component_new_call?(body) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def renders_output?(node)
|
|
105
|
+
render_call?(node) ||
|
|
106
|
+
referenced_helper?(node) { |body| render_call?(body) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# True when the node calls `.new` on a component constant or on
|
|
110
|
+
# `described_class` within a `describe` block.
|
|
111
|
+
def component_new_call?(node)
|
|
112
|
+
node.each_node(:send).any? do |send_node|
|
|
64
113
|
next unless send_node.method?(:new)
|
|
114
|
+
next if inside_error_expectation?(send_node)
|
|
65
115
|
|
|
66
|
-
send_node.receiver
|
|
116
|
+
component_receiver?(send_node.receiver)
|
|
67
117
|
end
|
|
68
118
|
end
|
|
69
119
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
120
|
+
# A component built inside an error assertion is exercising
|
|
121
|
+
# initialisation behaviour rather than rendered output, so there is
|
|
122
|
+
# nothing to render. Covers RSpec's `expect { ... }.to raise_error` and
|
|
123
|
+
# Minitest's `assert_raises`.
|
|
124
|
+
def inside_error_expectation?(node)
|
|
125
|
+
node.each_ancestor(:block).any? do |ancestor|
|
|
126
|
+
error_assertion?(ancestor)
|
|
73
127
|
end
|
|
74
128
|
end
|
|
75
129
|
|
|
76
|
-
def
|
|
77
|
-
send_node =
|
|
130
|
+
def error_assertion?(block)
|
|
131
|
+
send_node = block.send_node
|
|
78
132
|
return false unless send_node
|
|
133
|
+
return true if send_node.method?(:assert_raises)
|
|
134
|
+
|
|
135
|
+
send_node.method?(:expect) && error_expectation?(block)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def error_expectation?(expect_block)
|
|
139
|
+
parent = expect_block.parent
|
|
140
|
+
return false unless parent&.send_type?
|
|
141
|
+
return false unless %i[to not_to].include?(parent.method_name)
|
|
142
|
+
|
|
143
|
+
error_matcher?(parent.first_argument)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def error_matcher?(matcher)
|
|
147
|
+
matcher = matcher.send_node if matcher&.block_type?
|
|
148
|
+
matcher&.send_type? &&
|
|
149
|
+
%i[raise_error raise_exception].include?(matcher.method_name)
|
|
150
|
+
end
|
|
79
151
|
|
|
80
|
-
|
|
152
|
+
def component_receiver?(receiver)
|
|
153
|
+
return false unless receiver
|
|
154
|
+
return view_component_parent?(receiver) if receiver.const_type?
|
|
155
|
+
return false unless described_class_reference?(receiver)
|
|
156
|
+
|
|
157
|
+
described_class = enclosing_described_class(receiver)
|
|
158
|
+
described_class && view_component_parent?(described_class)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def render_call?(node)
|
|
162
|
+
node.each_node(:send).any? do |send_node|
|
|
163
|
+
RENDER_METHODS.include?(send_node.method_name)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# True when the node references a `let`/`subject` helper whose body
|
|
168
|
+
# satisfies the given block.
|
|
169
|
+
def referenced_helper?(node)
|
|
170
|
+
helper_bodies = helper_definitions(node)
|
|
171
|
+
|
|
172
|
+
referenced_helper_names(node).any? do |name|
|
|
173
|
+
body = helper_bodies[name]
|
|
174
|
+
body && yield(body)
|
|
175
|
+
end
|
|
81
176
|
end
|
|
82
177
|
end
|
|
83
178
|
end
|
data/script/verify
CHANGED
|
@@ -49,7 +49,7 @@ def main
|
|
|
49
49
|
|
|
50
50
|
Dir.chdir(config[:verification_dir]) do
|
|
51
51
|
configure_rubocop(config[:config_file], config[:display_name])
|
|
52
|
-
|
|
52
|
+
replace_gemfile
|
|
53
53
|
|
|
54
54
|
Bundler.with_unbundled_env do
|
|
55
55
|
output = run_rubocop
|
|
@@ -89,33 +89,41 @@ def download_source(dir, tarball_url, display_name)
|
|
|
89
89
|
system!("tar", "xz", "-C", dir, "--strip-components=1", "-f", "#{dir}/source.tar.gz")
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
# Replace the library's .rubocop.yml with a config that only runs our
|
|
93
|
+
# ViewComponent cops. The library's own config may reference gems outside the
|
|
94
|
+
# minimal bundle (see replace_gemfile), e.g. inherit_gem, require, or config
|
|
95
|
+
# for cops from other RuboCop extensions.
|
|
92
96
|
def configure_rubocop(config_file, display_name)
|
|
93
|
-
puts "
|
|
94
|
-
config =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
config[key] = if config[key].is_a?(Hash) && value.is_a?(Hash)
|
|
100
|
-
config[key].merge(value)
|
|
101
|
-
else
|
|
102
|
-
value
|
|
103
|
-
end
|
|
104
|
-
end
|
|
97
|
+
puts "Replacing .rubocop.yml with #{display_name} overrides..."
|
|
98
|
+
config = YAML.load_file(config_file)
|
|
99
|
+
|
|
100
|
+
# Silence warnings about unconfigured new cops - only ViewComponent cops run
|
|
101
|
+
config["AllCops"] ||= {}
|
|
102
|
+
config["AllCops"]["NewCops"] = "disable"
|
|
105
103
|
|
|
106
104
|
File.write(".rubocop.yml", YAML.dump(config))
|
|
107
105
|
end
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
# Replace the library's Gemfile with a minimal one. This avoids installing the
|
|
108
|
+
# library's own dependencies, which are not needed for static analysis and can
|
|
109
|
+
# fail to install or conflict with the rubocop version we require.
|
|
110
|
+
# `view_component` must be present so the project index can resolve
|
|
111
|
+
# `ViewComponent::Base` for ancestry-based detection.
|
|
112
|
+
def replace_gemfile
|
|
113
|
+
puts "Replacing Gemfile with minimal setup..."
|
|
114
|
+
File.write("Gemfile", <<~RUBY)
|
|
115
|
+
source "https://rubygems.org"
|
|
116
|
+
|
|
117
|
+
gem "rubocop"
|
|
118
|
+
gem "rubocop-view_component", path: "#{GEM_DIR}"
|
|
119
|
+
gem "view_component"
|
|
120
|
+
RUBY
|
|
112
121
|
end
|
|
113
122
|
|
|
114
123
|
def run_rubocop
|
|
115
124
|
FileUtils.rm_f("Gemfile.lock")
|
|
116
125
|
puts "Running bundle install..."
|
|
117
|
-
system!(
|
|
118
|
-
system!("bundle", "add", "rubocop")
|
|
126
|
+
system!("bundle", "install")
|
|
119
127
|
|
|
120
128
|
puts "Running RuboCop (ViewComponent cops only)..."
|
|
121
129
|
output, status = Open3.capture2(
|
|
@@ -128,6 +136,9 @@ def run_rubocop
|
|
|
128
136
|
|
|
129
137
|
puts "RuboCop exit status: #{status.exitstatus}"
|
|
130
138
|
|
|
139
|
+
# Exit status 0 means no offenses, 1 means offenses found (expected, since
|
|
140
|
+
# the libraries have known offenses). 2 means RuboCop itself failed.
|
|
141
|
+
abort "ERROR: RuboCop failed (exit status: #{status.exitstatus})" if status.exitstatus == 2
|
|
131
142
|
abort "ERROR: RuboCop produced no output (exit status: #{status.exitstatus})" if output.strip.empty?
|
|
132
143
|
|
|
133
144
|
output
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# This file is auto-generated by `script/verify --regenerate`.
|
|
2
|
+
# It captures known offenses in the library source.
|
|
3
|
+
---
|
|
4
|
+
ViewComponent/MissingPreview:
|
|
5
|
+
- 'app/components/flowbite/badge/dot.rb'
|
|
6
|
+
- 'app/components/flowbite/badge/pill.rb'
|
|
7
|
+
- 'app/components/flowbite/breadcrumb/home_icon.rb'
|
|
8
|
+
- 'app/components/flowbite/breadcrumb/item/current.rb'
|
|
9
|
+
- 'app/components/flowbite/breadcrumb/item/first.rb'
|
|
10
|
+
- 'app/components/flowbite/breadcrumb/separator_icon.rb'
|
|
11
|
+
- 'app/components/flowbite/button/outline.rb'
|
|
12
|
+
- 'app/components/flowbite/button/pill.rb'
|
|
13
|
+
- 'app/components/flowbite/card/title.rb'
|
|
14
|
+
- 'app/components/flowbite/input/date.rb'
|
|
15
|
+
- 'app/components/flowbite/input/date_time.rb'
|
|
16
|
+
- 'app/components/flowbite/input/email.rb'
|
|
17
|
+
- 'app/components/flowbite/input/file.rb'
|
|
18
|
+
- 'app/components/flowbite/input/hint.rb'
|
|
19
|
+
- 'app/components/flowbite/input/label.rb'
|
|
20
|
+
- 'app/components/flowbite/input/number.rb'
|
|
21
|
+
- 'app/components/flowbite/input/password.rb'
|
|
22
|
+
- 'app/components/flowbite/input/phone.rb'
|
|
23
|
+
- 'app/components/flowbite/input/textarea.rb'
|
|
24
|
+
- 'app/components/flowbite/input/url.rb'
|
|
25
|
+
- 'app/components/flowbite/input/validation_error.rb'
|
|
26
|
+
- 'app/components/flowbite/input_field/date.rb'
|
|
27
|
+
- 'app/components/flowbite/input_field/date_time.rb'
|
|
28
|
+
- 'app/components/flowbite/input_field/email.rb'
|
|
29
|
+
- 'app/components/flowbite/input_field/file.rb'
|
|
30
|
+
- 'app/components/flowbite/input_field/number.rb'
|
|
31
|
+
- 'app/components/flowbite/input_field/password.rb'
|
|
32
|
+
- 'app/components/flowbite/input_field/phone.rb'
|
|
33
|
+
- 'app/components/flowbite/input_field/text.rb'
|
|
34
|
+
- 'app/components/flowbite/input_field/textarea.rb'
|
|
35
|
+
- 'app/components/flowbite/input_field/url.rb'
|
|
36
|
+
- 'app/components/flowbite/pagination/chevron_left_icon.rb'
|
|
37
|
+
- 'app/components/flowbite/pagination/chevron_right_icon.rb'
|
|
38
|
+
- 'app/components/flowbite/pagination/gap.rb'
|
|
39
|
+
- 'app/components/flowbite/pagination/next_link.rb'
|
|
40
|
+
- 'app/components/flowbite/pagination/previous_link.rb'
|
|
41
|
+
- 'app/components/flowbite/sidebar/item.rb'
|
|
42
|
+
- 'app/components/flowbite/sidebar/navigation.rb'
|
|
43
|
+
- 'app/components/flowbite/toast/icon.rb'
|
|
44
|
+
ViewComponent/PreferPrivateMethods:
|
|
45
|
+
- 'app/components/flowbite/badge.rb'
|
|
46
|
+
- 'app/components/flowbite/input.rb'
|
|
47
|
+
- 'app/components/flowbite/input/checkbox.rb'
|
|
48
|
+
- 'app/components/flowbite/input/date.rb'
|
|
49
|
+
- 'app/components/flowbite/input/date_time.rb'
|
|
50
|
+
- 'app/components/flowbite/input/email.rb'
|
|
51
|
+
- 'app/components/flowbite/input/file.rb'
|
|
52
|
+
- 'app/components/flowbite/input/hint.rb'
|
|
53
|
+
- 'app/components/flowbite/input/label.rb'
|
|
54
|
+
- 'app/components/flowbite/input/number.rb'
|
|
55
|
+
- 'app/components/flowbite/input/password.rb'
|
|
56
|
+
- 'app/components/flowbite/input/phone.rb'
|
|
57
|
+
- 'app/components/flowbite/input/radio_button.rb'
|
|
58
|
+
- 'app/components/flowbite/input/select.rb'
|
|
59
|
+
- 'app/components/flowbite/input/url.rb'
|
|
60
|
+
- 'app/components/flowbite/input_field.rb'
|
|
61
|
+
- 'app/components/flowbite/input_field/select.rb'
|
|
62
|
+
- 'app/components/flowbite/pagination/link.rb'
|
|
63
|
+
- 'app/components/flowbite/pagination/next_link.rb'
|
|
64
|
+
- 'app/components/flowbite/pagination/previous_link.rb'
|
|
@@ -3,24 +3,20 @@
|
|
|
3
3
|
---
|
|
4
4
|
ViewComponent/ComponentSuffix:
|
|
5
5
|
- 'app/components/govuk_component/base.rb'
|
|
6
|
-
- 'app/components/govuk_component/header_component.rb'
|
|
7
|
-
- 'app/components/govuk_component/
|
|
6
|
+
- 'app/components/govuk_component/header_component.rb' # due to nested class
|
|
7
|
+
- 'app/components/govuk_component/language_navigation_component.rb' # due to nested class
|
|
8
|
+
- 'app/components/govuk_component/notification_banner_component.rb' # due to nested class
|
|
8
9
|
- 'app/components/govuk_component/pagination_component/adjacent_page.rb'
|
|
9
10
|
- 'app/components/govuk_component/pagination_component/item.rb'
|
|
10
|
-
- 'app/components/govuk_component/
|
|
11
|
-
- 'app/components/govuk_component/
|
|
11
|
+
- 'app/components/govuk_component/pagination_component/next_page.rb'
|
|
12
|
+
- 'app/components/govuk_component/pagination_component/previous_page.rb'
|
|
13
|
+
- 'app/components/govuk_component/panel_component.rb' # due to nested class
|
|
14
|
+
- 'app/components/govuk_component/tab_component.rb' # due to nested class
|
|
12
15
|
ViewComponent/PreferPrivateMethods:
|
|
13
16
|
- 'app/components/govuk_component/base.rb'
|
|
17
|
+
- 'app/components/govuk_component/language_navigation_component.rb'
|
|
14
18
|
- 'app/components/govuk_component/notification_banner_component.rb'
|
|
19
|
+
- 'app/components/govuk_component/pagination_component/next_page.rb'
|
|
15
20
|
- 'app/components/govuk_component/panel_component.rb'
|
|
16
21
|
- 'app/components/govuk_component/service_navigation_component.rb'
|
|
17
22
|
- 'app/components/govuk_component/tab_component.rb'
|
|
18
|
-
ViewComponent/TestRenderedOutput:
|
|
19
|
-
- 'spec/components/govuk_component/accordion_component_spec.rb'
|
|
20
|
-
- 'spec/components/govuk_component/breadcrumbs_component_spec.rb'
|
|
21
|
-
- 'spec/components/govuk_component/configuration/footer_component_configuration_spec.rb'
|
|
22
|
-
- 'spec/components/govuk_component/footer_component_spec.rb'
|
|
23
|
-
- 'spec/components/govuk_component/notification_banner_component_spec.rb'
|
|
24
|
-
- 'spec/components/govuk_component/pagination_component_spec.rb'
|
|
25
|
-
- 'spec/components/govuk_component/task_list_component_spec.rb'
|
|
26
|
-
- 'spec/components/govuk_component/warning_text_component_spec.rb'
|
|
@@ -17,6 +17,8 @@ ViewComponent/MissingPreview:
|
|
|
17
17
|
- 'app/components/polaris/inline_code_component.rb'
|
|
18
18
|
- 'app/components/polaris/label_component.rb'
|
|
19
19
|
- 'app/components/polaris/labelled_component.rb'
|
|
20
|
+
- 'app/components/polaris/layout/annotated_section.rb'
|
|
21
|
+
- 'app/components/polaris/layout/section.rb'
|
|
20
22
|
- 'app/components/polaris/list_component.rb'
|
|
21
23
|
- 'app/components/polaris/page_component.rb'
|
|
22
24
|
- 'app/components/polaris/placeholder_component.rb'
|
|
@@ -2,9 +2,66 @@
|
|
|
2
2
|
# It captures known offenses in the library source.
|
|
3
3
|
---
|
|
4
4
|
ViewComponent/MissingPreview:
|
|
5
|
+
- 'app/components/primer/alpha/action_bar/divider.rb'
|
|
6
|
+
- 'app/components/primer/alpha/action_bar/item.rb'
|
|
7
|
+
- 'app/components/primer/alpha/action_list/form_wrapper.rb'
|
|
8
|
+
- 'app/components/primer/alpha/action_menu/group.rb'
|
|
9
|
+
- 'app/components/primer/alpha/action_menu/heading.rb'
|
|
10
|
+
- 'app/components/primer/alpha/action_menu/list.rb'
|
|
11
|
+
- 'app/components/primer/alpha/action_menu/list_wrapper.rb'
|
|
12
|
+
- 'app/components/primer/alpha/auto_complete/item.rb'
|
|
13
|
+
- 'app/components/primer/alpha/dialog/body.rb'
|
|
14
|
+
- 'app/components/primer/alpha/dialog/footer.rb'
|
|
15
|
+
- 'app/components/primer/alpha/dialog/header.rb'
|
|
16
|
+
- 'app/components/primer/alpha/dropdown/menu.rb'
|
|
17
|
+
- 'app/components/primer/alpha/file_tree_view/directory_node.rb'
|
|
18
|
+
- 'app/components/primer/alpha/file_tree_view/file_node.rb'
|
|
19
|
+
- 'app/components/primer/alpha/form_button.rb'
|
|
20
|
+
- 'app/components/primer/alpha/image.rb'
|
|
21
|
+
- 'app/components/primer/alpha/layout.rb'
|
|
22
|
+
- 'app/components/primer/alpha/nav_list.rb'
|
|
23
|
+
- 'app/components/primer/alpha/nav_list/divider.rb'
|
|
24
|
+
- 'app/components/primer/alpha/nav_list/group.rb'
|
|
25
|
+
- 'app/components/primer/alpha/nav_list/heading.rb'
|
|
26
|
+
- 'app/components/primer/alpha/nav_list/item.rb'
|
|
27
|
+
- 'app/components/primer/alpha/overlay/body.rb'
|
|
28
|
+
- 'app/components/primer/alpha/overlay/footer.rb'
|
|
29
|
+
- 'app/components/primer/alpha/overlay/header.rb'
|
|
30
|
+
- 'app/components/primer/alpha/segmented_control/item.rb'
|
|
31
|
+
- 'app/components/primer/alpha/select_panel.rb'
|
|
32
|
+
- 'app/components/primer/alpha/submit_button.rb'
|
|
33
|
+
- 'app/components/primer/alpha/tab_container.rb'
|
|
34
|
+
- 'app/components/primer/alpha/tree_view/icon.rb'
|
|
35
|
+
- 'app/components/primer/alpha/tree_view/icon_pair.rb'
|
|
36
|
+
- 'app/components/primer/alpha/tree_view/leading_action.rb'
|
|
37
|
+
- 'app/components/primer/alpha/tree_view/loading_failure_message.rb'
|
|
38
|
+
- 'app/components/primer/alpha/tree_view/node.rb'
|
|
39
|
+
- 'app/components/primer/alpha/tree_view/skeleton_loader.rb'
|
|
40
|
+
- 'app/components/primer/alpha/tree_view/spinner_loader.rb'
|
|
41
|
+
- 'app/components/primer/alpha/tree_view/sub_tree.rb'
|
|
42
|
+
- 'app/components/primer/alpha/tree_view/sub_tree_container.rb'
|
|
43
|
+
- 'app/components/primer/alpha/tree_view/visual.rb'
|
|
44
|
+
- 'app/components/primer/beta/auto_complete/item.rb'
|
|
45
|
+
- 'app/components/primer/beta/auto_complete/no_result_item.rb'
|
|
46
|
+
- 'app/components/primer/beta/border_box/header.rb'
|
|
47
|
+
- 'app/components/primer/beta/breadcrumbs.rb'
|
|
48
|
+
- 'app/components/primer/beta/button_group.rb'
|
|
49
|
+
- 'app/components/primer/beta/clipboard_copy_button.rb'
|
|
50
|
+
- 'app/components/primer/beta/timeline_item.rb'
|
|
51
|
+
- 'app/components/primer/beta/truncate.rb'
|
|
5
52
|
- 'app/components/primer/blankslate_component.rb'
|
|
6
53
|
- 'app/components/primer/button_component.rb'
|
|
54
|
+
- 'app/components/primer/conditional_wrapper.rb'
|
|
55
|
+
- 'app/components/primer/content.rb'
|
|
56
|
+
- 'app/components/primer/icon_button.rb'
|
|
7
57
|
- 'app/components/primer/layout_component.rb'
|
|
58
|
+
- 'app/components/primer/tooltip.rb'
|
|
59
|
+
- 'app/components/primer/truncate.rb'
|
|
60
|
+
- 'test/components/beta/nav_list_test.rb'
|
|
61
|
+
- 'test/lib/primer/audited/dsl_test.rb'
|
|
62
|
+
- 'test/lib/primer/forms/form_test_component.rb'
|
|
63
|
+
- 'test/lib/primer/status/dsl_test.rb'
|
|
64
|
+
- 'test/test_helpers/components/primer/deny_component.rb'
|
|
8
65
|
ViewComponent/PreferPrivateMethods:
|
|
9
66
|
- 'app/components/primer/alpha/action_list.rb'
|
|
10
67
|
- 'app/components/primer/alpha/action_list/divider.rb'
|
|
@@ -18,5 +75,3 @@ ViewComponent/PreferPrivateMethods:
|
|
|
18
75
|
- 'app/components/primer/beta/nav_list/divider.rb'
|
|
19
76
|
- 'app/components/primer/beta/nav_list/group.rb'
|
|
20
77
|
- 'app/components/primer/beta/nav_list/item.rb'
|
|
21
|
-
ViewComponent/TestRenderedOutput:
|
|
22
|
-
- 'test/components/alpha/action_list_test.rb'
|
|
@@ -89,4 +89,20 @@ RSpec.describe RuboCop::Cop::ViewComponent::Base do
|
|
|
89
89
|
)
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
|
+
|
|
93
|
+
describe "#resolved_index_declaration" do
|
|
94
|
+
it "raises when project_index is not set" do
|
|
95
|
+
source = parse_source(<<~RUBY)
|
|
96
|
+
class Foo < ViewComponent::Base
|
|
97
|
+
end
|
|
98
|
+
RUBY
|
|
99
|
+
|
|
100
|
+
parent = source.ast.parent_class
|
|
101
|
+
|
|
102
|
+
expect { cop.send(:resolved_index_declaration, parent) }.to raise_error(
|
|
103
|
+
RuntimeError,
|
|
104
|
+
/UseProjectIndex/
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
92
108
|
end
|