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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef33893cd9f89fc1d39a7e94d65d7f6278fdda552603a303743713889f851e89
4
- data.tar.gz: fad82a125ffcb2463cda70830887e0f3b26f671d8f6a8bcfdc8f2c5a42dc1929
3
+ metadata.gz: d359676eea94fac5c19cabfdbb56688fa62cd50eb5264e04843f81bb75a9ae58
4
+ data.tar.gz: e1efa2b33bc90fa5f75255093c79984f4cd7f0340f6034a1ead911b780635298
5
5
  SHA512:
6
- metadata.gz: b60217625dca16c9b4d6394f1114c362cadad8223459789b59cd9a52c26219200b8e5a9ef0da72825a847a6d11e9816890013575d048902e3f8fc198192ccff4
7
- data.tar.gz: b896072336154eec7400ef95362544b90d00004678c86faff46ac6e68db8a0aace2a1f797185c7072fc707f345b5bc3ed5d1e44f8e3244f9a273dd671dc217a9
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`. 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).
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
- AllCops:
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, flash) directly.'
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 = config.for_all_cops["ViewComponentParentClasses"] || []
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.end_with?("Component")
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"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module ViewComponent
5
- VERSION = "0.4.1"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
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 { |os| os.sort_by { |o| o[:location] } }.sort.to_h
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, entries|
154
+ offenses.each do |cop, paths|
158
155
  lines << "#{cop}:"
159
- entries.each { |e| lines << " - #{e[:location]} # #{e[:message]}" }
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 current == expected
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 = (current.keys + expected.keys).uniq.sort
180
+ all_cops = (offenses.keys + expected.keys).uniq.sort
186
181
  all_cops.each do |cop|
187
- added = (current[cop] || []) - (expected[cop] || [])
188
- removed = (expected[cop] || []) - (current[cop] || [])
189
- added.each { |loc| puts " + #{cop}: #{loc}" }
190
- removed.each { |loc| puts " - #{cop}: #{loc}" }
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:1 # ViewComponent class names should end with `Component`. (https://viewcomponent.org/best_practices.html)
6
- - app/components/govuk_component/header_component.rb:46 # ViewComponent class names should end with `Component`. (https://viewcomponent.org/best_practices.html)
7
- - app/components/govuk_component/notification_banner_component.rb:33 # ViewComponent class names should end with `Component`. (https://viewcomponent.org/best_practices.html)
8
- - app/components/govuk_component/pagination_component/adjacent_page.rb:1 # ViewComponent class names should end with `Component`. (https://viewcomponent.org/best_practices.html)
9
- - app/components/govuk_component/pagination_component/item.rb:1 # ViewComponent class names should end with `Component`. (https://viewcomponent.org/best_practices.html)
10
- - app/components/govuk_component/tab_component.rb:21 # ViewComponent class names should end with `Component`. (https://viewcomponent.org/best_practices.html)
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:29 # Consider making `brand` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
13
- - app/components/govuk_component/base.rb:35 # Consider making `class_prefix` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
14
- - app/components/govuk_component/notification_banner_component.rb:55 # Consider making `link` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
15
- - app/components/govuk_component/notification_banner_component.rb:59 # Consider making `default_attributes` private. Only ViewComponent interface methods should be public. (https://viewcomponent.org/best_practices.html)
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:84 # 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)
25
- - spec/components/govuk_component/breadcrumbs_component_spec.rb:44 # 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)
26
- - spec/components/govuk_component/configuration/footer_component_configuration_spec.rb:42 # 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)
27
- - spec/components/govuk_component/footer_component_spec.rb:123 # 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)
28
- - spec/components/govuk_component/footer_component_spec.rb:182 # 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)
29
- - spec/components/govuk_component/footer_component_spec.rb:235 # 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)
30
- - spec/components/govuk_component/footer_component_spec.rb:254 # 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)
31
- - spec/components/govuk_component/footer_component_spec.rb:274 # 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)
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:4 # ViewComponent class names should end with `Component`.
6
- - app/components/polaris/base_checkbox.rb:2 # ViewComponent class names should end with `Component`.
7
- - app/components/polaris/base_radio_button.rb:2 # ViewComponent class names should end with `Component`.
8
- - app/components/polaris/headless_button.rb:4 # ViewComponent class names should end with `Component`.
9
- - app/components/polaris/layout/annotated_section.rb:5 # ViewComponent class names should end with `Component`.
10
- - app/components/polaris/layout/section.rb:5 # ViewComponent class names should end with `Component`.
11
- - app/components/polaris/text_field_component.rb:195 # ViewComponent class names should end with `Component`.
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:59 # Avoid accessing `request` directly in ViewComponents. Pass necessary data through the constructor instead.
27
+ - 'app/components/polaris/shopify_navigation_component.rb'
14
28
  ViewComponent/PreferPrivateMethods:
15
- - app/components/polaris/action_list/section_component.rb:16 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
16
- - app/components/polaris/avatar_component.rb:43 # Consider making `style_class` private. Only ViewComponent interface methods should be public.
17
- - app/components/polaris/avatar_component.rb:69 # Consider making `xor_hash` private. Only ViewComponent interface methods should be public.
18
- - app/components/polaris/banner_component.rb:77 # Consider making `default_icon` private. Only ViewComponent interface methods should be public.
19
- - app/components/polaris/base_checkbox.rb:24 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
20
- - app/components/polaris/base_checkbox.rb:40 # Consider making `indeterminate?` private. Only ViewComponent interface methods should be public.
21
- - app/components/polaris/base_radio_button.rb:21 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
22
- - app/components/polaris/button_group_component.rb:60 # Consider making `all_items` private. Only ViewComponent interface methods should be public.
23
- - app/components/polaris/checkbox_component.rb:62 # Consider making `indeterminate?` private. Only ViewComponent interface methods should be public.
24
- - app/components/polaris/choice_list_component.rb:58 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
25
- - app/components/polaris/choice_list_component.rb:62 # Consider making `multiple_choice_allowed?` private. Only ViewComponent interface methods should be public.
26
- - app/components/polaris/collapsible_component.rb:15 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
27
- - app/components/polaris/description_list_component.rb:24 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
28
- - app/components/polaris/dropzone_component.rb:142 # Consider making `drop_actions` private. Only ViewComponent interface methods should be public.
29
- - app/components/polaris/exception_list_component.rb:16 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
30
- - app/components/polaris/filters_component.rb:74 # Consider making `popover_arguments` private. Only ViewComponent interface methods should be public.
31
- - app/components/polaris/form_layout_component.rb:29 # Consider making `all_items` private. Only ViewComponent interface methods should be public.
32
- - app/components/polaris/headless_button.rb:104 # Consider making `html_options` private. Only ViewComponent interface methods should be public.
33
- - app/components/polaris/headless_button.rb:96 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
34
- - app/components/polaris/index_table/cell_component.rb:7 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
35
- - app/components/polaris/inline_error_component.rb:14 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
36
- - app/components/polaris/keyboard_key_component.rb:9 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
37
- - app/components/polaris/label_component.rb:32 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
38
- - app/components/polaris/layout_component.rb:38 # Consider making `all_sections` private. Only ViewComponent interface methods should be public.
39
- - app/components/polaris/modal/section_component.rb:6 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
40
- - app/components/polaris/navigation/item_component.rb:54 # Consider making `item_inner_wrapper_classes` private. Only ViewComponent interface methods should be public.
41
- - app/components/polaris/navigation/item_component.rb:62 # Consider making `link_classes` private. Only ViewComponent interface methods should be public.
42
- - app/components/polaris/navigation/item_component.rb:71 # Consider making `selected_sub_items?` private. Only ViewComponent interface methods should be public.
43
- - app/components/polaris/navigation_component.rb:11 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
44
- - app/components/polaris/navigation_list_component.rb:9 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
45
- - app/components/polaris/new_tabs_component.rb:33 # Consider making `renders?` private. Only ViewComponent interface methods should be public.
46
- - app/components/polaris/option_list/checkbox_component.rb:25 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
47
- - app/components/polaris/option_list/option_component.rb:6 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
48
- - app/components/polaris/option_list/radio_button_component.rb:32 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
49
- - app/components/polaris/option_list/section_component.rb:42 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
50
- - app/components/polaris/page_component.rb:57 # Consider making `title_length` private. Only ViewComponent interface methods should be public.
51
- - app/components/polaris/popover/section_component.rb:6 # Consider making `system_arguments` private. Only ViewComponent interface methods should be public.
52
- - app/components/polaris/popover_component.rb:112 # Consider making `popover_placement` private. Only ViewComponent interface methods should be public.
53
- - app/components/polaris/resource_item/shortcut_actions_component.rb:82 # Consider making `action_list_item_arguments` private. Only ViewComponent interface methods should be public.
54
- - app/components/polaris/resource_list_component.rb:37 # Consider making `resource_string` private. Only ViewComponent interface methods should be public.
55
- - app/components/polaris/resource_list_component.rb:43 # Consider making `count` private. Only ViewComponent interface methods should be public.
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:189 # Consider making `build_item` private. Only ViewComponent interface methods should be public.
6
- - app/components/primer/alpha/action_list.rb:217 # Consider making `build_avatar_item` private. Only ViewComponent interface methods should be public.
7
- - app/components/primer/alpha/action_list.rb:228 # Consider making `single_select?` private. Only ViewComponent interface methods should be public.
8
- - app/components/primer/alpha/action_list.rb:236 # Consider making `allows_selection?` private. Only ViewComponent interface methods should be public.
9
- - app/components/primer/alpha/action_list.rb:240 # Consider making `acts_as_listbox?` private. Only ViewComponent interface methods should be public.
10
- - app/components/primer/alpha/action_list.rb:244 # Consider making `acts_as_menu?` private. Only ViewComponent interface methods should be public.
11
- - app/components/primer/alpha/action_list.rb:248 # Consider making `required_form_arguments_given?` private. Only ViewComponent interface methods should be public.
12
- - app/components/primer/alpha/action_list.rb:257 # Consider making `will_add_item` private. Only ViewComponent interface methods should be public.
13
- - app/components/primer/alpha/action_list.rb:53 # Consider making `custom_element_name` private. Only ViewComponent interface methods should be public.
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:253 # Test instantiates a component but doesn't use `render_inline` or `render_preview`. Test the rendered output instead of component methods directly.
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
- "AllCops" => {
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
- "AllCops" => {
95
+ "ViewComponent/PreferComposition" => {
71
96
  "ViewComponentParentClasses" => ["Primer::Component"]
72
97
  }
73
98
  )
@@ -1,3 +1,7 @@
1
- AllCops:
1
+ ViewComponent:
2
2
  ViewComponentParentClasses:
3
3
  - GovukComponent::Base
4
+
5
+ # x-govuk components do not use ViewComponent previews
6
+ ViewComponent/MissingPreview:
7
+ Enabled: false
@@ -1,4 +1,45 @@
1
- AllCops:
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
- AllCops:
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.1
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: 4.0.6
135
+ rubygems_version: 3.6.9
134
136
  specification_version: 4
135
137
  summary: RuboCop extension for ViewComponent best practices
136
138
  test_files: []