rubocop-view_component 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/{CLAUDE.md → AGENTS.md} +6 -14
- data/Appraisals +5 -0
- data/README.md +50 -27
- data/config/default.yml +6 -5
- data/docs/rubydex.md +136 -0
- data/gemfiles/rubocop_minimum.gemfile +13 -0
- data/gemfiles/rubocop_minimum.gemfile.lock +162 -0
- data/lib/rubocop/cop/view_component/base.rb +56 -7
- data/lib/rubocop/cop/view_component/prefer_composition.rb +42 -9
- data/lib/rubocop/cop/view_component/prefer_private_methods.rb +46 -13
- data/lib/rubocop/cop/view_component/rspec_scope.rb +98 -0
- data/lib/rubocop/cop/view_component/supplementary_index.rb +142 -0
- data/lib/rubocop/cop/view_component/test_rendered_output.rb +107 -11
- data/lib/rubocop/view_component/version.rb +1 -1
- data/script/verify +29 -16
- data/spec/expected_flowbite_failures.yml +64 -0
- data/spec/expected_govuk_failures.yml +10 -12
- data/spec/expected_polaris_failures.yml +2 -0
- data/spec/expected_primer_failures.yml +60 -2
- data/spec/rubocop/cop/view_component/base_spec.rb +108 -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 +91 -4
- data/spec/rubocop/cop/view_component/prefer_private_methods_spec.rb +126 -4
- data/spec/rubocop/cop/view_component/test_rendered_output_spec.rb +287 -4
- data/spec/spec_helper.rb +10 -0
- data/spec/support/project_index_helpers.rb +71 -0
- data/verification/flowbite_rubocop_config.yml +12 -0
- data/verification/govuk_rubocop_config.yml +3 -6
- data/verification/libraries.yml +4 -0
- data/verification/polaris_rubocop_config.yml +3 -7
- data/verification/primer_rubocop_config.yml +5 -7
- metadata +29 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab88d6999ec2a99b6f9e459d22a14b9d727b62edd967088fa22aef709681dbdb
|
|
4
|
+
data.tar.gz: b06b97cebdb888ba95176af4d9d664c9f75bf75865eacb148bc327bba761f77f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b6aa5e13ce63d13205335bb2d900a29d57e80ce1b9d1906c92e3548d85941875b18235a36775849e35752fed347c260e842289fb8e805ad0ee7670c252c89b1
|
|
7
|
+
data.tar.gz: b65833f97c6211d366b5c0cdeba807e501f6ef10a8a878f63e0379de23be3c2a4842542e50e43171254886204990ae5224a4d3e3abbe90f72be1e05f9cfb0f08
|
data/.rubocop.yml
CHANGED
|
@@ -12,10 +12,14 @@ AllCops:
|
|
|
12
12
|
TargetRubyVersion: 3.2
|
|
13
13
|
NewCops: enable
|
|
14
14
|
SuggestExtensions: false
|
|
15
|
+
# Required by this gem's own cops, which resolve ancestry through the
|
|
16
|
+
# project index (see docs/rubydex.md).
|
|
17
|
+
UseProjectIndex: true
|
|
15
18
|
Exclude:
|
|
16
19
|
- 'verification/**/*'
|
|
17
20
|
- 'script/verify'
|
|
18
21
|
- 'spec/fixtures/**/*'
|
|
22
|
+
- 'gemfiles/**/*'
|
|
19
23
|
|
|
20
24
|
Metrics/BlockLength:
|
|
21
25
|
Exclude:
|
data/{CLAUDE.md → AGENTS.md}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
This file provides guidance to
|
|
3
|
+
This file provides guidance to coding agents when working with code in this repository.
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
@@ -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](docs/rubydex.md)
|
|
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/Appraisals
ADDED
data/README.md
CHANGED
|
@@ -13,9 +13,19 @@ gem 'rubocop-view_component', require: false
|
|
|
13
13
|
Add to your `.rubocop.yml`:
|
|
14
14
|
|
|
15
15
|
```yaml
|
|
16
|
-
|
|
16
|
+
plugins:
|
|
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
|
|
|
@@ -25,47 +35,40 @@ This gem provides several cops to enforce ViewComponent best practices:
|
|
|
25
35
|
- **ViewComponent/NoGlobalState** - Prevent direct access to `params`, `request`, `session`, etc.
|
|
26
36
|
- **ViewComponent/PreferPrivateMethods** - Suggest making helper methods private (analyzes ERB templates to avoid flagging methods used in views)
|
|
27
37
|
- **ViewComponent/PreferSlots** - Detect HTML parameters that should be slots
|
|
28
|
-
- **ViewComponent/PreferComposition** - Avoid inheriting one ViewComponent from another (prefer composition)
|
|
29
|
-
- **ViewComponent/TestRenderedOutput** - Encourage testing rendered output over private methods
|
|
30
|
-
- **ViewComponent/MissingPreview** - Ensure every ViewComponent has a corresponding preview file (
|
|
38
|
+
- **ViewComponent/PreferComposition** - Avoid inheriting one ViewComponent from another (prefer composition). Abstract bases without their own template, such as `ApplicationComponent`, are allowed
|
|
39
|
+
- **ViewComponent/TestRenderedOutput** - Encourage testing rendered output over private methods. Supports Minitest and RSpec styles, including components built via `described_class.new` or a `let`/`subject` helper
|
|
40
|
+
- **ViewComponent/MissingPreview** - Ensure every ViewComponent has a corresponding preview file (defaults to `test/components/previews` and `spec/components/previews` if `PreviewPaths` is not configured). Abstract base classes with descendants are automatically exempt.
|
|
31
41
|
|
|
32
42
|
## Optional Configuration
|
|
33
43
|
|
|
34
|
-
###
|
|
44
|
+
### Components Directory
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
Several cops (`ComponentSuffix`, `PreferComposition`, `TestRenderedOutput`) default to running only on files under `app/components/`. If your project uses a different path, override `Include` in your `.rubocop.yml`:
|
|
37
47
|
|
|
38
48
|
```yaml
|
|
39
49
|
# .rubocop.yml
|
|
40
|
-
ViewComponent:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ViewComponentParentClasses:
|
|
45
|
-
- MyApp::BaseComponent
|
|
50
|
+
ViewComponent/ComponentSuffix:
|
|
51
|
+
Include:
|
|
52
|
+
- 'app/components/**/*.rb'
|
|
53
|
+
- 'engines/*/app/components/**/*.rb'
|
|
46
54
|
```
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
### Additional Source Paths
|
|
57
|
+
|
|
58
|
+
`AllCops/ProjectIndexIncludesGems` only indexes a gem's `lib`, so a class that inherits through a
|
|
59
|
+
Rails engine's `app/components` stays unresolved. If your components inherit from an engine gem,
|
|
60
|
+
point the cops at its sources:
|
|
49
61
|
|
|
50
62
|
```yaml
|
|
51
63
|
# .rubocop.yml
|
|
52
64
|
ViewComponent:
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
- MyApp::BaseComponent
|
|
65
|
+
AdditionalSourcePaths:
|
|
66
|
+
- ../my_engine/app/components
|
|
56
67
|
```
|
|
57
68
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```yaml
|
|
63
|
-
# .rubocop.yml
|
|
64
|
-
ViewComponent/ComponentSuffix:
|
|
65
|
-
Include:
|
|
66
|
-
- 'app/components/**/*.rb'
|
|
67
|
-
- 'engines/*/app/components/**/*.rb'
|
|
68
|
-
```
|
|
69
|
+
Paths are relative to the config file that sets them. The cops index those directories themselves
|
|
70
|
+
and fall back to them when RuboCop's index can't resolve a constant. Unlike `ComponentNamespaces`,
|
|
71
|
+
this only adds declarations to the index — it does not treat a whole namespace as components.
|
|
69
72
|
|
|
70
73
|
### No Super
|
|
71
74
|
|
|
@@ -157,6 +160,26 @@ To force download the latest Polaris source:
|
|
|
157
160
|
script/verify polaris --update
|
|
158
161
|
```
|
|
159
162
|
|
|
163
|
+
### Flowbite Components
|
|
164
|
+
|
|
165
|
+
To verify against [substancelab/flowbite-components](https://github.com/substancelab/flowbite-components) locally:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
script/verify flowbite
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
If you intentionally change cop behavior, regenerate the snapshot:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
script/verify flowbite --regenerate
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
To force download the latest Flowbite source:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
script/verify flowbite --update
|
|
181
|
+
```
|
|
182
|
+
|
|
160
183
|
## Contributing
|
|
161
184
|
|
|
162
185
|
Bug reports and pull requests are welcome on GitHub at https://github.com/andyw8/rubocop-view_component.
|
data/config/default.yml
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
ViewComponent:
|
|
2
|
-
ViewComponentParentClasses:
|
|
3
|
-
- ViewComponent::Base
|
|
4
|
-
- ApplicationComponent
|
|
5
2
|
ComponentNamespaces: []
|
|
3
|
+
# Source directories the cops index themselves, in addition to RuboCop's
|
|
4
|
+
# project index. Use this when ancestry passes through a gem RuboCop does not
|
|
5
|
+
# index (notably a Rails engine's app/components, which
|
|
6
|
+
# AllCops/ProjectIndexIncludesGems misses because it only covers a gem's lib).
|
|
7
|
+
# Paths are relative to the config file that sets them.
|
|
8
|
+
AdditionalSourcePaths: []
|
|
6
9
|
|
|
7
10
|
ViewComponent/ComponentSuffix:
|
|
8
11
|
Description: 'Enforce -Component suffix for ViewComponent classes.'
|
|
@@ -18,8 +21,6 @@ ViewComponent/MissingPreview:
|
|
|
18
21
|
Enabled: true
|
|
19
22
|
VersionAdded: '0.5.0'
|
|
20
23
|
Severity: convention
|
|
21
|
-
Include:
|
|
22
|
-
- 'app/components/**/*_component.rb'
|
|
23
24
|
PreviewPaths:
|
|
24
25
|
- test/components/previews
|
|
25
26
|
- spec/components/previews
|
data/docs/rubydex.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# rubydex integration
|
|
2
|
+
|
|
3
|
+
This gem uses [rubydex](https://github.com/Shopify/rubydex), Shopify's high-performance
|
|
4
|
+
static analysis toolkit for Ruby, to answer questions that cannot be answered from a
|
|
5
|
+
single file's AST.
|
|
6
|
+
|
|
7
|
+
Most ViewComponent cops only care about *ancestry*: is this class (or the class it
|
|
8
|
+
inherits from) ultimately a `ViewComponent::Base` descendant? A class may inherit from
|
|
9
|
+
`ApplicationComponent`, which inherits from a gem's base class, which finally inherits
|
|
10
|
+
from `ViewComponent::Base` — none of which is visible in the file being inspected.
|
|
11
|
+
Resolving that chain requires a project-wide index, and that is what rubydex provides.
|
|
12
|
+
|
|
13
|
+
## How RuboCop exposes the index
|
|
14
|
+
|
|
15
|
+
RuboCop 1.89+ integrates rubydex behind an opt-in flag. This gem requires that
|
|
16
|
+
version (see `rubocop-view_component.gemspec`).
|
|
17
|
+
|
|
18
|
+
1. When `AllCops/UseProjectIndex` is `true`, RuboCop builds a resolved
|
|
19
|
+
`Rubydex::Graph` for the whole project (`RuboCop::ProjectIndexLoader`).
|
|
20
|
+
2. The graph is exposed to cops through `RuboCop::Cop::Base#project_index`.
|
|
21
|
+
3. Cops that mix in `RuboCop::Cop::ProjectIndexHelp` also get an
|
|
22
|
+
`external_dependency_checksum` override, which invalidates RuboCop's result cache
|
|
23
|
+
whenever an indexed file changes on disk.
|
|
24
|
+
|
|
25
|
+
If the `rubydex` gem is not installed, or the running Ruby is older than 3.2, RuboCop
|
|
26
|
+
warns and degrades to file-local analysis (`project_index` is `nil`).
|
|
27
|
+
|
|
28
|
+
## How this gem uses it
|
|
29
|
+
|
|
30
|
+
`ViewComponent::Base` (`lib/rubocop/cop/view_component/base.rb`) includes
|
|
31
|
+
`ProjectIndexHelp` and queries the index through `resolve_constant_in_index`:
|
|
32
|
+
|
|
33
|
+
- `view_component_class?` — resolves a class's superclass expression and checks it.
|
|
34
|
+
- `view_component_parent?` — resolves a constant and calls
|
|
35
|
+
`declaration.has_ancestor?("ViewComponent::Base")`.
|
|
36
|
+
- `view_component_parent_class?` — checks `has_ancestor?` plus `declaration.descendants`
|
|
37
|
+
to detect abstract base classes that have their own subclasses in the project.
|
|
38
|
+
- `resolved_index_declaration` — returns the raw declaration for a constant, or nil
|
|
39
|
+
when it isn't defined in the index. This lets a cop distinguish "resolved but not a
|
|
40
|
+
ViewComponent" from "not resolvable", and inspect the declaration (for example, to
|
|
41
|
+
locate the class's source file).
|
|
42
|
+
- `inside_view_component?` — finds the enclosing class and delegates to
|
|
43
|
+
`view_component_class?`.
|
|
44
|
+
|
|
45
|
+
`PreferComposition` uses `resolved_index_declaration` together with the parent's
|
|
46
|
+
template to tell a concrete component apart from an abstract base: it flags a parent
|
|
47
|
+
only when its name follows the component convention *and* the index resolves it to a
|
|
48
|
+
ViewComponent that has its own template. Bases without a template (such as
|
|
49
|
+
`ApplicationComponent`) and constants that resolve to something other than a
|
|
50
|
+
ViewComponent are allowed.
|
|
51
|
+
|
|
52
|
+
All of these call `require_project_index!` first, which raises with an actionable
|
|
53
|
+
message if the index is unavailable:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ViewComponent cops require `AllCops/UseProjectIndex: true` in your .rubocop.yml
|
|
57
|
+
for ancestry-based class detection.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The one exception is `ComponentNamespaces`: if a class's fully-qualified name matches a
|
|
61
|
+
configured namespace prefix, `view_component_class?` can return `true` without touching
|
|
62
|
+
the index, so projects that use explicit namespaces can avoid the requirement in that
|
|
63
|
+
specific case.
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
Enable the index in your `.rubocop.yml`:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
AllCops:
|
|
71
|
+
UseProjectIndex: true
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If your components inherit from a library (e.g. Primer) whose base class lives in a gem,
|
|
75
|
+
also enable gem indexing so the ancestry chain can be followed through the gem's source:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
AllCops:
|
|
79
|
+
ProjectIndexIncludesGems: true
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Rails engines
|
|
83
|
+
|
|
84
|
+
Gem indexing only covers a gem's `lib`. A Rails engine's components live under `app/components`,
|
|
85
|
+
so a chain that passes through one — such as a project's components inheriting
|
|
86
|
+
`MyEngine::ApplicationComponent` — stays unresolved even with the flag on. List the engine's
|
|
87
|
+
sources in `ViewComponent/AdditionalSourcePaths` and the cops index them in a small supplementary
|
|
88
|
+
graph of their own, used as a fallback when RuboCop's index can't resolve a constant:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
ViewComponent:
|
|
92
|
+
AdditionalSourcePaths:
|
|
93
|
+
- ../my_engine/app/components
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Paths are relative to the config file that sets them, and the supplementary files take part in the
|
|
97
|
+
result cache's checksum, so changing one invalidates a cached run. `ComponentNamespaces` is not a
|
|
98
|
+
substitute here: it treats *every* class under the namespace as a component, which is wrong for a
|
|
99
|
+
namespace that also holds helpers, tests, or other application code.
|
|
100
|
+
|
|
101
|
+
## Performance notes
|
|
102
|
+
|
|
103
|
+
- The index always covers the whole project (rooted at the directory of the config that
|
|
104
|
+
enabled it), not just the files in the current run, so cross-file offenses are
|
|
105
|
+
consistent between full runs, editor runs, and CI shards.
|
|
106
|
+
- `external_dependency_checksum` ties the result cache to the indexed files' mtimes and
|
|
107
|
+
sizes, so a warm cache is reused until a relevant file changes.
|
|
108
|
+
- On Windows, parallel inspection is skipped when the index is enabled.
|
|
109
|
+
|
|
110
|
+
## Testing
|
|
111
|
+
|
|
112
|
+
Specs do not build a full RuboCop run. `spec/support/project_index_helpers.rb` builds a
|
|
113
|
+
`Rubydex::Graph` directly from a set of in-memory sources:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
graph = Rubydex::Graph.new
|
|
117
|
+
graph.index_source("file:///user_component.rb", source, "ruby")
|
|
118
|
+
graph.resolve
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`ProjectIndexHelpers::LazyIndex` wraps the graph and lazily indexes the source under
|
|
122
|
+
inspection on first miss, mirroring how the real runner indexes every project file
|
|
123
|
+
(including the one being inspected). `spec_helper.rb` assigns this to each cop's
|
|
124
|
+
`project_index` before examples run.
|
|
125
|
+
|
|
126
|
+
The real-world verification suites (`verification/*_rubocop_config.yml`) set
|
|
127
|
+
`UseProjectIndex: true` so the cops are exercised through the actual RuboCop runner.
|
|
128
|
+
|
|
129
|
+
## Further reading
|
|
130
|
+
|
|
131
|
+
- rubydex repository: <https://github.com/Shopify/rubydex>
|
|
132
|
+
- Ruby API documentation: <https://shopify.github.io/rubydex/>
|
|
133
|
+
- "One engine, many tools — Introducing Rubydex":
|
|
134
|
+
<https://railsatscale.com/2026-05-12-one-engine-many-tools/>
|
|
135
|
+
- RuboCop 1.89 project-wide analysis announcement:
|
|
136
|
+
<https://metaredux.com/posts/2026/08/05/rubocop-1-89.html>
|
|
@@ -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,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rubydex"
|
|
4
|
+
require_relative "supplementary_index"
|
|
5
|
+
|
|
3
6
|
module RuboCop
|
|
4
7
|
module Cop
|
|
5
8
|
module ViewComponent
|
|
6
|
-
# Shared helper methods for ViewComponent cops
|
|
9
|
+
# Shared helper methods for ViewComponent cops.
|
|
10
|
+
# Requires rubydex with UseProjectIndex: true for ancestry resolution.
|
|
7
11
|
module Base
|
|
8
|
-
|
|
12
|
+
include ProjectIndexHelp
|
|
13
|
+
include SupplementaryIndex
|
|
14
|
+
|
|
15
|
+
VC_BASE = "ViewComponent::Base"
|
|
16
|
+
|
|
17
|
+
# Check if a class node inherits from ViewComponent::Base
|
|
9
18
|
def view_component_class?(node)
|
|
10
19
|
return false unless node&.class_type?
|
|
11
20
|
|
|
12
21
|
class_source = fully_qualified_name(node)
|
|
13
22
|
return true if component_namespaces.any? { |ns| class_source.start_with?(ns) }
|
|
14
23
|
|
|
24
|
+
require_project_index!
|
|
25
|
+
|
|
15
26
|
parent_class = node.parent_class
|
|
16
27
|
return false unless parent_class
|
|
17
28
|
|
|
18
29
|
view_component_parent?(parent_class)
|
|
19
30
|
end
|
|
20
31
|
|
|
21
|
-
# Check if node
|
|
32
|
+
# Check if a constant node resolves to a class with ViewComponent::Base as ancestor
|
|
22
33
|
def view_component_parent?(node)
|
|
23
|
-
|
|
34
|
+
declaration = view_component_declaration(node)
|
|
35
|
+
return false unless declaration
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
declaration.name == VC_BASE || declaration.has_ancestor?(VC_BASE)
|
|
26
38
|
end
|
|
27
39
|
|
|
28
|
-
# Check if a class node is itself
|
|
40
|
+
# Check if a class node is itself an abstract parent class
|
|
41
|
+
# (has ViewComponent::Base as ancestor and has descendants in the project)
|
|
29
42
|
def view_component_parent_class?(node)
|
|
30
43
|
return false unless node&.class_type?
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
declaration = view_component_declaration(node.identifier)
|
|
46
|
+
return false unless declaration
|
|
47
|
+
|
|
48
|
+
declaration.has_ancestor?(VC_BASE) &&
|
|
49
|
+
declaration.descendants.any? { |d| d.name != declaration.name }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Resolve a constant node to its declaration in the project index, or
|
|
53
|
+
# nil when the node is not a constant or isn't defined anywhere in the
|
|
54
|
+
# index. The declaration may or may not be a ViewComponent; callers can
|
|
55
|
+
# inspect it (for example, to locate the class's source file).
|
|
56
|
+
def resolved_index_declaration(node)
|
|
57
|
+
return nil unless node&.const_type?
|
|
58
|
+
|
|
59
|
+
require_project_index!
|
|
60
|
+
resolve_constant_in_index(node)
|
|
33
61
|
end
|
|
34
62
|
|
|
35
63
|
def fully_qualified_name(node)
|
|
@@ -51,11 +79,32 @@ module RuboCop
|
|
|
51
79
|
|
|
52
80
|
# Check if node is within a ViewComponent class
|
|
53
81
|
def inside_view_component?(node)
|
|
82
|
+
require_project_index!
|
|
83
|
+
|
|
54
84
|
klass = enclosing_class(node)
|
|
55
85
|
return false unless klass
|
|
56
86
|
|
|
57
87
|
view_component_class?(klass)
|
|
58
88
|
end
|
|
89
|
+
|
|
90
|
+
def require_project_index!
|
|
91
|
+
return if project_index
|
|
92
|
+
|
|
93
|
+
raise <<~MSG
|
|
94
|
+
ViewComponent cops require `AllCops/UseProjectIndex: true` in your .rubocop.yml
|
|
95
|
+
for ancestry-based class detection.
|
|
96
|
+
MSG
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# Resolve a constant node to a namespace declaration in the project
|
|
102
|
+
# index, or nil when the node is not a constant or does not resolve to a
|
|
103
|
+
# class or module.
|
|
104
|
+
def view_component_declaration(node)
|
|
105
|
+
declaration = resolved_index_declaration(node)
|
|
106
|
+
declaration if declaration.respond_to?(:has_ancestor?)
|
|
107
|
+
end
|
|
59
108
|
end
|
|
60
109
|
end
|
|
61
110
|
end
|