rubocop-view_component 0.4.1 → 0.5.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 +9 -1
- data/config/default.yml +16 -3
- data/lib/rubocop/cop/view_component/base.rb +8 -1
- data/lib/rubocop/cop/view_component/missing_preview.rb +50 -0
- data/lib/rubocop/cop/view_component/no_global_state.rb +5 -0
- data/lib/rubocop/cop/view_component/prefer_composition.rb +7 -1
- data/lib/rubocop/cop/view_component_cops.rb +1 -0
- data/lib/rubocop/view_component/version.rb +1 -1
- data/script/verify +12 -17
- data/spec/expected_govuk_failures.yml +18 -33
- data/spec/expected_polaris_failures.yml +63 -59
- data/spec/expected_primer_failures.yml +14 -22
- data/spec/rubocop/cop/view_component/component_suffix_spec.rb +1 -1
- data/spec/rubocop/cop/view_component/missing_preview_spec.rb +97 -0
- data/spec/rubocop/cop/view_component/prefer_composition_spec.rb +26 -1
- data/verification/govuk_rubocop_config.yml +5 -1
- data/verification/polaris_rubocop_config.yml +42 -1
- data/verification/primer_rubocop_config.yml +7 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d359676eea94fac5c19cabfdbb56688fa62cd50eb5264e04843f81bb75a9ae58
|
|
4
|
+
data.tar.gz: e1efa2b33bc90fa5f75255093c79984f4cd7f0340f6034a1ead911b780635298
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51577192c4ad213c49b41a116b6349387d5265a19679626ae0185d783d259803fb9d3a572a1ca449255baebfbdf0a606a72a95f0597803f4bd4844b751b267b9
|
|
7
|
+
data.tar.gz: '0876d2f67a173ee761d25b68115c8526dc51befa3841b0f9ad09091e1420827e786a442398bd694fcf14c38bbec07190fd11de0b2036e1517311bc66becac6be'
|
data/README.md
CHANGED
|
@@ -27,6 +27,7 @@ This gem provides several cops to enforce ViewComponent best practices:
|
|
|
27
27
|
- **ViewComponent/PreferSlots** - Detect HTML parameters that should be slots
|
|
28
28
|
- **ViewComponent/PreferComposition** - Avoid inheriting one ViewComponent from another (prefer composition)
|
|
29
29
|
- **ViewComponent/TestRenderedOutput** - Encourage testing rendered output over private methods
|
|
30
|
+
- **ViewComponent/MissingPreview** - Ensure every ViewComponent has a corresponding preview file (requires `PreviewPaths` configuration)
|
|
30
31
|
|
|
31
32
|
## Optional Configuration
|
|
32
33
|
|
|
@@ -56,7 +57,14 @@ Lint/MissingSuper:
|
|
|
56
57
|
|
|
57
58
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
58
59
|
|
|
59
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
60
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
61
|
+
|
|
62
|
+
To release a new version:
|
|
63
|
+
|
|
64
|
+
1. Update the version number in `lib/rubocop/view_component/version.rb`
|
|
65
|
+
2. Run `bundle install` to update `Gemfile.lock`
|
|
66
|
+
3. Commit and push both files
|
|
67
|
+
4. Trigger the [Push Gem](https://github.com/andyw8/rubocop-view_component/actions/workflows/push_gem.yml) workflow via GitHub Actions (uses trusted publishing — no API key needed)
|
|
60
68
|
|
|
61
69
|
## Real-World Verification
|
|
62
70
|
|
data/config/default.yml
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
ViewComponent:
|
|
2
2
|
ViewComponentParentClasses: []
|
|
3
|
+
ComponentNamespaces: []
|
|
3
4
|
|
|
4
5
|
ViewComponent/ComponentSuffix:
|
|
5
6
|
Description: 'Enforce -Component suffix for ViewComponent classes.'
|
|
@@ -8,8 +9,20 @@ ViewComponent/ComponentSuffix:
|
|
|
8
9
|
Severity: convention
|
|
9
10
|
StyleGuide: 'https://viewcomponent.org/best_practices.html'
|
|
10
11
|
|
|
12
|
+
ViewComponent/MissingPreview:
|
|
13
|
+
Description: 'Ensure every ViewComponent has a corresponding preview file.'
|
|
14
|
+
Enabled: true
|
|
15
|
+
VersionAdded: '0.5.0'
|
|
16
|
+
Severity: convention
|
|
17
|
+
Include:
|
|
18
|
+
- 'app/components/**/*_component.rb'
|
|
19
|
+
PreviewPaths:
|
|
20
|
+
- test/components/previews
|
|
21
|
+
- spec/components/previews
|
|
22
|
+
|
|
11
23
|
ViewComponent/NoGlobalState:
|
|
12
|
-
Description: 'Avoid accessing global state (params, request, session, cookies,
|
|
24
|
+
Description: 'Avoid accessing global state (params, request, session, cookies,
|
|
25
|
+
flash) directly.'
|
|
13
26
|
Enabled: true
|
|
14
27
|
VersionAdded: '0.1'
|
|
15
28
|
Severity: convention
|
|
@@ -37,7 +50,7 @@ ViewComponent/PreferComposition:
|
|
|
37
50
|
Enabled: true
|
|
38
51
|
VersionAdded: '0.3'
|
|
39
52
|
Severity: convention
|
|
40
|
-
StyleGuide: 'https://viewcomponent.org/best_practices.html'
|
|
53
|
+
StyleGuide: 'https://viewcomponent.org/best_practices.html#avoid-inheritance'
|
|
41
54
|
|
|
42
55
|
ViewComponent/PreferSlots:
|
|
43
56
|
Description: 'Prefer slots over HTML string parameters.'
|
|
@@ -9,6 +9,9 @@ module RuboCop
|
|
|
9
9
|
def view_component_class?(node)
|
|
10
10
|
return false unless node&.class_type?
|
|
11
11
|
|
|
12
|
+
class_source = node.identifier.source
|
|
13
|
+
return true if component_namespaces.any? { |ns| class_source.start_with?(ns) }
|
|
14
|
+
|
|
12
15
|
parent_class = node.parent_class
|
|
13
16
|
return false unless parent_class
|
|
14
17
|
|
|
@@ -23,10 +26,14 @@ module RuboCop
|
|
|
23
26
|
source = node.source
|
|
24
27
|
return true if source == "ViewComponent::Base" || source == "ApplicationComponent"
|
|
25
28
|
|
|
26
|
-
additional =
|
|
29
|
+
additional = cop_config["ViewComponentParentClasses"] || []
|
|
27
30
|
additional.include?(source)
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
def component_namespaces
|
|
34
|
+
cop_config["ComponentNamespaces"] || []
|
|
35
|
+
end
|
|
36
|
+
|
|
30
37
|
# Find the enclosing class node
|
|
31
38
|
def enclosing_class(node)
|
|
32
39
|
node.each_ancestor(:class).first
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module ViewComponent
|
|
6
|
+
# Ensures that every ViewComponent has a corresponding preview file.
|
|
7
|
+
#
|
|
8
|
+
# Looks for previews in the configured PreviewPaths, supporting both
|
|
9
|
+
# naming conventions: `user_preview.rb` and `user_component_preview.rb`.
|
|
10
|
+
#
|
|
11
|
+
class MissingPreview < RuboCop::Cop::Base
|
|
12
|
+
include ViewComponent::Base
|
|
13
|
+
|
|
14
|
+
MSG = "No preview found for %<component>s (looked in: %<paths>s)."
|
|
15
|
+
|
|
16
|
+
def on_class(node)
|
|
17
|
+
return unless view_component_class?(node)
|
|
18
|
+
|
|
19
|
+
class_name = node.identifier.source
|
|
20
|
+
return if preview_exists?(class_name)
|
|
21
|
+
|
|
22
|
+
add_offense(node.identifier, message: format(MSG, component: class_name, paths: preview_paths.join(", ")))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def preview_exists?(class_name)
|
|
28
|
+
preview_paths.any? do |preview_path|
|
|
29
|
+
candidate_filenames(class_name).any? do |filename|
|
|
30
|
+
File.exist?(File.join(preview_path, filename))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def candidate_filenames(class_name)
|
|
36
|
+
base = class_name.gsub(/Component$/, "").gsub("::", "/").gsub(/([A-Z])/, '_\1').downcase.gsub("/_", "/")
|
|
37
|
+
base = base.delete_prefix("_").delete_prefix("/")
|
|
38
|
+
[
|
|
39
|
+
"#{base}_preview.rb",
|
|
40
|
+
"#{base}_component_preview.rb"
|
|
41
|
+
]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def preview_paths
|
|
45
|
+
cop_config.fetch("PreviewPaths", [])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -5,6 +5,11 @@ module RuboCop
|
|
|
5
5
|
module ViewComponent
|
|
6
6
|
# Prevents direct access to global state within ViewComponent classes.
|
|
7
7
|
#
|
|
8
|
+
# ViewComponent's own documentation notes that accessing `request` directly
|
|
9
|
+
# "introduces coupling that inhibits encapsulation & reuse, often making
|
|
10
|
+
# testing difficult." The same principle applies to `params`, `session`,
|
|
11
|
+
# `cookies`, and `flash`.
|
|
12
|
+
#
|
|
8
13
|
# @example
|
|
9
14
|
# # bad
|
|
10
15
|
# class UserComponent < ViewComponent::Base
|
|
@@ -36,7 +36,13 @@ module RuboCop
|
|
|
36
36
|
def component_like_parent?(node)
|
|
37
37
|
return false unless node.const_type?
|
|
38
38
|
|
|
39
|
-
node.source
|
|
39
|
+
source = node.source
|
|
40
|
+
source.end_with?("Component") ||
|
|
41
|
+
component_namespaces.any? { |ns| source.start_with?(ns) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def component_namespaces
|
|
45
|
+
cop_config.fetch("ComponentNamespaces", [])
|
|
40
46
|
end
|
|
41
47
|
end
|
|
42
48
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "view_component/base"
|
|
4
4
|
require_relative "view_component/component_suffix"
|
|
5
|
+
require_relative "view_component/missing_preview"
|
|
5
6
|
require_relative "view_component/no_global_state"
|
|
6
7
|
require_relative "view_component/prefer_private_methods"
|
|
7
8
|
require_relative "view_component/prefer_composition"
|
data/script/verify
CHANGED
|
@@ -134,18 +134,15 @@ end
|
|
|
134
134
|
|
|
135
135
|
def extract_offenses(rubocop_output)
|
|
136
136
|
data = JSON.parse(rubocop_output)
|
|
137
|
-
offenses_by_cop = Hash.new { |h, k| h[k] =
|
|
137
|
+
offenses_by_cop = Hash.new { |h, k| h[k] = Set.new }
|
|
138
138
|
|
|
139
139
|
data["files"].each do |file|
|
|
140
140
|
file["offenses"].each do |offense|
|
|
141
|
-
offenses_by_cop[offense["cop_name"]] <<
|
|
142
|
-
location: "#{file["path"]}:#{offense["location"]["start_line"]}",
|
|
143
|
-
message: offense["message"]
|
|
144
|
-
}
|
|
141
|
+
offenses_by_cop[offense["cop_name"]] << file["path"]
|
|
145
142
|
end
|
|
146
143
|
end
|
|
147
144
|
|
|
148
|
-
offenses_by_cop.transform_values { |
|
|
145
|
+
offenses_by_cop.transform_values { |paths| paths.sort }.sort.to_h
|
|
149
146
|
end
|
|
150
147
|
|
|
151
148
|
def regenerate(offenses, results_file)
|
|
@@ -154,18 +151,17 @@ def regenerate(offenses, results_file)
|
|
|
154
151
|
lines << "# It captures known offenses in the library source."
|
|
155
152
|
lines << "---"
|
|
156
153
|
|
|
157
|
-
offenses.each do |cop,
|
|
154
|
+
offenses.each do |cop, paths|
|
|
158
155
|
lines << "#{cop}:"
|
|
159
|
-
|
|
156
|
+
paths.each { |path| lines << " - '#{path}'" }
|
|
160
157
|
end
|
|
161
158
|
|
|
162
159
|
File.write(results_file, lines.join("\n") + "\n")
|
|
163
160
|
total = offenses.values.sum(&:length)
|
|
164
|
-
puts "#{total} offense(s) written to #{results_file}"
|
|
161
|
+
puts "#{total} file(s) with offense(s) written to #{results_file}"
|
|
165
162
|
end
|
|
166
163
|
|
|
167
164
|
def load_expected(results_file)
|
|
168
|
-
# YAML strips inline comments, so each entry loads as just the location string
|
|
169
165
|
YAML.load_file(results_file)
|
|
170
166
|
end
|
|
171
167
|
|
|
@@ -175,19 +171,18 @@ def verify(offenses, results_file)
|
|
|
175
171
|
end
|
|
176
172
|
|
|
177
173
|
expected = load_expected(results_file)
|
|
178
|
-
current = offenses.transform_values { |os| os.map { |o| o[:location] } }
|
|
179
174
|
|
|
180
|
-
if
|
|
175
|
+
if offenses == expected
|
|
181
176
|
puts "Verification passed: output matches #{results_file}"
|
|
182
177
|
else
|
|
183
178
|
puts "Verification failed: output differs from #{results_file}"
|
|
184
179
|
|
|
185
|
-
all_cops = (
|
|
180
|
+
all_cops = (offenses.keys + expected.keys).uniq.sort
|
|
186
181
|
all_cops.each do |cop|
|
|
187
|
-
added = (
|
|
188
|
-
removed = (expected[cop] || []) - (
|
|
189
|
-
added.each { |
|
|
190
|
-
removed.each { |
|
|
182
|
+
added = (offenses[cop] || []) - (expected[cop] || [])
|
|
183
|
+
removed = (expected[cop] || []) - (offenses[cop] || [])
|
|
184
|
+
added.each { |path| puts " + #{cop}: #{path}" }
|
|
185
|
+
removed.each { |path| puts " - #{cop}: #{path}" }
|
|
191
186
|
end
|
|
192
187
|
|
|
193
188
|
exit 1
|
|
@@ -2,38 +2,23 @@
|
|
|
2
2
|
# It captures known offenses in the library source.
|
|
3
3
|
---
|
|
4
4
|
ViewComponent/ComponentSuffix:
|
|
5
|
-
- app/components/govuk_component/base.rb
|
|
6
|
-
- app/components/govuk_component/header_component.rb
|
|
7
|
-
- app/components/govuk_component/notification_banner_component.rb
|
|
8
|
-
- app/components/govuk_component/pagination_component/adjacent_page.rb
|
|
9
|
-
- app/components/govuk_component/pagination_component/item.rb
|
|
10
|
-
- app/components/govuk_component/tab_component.rb
|
|
5
|
+
- 'app/components/govuk_component/base.rb'
|
|
6
|
+
- 'app/components/govuk_component/header_component.rb'
|
|
7
|
+
- 'app/components/govuk_component/notification_banner_component.rb'
|
|
8
|
+
- 'app/components/govuk_component/pagination_component/adjacent_page.rb'
|
|
9
|
+
- 'app/components/govuk_component/pagination_component/item.rb'
|
|
10
|
+
- 'app/components/govuk_component/tab_component.rb'
|
|
11
11
|
ViewComponent/PreferPrivateMethods:
|
|
12
|
-
- app/components/govuk_component/base.rb
|
|
13
|
-
- app/components/govuk_component/
|
|
14
|
-
- app/components/govuk_component/
|
|
15
|
-
- app/components/govuk_component/
|
|
16
|
-
- app/components/govuk_component/service_navigation_component.rb:54 # Consider making `navigation` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
17
|
-
- app/components/govuk_component/service_navigation_component.rb:62 # Consider making `navigation_list` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
18
|
-
- app/components/govuk_component/tab_component.rb:32 # Consider making `hidden_class` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
19
|
-
- app/components/govuk_component/tab_component.rb:38 # Consider making `li_classes` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
20
|
-
- app/components/govuk_component/tab_component.rb:42 # Consider making `li_link` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
21
|
-
- app/components/govuk_component/tab_component.rb:46 # Consider making `default_attributes` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
22
|
-
- app/components/govuk_component/tab_component.rb:50 # Consider making `combined_attributes` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
|
|
12
|
+
- 'app/components/govuk_component/base.rb'
|
|
13
|
+
- 'app/components/govuk_component/notification_banner_component.rb'
|
|
14
|
+
- 'app/components/govuk_component/service_navigation_component.rb'
|
|
15
|
+
- 'app/components/govuk_component/tab_component.rb'
|
|
23
16
|
ViewComponent/TestRenderedOutput:
|
|
24
|
-
- spec/components/govuk_component/accordion_component_spec.rb
|
|
25
|
-
- spec/components/govuk_component/breadcrumbs_component_spec.rb
|
|
26
|
-
- spec/components/govuk_component/configuration/footer_component_configuration_spec.rb
|
|
27
|
-
- spec/components/govuk_component/footer_component_spec.rb
|
|
28
|
-
- spec/components/govuk_component/
|
|
29
|
-
- spec/components/govuk_component/
|
|
30
|
-
- spec/components/govuk_component/
|
|
31
|
-
- spec/components/govuk_component/
|
|
32
|
-
- spec/components/govuk_component/footer_component_spec.rb:294 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
33
|
-
- spec/components/govuk_component/footer_component_spec.rb:312 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
34
|
-
- spec/components/govuk_component/footer_component_spec.rb:336 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
35
|
-
- spec/components/govuk_component/notification_banner_component_spec.rb:59 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
36
|
-
- spec/components/govuk_component/pagination_component_spec.rb:317 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
37
|
-
- spec/components/govuk_component/task_list_component_spec.rb:139 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
38
|
-
- spec/components/govuk_component/warning_text_component_spec.rb:11 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
39
|
-
- spec/components/govuk_component/warning_text_component_spec.rb:41 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly. (https://viewcomponent.org/guide/testing.html)
|
|
17
|
+
- 'spec/components/govuk_component/accordion_component_spec.rb'
|
|
18
|
+
- 'spec/components/govuk_component/breadcrumbs_component_spec.rb'
|
|
19
|
+
- 'spec/components/govuk_component/configuration/footer_component_configuration_spec.rb'
|
|
20
|
+
- 'spec/components/govuk_component/footer_component_spec.rb'
|
|
21
|
+
- 'spec/components/govuk_component/notification_banner_component_spec.rb'
|
|
22
|
+
- 'spec/components/govuk_component/pagination_component_spec.rb'
|
|
23
|
+
- 'spec/components/govuk_component/task_list_component_spec.rb'
|
|
24
|
+
- 'spec/components/govuk_component/warning_text_component_spec.rb'
|
|
@@ -2,64 +2,68 @@
|
|
|
2
2
|
# It captures known offenses in the library source.
|
|
3
3
|
---
|
|
4
4
|
ViewComponent/ComponentSuffix:
|
|
5
|
-
- app/components/polaris/base_button.rb
|
|
6
|
-
- app/components/polaris/base_checkbox.rb
|
|
7
|
-
- app/components/polaris/base_radio_button.rb
|
|
8
|
-
- app/components/polaris/headless_button.rb
|
|
9
|
-
- app/components/polaris/layout/annotated_section.rb
|
|
10
|
-
- app/components/polaris/layout/section.rb
|
|
11
|
-
- app/components/polaris/text_field_component.rb
|
|
5
|
+
- 'app/components/polaris/base_button.rb'
|
|
6
|
+
- 'app/components/polaris/base_checkbox.rb'
|
|
7
|
+
- 'app/components/polaris/base_radio_button.rb'
|
|
8
|
+
- 'app/components/polaris/headless_button.rb'
|
|
9
|
+
- 'app/components/polaris/layout/annotated_section.rb'
|
|
10
|
+
- 'app/components/polaris/layout/section.rb'
|
|
11
|
+
- 'app/components/polaris/text_field_component.rb'
|
|
12
|
+
ViewComponent/MissingPreview:
|
|
13
|
+
- 'app/components/polaris/button_group_component.rb'
|
|
14
|
+
- 'app/components/polaris/choice_component.rb'
|
|
15
|
+
- 'app/components/polaris/description_list_component.rb'
|
|
16
|
+
- 'app/components/polaris/filters_component.rb'
|
|
17
|
+
- 'app/components/polaris/inline_code_component.rb'
|
|
18
|
+
- 'app/components/polaris/label_component.rb'
|
|
19
|
+
- 'app/components/polaris/labelled_component.rb'
|
|
20
|
+
- 'app/components/polaris/list_component.rb'
|
|
21
|
+
- 'app/components/polaris/page_component.rb'
|
|
22
|
+
- 'app/components/polaris/placeholder_component.rb'
|
|
23
|
+
- 'app/components/polaris/shopify_navigation_component.rb'
|
|
24
|
+
- 'app/components/polaris/text_field_component.rb'
|
|
25
|
+
- 'app/components/polaris/toast_component.rb'
|
|
12
26
|
ViewComponent/NoGlobalState:
|
|
13
|
-
- app/components/polaris/shopify_navigation_component.rb
|
|
27
|
+
- 'app/components/polaris/shopify_navigation_component.rb'
|
|
14
28
|
ViewComponent/PreferPrivateMethods:
|
|
15
|
-
- app/components/polaris/action_list/section_component.rb
|
|
16
|
-
- app/components/polaris/avatar_component.rb
|
|
17
|
-
- app/components/polaris/
|
|
18
|
-
- app/components/polaris/
|
|
19
|
-
- app/components/polaris/
|
|
20
|
-
- app/components/polaris/
|
|
21
|
-
- app/components/polaris/
|
|
22
|
-
- app/components/polaris/
|
|
23
|
-
- app/components/polaris/
|
|
24
|
-
- app/components/polaris/
|
|
25
|
-
- app/components/polaris/
|
|
26
|
-
- app/components/polaris/
|
|
27
|
-
- app/components/polaris/
|
|
28
|
-
- app/components/polaris/
|
|
29
|
-
- app/components/polaris/
|
|
30
|
-
- app/components/polaris/
|
|
31
|
-
- app/components/polaris/
|
|
32
|
-
- app/components/polaris/
|
|
33
|
-
- app/components/polaris/
|
|
34
|
-
- app/components/polaris/
|
|
35
|
-
- app/components/polaris/
|
|
36
|
-
- app/components/polaris/
|
|
37
|
-
- app/components/polaris/
|
|
38
|
-
- app/components/polaris/
|
|
39
|
-
- app/components/polaris/
|
|
40
|
-
- app/components/polaris/
|
|
41
|
-
- app/components/polaris/
|
|
42
|
-
- app/components/polaris/
|
|
43
|
-
- app/components/polaris/
|
|
44
|
-
- app/components/polaris/
|
|
45
|
-
- app/components/polaris/
|
|
46
|
-
- app/components/polaris/
|
|
47
|
-
- app/components/polaris/
|
|
48
|
-
- app/components/polaris/
|
|
49
|
-
- app/components/polaris/
|
|
50
|
-
- app/components/polaris/
|
|
51
|
-
- app/components/polaris/
|
|
52
|
-
- app/components/polaris/
|
|
53
|
-
- app/components/polaris/
|
|
54
|
-
- app/components/polaris/
|
|
55
|
-
- app/components/polaris/
|
|
56
|
-
- app/components/polaris/select_component.rb:88 # Consider making `hides_label?` private. Only ViewComponent interface methods should be public.
|
|
57
|
-
- app/components/polaris/shopify_navigation_component.rb:40 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
|
|
58
|
-
- app/components/polaris/shopify_navigation_component.rb:56 # Consider making `detect_active` private. Only ViewComponent interface methods should be public.
|
|
59
|
-
- app/components/polaris/skeleton_display_text_component.rb:17 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
|
|
60
|
-
- app/components/polaris/skeleton_thumbnail_component.rb:16 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
|
|
61
|
-
- app/components/polaris/tabs_component.rb:33 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
|
|
62
|
-
- app/components/polaris/text_component.rb:114 # Consider making `alignment_class` private. Only ViewComponent interface methods should be public.
|
|
63
|
-
- app/components/polaris/text_component.rb:120 # Consider making `color_class` private. Only ViewComponent interface methods should be public.
|
|
64
|
-
- app/components/polaris/text_component.rb:126 # Consider making `font_weight_class` private. Only ViewComponent interface methods should be public.
|
|
65
|
-
- app/components/polaris/thumbnail_component.rb:39 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
|
|
29
|
+
- 'app/components/polaris/action_list/section_component.rb'
|
|
30
|
+
- 'app/components/polaris/avatar_component.rb'
|
|
31
|
+
- 'app/components/polaris/banner_component.rb'
|
|
32
|
+
- 'app/components/polaris/base_checkbox.rb'
|
|
33
|
+
- 'app/components/polaris/base_radio_button.rb'
|
|
34
|
+
- 'app/components/polaris/button_group_component.rb'
|
|
35
|
+
- 'app/components/polaris/checkbox_component.rb'
|
|
36
|
+
- 'app/components/polaris/choice_list_component.rb'
|
|
37
|
+
- 'app/components/polaris/collapsible_component.rb'
|
|
38
|
+
- 'app/components/polaris/description_list_component.rb'
|
|
39
|
+
- 'app/components/polaris/dropzone_component.rb'
|
|
40
|
+
- 'app/components/polaris/exception_list_component.rb'
|
|
41
|
+
- 'app/components/polaris/filters_component.rb'
|
|
42
|
+
- 'app/components/polaris/form_layout_component.rb'
|
|
43
|
+
- 'app/components/polaris/headless_button.rb'
|
|
44
|
+
- 'app/components/polaris/index_table/cell_component.rb'
|
|
45
|
+
- 'app/components/polaris/inline_error_component.rb'
|
|
46
|
+
- 'app/components/polaris/keyboard_key_component.rb'
|
|
47
|
+
- 'app/components/polaris/label_component.rb'
|
|
48
|
+
- 'app/components/polaris/layout_component.rb'
|
|
49
|
+
- 'app/components/polaris/modal/section_component.rb'
|
|
50
|
+
- 'app/components/polaris/navigation/item_component.rb'
|
|
51
|
+
- 'app/components/polaris/navigation_component.rb'
|
|
52
|
+
- 'app/components/polaris/navigation_list_component.rb'
|
|
53
|
+
- 'app/components/polaris/new_tabs_component.rb'
|
|
54
|
+
- 'app/components/polaris/option_list/checkbox_component.rb'
|
|
55
|
+
- 'app/components/polaris/option_list/option_component.rb'
|
|
56
|
+
- 'app/components/polaris/option_list/radio_button_component.rb'
|
|
57
|
+
- 'app/components/polaris/option_list/section_component.rb'
|
|
58
|
+
- 'app/components/polaris/page_component.rb'
|
|
59
|
+
- 'app/components/polaris/popover/section_component.rb'
|
|
60
|
+
- 'app/components/polaris/popover_component.rb'
|
|
61
|
+
- 'app/components/polaris/resource_item/shortcut_actions_component.rb'
|
|
62
|
+
- 'app/components/polaris/resource_list_component.rb'
|
|
63
|
+
- 'app/components/polaris/select_component.rb'
|
|
64
|
+
- 'app/components/polaris/shopify_navigation_component.rb'
|
|
65
|
+
- 'app/components/polaris/skeleton_display_text_component.rb'
|
|
66
|
+
- 'app/components/polaris/skeleton_thumbnail_component.rb'
|
|
67
|
+
- 'app/components/polaris/tabs_component.rb'
|
|
68
|
+
- 'app/components/polaris/text_component.rb'
|
|
69
|
+
- 'app/components/polaris/thumbnail_component.rb'
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
# This file is auto-generated by `script/verify --regenerate`.
|
|
2
2
|
# It captures known offenses in the library source.
|
|
3
3
|
---
|
|
4
|
+
ViewComponent/MissingPreview:
|
|
5
|
+
- 'app/components/primer/blankslate_component.rb'
|
|
6
|
+
- 'app/components/primer/button_component.rb'
|
|
7
|
+
- 'app/components/primer/layout_component.rb'
|
|
4
8
|
ViewComponent/PreferPrivateMethods:
|
|
5
|
-
- app/components/primer/alpha/action_list.rb
|
|
6
|
-
- app/components/primer/alpha/action_list.rb
|
|
7
|
-
- app/components/primer/alpha/action_list.rb
|
|
8
|
-
- app/components/primer/alpha/
|
|
9
|
-
- app/components/primer/alpha/
|
|
10
|
-
- app/components/primer/alpha/
|
|
11
|
-
- app/components/primer/alpha/
|
|
12
|
-
- app/components/primer/
|
|
13
|
-
- app/components/primer/
|
|
14
|
-
- app/components/primer/alpha/action_list/divider.rb:33 # Consider making `active?` private. Only ViewComponent interface methods should be public.
|
|
15
|
-
- app/components/primer/alpha/action_list/form_wrapper.rb:53 # Consider making `get?` private. Only ViewComponent interface methods should be public.
|
|
16
|
-
- app/components/primer/alpha/dropdown/menu.rb:96 # Consider making `divider?` private. Only ViewComponent interface methods should be public.
|
|
17
|
-
- app/components/primer/alpha/form_control.rb:101 # Consider making `visually_hide_label?` private. Only ViewComponent interface methods should be public.
|
|
18
|
-
- app/components/primer/alpha/form_control.rb:107 # Consider making `full_width?` private. Only ViewComponent interface methods should be public.
|
|
19
|
-
- app/components/primer/alpha/toggle_switch.rb:94 # Consider making `on?` private. Only ViewComponent interface methods should be public.
|
|
20
|
-
- app/components/primer/alpha/toggle_switch.rb:98 # Consider making `enabled?` private. Only ViewComponent interface methods should be public.
|
|
21
|
-
- app/components/primer/alpha/tree_view/node.rb:155 # Consider making `merge_system_arguments!` private. Only ViewComponent interface methods should be public.
|
|
22
|
-
- app/components/primer/beta/avatar.rb:22 # Consider making `link?` private. Only ViewComponent interface methods should be public.
|
|
23
|
-
- app/components/primer/beta/nav_list.rb:116 # Consider making `build_item` private. Only ViewComponent interface methods should be public.
|
|
24
|
-
- app/components/primer/beta/nav_list.rb:139 # Consider making `build_avatar_item` private. Only ViewComponent interface methods should be public.
|
|
9
|
+
- 'app/components/primer/alpha/action_list.rb'
|
|
10
|
+
- 'app/components/primer/alpha/action_list/divider.rb'
|
|
11
|
+
- 'app/components/primer/alpha/action_list/form_wrapper.rb'
|
|
12
|
+
- 'app/components/primer/alpha/dropdown/menu.rb'
|
|
13
|
+
- 'app/components/primer/alpha/form_control.rb'
|
|
14
|
+
- 'app/components/primer/alpha/toggle_switch.rb'
|
|
15
|
+
- 'app/components/primer/alpha/tree_view/node.rb'
|
|
16
|
+
- 'app/components/primer/beta/avatar.rb'
|
|
17
|
+
- 'app/components/primer/beta/nav_list.rb'
|
|
25
18
|
ViewComponent/TestRenderedOutput:
|
|
26
|
-
- test/components/alpha/action_list_test.rb
|
|
27
|
-
- test/components/alpha/action_list_test.rb:268 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
|
|
19
|
+
- 'test/components/alpha/action_list_test.rb'
|
|
@@ -77,7 +77,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::ComponentSuffix, :config do
|
|
|
77
77
|
context "when ViewComponentParentClasses is configured" do
|
|
78
78
|
let(:config) do
|
|
79
79
|
RuboCop::Config.new(
|
|
80
|
-
"
|
|
80
|
+
"ViewComponent/ComponentSuffix" => {
|
|
81
81
|
"ViewComponentParentClasses" => ["Primer::Component"]
|
|
82
82
|
}
|
|
83
83
|
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
4
|
+
let(:config) do
|
|
5
|
+
RuboCop::Config.new(
|
|
6
|
+
"ViewComponent/MissingPreview" => {
|
|
7
|
+
"Enabled" => true,
|
|
8
|
+
"PreviewPaths" => ["/previews"]
|
|
9
|
+
}
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
context "when a preview file exists" do
|
|
14
|
+
it "does not register an offense" do
|
|
15
|
+
allow(File).to receive(:exist?).and_return(true)
|
|
16
|
+
|
|
17
|
+
expect_no_offenses(<<~RUBY, "/app/components/user_component.rb")
|
|
18
|
+
class UserComponent < ViewComponent::Base
|
|
19
|
+
end
|
|
20
|
+
RUBY
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "when no preview file exists" do
|
|
25
|
+
it "registers an offense" do
|
|
26
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
27
|
+
|
|
28
|
+
expect_offense(<<~RUBY, "/app/components/user_component.rb")
|
|
29
|
+
class UserComponent < ViewComponent::Base
|
|
30
|
+
^^^^^^^^^^^^^ No preview found for UserComponent (looked in: /previews).
|
|
31
|
+
end
|
|
32
|
+
RUBY
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "when ComponentNamespaces is configured" do
|
|
37
|
+
let(:config) do
|
|
38
|
+
RuboCop::Config.new(
|
|
39
|
+
"ViewComponent/MissingPreview" => {
|
|
40
|
+
"Enabled" => true,
|
|
41
|
+
"PreviewPaths" => ["/previews"],
|
|
42
|
+
"ComponentNamespaces" => ["V2::"]
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "registers an offense for a component in a configured namespace" do
|
|
48
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
49
|
+
|
|
50
|
+
expect_offense(<<~RUBY, "/app/components/v2/table.rb")
|
|
51
|
+
class V2::Table < SomeBase
|
|
52
|
+
^^^^^^^^^ No preview found for V2::Table (looked in: /previews).
|
|
53
|
+
end
|
|
54
|
+
RUBY
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "checks the correct preview path for namespaced classes" do
|
|
58
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
59
|
+
allow(File).to receive(:exist?).with("/previews/v2/grouped_multi_select_preview.rb").and_return(true)
|
|
60
|
+
|
|
61
|
+
expect_no_offenses(<<~RUBY, "/app/components/v2/grouped_multi_select.rb")
|
|
62
|
+
class V2::GroupedMultiSelect < V2::MultiSelect
|
|
63
|
+
end
|
|
64
|
+
RUBY
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "checks the correct preview path for deeply namespaced classes" do
|
|
68
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
69
|
+
allow(File).to receive(:exist?).with("/previews/v2/catalogs/index_table_preview.rb").and_return(true)
|
|
70
|
+
|
|
71
|
+
expect_no_offenses(<<~RUBY, "/app/components/v2/catalogs/index_table.rb")
|
|
72
|
+
class V2::Catalogs::IndexTable < V2::Table
|
|
73
|
+
end
|
|
74
|
+
RUBY
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "does not register an offense for a non-component class in a configured namespace" do
|
|
78
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
79
|
+
|
|
80
|
+
expect_no_offenses(<<~RUBY, "/app/models/user.rb")
|
|
81
|
+
class User < SomeBase
|
|
82
|
+
end
|
|
83
|
+
RUBY
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "when not a ViewComponent" do
|
|
88
|
+
it "does not register an offense" do
|
|
89
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
90
|
+
|
|
91
|
+
expect_no_offenses(<<~RUBY, "/app/components/user_component.rb")
|
|
92
|
+
class UserComponent
|
|
93
|
+
end
|
|
94
|
+
RUBY
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -64,10 +64,35 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferComposition, :config do
|
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
context "when ComponentNamespaces is configured" do
|
|
68
|
+
let(:config) do
|
|
69
|
+
RuboCop::Config.new(
|
|
70
|
+
"ViewComponent/PreferComposition" => {
|
|
71
|
+
"ComponentNamespaces" => ["V2::"]
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "registers an offense for a class in a configured namespace" do
|
|
77
|
+
expect_offense(<<~RUBY)
|
|
78
|
+
class UserCard < V2::Table
|
|
79
|
+
^^^^^^^^^ Avoid inheriting from another ViewComponent.
|
|
80
|
+
end
|
|
81
|
+
RUBY
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "does not register an offense for a class outside configured namespaces" do
|
|
85
|
+
expect_no_offenses(<<~RUBY)
|
|
86
|
+
class UserCard < SomeOtherBase
|
|
87
|
+
end
|
|
88
|
+
RUBY
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
67
92
|
context "when ViewComponentParentClasses is configured" do
|
|
68
93
|
let(:config) do
|
|
69
94
|
RuboCop::Config.new(
|
|
70
|
-
"
|
|
95
|
+
"ViewComponent/PreferComposition" => {
|
|
71
96
|
"ViewComponentParentClasses" => ["Primer::Component"]
|
|
72
97
|
}
|
|
73
98
|
)
|
|
@@ -1,4 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
ViewComponent:
|
|
2
2
|
ViewComponentParentClasses:
|
|
3
3
|
- Polaris::Component
|
|
4
4
|
- Component
|
|
5
|
+
|
|
6
|
+
ViewComponent/MissingPreview:
|
|
7
|
+
PreviewPaths:
|
|
8
|
+
- demo/app/previews
|
|
9
|
+
Exclude:
|
|
10
|
+
- app/components/polaris/base_component.rb
|
|
11
|
+
- app/components/polaris/base_button.rb
|
|
12
|
+
- app/components/polaris/base_checkbox.rb
|
|
13
|
+
- app/components/polaris/base_radio_button.rb
|
|
14
|
+
# Sub-components are rendered as part of their parent component's preview
|
|
15
|
+
# rather than having dedicated preview files
|
|
16
|
+
- app/components/polaris/action_list/item_component.rb
|
|
17
|
+
- app/components/polaris/action_list/section_component.rb
|
|
18
|
+
- app/components/polaris/autocomplete/action_component.rb
|
|
19
|
+
- app/components/polaris/autocomplete/option_component.rb
|
|
20
|
+
- app/components/polaris/autocomplete/section_component.rb
|
|
21
|
+
- app/components/polaris/card/header_component.rb
|
|
22
|
+
- app/components/polaris/card/section_component.rb
|
|
23
|
+
- app/components/polaris/data_table/cell_component.rb
|
|
24
|
+
- app/components/polaris/data_table/column_component.rb
|
|
25
|
+
- app/components/polaris/exception_list/item_component.rb
|
|
26
|
+
- app/components/polaris/form_layout/group_component.rb
|
|
27
|
+
- app/components/polaris/form_layout/item_component.rb
|
|
28
|
+
- app/components/polaris/frame/save_bar_component.rb
|
|
29
|
+
- app/components/polaris/frame/top_bar_component.rb
|
|
30
|
+
- app/components/polaris/index_table/cell_component.rb
|
|
31
|
+
- app/components/polaris/index_table/column_component.rb
|
|
32
|
+
- app/components/polaris/modal/section_component.rb
|
|
33
|
+
- app/components/polaris/navigation/item_component.rb
|
|
34
|
+
- app/components/polaris/navigation/section_component.rb
|
|
35
|
+
- app/components/polaris/new_tabs/tab_component.rb
|
|
36
|
+
- app/components/polaris/option_list/checkbox_component.rb
|
|
37
|
+
- app/components/polaris/option_list/option_component.rb
|
|
38
|
+
- app/components/polaris/option_list/radio_button_component.rb
|
|
39
|
+
- app/components/polaris/option_list/section_component.rb
|
|
40
|
+
- app/components/polaris/popover/pane_component.rb
|
|
41
|
+
- app/components/polaris/popover/section_component.rb
|
|
42
|
+
- app/components/polaris/resource_item/shortcut_actions_component.rb
|
|
43
|
+
- app/components/polaris/stack/item_component.rb
|
|
44
|
+
- app/components/polaris/tabs/tab_component.rb
|
|
45
|
+
- app/components/polaris/top_bar/user_menu_component.rb
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
ViewComponent:
|
|
2
2
|
ViewComponentParentClasses:
|
|
3
3
|
- Primer::Component
|
|
4
4
|
- Primer::BaseComponent
|
|
@@ -22,3 +22,9 @@ ViewComponent/TestRenderedOutput:
|
|
|
22
22
|
- test/components/base_component_test.rb
|
|
23
23
|
- test/components/component_test.rb
|
|
24
24
|
- test/lib/primer/attributes_helper_test.rb
|
|
25
|
+
|
|
26
|
+
ViewComponent/MissingPreview:
|
|
27
|
+
PreviewPaths:
|
|
28
|
+
- previews
|
|
29
|
+
Exclude:
|
|
30
|
+
- app/components/primer/base_component.rb
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-view_component
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Waite
|
|
@@ -82,6 +82,7 @@ files:
|
|
|
82
82
|
- lib/rubocop-view_component.rb
|
|
83
83
|
- lib/rubocop/cop/view_component/base.rb
|
|
84
84
|
- lib/rubocop/cop/view_component/component_suffix.rb
|
|
85
|
+
- lib/rubocop/cop/view_component/missing_preview.rb
|
|
85
86
|
- lib/rubocop/cop/view_component/no_global_state.rb
|
|
86
87
|
- lib/rubocop/cop/view_component/prefer_composition.rb
|
|
87
88
|
- lib/rubocop/cop/view_component/prefer_private_methods.rb
|
|
@@ -99,6 +100,7 @@ files:
|
|
|
99
100
|
- spec/fixtures/components/template_method_component.html.erb
|
|
100
101
|
- spec/fixtures/components/template_method_component.rb
|
|
101
102
|
- spec/rubocop/cop/view_component/component_suffix_spec.rb
|
|
103
|
+
- spec/rubocop/cop/view_component/missing_preview_spec.rb
|
|
102
104
|
- spec/rubocop/cop/view_component/no_global_state_spec.rb
|
|
103
105
|
- spec/rubocop/cop/view_component/prefer_composition_spec.rb
|
|
104
106
|
- spec/rubocop/cop/view_component/prefer_private_methods_spec.rb
|
|
@@ -130,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
130
132
|
- !ruby/object:Gem::Version
|
|
131
133
|
version: '0'
|
|
132
134
|
requirements: []
|
|
133
|
-
rubygems_version:
|
|
135
|
+
rubygems_version: 3.6.9
|
|
134
136
|
specification_version: 4
|
|
135
137
|
summary: RuboCop extension for ViewComponent best practices
|
|
136
138
|
test_files: []
|