rubocop-view_component 0.5.1 → 0.6.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 +32 -0
- data/CLAUDE.md +4 -4
- data/README.md +29 -4
- data/Rakefile +3 -5
- data/config/default.yml +7 -1
- data/lib/rubocop/cop/view_component/base.rb +17 -7
- data/lib/rubocop/cop/view_component/missing_preview.rb +9 -7
- data/lib/rubocop/cop/view_component/no_global_state.rb +14 -0
- data/lib/rubocop/cop/view_component/prefer_private_methods.rb +15 -13
- data/lib/rubocop/cop/view_component/prefer_slots.rb +10 -15
- data/lib/rubocop/cop/view_component/template_analyzer.rb +19 -19
- data/lib/rubocop/cop/view_component/test_rendered_output.rb +3 -1
- data/lib/rubocop/view_component/version.rb +1 -1
- data/script/verify +13 -16
- data/spec/expected_polaris_failures.yml +0 -1
- data/spec/rubocop/cop/view_component/component_suffix_spec.rb +27 -2
- data/spec/rubocop/cop/view_component/missing_preview_spec.rb +125 -6
- data/spec/rubocop/cop/view_component/no_global_state_spec.rb +13 -0
- data/spec/rubocop/cop/view_component/prefer_private_methods_spec.rb +7 -5
- data/spec/rubocop/cop/view_component/test_rendered_output_spec.rb +1 -1
- data/verification/govuk_rubocop_config.yml +3 -0
- data/verification/polaris_rubocop_config.yml +3 -0
- data/verification/primer_rubocop_config.yml +3 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be0a1c1c9a7489ed311953b5f67c7108dbca8b1f43debef36026ec011781a549
|
|
4
|
+
data.tar.gz: 0b62b04534ab81725ab96d28c0b1931671d92944277ac7b5217f4d36d9d91f9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7fc23bef50c35486b422362f9e738e5347b41eaeb8171f2dfca21884dbd87542f2db1e22618c1b239a1bfd2d878b448cb208539678c37b6e622086c1ee4d2871
|
|
7
|
+
data.tar.gz: f67e8fc0ec682da84318f7078bc5eaffd4a7e9907e9d4e281a224e240e57a13b4f4ec010ef31cce3f20528b4f6fc4ce5acf538385736a837095ef21a90fcf641
|
data/.rubocop.yml
CHANGED
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
plugins:
|
|
2
2
|
- rubocop-internal_affairs
|
|
3
|
+
- rubocop-rake
|
|
4
|
+
- rubocop-rspec
|
|
3
5
|
- rubocop-view_component
|
|
4
6
|
|
|
7
|
+
inherit_mode:
|
|
8
|
+
merge:
|
|
9
|
+
- Exclude
|
|
10
|
+
|
|
11
|
+
AllCops:
|
|
12
|
+
TargetRubyVersion: 3.2
|
|
13
|
+
NewCops: enable
|
|
14
|
+
SuggestExtensions: false
|
|
15
|
+
Exclude:
|
|
16
|
+
- 'verification/**/*'
|
|
17
|
+
- 'script/verify'
|
|
18
|
+
- 'spec/fixtures/**/*'
|
|
19
|
+
|
|
20
|
+
Metrics/BlockLength:
|
|
21
|
+
Exclude:
|
|
22
|
+
- 'spec/**/*'
|
|
23
|
+
- '*.gemspec'
|
|
24
|
+
|
|
25
|
+
RSpec/ExampleLength:
|
|
26
|
+
Max: 20
|
|
27
|
+
|
|
28
|
+
Style/StringLiterals:
|
|
29
|
+
EnforcedStyle: double_quotes
|
|
30
|
+
|
|
31
|
+
Style/StringLiteralsInInterpolation:
|
|
32
|
+
EnforcedStyle: double_quotes
|
|
33
|
+
|
|
34
|
+
InternalAffairs/RedundantLetRuboCopConfigNew:
|
|
35
|
+
Enabled: false
|
|
36
|
+
|
|
5
37
|
Naming/FileName:
|
|
6
38
|
Exclude:
|
|
7
39
|
- lib/rubocop-view_component.rb
|
data/CLAUDE.md
CHANGED
|
@@ -11,7 +11,7 @@ This is a RuboCop extension that enforces ViewComponent best practices. It provi
|
|
|
11
11
|
### Testing
|
|
12
12
|
- `rake spec` - Run all RSpec tests
|
|
13
13
|
- `bundle exec rspec spec/rubocop/cop/view_component/FILENAME_spec.rb` - Run a specific spec file
|
|
14
|
-
- `rake
|
|
14
|
+
- `rake rubocop` - Run RuboCop linting
|
|
15
15
|
- `rake` - Run both tests and linting (default task)
|
|
16
16
|
|
|
17
17
|
### Verification
|
|
@@ -52,17 +52,17 @@ All cops inherit from `RuboCop::Cop::Base` and are located in `lib/rubocop/cop/v
|
|
|
52
52
|
|
|
53
53
|
### Configuration
|
|
54
54
|
|
|
55
|
-
The `
|
|
55
|
+
The `ViewComponent` config supports `ViewComponentParentClasses` to configure additional base classes beyond `ViewComponent::Base` and `ApplicationComponent`:
|
|
56
56
|
|
|
57
57
|
```yaml
|
|
58
|
-
|
|
58
|
+
ViewComponent:
|
|
59
59
|
ViewComponentParentClasses:
|
|
60
60
|
- MyApp::BaseComponent
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
### Verification System
|
|
64
64
|
|
|
65
|
-
The `script/verify` script downloads real component libraries, runs all ViewComponent cops, and compares results to checked-in snapshots. This catches regressions when cop behavior changes. Libraries are configured in `verification/libraries.yml`, downloaded to `verification/LIBRARY/`, and expected results stored in `spec/expected_LIBRARY_failures.
|
|
65
|
+
The `script/verify` script downloads real component libraries, runs all ViewComponent cops, and compares results to checked-in snapshots. This catches regressions when cop behavior changes. Libraries are configured in `verification/libraries.yml`, downloaded to `verification/LIBRARY/`, and expected results stored in `spec/expected_LIBRARY_failures.yml`.
|
|
66
66
|
|
|
67
67
|
## Implementation Notes
|
|
68
68
|
|
data/README.md
CHANGED
|
@@ -27,24 +27,49 @@ 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
|
+
- **ViewComponent/MissingPreview** - Ensure every ViewComponent has a corresponding preview file (requires `PreviewPaths` configuration). Classes listed in `ViewComponentParentClasses` are automatically exempt, as abstract base classes are not rendered standalone.
|
|
31
31
|
|
|
32
32
|
## Optional Configuration
|
|
33
33
|
|
|
34
34
|
### Base Class
|
|
35
35
|
|
|
36
|
-
By default, the cops detect classes that inherit from `ViewComponent::Base` or `ApplicationComponent`.
|
|
36
|
+
By default, the cops detect classes that inherit from `ViewComponent::Base` or `ApplicationComponent`. To add extra parent classes, use `inherit_mode: merge` so the defaults are preserved:
|
|
37
37
|
|
|
38
38
|
```yaml
|
|
39
39
|
# .rubocop.yml
|
|
40
|
-
|
|
40
|
+
ViewComponent:
|
|
41
|
+
inherit_mode:
|
|
42
|
+
merge:
|
|
43
|
+
- ViewComponentParentClasses
|
|
41
44
|
ViewComponentParentClasses:
|
|
42
45
|
- MyApp::BaseComponent
|
|
43
46
|
```
|
|
44
47
|
|
|
48
|
+
To replace the defaults entirely (e.g. if your project has renamed `ApplicationComponent`), omit `inherit_mode`:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
# .rubocop.yml
|
|
52
|
+
ViewComponent:
|
|
53
|
+
ViewComponentParentClasses:
|
|
54
|
+
- ViewComponent::Base
|
|
55
|
+
- MyApp::BaseComponent
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Components Directory
|
|
59
|
+
|
|
60
|
+
Several cops (`ComponentSuffix`, `PreferComposition`, `MissingPreview`, `TestRenderedOutput`) default to running only on files under `app/components/`. If your project uses a different path, override `Include` in your `.rubocop.yml`:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
# .rubocop.yml
|
|
64
|
+
ViewComponent/ComponentSuffix:
|
|
65
|
+
Include:
|
|
66
|
+
- 'app/components/**/*.rb'
|
|
67
|
+
- 'engines/*/app/components/**/*.rb'
|
|
68
|
+
```
|
|
69
|
+
|
|
45
70
|
### No Super
|
|
46
71
|
|
|
47
|
-
|
|
72
|
+
ViewComponent convention is to not call `super` in component initializers, but that may cause `Lint/MissingSuper` failures from RuboCop. We suggest disabling that rule for your view components directory, for example:
|
|
48
73
|
|
|
49
74
|
```yaml
|
|
50
75
|
# .rubocop.yml
|
data/Rakefile
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
|
-
require "
|
|
4
|
+
require "rubocop/rake_task"
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
RuboCop::RakeTask.new
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
task default: %i[spec standard]
|
|
8
|
+
task default: %i[spec rubocop]
|
|
11
9
|
|
|
12
10
|
require "rspec/core/rake_task"
|
|
13
11
|
|
data/config/default.yml
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
ViewComponent:
|
|
2
|
-
ViewComponentParentClasses:
|
|
2
|
+
ViewComponentParentClasses:
|
|
3
|
+
- ViewComponent::Base
|
|
4
|
+
- ApplicationComponent
|
|
3
5
|
ComponentNamespaces: []
|
|
4
6
|
|
|
5
7
|
ViewComponent/ComponentSuffix:
|
|
@@ -8,6 +10,8 @@ ViewComponent/ComponentSuffix:
|
|
|
8
10
|
VersionAdded: '0.1'
|
|
9
11
|
Severity: convention
|
|
10
12
|
StyleGuide: 'https://viewcomponent.org/best_practices.html'
|
|
13
|
+
Include:
|
|
14
|
+
- 'app/components/**/*.rb'
|
|
11
15
|
|
|
12
16
|
ViewComponent/MissingPreview:
|
|
13
17
|
Description: 'Ensure every ViewComponent has a corresponding preview file.'
|
|
@@ -52,6 +56,8 @@ ViewComponent/PreferComposition:
|
|
|
52
56
|
VersionAdded: '0.3'
|
|
53
57
|
Severity: convention
|
|
54
58
|
StyleGuide: 'https://viewcomponent.org/best_practices.html#avoid-inheritance'
|
|
59
|
+
Include:
|
|
60
|
+
- 'app/components/**/*.rb'
|
|
55
61
|
ComponentNamespaces: []
|
|
56
62
|
|
|
57
63
|
ViewComponent/PreferSlots:
|
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
|
9
9
|
def view_component_class?(node)
|
|
10
10
|
return false unless node&.class_type?
|
|
11
11
|
|
|
12
|
-
class_source = node
|
|
12
|
+
class_source = fully_qualified_name(node)
|
|
13
13
|
return true if component_namespaces.any? { |ns| class_source.start_with?(ns) }
|
|
14
14
|
|
|
15
15
|
parent_class = node.parent_class
|
|
@@ -18,16 +18,26 @@ module RuboCop
|
|
|
18
18
|
view_component_parent?(parent_class)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
# Check if node represents
|
|
22
|
-
# or a configured additional parent class
|
|
21
|
+
# Check if node represents a configured parent class
|
|
23
22
|
def view_component_parent?(node)
|
|
24
23
|
return false unless node.const_type?
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
(cop_config["ViewComponentParentClasses"] || []).include?(node.source)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if a class node is itself one of the registered parent classes.
|
|
29
|
+
def view_component_parent_class?(node)
|
|
30
|
+
return false unless node&.class_type?
|
|
31
|
+
|
|
32
|
+
(cop_config["ViewComponentParentClasses"] || []).include?(fully_qualified_name(node))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fully_qualified_name(node)
|
|
36
|
+
namespace = node.parent_module_name
|
|
37
|
+
short_name = node.identifier.source
|
|
38
|
+
return short_name if namespace.nil? || namespace == "Object"
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
additional.include?(source)
|
|
40
|
+
"#{namespace}::#{short_name}"
|
|
31
41
|
end
|
|
32
42
|
|
|
33
43
|
def component_namespaces
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/inflector"
|
|
4
|
+
|
|
3
5
|
module RuboCop
|
|
4
6
|
module Cop
|
|
5
7
|
module ViewComponent
|
|
@@ -15,8 +17,9 @@ module RuboCop
|
|
|
15
17
|
|
|
16
18
|
def on_class(node)
|
|
17
19
|
return unless view_component_class?(node)
|
|
20
|
+
return if view_component_parent_class?(node)
|
|
18
21
|
|
|
19
|
-
class_name = node
|
|
22
|
+
class_name = fully_qualified_name(node)
|
|
20
23
|
return if preview_exists?(class_name)
|
|
21
24
|
|
|
22
25
|
add_offense(node.identifier, message: format(MSG, component: class_name, paths: preview_paths.join(", ")))
|
|
@@ -33,12 +36,11 @@ module RuboCop
|
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def candidate_filenames(class_name)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
]
|
|
39
|
+
bases = [ActiveSupport::Inflector.underscore(class_name.delete_suffix("Component"))]
|
|
40
|
+
short_name = class_name.split("::").last
|
|
41
|
+
short_base = ActiveSupport::Inflector.underscore(short_name.delete_suffix("Component"))
|
|
42
|
+
bases << short_base if short_base != bases.first
|
|
43
|
+
bases.flat_map { |base| ["#{base}_preview.rb", "#{base}_component_preview.rb"] }
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def preview_paths
|
|
@@ -45,18 +45,32 @@ module RuboCop
|
|
|
45
45
|
|
|
46
46
|
RESTRICT_ON_SEND = GLOBAL_STATE_METHODS
|
|
47
47
|
|
|
48
|
+
# @!method global_state_access?(node)
|
|
48
49
|
def_node_matcher :global_state_access?, <<~PATTERN
|
|
49
50
|
(send nil? ${:params :request :session :cookies :flash} ...)
|
|
50
51
|
PATTERN
|
|
51
52
|
|
|
53
|
+
# @!method sorbet_sig_block?(node)
|
|
54
|
+
def_node_matcher :sorbet_sig_block?, <<~PATTERN
|
|
55
|
+
(block (send nil? :sig) ...)
|
|
56
|
+
PATTERN
|
|
57
|
+
|
|
52
58
|
def on_send(node)
|
|
53
59
|
return unless inside_view_component?(node)
|
|
54
60
|
|
|
55
61
|
method_name = global_state_access?(node)
|
|
56
62
|
return unless method_name
|
|
63
|
+
return if sorbet_signature?(node)
|
|
57
64
|
|
|
58
65
|
add_offense(node, message: format(MSG, method: method_name))
|
|
59
66
|
end
|
|
67
|
+
alias on_csend on_send
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def sorbet_signature?(node)
|
|
72
|
+
node.each_ancestor(:block).any? { |ancestor| sorbet_sig_block?(ancestor) }
|
|
73
|
+
end
|
|
60
74
|
end
|
|
61
75
|
end
|
|
62
76
|
end
|
|
@@ -40,29 +40,32 @@ module RuboCop
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
42
|
def check_public_methods(class_node)
|
|
43
|
-
current_visibility = :public
|
|
44
|
-
template_method_calls = methods_called_in_templates
|
|
45
|
-
|
|
46
43
|
body = class_node.body
|
|
47
44
|
return unless body
|
|
48
45
|
|
|
49
46
|
children = body.begin_type? ? body.children : [body]
|
|
47
|
+
check_children(children, methods_called_in_templates)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def check_children(children, template_method_calls)
|
|
51
|
+
current_visibility = :public
|
|
50
52
|
|
|
51
53
|
children.each do |child|
|
|
52
54
|
if visibility_modifier?(child)
|
|
53
55
|
current_visibility = child.method_name
|
|
54
|
-
|
|
56
|
+
elsif should_flag?(child, current_visibility, template_method_calls)
|
|
57
|
+
add_offense(child, message: format(MSG, method_name: child.method_name))
|
|
55
58
|
end
|
|
56
|
-
|
|
57
|
-
next unless child.def_type?
|
|
58
|
-
next unless current_visibility == :public
|
|
59
|
-
next if allowed_public_method?(child.method_name)
|
|
60
|
-
next if template_method_calls.include?(child.method_name)
|
|
61
|
-
|
|
62
|
-
add_offense(child, message: format(MSG, method_name: child.method_name))
|
|
63
59
|
end
|
|
64
60
|
end
|
|
65
61
|
|
|
62
|
+
def should_flag?(child, visibility, template_method_calls)
|
|
63
|
+
child.def_type? &&
|
|
64
|
+
visibility == :public &&
|
|
65
|
+
!allowed_public_method?(child.method_name) &&
|
|
66
|
+
!template_method_calls.include?(child.method_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
66
69
|
def allowed_public_method?(method_name)
|
|
67
70
|
allowed_public_methods.include?(method_name.to_s) ||
|
|
68
71
|
allowed_public_method_patterns.any? { |pattern| method_name.to_s.match?(pattern) }
|
|
@@ -84,8 +87,7 @@ module RuboCop
|
|
|
84
87
|
template_paths.each_with_object(Set.new) do |path, methods|
|
|
85
88
|
methods.merge(extract_method_calls(path))
|
|
86
89
|
end
|
|
87
|
-
rescue => e
|
|
88
|
-
# Graceful degradation on errors
|
|
90
|
+
rescue StandardError => e
|
|
89
91
|
warn "Warning: Failed to analyze templates: #{e.message}" if ENV["RUBOCOP_DEBUG"]
|
|
90
92
|
Set.new
|
|
91
93
|
end
|
|
@@ -31,6 +31,7 @@ module RuboCop
|
|
|
31
31
|
|
|
32
32
|
HTML_PARAM_PATTERN = /_html$/
|
|
33
33
|
|
|
34
|
+
# @!method html_safe_call?(node)
|
|
34
35
|
def_node_search :html_safe_call?, "(send _ :html_safe)"
|
|
35
36
|
|
|
36
37
|
def on_class(node)
|
|
@@ -46,29 +47,23 @@ module RuboCop
|
|
|
46
47
|
|
|
47
48
|
def find_initialize(class_node)
|
|
48
49
|
class_node.each_descendant(:def).find do |def_node|
|
|
49
|
-
def_node.
|
|
50
|
+
def_node.method?(:initialize)
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def check_initialize_params(initialize_node)
|
|
54
55
|
initialize_node.arguments.each do |arg|
|
|
55
|
-
next unless arg.
|
|
56
|
+
next unless arg.type?(:kwoptarg, :kwarg)
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
check_param(arg)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
add_offense(arg, message: format(MSG, slot_method: suggested_slot))
|
|
63
|
-
next
|
|
64
|
-
end
|
|
62
|
+
def check_param(arg)
|
|
63
|
+
param_name = arg.children[0]
|
|
64
|
+
return unless html_param_name?(param_name) || (arg.kwoptarg_type? && html_safe_call?(arg))
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
if arg.kwoptarg_type? && html_safe_call?(arg)
|
|
68
|
-
suggested_slot = suggest_slot_name(param_name)
|
|
69
|
-
add_offense(arg, message: format(MSG, slot_method: suggested_slot))
|
|
70
|
-
end
|
|
71
|
-
end
|
|
66
|
+
add_offense(arg, message: format(MSG, slot_method: suggest_slot_name(param_name)))
|
|
72
67
|
end
|
|
73
68
|
|
|
74
69
|
def html_param_name?(name)
|
|
@@ -16,26 +16,26 @@ module RuboCop
|
|
|
16
16
|
base_path = component_path.sub(/\.rb$/, "")
|
|
17
17
|
component_dir = File.dirname(component_path)
|
|
18
18
|
component_name = File.basename(component_path, ".rb")
|
|
19
|
+
sidecar_dir = File.join(component_dir, component_name)
|
|
19
20
|
|
|
20
|
-
paths =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
sibling_template = "#{base_path}.html.erb"
|
|
24
|
-
paths << sibling_template if File.exist?(sibling_template)
|
|
25
|
-
|
|
26
|
-
# Check for sidecar template: same_name/same_name.html.erb
|
|
27
|
-
sidecar_template = File.join(component_dir, component_name, "#{component_name}.html.erb")
|
|
28
|
-
paths << sidecar_template if File.exist?(sidecar_template)
|
|
29
|
-
|
|
30
|
-
# Check for variants: same_name.*.html.erb
|
|
31
|
-
variant_pattern = "#{base_path}.*.html.erb"
|
|
32
|
-
paths.concat(Dir.glob(variant_pattern))
|
|
21
|
+
paths = sibling_templates(base_path) + sidecar_templates(sidecar_dir, component_name)
|
|
22
|
+
paths.uniq
|
|
23
|
+
end
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
def sibling_templates(base_path)
|
|
26
|
+
paths = []
|
|
27
|
+
sibling = "#{base_path}.html.erb"
|
|
28
|
+
paths << sibling if File.exist?(sibling)
|
|
29
|
+
paths.concat(Dir.glob("#{base_path}.*.html.erb"))
|
|
30
|
+
paths
|
|
31
|
+
end
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
def sidecar_templates(sidecar_dir, component_name)
|
|
34
|
+
paths = []
|
|
35
|
+
sidecar = File.join(sidecar_dir, "#{component_name}.html.erb")
|
|
36
|
+
paths << sidecar if File.exist?(sidecar)
|
|
37
|
+
paths.concat(Dir.glob(File.join(sidecar_dir, "#{component_name}.*.html.erb")))
|
|
38
|
+
paths
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Extract method calls from an ERB template
|
|
@@ -49,7 +49,7 @@ module RuboCop
|
|
|
49
49
|
|
|
50
50
|
# Parse the extracted Ruby code
|
|
51
51
|
parse_ruby_for_method_calls(ruby_code)
|
|
52
|
-
rescue => e
|
|
52
|
+
rescue StandardError => e
|
|
53
53
|
# Graceful degradation on parse errors
|
|
54
54
|
warn "Warning: Failed to parse template #{template_path}: #{e.message}" if ENV["RUBOCOP_DEBUG"]
|
|
55
55
|
Set.new
|
|
@@ -78,7 +78,7 @@ module RuboCop
|
|
|
78
78
|
return unless node.respond_to?(:type)
|
|
79
79
|
|
|
80
80
|
# Look for send nodes with nil receiver (local method calls)
|
|
81
|
-
if node.
|
|
81
|
+
if node.send_type? && node.receiver.nil?
|
|
82
82
|
method_name = node.method_name
|
|
83
83
|
method_calls.add(method_name)
|
|
84
84
|
end
|
|
@@ -28,6 +28,7 @@ module RuboCop
|
|
|
28
28
|
# Check Minitest-style test methods
|
|
29
29
|
def on_def(node)
|
|
30
30
|
return unless within_test_paths?
|
|
31
|
+
|
|
31
32
|
method_name = node.method_name.to_s
|
|
32
33
|
return unless method_name.start_with?("test_")
|
|
33
34
|
return unless instantiates_component?(node)
|
|
@@ -45,6 +46,7 @@ module RuboCop
|
|
|
45
46
|
|
|
46
47
|
add_offense(node)
|
|
47
48
|
end
|
|
49
|
+
alias on_numblock on_block
|
|
48
50
|
|
|
49
51
|
private
|
|
50
52
|
|
|
@@ -58,7 +60,7 @@ module RuboCop
|
|
|
58
60
|
|
|
59
61
|
def instantiates_component?(node)
|
|
60
62
|
node.each_descendant(:send).any? do |send_node|
|
|
61
|
-
next unless send_node.
|
|
63
|
+
next unless send_node.method?(:new)
|
|
62
64
|
|
|
63
65
|
send_node.receiver&.const_type?
|
|
64
66
|
end
|
data/script/verify
CHANGED
|
@@ -68,8 +68,8 @@ def parse_library_arg(libraries)
|
|
|
68
68
|
library_arg = ARGV.find { |arg| !arg.start_with?("--") }
|
|
69
69
|
|
|
70
70
|
if library_arg.nil?
|
|
71
|
-
abort "Usage: #{$PROGRAM_NAME} <library> [--regenerate] [--update]\n" \
|
|
72
|
-
"
|
|
71
|
+
abort "Usage: #{$PROGRAM_NAME} <library> [--regenerate] [--update]\n " \
|
|
72
|
+
"library: #{libraries.keys.join(", ")}"
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
unless libraries.key?(library_arg)
|
|
@@ -79,8 +79,8 @@ def parse_library_arg(libraries)
|
|
|
79
79
|
library_arg
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
def system!(*
|
|
83
|
-
system(
|
|
82
|
+
def system!(*)
|
|
83
|
+
system(*, exception: true)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def download_source(dir, tarball_url, display_name)
|
|
@@ -97,10 +97,10 @@ def configure_rubocop(config_file, display_name)
|
|
|
97
97
|
# Merge the library config into the existing config
|
|
98
98
|
library_config.each do |key, value|
|
|
99
99
|
config[key] = if config[key].is_a?(Hash) && value.is_a?(Hash)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
config[key].merge(value)
|
|
101
|
+
else
|
|
102
|
+
value
|
|
103
|
+
end
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
File.write(".rubocop.yml", YAML.dump(config))
|
|
@@ -118,16 +118,15 @@ def run_rubocop
|
|
|
118
118
|
puts "Running RuboCop (ViewComponent cops only)..."
|
|
119
119
|
output, status = Open3.capture2(
|
|
120
120
|
"bundle", "exec", "rubocop",
|
|
121
|
-
"--
|
|
121
|
+
"--plugin", "rubocop-view_component",
|
|
122
122
|
"--only", "ViewComponent",
|
|
123
|
+
"--config", ".rubocop.yml",
|
|
123
124
|
"--format", "json"
|
|
124
125
|
)
|
|
125
126
|
|
|
126
127
|
puts "RuboCop exit status: #{status.exitstatus}"
|
|
127
128
|
|
|
128
|
-
if output.strip.empty?
|
|
129
|
-
abort "ERROR: RuboCop produced no output (exit status: #{status.exitstatus})"
|
|
130
|
-
end
|
|
129
|
+
abort "ERROR: RuboCop produced no output (exit status: #{status.exitstatus})" if output.strip.empty?
|
|
131
130
|
|
|
132
131
|
output
|
|
133
132
|
end
|
|
@@ -166,11 +165,9 @@ def load_expected(results_file)
|
|
|
166
165
|
end
|
|
167
166
|
|
|
168
167
|
def verify(offenses, results_file)
|
|
169
|
-
unless File.exist?(results_file)
|
|
170
|
-
abort "ERROR: #{results_file} not found. Run '#{$PROGRAM_NAME} --regenerate' first."
|
|
171
|
-
end
|
|
168
|
+
abort "ERROR: #{results_file} not found. Run '#{$PROGRAM_NAME} --regenerate' first." unless File.exist?(results_file)
|
|
172
169
|
|
|
173
|
-
expected = load_expected(results_file)
|
|
170
|
+
expected = load_expected(results_file) || {}
|
|
174
171
|
|
|
175
172
|
if offenses == expected
|
|
176
173
|
puts "Verification passed: output matches #{results_file}"
|
|
@@ -22,7 +22,6 @@ ViewComponent/MissingPreview:
|
|
|
22
22
|
- 'app/components/polaris/placeholder_component.rb'
|
|
23
23
|
- 'app/components/polaris/shopify_navigation_component.rb'
|
|
24
24
|
- 'app/components/polaris/text_field_component.rb'
|
|
25
|
-
- 'app/components/polaris/toast_component.rb'
|
|
26
25
|
ViewComponent/NoGlobalState:
|
|
27
26
|
- 'app/components/polaris/shopify_navigation_component.rb'
|
|
28
27
|
ViewComponent/PreferPrivateMethods:
|
|
@@ -74,11 +74,11 @@ RSpec.describe RuboCop::Cop::ViewComponent::ComponentSuffix, :config do
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
context "when ViewComponentParentClasses is configured" do
|
|
77
|
+
context "when ViewComponentParentClasses is configured with merge" do
|
|
78
78
|
let(:config) do
|
|
79
79
|
RuboCop::Config.new(
|
|
80
80
|
"ViewComponent/ComponentSuffix" => {
|
|
81
|
-
"ViewComponentParentClasses" => ["Primer::Component"]
|
|
81
|
+
"ViewComponentParentClasses" => ["ViewComponent::Base", "ApplicationComponent", "Primer::Component"]
|
|
82
82
|
}
|
|
83
83
|
)
|
|
84
84
|
end
|
|
@@ -107,6 +107,31 @@ RSpec.describe RuboCop::Cop::ViewComponent::ComponentSuffix, :config do
|
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
context "when ViewComponentParentClasses is configured replacing defaults" do
|
|
111
|
+
let(:config) do
|
|
112
|
+
RuboCop::Config.new(
|
|
113
|
+
"ViewComponent/ComponentSuffix" => {
|
|
114
|
+
"ViewComponentParentClasses" => ["Primer::Component"]
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "registers an offense for classes inheriting from a configured parent" do
|
|
120
|
+
expect_offense(<<~RUBY)
|
|
121
|
+
class FooBar < Primer::Component
|
|
122
|
+
^^^^^^ ViewComponent class names should end with `Component`.
|
|
123
|
+
end
|
|
124
|
+
RUBY
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "does not recognize default parent classes that were replaced" do
|
|
128
|
+
expect_no_offenses(<<~RUBY)
|
|
129
|
+
class FooBar < ViewComponent::Base
|
|
130
|
+
end
|
|
131
|
+
RUBY
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
110
135
|
context "with compact nested class syntax" do
|
|
111
136
|
it "registers offense for compact syntax" do
|
|
112
137
|
expect_offense(<<~RUBY)
|
|
@@ -5,14 +5,16 @@ RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
|
5
5
|
RuboCop::Config.new(
|
|
6
6
|
"ViewComponent/MissingPreview" => {
|
|
7
7
|
"Enabled" => true,
|
|
8
|
-
"PreviewPaths" => ["/previews"]
|
|
8
|
+
"PreviewPaths" => ["/previews"],
|
|
9
|
+
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
9
10
|
}
|
|
10
11
|
)
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
context "when a preview file exists" do
|
|
14
15
|
it "does not register an offense" do
|
|
15
|
-
allow(File).to receive(:exist?).and_return(
|
|
16
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
17
|
+
allow(File).to receive(:exist?).with("/previews/user_preview.rb").and_return(true)
|
|
16
18
|
|
|
17
19
|
expect_no_offenses(<<~RUBY, "/app/components/user_component.rb")
|
|
18
20
|
class UserComponent < ViewComponent::Base
|
|
@@ -23,7 +25,8 @@ RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
|
23
25
|
|
|
24
26
|
context "when no preview file exists" do
|
|
25
27
|
it "registers an offense" do
|
|
26
|
-
allow(File).to receive(:exist?).and_return(false)
|
|
28
|
+
allow(File).to receive(:exist?).with("/previews/user_preview.rb").and_return(false)
|
|
29
|
+
allow(File).to receive(:exist?).with("/previews/user_component_preview.rb").and_return(false)
|
|
27
30
|
|
|
28
31
|
expect_offense(<<~RUBY, "/app/components/user_component.rb")
|
|
29
32
|
class UserComponent < ViewComponent::Base
|
|
@@ -45,7 +48,10 @@ RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
it "registers an offense for a component in a configured namespace" do
|
|
48
|
-
allow(File).to receive(:exist?).and_return(false)
|
|
51
|
+
allow(File).to receive(:exist?).with("/previews/v2/table_preview.rb").and_return(false)
|
|
52
|
+
allow(File).to receive(:exist?).with("/previews/v2/table_component_preview.rb").and_return(false)
|
|
53
|
+
allow(File).to receive(:exist?).with("/previews/table_preview.rb").and_return(false)
|
|
54
|
+
allow(File).to receive(:exist?).with("/previews/table_component_preview.rb").and_return(false)
|
|
49
55
|
|
|
50
56
|
expect_offense(<<~RUBY, "/app/components/v2/table.rb")
|
|
51
57
|
class V2::Table < SomeBase
|
|
@@ -82,16 +88,129 @@ RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
|
82
88
|
end
|
|
83
89
|
RUBY
|
|
84
90
|
end
|
|
91
|
+
|
|
92
|
+
it "registers an offense for a component declared with nested module form when no preview exists" do
|
|
93
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
94
|
+
|
|
95
|
+
expect_offense(<<~RUBY, "/app/components/v2/table.rb")
|
|
96
|
+
module V2
|
|
97
|
+
class Table < SomeBase
|
|
98
|
+
^^^^^ No preview found for V2::Table (looked in: /previews).
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
RUBY
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "does not register an offense for a component declared with nested module form when preview exists" do
|
|
105
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
106
|
+
allow(File).to receive(:exist?).with("/previews/v2/grouped_multi_select_preview.rb").and_return(true)
|
|
107
|
+
|
|
108
|
+
expect_no_offenses(<<~RUBY, "/app/components/v2/grouped_multi_select.rb")
|
|
109
|
+
module V2
|
|
110
|
+
class GroupedMultiSelect < V2::MultiSelect
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
RUBY
|
|
114
|
+
end
|
|
85
115
|
end
|
|
86
116
|
|
|
87
|
-
context "when
|
|
88
|
-
it "does not register an offense" do
|
|
117
|
+
context "when the component is declared with nested module form" do
|
|
118
|
+
it "does not register an offense when preview exists at the namespaced path" do
|
|
119
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
120
|
+
allow(File).to receive(:exist?).with("/previews/admin/page_component_preview.rb").and_return(true)
|
|
121
|
+
|
|
122
|
+
expect_no_offenses(<<~RUBY, "/app/components/admin/page_component.rb")
|
|
123
|
+
module Admin
|
|
124
|
+
class PageComponent < ApplicationComponent
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
RUBY
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "registers an offense when no preview exists" do
|
|
89
131
|
allow(File).to receive(:exist?).and_return(false)
|
|
90
132
|
|
|
133
|
+
expect_offense(<<~RUBY, "/app/components/admin/page_component.rb")
|
|
134
|
+
module Admin
|
|
135
|
+
class PageComponent < ApplicationComponent
|
|
136
|
+
^^^^^^^^^^^^^ No preview found for Admin::PageComponent (looked in: /previews).
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
RUBY
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "when the namespace contains an acronym" do
|
|
144
|
+
it "does not register an offense when preview exists at the correct path" do
|
|
145
|
+
ActiveSupport::Inflector.inflections(:en) { |i| i.acronym "UI" }
|
|
146
|
+
|
|
147
|
+
allow(File).to receive(:exist?).and_return(false)
|
|
148
|
+
allow(File).to receive(:exist?).with("/previews/ui/button_component_preview.rb").and_return(true)
|
|
149
|
+
|
|
150
|
+
expect_no_offenses(<<~RUBY, "/app/components/ui/button_component.rb")
|
|
151
|
+
module UI
|
|
152
|
+
class ButtonComponent < ViewComponent::Base
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
RUBY
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context "when not a ViewComponent" do
|
|
160
|
+
it "does not register an offense" do
|
|
91
161
|
expect_no_offenses(<<~RUBY, "/app/components/user_component.rb")
|
|
92
162
|
class UserComponent
|
|
93
163
|
end
|
|
94
164
|
RUBY
|
|
95
165
|
end
|
|
96
166
|
end
|
|
167
|
+
|
|
168
|
+
context "when the class is itself a registered parent class" do
|
|
169
|
+
it "does not register an offense for a built-in parent class" do
|
|
170
|
+
expect_no_offenses(<<~RUBY, "/app/components/application_component.rb")
|
|
171
|
+
class ApplicationComponent < ViewComponent::Base
|
|
172
|
+
end
|
|
173
|
+
RUBY
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "when a custom parent class is configured" do
|
|
177
|
+
let(:config) do
|
|
178
|
+
RuboCop::Config.new(
|
|
179
|
+
"ViewComponent/MissingPreview" => {
|
|
180
|
+
"Enabled" => true,
|
|
181
|
+
"PreviewPaths" => ["/previews"],
|
|
182
|
+
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent BaseComponent]
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "does not register an offense for the custom parent class" do
|
|
188
|
+
expect_no_offenses(<<~RUBY, "/app/components/base_component.rb")
|
|
189
|
+
class BaseComponent < ViewComponent::Base
|
|
190
|
+
end
|
|
191
|
+
RUBY
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
context "when a namespaced custom parent class is configured" do
|
|
196
|
+
let(:config) do
|
|
197
|
+
RuboCop::Config.new(
|
|
198
|
+
"ViewComponent/MissingPreview" => {
|
|
199
|
+
"Enabled" => true,
|
|
200
|
+
"PreviewPaths" => ["/previews"],
|
|
201
|
+
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent MyApp::BaseComponent]
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "does not register an offense for the namespaced custom parent class" do
|
|
207
|
+
expect_no_offenses(<<~RUBY, "/app/components/my_app/base_component.rb")
|
|
208
|
+
module MyApp
|
|
209
|
+
class BaseComponent < ViewComponent::Base
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
RUBY
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
97
216
|
end
|
|
@@ -105,6 +105,19 @@ RSpec.describe RuboCop::Cop::ViewComponent::NoGlobalState, :config do
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
context "with Sorbet signatures" do
|
|
109
|
+
it "does not register offense for params in sig block" do
|
|
110
|
+
expect_no_offenses(<<~RUBY)
|
|
111
|
+
class UserComponent < ViewComponent::Base
|
|
112
|
+
sig { params(descriptor: String, admin_view: T::Boolean).returns(T.nilable(String)) }
|
|
113
|
+
def call(descriptor:, admin_view:)
|
|
114
|
+
@descriptor = descriptor
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
RUBY
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
108
121
|
context "when not in a ViewComponent" do
|
|
109
122
|
it "does not register offense in regular classes" do
|
|
110
123
|
expect_no_offenses(<<~RUBY)
|
|
@@ -144,10 +144,11 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
144
144
|
context "with custom patterns" do
|
|
145
145
|
let(:config) do
|
|
146
146
|
RuboCop::Config.new(
|
|
147
|
-
"AllCops" => {"DisplayCopNames" => true},
|
|
147
|
+
"AllCops" => { "DisplayCopNames" => true },
|
|
148
148
|
"ViewComponent/PreferPrivateMethods" => {
|
|
149
149
|
"AllowedPublicMethods" => %w[initialize call],
|
|
150
|
-
"AllowedPublicMethodPatterns" => ["^render_", "^with_"]
|
|
150
|
+
"AllowedPublicMethodPatterns" => ["^render_", "^with_"],
|
|
151
|
+
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
151
152
|
}
|
|
152
153
|
)
|
|
153
154
|
end
|
|
@@ -186,10 +187,11 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
186
187
|
context "with custom AllowedPublicMethods" do
|
|
187
188
|
let(:config) do
|
|
188
189
|
RuboCop::Config.new(
|
|
189
|
-
"AllCops" => {"DisplayCopNames" => true},
|
|
190
|
+
"AllCops" => { "DisplayCopNames" => true },
|
|
190
191
|
"ViewComponent/PreferPrivateMethods" => {
|
|
191
192
|
"AllowedPublicMethods" => %w[initialize call custom_public_method],
|
|
192
|
-
"AllowedPublicMethodPatterns" => []
|
|
193
|
+
"AllowedPublicMethodPatterns" => [],
|
|
194
|
+
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
193
195
|
}
|
|
194
196
|
)
|
|
195
197
|
end
|
|
@@ -261,7 +263,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
261
263
|
context "with template files" do
|
|
262
264
|
let(:component_file) { "spec/fixtures/components/template_method_component.rb" }
|
|
263
265
|
|
|
264
|
-
it "
|
|
266
|
+
it "registers methods called in template, but flags unused methods" do
|
|
265
267
|
expect_offense(<<~RUBY, component_file)
|
|
266
268
|
# frozen_string_literal: true
|
|
267
269
|
|
|
@@ -99,7 +99,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::TestRenderedOutput, :config do
|
|
|
99
99
|
context "with TestPaths configured" do
|
|
100
100
|
let(:config) do
|
|
101
101
|
RuboCop::Config.new(
|
|
102
|
-
"AllCops" => {"DisplayCopNames" => true},
|
|
102
|
+
"AllCops" => { "DisplayCopNames" => true },
|
|
103
103
|
"ViewComponent/TestRenderedOutput" => {
|
|
104
104
|
"TestPaths" => ["spec/components/v2/"]
|
|
105
105
|
}
|
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.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Waite
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: herb
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -118,6 +132,7 @@ metadata:
|
|
|
118
132
|
homepage_uri: https://github.com/andyw8/rubocop-view_component
|
|
119
133
|
source_code_uri: https://github.com/andyw8/rubocop-view_component
|
|
120
134
|
default_lint_roller_plugin: RuboCop::ViewComponent::Plugin
|
|
135
|
+
rubygems_mfa_required: 'true'
|
|
121
136
|
rdoc_options: []
|
|
122
137
|
require_paths:
|
|
123
138
|
- lib
|
|
@@ -125,7 +140,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
125
140
|
requirements:
|
|
126
141
|
- - ">="
|
|
127
142
|
- !ruby/object:Gem::Version
|
|
128
|
-
version: 2.
|
|
143
|
+
version: 3.2.0
|
|
129
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
145
|
requirements:
|
|
131
146
|
- - ">="
|