rubocop-view_component 0.6.0 → 0.7.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 +1 -0
- data/Appraisals +5 -0
- data/CLAUDE.md +4 -12
- data/README.md +11 -25
- data/config/default.yml +0 -3
- data/gemfiles/rubocop_minimum.gemfile +13 -0
- data/gemfiles/rubocop_minimum.gemfile.lock +162 -0
- data/lib/rubocop/cop/view_component/base.rb +38 -6
- data/lib/rubocop/cop/view_component/test_rendered_output.rb +1 -0
- data/lib/rubocop/view_component/version.rb +1 -1
- data/script/verify +3 -1
- data/spec/expected_govuk_failures.yml +2 -0
- data/spec/expected_primer_failures.yml +3 -0
- data/spec/rubocop/cop/view_component/base_spec.rb +92 -0
- data/spec/rubocop/cop/view_component/component_suffix_spec.rb +0 -58
- data/spec/rubocop/cop/view_component/missing_preview_spec.rb +1 -42
- data/spec/rubocop/cop/view_component/prefer_composition_spec.rb +0 -17
- data/spec/rubocop/cop/view_component/prefer_private_methods_spec.rb +2 -4
- data/spec/spec_helper.rb +10 -0
- data/spec/support/project_index_helpers.rb +71 -0
- data/verification/govuk_rubocop_config.yml +3 -6
- data/verification/polaris_rubocop_config.yml +3 -7
- data/verification/primer_rubocop_config.yml +5 -7
- metadata +23 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa0434208243f306c17f51b681abdf59f6152d6afdd0014c608c5b4e08e893c2
|
|
4
|
+
data.tar.gz: e709241be864e024f8ce410b6620d62bcdd12cd090e2593b378c0169a628f1e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d0a9815a58ab3326c9366e7abb2d85cd18e1c87216c731c2fccfeba3d70eae2188856b1c619ba9be2d37c776d463d00efe4f2b7d680998e6e7679b582ab22da
|
|
7
|
+
data.tar.gz: f195f94ca71e89a4f807a8074ddac30a65462faab27dc01b18f1f58f4a29d85df5699a4dcead7a670e7f311b3fe87816cd49ae98b3813540fa2e4c0707273fc8
|
data/.rubocop.yml
CHANGED
data/Appraisals
ADDED
data/CLAUDE.md
CHANGED
|
@@ -40,8 +40,10 @@ All cops inherit from `RuboCop::Cop::Base` and are located in `lib/rubocop/cop/v
|
|
|
40
40
|
### Shared Modules
|
|
41
41
|
|
|
42
42
|
**`ViewComponent::Base`** (`lib/rubocop/cop/view_component/base.rb`)
|
|
43
|
-
-
|
|
44
|
-
- Provides `
|
|
43
|
+
- Includes `RuboCop::Cop::ProjectIndexHelp` for rubydex integration
|
|
44
|
+
- Provides `view_component_class?(node)` - Detects ViewComponent classes via ancestry resolution
|
|
45
|
+
- Provides `view_component_parent?(node)` - Checks if inheriting from ViewComponent::Base
|
|
46
|
+
- Provides `view_component_parent_class?(node)` - Checks if a class is an abstract parent (has descendants)
|
|
45
47
|
- Provides `inside_view_component?(node)` - Checks if code is within a ViewComponent
|
|
46
48
|
|
|
47
49
|
**`TemplateAnalyzer`** (`lib/rubocop/cop/view_component/template_analyzer.rb`)
|
|
@@ -50,16 +52,6 @@ All cops inherit from `RuboCop::Cop::Base` and are located in `lib/rubocop/cop/v
|
|
|
50
52
|
- Handles both sibling templates (`component.html.erb`) and sidecar templates (`component/component.html.erb`)
|
|
51
53
|
- Uses the `herb` gem to parse ERB and extract Ruby code
|
|
52
54
|
|
|
53
|
-
### Configuration
|
|
54
|
-
|
|
55
|
-
The `ViewComponent` config supports `ViewComponentParentClasses` to configure additional base classes beyond `ViewComponent::Base` and `ApplicationComponent`:
|
|
56
|
-
|
|
57
|
-
```yaml
|
|
58
|
-
ViewComponent:
|
|
59
|
-
ViewComponentParentClasses:
|
|
60
|
-
- MyApp::BaseComponent
|
|
61
|
-
```
|
|
62
|
-
|
|
63
55
|
### Verification System
|
|
64
56
|
|
|
65
57
|
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`.
|
data/README.md
CHANGED
|
@@ -16,6 +16,16 @@ Add to your `.rubocop.yml`:
|
|
|
16
16
|
require:
|
|
17
17
|
- rubocop-view_component
|
|
18
18
|
```
|
|
19
|
+
**Note:** From v0.7.0 onwards, this gem makes use of [rubydex](https://github.com/Shopify/rubydex) to discover which classes inherit from `ViewComponent::Base`. `UseProjectIndex` is required. Without it, cops that depend on ancestry detection will raise an error.
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
AllCops:
|
|
23
|
+
UseProjectIndex: true
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If you use a ViewComponent library such as [primer](https://github.com/primer/view_components) then you'll also need to set `ProjectIndexIncludesGems: true` so the cops can resolve ancestry chains that pass through gem classes.
|
|
27
|
+
|
|
28
|
+
For more background on how RuboCop uses rubydex for cross-file analysis, see [RuboCop 1.89: Project-Wide Analysis with Rubydex](https://metaredux.com/posts/2026/08/05/rubocop-1-89.html).
|
|
19
29
|
|
|
20
30
|
## Cops
|
|
21
31
|
|
|
@@ -27,34 +37,10 @@ This gem provides several cops to enforce ViewComponent best practices:
|
|
|
27
37
|
- **ViewComponent/PreferSlots** - Detect HTML parameters that should be slots
|
|
28
38
|
- **ViewComponent/PreferComposition** - Avoid inheriting one ViewComponent from another (prefer composition)
|
|
29
39
|
- **ViewComponent/TestRenderedOutput** - Encourage testing rendered output over private methods
|
|
30
|
-
- **ViewComponent/MissingPreview** - Ensure every ViewComponent has a corresponding preview file (requires `PreviewPaths` configuration).
|
|
40
|
+
- **ViewComponent/MissingPreview** - Ensure every ViewComponent has a corresponding preview file (requires `PreviewPaths` configuration). Abstract base classes with descendants are automatically exempt.
|
|
31
41
|
|
|
32
42
|
## Optional Configuration
|
|
33
43
|
|
|
34
|
-
### Base Class
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
```yaml
|
|
39
|
-
# .rubocop.yml
|
|
40
|
-
ViewComponent:
|
|
41
|
-
inherit_mode:
|
|
42
|
-
merge:
|
|
43
|
-
- ViewComponentParentClasses
|
|
44
|
-
ViewComponentParentClasses:
|
|
45
|
-
- MyApp::BaseComponent
|
|
46
|
-
```
|
|
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
44
|
### Components Directory
|
|
59
45
|
|
|
60
46
|
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`:
|
data/config/default.yml
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ..
|
|
3
|
+
specs:
|
|
4
|
+
rubocop-view_component (0.6.0)
|
|
5
|
+
activesupport
|
|
6
|
+
herb
|
|
7
|
+
lint_roller (~> 1.1)
|
|
8
|
+
parser
|
|
9
|
+
rubocop (>= 1.89.0)
|
|
10
|
+
rubydex
|
|
11
|
+
|
|
12
|
+
GEM
|
|
13
|
+
remote: https://rubygems.org/
|
|
14
|
+
specs:
|
|
15
|
+
activesupport (8.1.3.1)
|
|
16
|
+
base64
|
|
17
|
+
bigdecimal
|
|
18
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
19
|
+
connection_pool (>= 2.2.5)
|
|
20
|
+
drb
|
|
21
|
+
i18n (>= 1.6, < 2)
|
|
22
|
+
json
|
|
23
|
+
logger (>= 1.4.2)
|
|
24
|
+
minitest (>= 5.1)
|
|
25
|
+
securerandom (>= 0.3)
|
|
26
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
27
|
+
uri (>= 0.13.1)
|
|
28
|
+
appraisal (2.5.0)
|
|
29
|
+
bundler
|
|
30
|
+
rake
|
|
31
|
+
thor (>= 0.14.0)
|
|
32
|
+
ast (2.4.3)
|
|
33
|
+
base64 (0.3.0)
|
|
34
|
+
bigdecimal (4.1.2)
|
|
35
|
+
concurrent-ruby (1.3.8)
|
|
36
|
+
connection_pool (3.0.2)
|
|
37
|
+
diff-lcs (1.6.2)
|
|
38
|
+
drb (2.2.3)
|
|
39
|
+
erb (6.0.7)
|
|
40
|
+
herb (0.10.3)
|
|
41
|
+
herb (0.10.3-aarch64-linux-gnu)
|
|
42
|
+
herb (0.10.3-aarch64-linux-musl)
|
|
43
|
+
herb (0.10.3-arm-linux-gnu)
|
|
44
|
+
herb (0.10.3-arm-linux-musl)
|
|
45
|
+
herb (0.10.3-arm64-darwin)
|
|
46
|
+
herb (0.10.3-x86-linux-gnu)
|
|
47
|
+
herb (0.10.3-x86-linux-musl)
|
|
48
|
+
herb (0.10.3-x86_64-darwin)
|
|
49
|
+
herb (0.10.3-x86_64-linux-gnu)
|
|
50
|
+
herb (0.10.3-x86_64-linux-musl)
|
|
51
|
+
i18n (1.15.2)
|
|
52
|
+
concurrent-ruby (~> 1.0)
|
|
53
|
+
io-console (0.9.1)
|
|
54
|
+
irb (1.18.0)
|
|
55
|
+
pp (>= 0.6.0)
|
|
56
|
+
prism (>= 1.3.0)
|
|
57
|
+
rdoc (>= 4.0.0)
|
|
58
|
+
reline (>= 0.4.2)
|
|
59
|
+
json (2.21.2)
|
|
60
|
+
language_server-protocol (3.17.0.6)
|
|
61
|
+
lint_roller (1.1.0)
|
|
62
|
+
logger (1.7.0)
|
|
63
|
+
minitest (6.0.6)
|
|
64
|
+
drb (~> 2.0)
|
|
65
|
+
prism (~> 1.5)
|
|
66
|
+
parallel (1.28.0)
|
|
67
|
+
parser (3.3.12.0)
|
|
68
|
+
ast (~> 2.4.1)
|
|
69
|
+
racc
|
|
70
|
+
pp (0.6.4)
|
|
71
|
+
prettyprint
|
|
72
|
+
prettyprint (0.2.0)
|
|
73
|
+
prism (1.9.0)
|
|
74
|
+
racc (1.8.1)
|
|
75
|
+
rainbow (3.1.1)
|
|
76
|
+
rake (13.4.2)
|
|
77
|
+
rbs (4.1.2)
|
|
78
|
+
logger
|
|
79
|
+
prism (>= 1.6.0)
|
|
80
|
+
tsort
|
|
81
|
+
rdoc (8.0.0)
|
|
82
|
+
erb
|
|
83
|
+
prism (>= 1.6.0)
|
|
84
|
+
rbs (>= 4.0.0)
|
|
85
|
+
tsort
|
|
86
|
+
regexp_parser (2.12.0)
|
|
87
|
+
reline (0.7.0)
|
|
88
|
+
io-console (~> 0.5)
|
|
89
|
+
rspec (3.13.2)
|
|
90
|
+
rspec-core (~> 3.13.0)
|
|
91
|
+
rspec-expectations (~> 3.13.0)
|
|
92
|
+
rspec-mocks (~> 3.13.0)
|
|
93
|
+
rspec-core (3.13.6)
|
|
94
|
+
rspec-support (~> 3.13.0)
|
|
95
|
+
rspec-expectations (3.13.5)
|
|
96
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
97
|
+
rspec-support (~> 3.13.0)
|
|
98
|
+
rspec-mocks (3.13.8)
|
|
99
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
100
|
+
rspec-support (~> 3.13.0)
|
|
101
|
+
rspec-support (3.13.7)
|
|
102
|
+
rubocop (1.89.0)
|
|
103
|
+
json (~> 2.3)
|
|
104
|
+
language_server-protocol (~> 3.17.0.2)
|
|
105
|
+
lint_roller (~> 1.1.0)
|
|
106
|
+
parallel (>= 1.10)
|
|
107
|
+
parser (>= 3.3.0.2)
|
|
108
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
109
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
110
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
111
|
+
ruby-progressbar (~> 1.7)
|
|
112
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
113
|
+
rubocop-ast (1.50.0)
|
|
114
|
+
parser (>= 3.3.7.2)
|
|
115
|
+
prism (~> 1.7)
|
|
116
|
+
rubocop-rake (0.7.1)
|
|
117
|
+
lint_roller (~> 1.1)
|
|
118
|
+
rubocop (>= 1.72.1)
|
|
119
|
+
rubocop-rspec (3.7.0)
|
|
120
|
+
lint_roller (~> 1.1)
|
|
121
|
+
rubocop (~> 1.72, >= 1.72.1)
|
|
122
|
+
ruby-progressbar (1.13.0)
|
|
123
|
+
rubydex (0.3.0)
|
|
124
|
+
rubydex (0.3.0-aarch64-linux)
|
|
125
|
+
rubydex (0.3.0-arm64-darwin)
|
|
126
|
+
rubydex (0.3.0-x86_64-darwin)
|
|
127
|
+
rubydex (0.3.0-x86_64-linux)
|
|
128
|
+
securerandom (0.4.1)
|
|
129
|
+
thor (1.5.0)
|
|
130
|
+
tsort (0.2.0)
|
|
131
|
+
tzinfo (2.0.6)
|
|
132
|
+
concurrent-ruby (~> 1.0)
|
|
133
|
+
unicode-display_width (3.2.0)
|
|
134
|
+
unicode-emoji (~> 4.1)
|
|
135
|
+
unicode-emoji (4.2.0)
|
|
136
|
+
uri (1.1.1)
|
|
137
|
+
|
|
138
|
+
PLATFORMS
|
|
139
|
+
aarch64-linux-gnu
|
|
140
|
+
aarch64-linux-musl
|
|
141
|
+
arm-linux-gnu
|
|
142
|
+
arm-linux-musl
|
|
143
|
+
arm64-darwin
|
|
144
|
+
ruby
|
|
145
|
+
x86-linux-gnu
|
|
146
|
+
x86-linux-musl
|
|
147
|
+
x86_64-darwin
|
|
148
|
+
x86_64-linux-gnu
|
|
149
|
+
x86_64-linux-musl
|
|
150
|
+
|
|
151
|
+
DEPENDENCIES
|
|
152
|
+
appraisal
|
|
153
|
+
irb
|
|
154
|
+
rake (~> 13.4)
|
|
155
|
+
rspec
|
|
156
|
+
rubocop (= 1.89.0)
|
|
157
|
+
rubocop-rake
|
|
158
|
+
rubocop-rspec
|
|
159
|
+
rubocop-view_component!
|
|
160
|
+
|
|
161
|
+
BUNDLED WITH
|
|
162
|
+
2.7.2
|
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rubydex"
|
|
4
|
+
|
|
3
5
|
module RuboCop
|
|
4
6
|
module Cop
|
|
5
7
|
module ViewComponent
|
|
6
|
-
# Shared helper methods for ViewComponent cops
|
|
8
|
+
# Shared helper methods for ViewComponent cops.
|
|
9
|
+
# Requires rubydex with UseProjectIndex: true for ancestry resolution.
|
|
7
10
|
module Base
|
|
8
|
-
|
|
11
|
+
include ProjectIndexHelp
|
|
12
|
+
|
|
13
|
+
VC_BASE = "ViewComponent::Base"
|
|
14
|
+
|
|
15
|
+
# Check if a class node inherits from ViewComponent::Base
|
|
9
16
|
def view_component_class?(node)
|
|
10
17
|
return false unless node&.class_type?
|
|
11
18
|
|
|
12
19
|
class_source = fully_qualified_name(node)
|
|
13
20
|
return true if component_namespaces.any? { |ns| class_source.start_with?(ns) }
|
|
14
21
|
|
|
22
|
+
require_project_index!
|
|
23
|
+
|
|
15
24
|
parent_class = node.parent_class
|
|
16
25
|
return false unless parent_class
|
|
17
26
|
|
|
18
27
|
view_component_parent?(parent_class)
|
|
19
28
|
end
|
|
20
29
|
|
|
21
|
-
# Check if node
|
|
30
|
+
# Check if a constant node resolves to a class with ViewComponent::Base as ancestor
|
|
22
31
|
def view_component_parent?(node)
|
|
23
32
|
return false unless node.const_type?
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
require_project_index!
|
|
35
|
+
|
|
36
|
+
declaration = resolve_constant_in_index(node)
|
|
37
|
+
return false unless declaration.respond_to?(:has_ancestor?)
|
|
38
|
+
|
|
39
|
+
declaration.name == VC_BASE || declaration.has_ancestor?(VC_BASE)
|
|
26
40
|
end
|
|
27
41
|
|
|
28
|
-
# Check if a class node is itself
|
|
42
|
+
# Check if a class node is itself an abstract parent class
|
|
43
|
+
# (has ViewComponent::Base as ancestor and has descendants in the project)
|
|
29
44
|
def view_component_parent_class?(node)
|
|
30
45
|
return false unless node&.class_type?
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
require_project_index!
|
|
48
|
+
|
|
49
|
+
declaration = resolve_constant_in_index(node.identifier)
|
|
50
|
+
return false unless declaration.respond_to?(:has_ancestor?)
|
|
51
|
+
|
|
52
|
+
declaration.has_ancestor?(VC_BASE) &&
|
|
53
|
+
declaration.descendants.any? { |d| d.name != declaration.name }
|
|
33
54
|
end
|
|
34
55
|
|
|
35
56
|
def fully_qualified_name(node)
|
|
@@ -51,11 +72,22 @@ module RuboCop
|
|
|
51
72
|
|
|
52
73
|
# Check if node is within a ViewComponent class
|
|
53
74
|
def inside_view_component?(node)
|
|
75
|
+
require_project_index!
|
|
76
|
+
|
|
54
77
|
klass = enclosing_class(node)
|
|
55
78
|
return false unless klass
|
|
56
79
|
|
|
57
80
|
view_component_class?(klass)
|
|
58
81
|
end
|
|
82
|
+
|
|
83
|
+
def require_project_index!
|
|
84
|
+
return if project_index
|
|
85
|
+
|
|
86
|
+
raise <<~MSG
|
|
87
|
+
ViewComponent cops require `AllCops/UseProjectIndex: true` in your .rubocop.yml
|
|
88
|
+
for ancestry-based class detection.
|
|
89
|
+
MSG
|
|
90
|
+
end
|
|
59
91
|
end
|
|
60
92
|
end
|
|
61
93
|
end
|
data/script/verify
CHANGED
|
@@ -112,8 +112,10 @@ def add_gem_to_gemfile
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def run_rubocop
|
|
115
|
+
FileUtils.rm_f("Gemfile.lock")
|
|
115
116
|
puts "Running bundle install..."
|
|
116
|
-
system!("bundle", "install")
|
|
117
|
+
system!({ "BUNDLE_WITHOUT" => "development" }, "bundle", "install")
|
|
118
|
+
system!("bundle", "add", "rubocop")
|
|
117
119
|
|
|
118
120
|
puts "Running RuboCop (ViewComponent cops only)..."
|
|
119
121
|
output, status = Open3.capture2(
|
|
@@ -7,10 +7,12 @@ ViewComponent/ComponentSuffix:
|
|
|
7
7
|
- 'app/components/govuk_component/notification_banner_component.rb'
|
|
8
8
|
- 'app/components/govuk_component/pagination_component/adjacent_page.rb'
|
|
9
9
|
- 'app/components/govuk_component/pagination_component/item.rb'
|
|
10
|
+
- 'app/components/govuk_component/panel_component.rb'
|
|
10
11
|
- 'app/components/govuk_component/tab_component.rb'
|
|
11
12
|
ViewComponent/PreferPrivateMethods:
|
|
12
13
|
- 'app/components/govuk_component/base.rb'
|
|
13
14
|
- 'app/components/govuk_component/notification_banner_component.rb'
|
|
15
|
+
- 'app/components/govuk_component/panel_component.rb'
|
|
14
16
|
- 'app/components/govuk_component/service_navigation_component.rb'
|
|
15
17
|
- 'app/components/govuk_component/tab_component.rb'
|
|
16
18
|
ViewComponent/TestRenderedOutput:
|
|
@@ -15,5 +15,8 @@ ViewComponent/PreferPrivateMethods:
|
|
|
15
15
|
- 'app/components/primer/alpha/tree_view/node.rb'
|
|
16
16
|
- 'app/components/primer/beta/avatar.rb'
|
|
17
17
|
- 'app/components/primer/beta/nav_list.rb'
|
|
18
|
+
- 'app/components/primer/beta/nav_list/divider.rb'
|
|
19
|
+
- 'app/components/primer/beta/nav_list/group.rb'
|
|
20
|
+
- 'app/components/primer/beta/nav_list/item.rb'
|
|
18
21
|
ViewComponent/TestRenderedOutput:
|
|
19
22
|
- 'test/components/alpha/action_list_test.rb'
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe RuboCop::Cop::ViewComponent::Base do
|
|
4
|
+
let(:cop_class) { RuboCop::Cop::ViewComponent::ComponentSuffix }
|
|
5
|
+
let(:cop) { cop_class.new(RuboCop::Config.new) }
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
cop.project_index = nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "#view_component_class?" do
|
|
12
|
+
it "raises when project_index is not set and class is not in ComponentNamespaces" do
|
|
13
|
+
source = parse_source(<<~RUBY)
|
|
14
|
+
class Foo < ViewComponent::Base
|
|
15
|
+
end
|
|
16
|
+
RUBY
|
|
17
|
+
|
|
18
|
+
expect { cop.send(:view_component_class?, source.ast) }.to raise_error(
|
|
19
|
+
RuntimeError,
|
|
20
|
+
/UseProjectIndex/
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "does not raise when class matches a ComponentNamespace" do
|
|
25
|
+
config = RuboCop::Config.new(
|
|
26
|
+
"ViewComponent/ComponentSuffix" => {
|
|
27
|
+
"ComponentNamespaces" => ["MyApp::"]
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
cop = cop_class.new(config)
|
|
31
|
+
cop.project_index = nil
|
|
32
|
+
|
|
33
|
+
source = parse_source(<<~RUBY)
|
|
34
|
+
module MyApp
|
|
35
|
+
class Foo < ViewComponent::Base
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
RUBY
|
|
39
|
+
|
|
40
|
+
class_node = source.ast.body
|
|
41
|
+
|
|
42
|
+
expect(cop.send(:view_component_class?, class_node)).to be true
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#view_component_parent?" do
|
|
47
|
+
it "raises when project_index is not set" do
|
|
48
|
+
source = parse_source(<<~RUBY)
|
|
49
|
+
class Foo < ViewComponent::Base
|
|
50
|
+
end
|
|
51
|
+
RUBY
|
|
52
|
+
|
|
53
|
+
parent = source.ast.parent_class
|
|
54
|
+
|
|
55
|
+
expect { cop.send(:view_component_parent?, parent) }.to raise_error(
|
|
56
|
+
RuntimeError,
|
|
57
|
+
/UseProjectIndex/
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "#view_component_parent_class?" do
|
|
63
|
+
it "raises when project_index is not set" do
|
|
64
|
+
source = parse_source(<<~RUBY)
|
|
65
|
+
class Foo < ViewComponent::Base
|
|
66
|
+
end
|
|
67
|
+
RUBY
|
|
68
|
+
|
|
69
|
+
expect { cop.send(:view_component_parent_class?, source.ast) }.to raise_error(
|
|
70
|
+
RuntimeError,
|
|
71
|
+
/UseProjectIndex/
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "#inside_view_component?" do
|
|
77
|
+
it "raises when project_index is not set" do
|
|
78
|
+
source = parse_source(<<~RUBY)
|
|
79
|
+
class Foo < ViewComponent::Base
|
|
80
|
+
def call; end
|
|
81
|
+
end
|
|
82
|
+
RUBY
|
|
83
|
+
|
|
84
|
+
method_node = source.ast.body
|
|
85
|
+
|
|
86
|
+
expect { cop.send(:inside_view_component?, method_node) }.to raise_error(
|
|
87
|
+
RuntimeError,
|
|
88
|
+
/UseProjectIndex/
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -74,64 +74,6 @@ RSpec.describe RuboCop::Cop::ViewComponent::ComponentSuffix, :config do
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
context "when ViewComponentParentClasses is configured with merge" do
|
|
78
|
-
let(:config) do
|
|
79
|
-
RuboCop::Config.new(
|
|
80
|
-
"ViewComponent/ComponentSuffix" => {
|
|
81
|
-
"ViewComponentParentClasses" => ["ViewComponent::Base", "ApplicationComponent", "Primer::Component"]
|
|
82
|
-
}
|
|
83
|
-
)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "registers an offense for classes inheriting from a configured parent" do
|
|
87
|
-
expect_offense(<<~RUBY)
|
|
88
|
-
class FooBar < Primer::Component
|
|
89
|
-
^^^^^^ ViewComponent class names should end with `Component`.
|
|
90
|
-
end
|
|
91
|
-
RUBY
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
it "does not register offense when class name ends with Component" do
|
|
95
|
-
expect_no_offenses(<<~RUBY)
|
|
96
|
-
class FooBarComponent < Primer::Component
|
|
97
|
-
end
|
|
98
|
-
RUBY
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "still recognizes the default parent classes" do
|
|
102
|
-
expect_offense(<<~RUBY)
|
|
103
|
-
class FooBar < ViewComponent::Base
|
|
104
|
-
^^^^^^ ViewComponent class names should end with `Component`.
|
|
105
|
-
end
|
|
106
|
-
RUBY
|
|
107
|
-
end
|
|
108
|
-
end
|
|
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
|
-
|
|
135
77
|
context "with compact nested class syntax" do
|
|
136
78
|
it "registers offense for compact syntax" do
|
|
137
79
|
expect_offense(<<~RUBY)
|
|
@@ -5,8 +5,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
|
5
5
|
RuboCop::Config.new(
|
|
6
6
|
"ViewComponent/MissingPreview" => {
|
|
7
7
|
"Enabled" => true,
|
|
8
|
-
"PreviewPaths" => ["/previews"]
|
|
9
|
-
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
8
|
+
"PreviewPaths" => ["/previews"]
|
|
10
9
|
}
|
|
11
10
|
)
|
|
12
11
|
end
|
|
@@ -172,45 +171,5 @@ RSpec.describe RuboCop::Cop::ViewComponent::MissingPreview, :config do
|
|
|
172
171
|
end
|
|
173
172
|
RUBY
|
|
174
173
|
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
174
|
end
|
|
216
175
|
end
|
|
@@ -88,21 +88,4 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferComposition, :config do
|
|
|
88
88
|
RUBY
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
|
-
|
|
92
|
-
context "when ViewComponentParentClasses is configured" do
|
|
93
|
-
let(:config) do
|
|
94
|
-
RuboCop::Config.new(
|
|
95
|
-
"ViewComponent/PreferComposition" => {
|
|
96
|
-
"ViewComponentParentClasses" => ["Primer::Component"]
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "does not register an offense for configured parent classes" do
|
|
102
|
-
expect_no_offenses(<<~RUBY)
|
|
103
|
-
class FooBarComponent < Primer::Component
|
|
104
|
-
end
|
|
105
|
-
RUBY
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
91
|
end
|
|
@@ -147,8 +147,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
147
147
|
"AllCops" => { "DisplayCopNames" => true },
|
|
148
148
|
"ViewComponent/PreferPrivateMethods" => {
|
|
149
149
|
"AllowedPublicMethods" => %w[initialize call],
|
|
150
|
-
"AllowedPublicMethodPatterns" => ["^render_", "^with_"]
|
|
151
|
-
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
150
|
+
"AllowedPublicMethodPatterns" => ["^render_", "^with_"]
|
|
152
151
|
}
|
|
153
152
|
)
|
|
154
153
|
end
|
|
@@ -190,8 +189,7 @@ RSpec.describe RuboCop::Cop::ViewComponent::PreferPrivateMethods, :config do
|
|
|
190
189
|
"AllCops" => { "DisplayCopNames" => true },
|
|
191
190
|
"ViewComponent/PreferPrivateMethods" => {
|
|
192
191
|
"AllowedPublicMethods" => %w[initialize call custom_public_method],
|
|
193
|
-
"AllowedPublicMethodPatterns" => []
|
|
194
|
-
"ViewComponentParentClasses" => %w[ViewComponent::Base ApplicationComponent]
|
|
192
|
+
"AllowedPublicMethodPatterns" => []
|
|
195
193
|
}
|
|
196
194
|
)
|
|
197
195
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "rubocop-view_component"
|
|
4
4
|
require "rubocop/rspec/support"
|
|
5
|
+
require_relative "support/project_index_helpers"
|
|
5
6
|
|
|
6
7
|
RSpec.configure do |config|
|
|
7
8
|
config.disable_monkey_patching!
|
|
@@ -11,4 +12,13 @@ RSpec.configure do |config|
|
|
|
11
12
|
|
|
12
13
|
config.order = :random
|
|
13
14
|
Kernel.srand config.seed
|
|
15
|
+
|
|
16
|
+
config.include ViewComponent::ProjectIndexHelpers
|
|
17
|
+
|
|
18
|
+
config.before do |example|
|
|
19
|
+
next unless example.metadata[:config]
|
|
20
|
+
|
|
21
|
+
graph = build_index
|
|
22
|
+
cop.project_index = ViewComponent::ProjectIndexHelpers::LazyIndex.new(graph, cop)
|
|
23
|
+
end
|
|
14
24
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubydex"
|
|
4
|
+
|
|
5
|
+
module ViewComponent
|
|
6
|
+
module ProjectIndexHelpers
|
|
7
|
+
DEFAULT_SOURCES = {
|
|
8
|
+
"file:///view_component.rb" => <<~RUBY,
|
|
9
|
+
module ViewComponent
|
|
10
|
+
class Base
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
RUBY
|
|
14
|
+
"file:///application_component.rb" => <<~RUBY,
|
|
15
|
+
class ApplicationComponent < ViewComponent::Base
|
|
16
|
+
end
|
|
17
|
+
RUBY
|
|
18
|
+
"file:///user_component.rb" => <<~RUBY
|
|
19
|
+
class UserComponent < ApplicationComponent
|
|
20
|
+
end
|
|
21
|
+
RUBY
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
def build_index(sources = {})
|
|
25
|
+
graph = Rubydex::Graph.new
|
|
26
|
+
DEFAULT_SOURCES.merge(sources).each { |uri, source| graph.index_source(uri, source, "ruby") }
|
|
27
|
+
graph.resolve
|
|
28
|
+
graph
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Wraps a graph to lazily index the source being inspected when
|
|
32
|
+
# resolve_constant is called. This mirrors how the real RuboCop runner
|
|
33
|
+
# indexes all project files, including the one being inspected.
|
|
34
|
+
class LazyIndex
|
|
35
|
+
def initialize(base_graph, cop)
|
|
36
|
+
@base_graph = base_graph
|
|
37
|
+
@cop = cop
|
|
38
|
+
@rebuilt = false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_constant(name, nesting)
|
|
42
|
+
result = @base_graph.resolve_constant(name, nesting)
|
|
43
|
+
return result if result
|
|
44
|
+
|
|
45
|
+
rebuild_once
|
|
46
|
+
@base_graph.resolve_constant(name, nesting)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def method_missing(name, ...)
|
|
50
|
+
@base_graph.send(name, ...)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def respond_to_missing?(name, include_private = false)
|
|
54
|
+
@base_graph.respond_to?(name, include_private) || super
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def rebuild_once
|
|
60
|
+
return if @rebuilt
|
|
61
|
+
|
|
62
|
+
@rebuilt = true
|
|
63
|
+
source = @cop&.processed_source
|
|
64
|
+
return unless source&.raw_source
|
|
65
|
+
|
|
66
|
+
@base_graph.index_source("file:///test_source.rb", source.raw_source, "ruby")
|
|
67
|
+
@base_graph.resolve
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- ViewComponentParentClasses
|
|
5
|
-
ViewComponentParentClasses:
|
|
6
|
-
- GovukComponent::Base
|
|
1
|
+
AllCops:
|
|
2
|
+
UseProjectIndex: true
|
|
3
|
+
ProjectIndexIncludesGems: true
|
|
7
4
|
|
|
8
5
|
# x-govuk components do not use ViewComponent previews
|
|
9
6
|
ViewComponent/MissingPreview:
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- ViewComponentParentClasses
|
|
5
|
-
ViewComponentParentClasses:
|
|
6
|
-
- Polaris::Component
|
|
7
|
-
- Component
|
|
1
|
+
AllCops:
|
|
2
|
+
UseProjectIndex: true
|
|
3
|
+
ProjectIndexIncludesGems: true
|
|
8
4
|
|
|
9
5
|
ViewComponent/MissingPreview:
|
|
10
6
|
PreviewPaths:
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
- ViewComponentParentClasses
|
|
5
|
-
ViewComponentParentClasses:
|
|
6
|
-
- Primer::Component
|
|
7
|
-
- Primer::BaseComponent
|
|
1
|
+
AllCops:
|
|
2
|
+
UseProjectIndex: true
|
|
3
|
+
ProjectIndexIncludesGems: true
|
|
8
4
|
|
|
9
5
|
# Primer seems to intentionally omit the Component suffix from most class names
|
|
10
6
|
# (116 out of 131 components), so this cop is not useful here.
|
|
@@ -31,3 +27,5 @@ ViewComponent/MissingPreview:
|
|
|
31
27
|
- previews
|
|
32
28
|
Exclude:
|
|
33
29
|
- app/components/primer/base_component.rb
|
|
30
|
+
# Deprecated wrapper with no preview
|
|
31
|
+
- app/components/primer/navigation/tab_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.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Waite
|
|
@@ -65,20 +65,34 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubydex
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: rubocop
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
71
85
|
requirements:
|
|
72
86
|
- - ">="
|
|
73
87
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 1.
|
|
88
|
+
version: 1.89.0
|
|
75
89
|
type: :runtime
|
|
76
90
|
prerelease: false
|
|
77
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
92
|
requirements:
|
|
79
93
|
- - ">="
|
|
80
94
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 1.
|
|
95
|
+
version: 1.89.0
|
|
82
96
|
description: A RuboCop extension that enforces ViewComponent best practices and conventions
|
|
83
97
|
email:
|
|
84
98
|
- andyw8@users.noreply.github.com
|
|
@@ -88,11 +102,14 @@ extra_rdoc_files: []
|
|
|
88
102
|
files:
|
|
89
103
|
- ".rspec"
|
|
90
104
|
- ".rubocop.yml"
|
|
105
|
+
- Appraisals
|
|
91
106
|
- CLAUDE.md
|
|
92
107
|
- LICENSE.txt
|
|
93
108
|
- README.md
|
|
94
109
|
- Rakefile
|
|
95
110
|
- config/default.yml
|
|
111
|
+
- gemfiles/rubocop_minimum.gemfile
|
|
112
|
+
- gemfiles/rubocop_minimum.gemfile.lock
|
|
96
113
|
- lib/rubocop-view_component.rb
|
|
97
114
|
- lib/rubocop/cop/view_component/base.rb
|
|
98
115
|
- lib/rubocop/cop/view_component/component_suffix.rb
|
|
@@ -113,6 +130,7 @@ files:
|
|
|
113
130
|
- spec/expected_primer_failures.yml
|
|
114
131
|
- spec/fixtures/components/template_method_component.html.erb
|
|
115
132
|
- spec/fixtures/components/template_method_component.rb
|
|
133
|
+
- spec/rubocop/cop/view_component/base_spec.rb
|
|
116
134
|
- spec/rubocop/cop/view_component/component_suffix_spec.rb
|
|
117
135
|
- spec/rubocop/cop/view_component/missing_preview_spec.rb
|
|
118
136
|
- spec/rubocop/cop/view_component/no_global_state_spec.rb
|
|
@@ -121,6 +139,7 @@ files:
|
|
|
121
139
|
- spec/rubocop/cop/view_component/prefer_slots_spec.rb
|
|
122
140
|
- spec/rubocop/cop/view_component/test_rendered_output_spec.rb
|
|
123
141
|
- spec/spec_helper.rb
|
|
142
|
+
- spec/support/project_index_helpers.rb
|
|
124
143
|
- verification/govuk_rubocop_config.yml
|
|
125
144
|
- verification/libraries.yml
|
|
126
145
|
- verification/polaris_rubocop_config.yml
|
|
@@ -147,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
147
166
|
- !ruby/object:Gem::Version
|
|
148
167
|
version: '0'
|
|
149
168
|
requirements: []
|
|
150
|
-
rubygems_version:
|
|
169
|
+
rubygems_version: 4.0.16
|
|
151
170
|
specification_version: 4
|
|
152
171
|
summary: RuboCop extension for ViewComponent best practices
|
|
153
172
|
test_files: []
|