rubocop-view_component 0.1.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.
data/PLAN.md ADDED
@@ -0,0 +1,625 @@
1
+ # RuboCop ViewComponent - Implementation Plan
2
+
3
+ ## Overview
4
+
5
+ This document outlines the plan for building `rubocop-view_component`, a RuboCop extension to enforce ViewComponent best practices based on the official [ViewComponent Best Practices](https://viewcomponent.org/best_practices.html).
6
+
7
+ ## Research Summary
8
+
9
+ ### Existing Work
10
+
11
+ **Primer ViewComponents** (GitHub's design system) has custom RuboCop cops for ViewComponent:
12
+ - Located at: `lib/rubocop/cop/primer/`
13
+ - Includes cops like `Primer/NoTagMemoize`
14
+ - Source: [Primer ViewComponents Linting Docs](https://github.com/primer/view_components/blob/main/docs/contributors/linting.md)
15
+ - Note: We will not inherit from their configuration, but can reference their implementation patterns
16
+
17
+ ### Key Technologies
18
+
19
+ 1. **RuboCop AST Processing**
20
+ - Cops inherit from `RuboCop::Cop::Base`
21
+ - Use `def_node_matcher` for declarative pattern matching
22
+ - Hook into callbacks like `on_send`, `on_class`, `on_def`
23
+ - Node patterns use syntax like `(send nil? :method_name)`
24
+ - Reference: [RuboCop Node Pattern Docs](https://docs.rubocop.org/rubocop-ast/node_pattern.html)
25
+
26
+ 2. **Testing ViewComponents**
27
+ - Use `render_inline(Component.new)` in tests
28
+ - Assert against rendered output, not instance methods
29
+ - Slots defined with `renders_one`/`renders_many`
30
+ - Reference: [ViewComponent Testing Guide](https://viewcomponent.org/guide/testing.html)
31
+
32
+ ---
33
+
34
+ ## Proposed Cops (Priority Order)
35
+
36
+ ### Phase 1: High-Value, Easy to Implement
37
+
38
+ #### 1. `ViewComponent/ComponentSuffix`
39
+ **Priority:** HIGH
40
+ **Complexity:** LOW
41
+
42
+ **Description:**
43
+ Enforce that all ViewComponent classes end with the `-Component` suffix to follow Rails naming conventions.
44
+
45
+ **Detection Pattern:**
46
+ ```ruby
47
+ # Bad
48
+ class FooBar < ViewComponent::Base
49
+ class UserCard < ApplicationComponent
50
+
51
+ # Good
52
+ class FooBarComponent < ViewComponent::Base
53
+ class UserCardComponent < ApplicationComponent
54
+ ```
55
+
56
+ **Implementation:**
57
+ - Hook: `on_class`
58
+ - Node Pattern: `(class (const _ !/_Component$/) (const ...) ...)`
59
+ - Check if superclass is `ViewComponent::Base` or inherits from it
60
+ - Suggest renaming to include `Component` suffix
61
+
62
+ **Autocorrect:** No
63
+
64
+ ---
65
+
66
+ #### 2. `ViewComponent/NoGlobalState`
67
+ **Priority:** HIGH
68
+ **Complexity:** MEDIUM
69
+
70
+ **Description:**
71
+ Prevent direct access to global state (`params`, `request`, `session`, `cookies`, `flash`) within components. These should be passed as constructor arguments.
72
+
73
+ **Detection Pattern:**
74
+ ```ruby
75
+ # Bad
76
+ class UserComponent < ViewComponent::Base
77
+ def initialize(user)
78
+ @user = user
79
+ end
80
+
81
+ def admin?
82
+ params[:admin] == "true" # Direct access to params
83
+ end
84
+
85
+ def user_agent
86
+ request.user_agent # Direct access to request
87
+ end
88
+ end
89
+
90
+ # Good
91
+ class UserComponent < ViewComponent::Base
92
+ def initialize(user, admin: false)
93
+ @user = user
94
+ @admin = admin
95
+ end
96
+
97
+ def admin?
98
+ @admin
99
+ end
100
+ end
101
+ ```
102
+
103
+ **Implementation:**
104
+ - Hook: `on_send`
105
+ - Node Pattern: `(send nil? {:params :request :session :cookies :flash})`
106
+ - Check if within a ViewComponent class context
107
+ - Suggest passing as initialization parameter
108
+
109
+ **Autocorrect:** No (requires refactoring)
110
+
111
+ **References:**
112
+ - [ViewComponent Best Practices - Avoid Global State](https://viewcomponent.org/best_practices.html)
113
+
114
+ ---
115
+
116
+ #### 3. `ViewComponent/PreferPrivateMethods`
117
+ **Priority:** MEDIUM
118
+ **Complexity:** LOW
119
+
120
+ **Description:**
121
+ Suggest making helper methods private since they remain accessible in templates anyway. Only standard ViewComponent interface methods should be public.
122
+
123
+ **Detection Pattern:**
124
+ ```ruby
125
+ # Bad
126
+ class CardComponent < ViewComponent::Base
127
+ def initialize(title)
128
+ @title = title
129
+ end
130
+
131
+ def formatted_title # Should be private
132
+ @title.upcase
133
+ end
134
+ end
135
+
136
+ # Good
137
+ class CardComponent < ViewComponent::Base
138
+ def initialize(title)
139
+ @title = title
140
+ end
141
+
142
+ private
143
+
144
+ def formatted_title
145
+ @title.upcase
146
+ end
147
+ end
148
+ ```
149
+
150
+ **Implementation:**
151
+ - Hook: `on_def`
152
+ - Allowlist: `initialize`, `call`, `before_render`, `render?`
153
+ - Check visibility of other methods
154
+ - Suggest making non-interface methods private
155
+
156
+ **Autocorrect:** No
157
+
158
+ ---
159
+
160
+ #### 4. `ViewComponent/PreferSlots`
161
+ **Priority:** MEDIUM
162
+ **Complexity:** MEDIUM
163
+
164
+ **Description:**
165
+ Detect parameters that accept HTML content and suggest using slots instead. This maintains Rails' HTML sanitization protections.
166
+
167
+ **Detection Pattern:**
168
+ ```ruby
169
+ # Bad
170
+ class ModalComponent < ViewComponent::Base
171
+ def initialize(title:, body_html:) # HTML as string parameter
172
+ @title = title
173
+ @body_html = body_html
174
+ end
175
+ end
176
+
177
+ # Usage (unsafe)
178
+ <%= render ModalComponent.new(
179
+ title: "Alert",
180
+ body_html: "<p>#{user_input}</p>".html_safe
181
+ ) %>
182
+
183
+ # Good
184
+ class ModalComponent < ViewComponent::Base
185
+ renders_one :body
186
+
187
+ def initialize(title:)
188
+ @title = title
189
+ end
190
+ end
191
+
192
+ # Usage (safe)
193
+ <%= render ModalComponent.new(title: "Alert") do |c| %>
194
+ <% c.with_body do %>
195
+ <p><%= user_input %></p>
196
+ <% end %>
197
+ <% end %>
198
+ ```
199
+
200
+ **Implementation:**
201
+ - Hook: `on_def` (check `initialize` method)
202
+ - Look for parameters ending in `_html`, `_content`, or types suggesting HTML
203
+ - Look for `.html_safe` calls in parameter defaults
204
+ - Suggest using `renders_one` or `renders_many` instead
205
+
206
+ **Autocorrect:** No (requires refactoring)
207
+
208
+ **Security Impact:** HIGH - prevents XSS vulnerabilities
209
+
210
+ **References:**
211
+ - [ViewComponent Best Practices - Prefer Slots Over HTML Arguments](https://viewcomponent.org/best_practices.html)
212
+ - [ViewComponent Slots Guide](https://viewcomponent.org/guide/slots.html)
213
+
214
+ ---
215
+
216
+ ### Phase 2: Architectural Quality
217
+
218
+ #### 5. `ViewComponent/PreferComposition`
219
+ **Priority:** MEDIUM
220
+ **Complexity:** MEDIUM
221
+
222
+ **Description:**
223
+ Detect inheritance chains deeper than one level and suggest composition instead. Inheriting one component from another causes confusion when each has its own template.
224
+
225
+ **Detection Pattern:**
226
+ ```ruby
227
+ # Bad
228
+ class BaseCard < ViewComponent::Base
229
+ # template: base_card.html.erb
230
+ end
231
+
232
+ class UserCard < BaseCard # Inheritance from another component
233
+ # template: user_card.html.erb - confusing!
234
+ end
235
+
236
+ # Good
237
+ class UserCardComponent < ViewComponent::Base
238
+ def initialize(user)
239
+ @user = user
240
+ end
241
+
242
+ # Render BaseCardComponent within template via composition
243
+ end
244
+ ```
245
+
246
+ **Implementation:**
247
+ - Hook: `on_class`
248
+ - Track inheritance chain depth
249
+ - Detect if superclass is a ViewComponent (not `ViewComponent::Base` or `ApplicationComponent`)
250
+ - Suggest wrapping pattern instead
251
+
252
+ **Autocorrect:** No (requires architectural refactoring)
253
+
254
+ **References:**
255
+ - [ViewComponent Best Practices - Composition Over Inheritance](https://viewcomponent.org/best_practices.html)
256
+
257
+ ---
258
+
259
+ #### 6. `ViewComponent/AvoidSingleUseComponents`
260
+ **Priority:** LOW
261
+ **Complexity:** MEDIUM
262
+
263
+ **Description:**
264
+ Detect components that appear to be single-use (no methods, no slots, minimal logic) and suggest reconsidering if the component adds value over a partial.
265
+
266
+ **Detection Pattern:**
267
+ ```ruby
268
+ # Questionable
269
+ class SimpleWrapperComponent < ViewComponent::Base
270
+ def initialize(content)
271
+ @content = content
272
+ end
273
+ # No other methods, no slots, no logic
274
+ end
275
+
276
+ # Better as partial or inline template
277
+ ```
278
+
279
+ **Implementation:**
280
+ - Hook: `on_class`
281
+ - Analyze class body for:
282
+ - Number of instance methods (beyond `initialize`)
283
+ - Presence of slots (`renders_one`, `renders_many`)
284
+ - Complexity of logic
285
+ - Provide informational warning if component seems trivial
286
+
287
+ **Autocorrect:** No
288
+
289
+ **Severity:** Information (not error)
290
+
291
+ **References:**
292
+ - [ViewComponent Best Practices - Minimize One-Offs](https://viewcomponent.org/best_practices.html)
293
+
294
+ ---
295
+
296
+ ### Phase 3: Testing Best Practices
297
+
298
+ #### 7. `ViewComponent/TestRenderedOutput`
299
+ **Priority:** MEDIUM
300
+ **Complexity:** MEDIUM
301
+
302
+ **Description:**
303
+ In test files, detect assertions against component instance methods and suggest using `render_inline` with content assertions instead.
304
+
305
+ **Detection Pattern:**
306
+ ```ruby
307
+ # Bad
308
+ test "formats title" do
309
+ component = TitleComponent.new("hello")
310
+ assert_equal "HELLO", component.formatted_title # Testing private method
311
+ end
312
+
313
+ # Good
314
+ test "renders formatted title" do
315
+ render_inline TitleComponent.new("hello")
316
+ assert_text "HELLO"
317
+ end
318
+ ```
319
+
320
+ **Implementation:**
321
+ - Hook: `on_send`
322
+ - Context: Within test files (`*_test.rb`, `*_spec.rb`)
323
+ - Detect: Method calls on component instances (not `render_inline` results)
324
+ - Suggest: Using `render_inline` and asserting against rendered output
325
+
326
+ **Autocorrect:** No
327
+
328
+ **References:**
329
+ - [ViewComponent Best Practices - Test Rendered Output](https://viewcomponent.org/best_practices.html)
330
+ - [ViewComponent Testing Guide](https://viewcomponent.org/guide/testing.html)
331
+
332
+ ---
333
+
334
+
335
+ ## Technical Architecture
336
+
337
+ ### Directory Structure
338
+
339
+ ```
340
+ lib/
341
+ ├── rubocop/
342
+ │ ├── cop/
343
+ │ │ ├── view_component_cops.rb # Requires all cops
344
+ │ │ └── view_component/
345
+ │ │ ├── component_suffix.rb
346
+ │ │ ├── no_global_state.rb
347
+ │ │ ├── prefer_private_methods.rb
348
+ │ │ ├── prefer_slots.rb
349
+ │ │ ├── prefer_composition.rb
350
+ │ │ ├── avoid_single_use_components.rb
351
+ │ │ └── test_rendered_output.rb
352
+ │ └── view_component/
353
+ │ ├── version.rb
354
+ │ ├── plugin.rb
355
+ │ └── inject.rb # Config injection
356
+ ├── rubocop-view_component.rb
357
+ config/
358
+ └── default.yml # Default cop configuration
359
+ spec/
360
+ └── rubocop/
361
+ └── cop/
362
+ └── view_component/
363
+ ├── component_suffix_spec.rb
364
+ └── ...
365
+ ```
366
+
367
+ ### Cop Template
368
+
369
+ Each cop should follow this structure:
370
+
371
+ ```ruby
372
+ # frozen_string_literal: true
373
+
374
+ module RuboCop
375
+ module Cop
376
+ module ViewComponent
377
+ # Enforces [best practice name].
378
+ #
379
+ # @example
380
+ # # bad
381
+ # [bad code example]
382
+ #
383
+ # # good
384
+ # [good code example]
385
+ #
386
+ class CopName < Base
387
+ MSG = 'Explain the problem and suggest solution.'
388
+ RESTRICT_ON_SEND = %i[method_name].freeze # Optional optimization
389
+
390
+ def_node_matcher :pattern_name, <<~PATTERN
391
+ (send ...)
392
+ PATTERN
393
+
394
+ def on_send(node)
395
+ return unless pattern_name(node)
396
+ # Check conditions
397
+
398
+ add_offense(node)
399
+ end
400
+
401
+ private
402
+
403
+ def in_view_component?(node)
404
+ # Helper to check if within ViewComponent class
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end
410
+ ```
411
+
412
+ ### Configuration (config/default.yml)
413
+
414
+ ```yaml
415
+ ViewComponent/ComponentSuffix:
416
+ Description: 'Enforce -Component suffix for ViewComponent classes.'
417
+ Enabled: true
418
+ VersionAdded: '0.1'
419
+ Severity: warning
420
+
421
+ ViewComponent/NoGlobalState:
422
+ Description: 'Avoid accessing global state (params, request, session, etc.) directly.'
423
+ Enabled: true
424
+ VersionAdded: '0.1'
425
+ Severity: warning
426
+
427
+ ViewComponent/PreferPrivateMethods:
428
+ Description: 'Suggest making helper methods private.'
429
+ Enabled: true
430
+ VersionAdded: '0.1'
431
+ Severity: convention
432
+ AllowedPublicMethods:
433
+ - initialize
434
+ - call
435
+ - before_render
436
+ - render?
437
+
438
+ # ... etc
439
+ ```
440
+
441
+ ### Helper Modules
442
+
443
+ Create shared utilities:
444
+
445
+ ```ruby
446
+ # lib/rubocop/cop/view_component/helpers.rb
447
+ module RuboCop
448
+ module Cop
449
+ module ViewComponent
450
+ module Helpers
451
+ def view_component_class?(node)
452
+ # Check if node is within a ViewComponent class
453
+ end
454
+
455
+ def inherits_from_view_component?(class_node)
456
+ # Check inheritance chain
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end
462
+ ```
463
+
464
+ ---
465
+
466
+ ## Implementation Phases
467
+
468
+ ### Phase 1: Foundation (Week 1-2)
469
+ - [ ] Set up proper gem structure with lint_roller integration
470
+ - [ ] Create base helper modules
471
+ - [ ] Implement `ComponentSuffix` cop
472
+ - [ ] Implement `NoGlobalState` cop
473
+ - [ ] Write comprehensive tests for Phase 1 cops
474
+ - [ ] Update default.yml configuration
475
+
476
+ ### Phase 2: Core Best Practices (Week 3-4)
477
+ - [ ] Implement `PreferPrivateMethods` cop
478
+ - [ ] Implement `PreferSlots` cop
479
+ - [ ] Implement `PreferComposition` cop
480
+ - [ ] Documentation and examples
481
+
482
+ ### Phase 3: Testing & Quality (Week 5-6)
483
+ - [ ] Implement `TestRenderedOutput` cop
484
+ - [ ] Implement `AvoidSingleUseComponents` cop
485
+ - [ ] Add performance optimizations
486
+ - [ ] Integration testing with real-world ViewComponent projects
487
+ - [ ] Performance benchmarking
488
+ - [ ] Documentation site
489
+
490
+ ---
491
+
492
+ ## Testing Strategy
493
+
494
+ ### Cop Testing
495
+ Each cop should have:
496
+
497
+ 1. **Positive cases** - Code that triggers the offense
498
+ 2. **Negative cases** - Code that should not trigger
499
+ 3. **Edge cases** - Boundary conditions
500
+
501
+ Example test structure:
502
+ ```ruby
503
+ RSpec.describe RuboCop::Cop::ViewComponent::NoGlobalState, :config do
504
+ it 'registers an offense when accessing params' do
505
+ expect_offense(<<~RUBY)
506
+ class MyComponent < ViewComponent::Base
507
+ def admin?
508
+ params[:admin]
509
+ ^^^^^^ Avoid accessing global state directly. Pass as initialization parameter.
510
+ end
511
+ end
512
+ RUBY
513
+ end
514
+
515
+ it 'does not register offense for instance variables' do
516
+ expect_no_offenses(<<~RUBY)
517
+ class MyComponent < ViewComponent::Base
518
+ def initialize(admin:)
519
+ @admin = admin
520
+ end
521
+
522
+ def admin?
523
+ @admin
524
+ end
525
+ end
526
+ RUBY
527
+ end
528
+ end
529
+ ```
530
+
531
+ ### Integration Testing
532
+ - Integration testing with fixture ViewComponent projects
533
+
534
+ ---
535
+
536
+ ## Documentation Plan
537
+
538
+ ### README.md Updates
539
+ - Clear installation instructions
540
+ - Quick start guide
541
+ - List of all cops with examples
542
+ - Configuration options
543
+ - Integration with CI/CD
544
+
545
+ ### Individual Cop Documentation
546
+ Each cop should have:
547
+ - Clear description
548
+ - Why it exists (reference to best practice)
549
+ - Bad/good code examples
550
+ - Configuration options
551
+
552
+ ---
553
+
554
+ ## Future Enhancements
555
+
556
+ ### Potential Additional Cops
557
+
558
+ 1. **`ViewComponent/MinimizeTemplateLogic`**
559
+ - Detect complex Ruby logic in `.html.erb` templates
560
+ - Suggest extracting to component methods
561
+ - **Challenge:** Requires parsing ERB templates
562
+ - Reference: [ViewComponent Best Practices - Minimize Template Logic](https://viewcomponent.org/best_practices.html)
563
+
564
+ 2. **`ViewComponent/NoQueryInComponent`**
565
+ - Detect ActiveRecord queries in components
566
+ - Components should receive data, not fetch it
567
+
568
+ 3. **`ViewComponent/PreferStrictLocals`**
569
+ - Encourage use of `locals` declaration in templates
570
+ - Catches typos and documents component API
571
+
572
+ 4. **`ViewComponent/SlotNamingConvention`**
573
+ - Enforce naming conventions for slots
574
+ - Singular for `renders_one`, plural for `renders_many`
575
+
576
+ 5. **`ViewComponent/NoControllerHelpers`**
577
+ - Detect usage of controller-specific helpers
578
+ - Promotes reusability
579
+
580
+
581
+ ## References & Resources
582
+
583
+ ### Official Documentation
584
+ - [ViewComponent Best Practices](https://viewcomponent.org/best_practices.html)
585
+ - [ViewComponent Testing Guide](https://viewcomponent.org/guide/testing.html)
586
+ - [ViewComponent Slots Guide](https://viewcomponent.org/guide/slots.html)
587
+ - [RuboCop Development Guide](https://docs.rubocop.org/rubocop/development.html)
588
+ - [RuboCop Node Patterns](https://docs.rubocop.org/rubocop-ast/node_pattern.html)
589
+
590
+ ### Community Resources
591
+ - [Custom Cops for RuboCop - Evil Martians](https://evilmartians.com/chronicles/custom-cops-for-rubocop-an-emergency-service-for-your-codebase)
592
+ - [Create a Custom RuboCop Cop - FastRuby.io](https://www.fastruby.io/blog/rubocop/code-quality/create-a-custom-rubocop-cop.html)
593
+ - [Thoughtbot - Custom Cops](https://thoughtbot.com/blog/rubocop-custom-cops-for-custom-needs)
594
+ - [RuboCop RSpec Cops](https://docs.rubocop.org/rubocop-rspec/cops_rspec.html)
595
+ - [Shopify ERB Lint](https://github.com/Shopify/erb_lint)
596
+
597
+ ### Similar Projects
598
+ - [rubocop-rails](https://github.com/rubocop/rubocop-rails)
599
+ - [rubocop-rspec](https://github.com/rubocop/rubocop-rspec)
600
+ - [Primer ViewComponents](https://github.com/primer/view_components) - Has custom cops
601
+
602
+ ### ViewComponent Anti-Patterns
603
+ - [ViewComponent Tips](https://railsnotes.xyz/blog/rails-viewcomponent-tips)
604
+ - [Advanced ViewComponent Patterns](https://dev.to/abeidahmed/advanced-viewcomponent-patterns-in-rails-2b4m)
605
+ - [ViewComponent in the Wild - Evil Martians](https://evilmartians.com/chronicles/viewcomponent-in-the-wild-building-modern-rails-frontends)
606
+
607
+ ---
608
+
609
+ ## Next Steps
610
+
611
+ 1. **Review this plan** with stakeholders/community
612
+ 2. **Set up development environment** with proper test fixtures
613
+ 3. **Start with Phase 1** - implement high-value, low-complexity cops first
614
+ 4. **Get early feedback** from ViewComponent users
615
+ 5. **Iterate based on real-world usage**
616
+
617
+ ---
618
+
619
+ ## Questions to Resolve
620
+
621
+ 1. Should we support legacy ViewComponent versions (< 3.0)? **NO** - Only support ViewComponent 3.0+
622
+ 2. How to handle custom base classes (e.g., `ApplicationComponent`)? **TBD** - Discuss later
623
+ 3. Should we integrate with erb-lint or keep separate? **SEPARATE** - Keep as separate tool
624
+ 4. What's the policy on autocorrect? **NO AUTOCORRECT** - Detection only, no automatic fixes
625
+ 5. Should we coordinate with Primer ViewComponents team to avoid duplication? **NO** - Independent project
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # RuboCop::ViewComponent
2
+
3
+ A RuboCop extension that enforces [ViewComponent best practices](https://viewcomponent.org/best_practices.html).
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rubocop-view_component', require: false
11
+ ```
12
+
13
+ Add to your `.rubocop.yml`:
14
+
15
+ ```yaml
16
+ require:
17
+ - rubocop-view_component
18
+ ```
19
+
20
+ ## Cops
21
+
22
+ This gem provides several cops to enforce ViewComponent best practices:
23
+
24
+ - **ViewComponent/ComponentSuffix** - Enforce `-Component` suffix for ViewComponent classes
25
+ - **ViewComponent/NoGlobalState** - Prevent direct access to `params`, `request`, `session`, etc.
26
+ - **ViewComponent/PreferPrivateMethods** - Suggest making helper methods private
27
+ - **ViewComponent/PreferSlots** - Detect HTML parameters that should be slots
28
+ - **ViewComponent/PreferComposition** - Discourage deep inheritance chains
29
+ - **ViewComponent/TestRenderedOutput** - Encourage testing rendered output over private methods
30
+
31
+ See [PLAN.md](PLAN.md) for detailed cop descriptions and implementation status.
32
+
33
+ ## Usage
34
+
35
+ Run RuboCop as usual:
36
+
37
+ ```bash
38
+ bundle exec rubocop
39
+ ```
40
+
41
+ ## Development
42
+
43
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
44
+
45
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
46
+
47
+ ## Contributing
48
+
49
+ Bug reports and pull requests are welcome on GitHub at https://github.com/andyw8/rubocop-view_component.
50
+
51
+ ## License
52
+
53
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
11
+
12
+ require "rspec/core/rake_task"
13
+
14
+ RSpec::Core::RakeTask.new(:spec) do |spec|
15
+ spec.pattern = FileList["spec/**/*_spec.rb"]
16
+ end
17
+
18
+ desc "Generate a new cop with a template"
19
+ task :new_cop, [:cop] do |_task, args|
20
+ require "rubocop"
21
+
22
+ cop_name = args.fetch(:cop) do
23
+ warn "usage: bundle exec rake new_cop[Department/Name]"
24
+ exit!
25
+ end
26
+
27
+ generator = RuboCop::Cop::Generator.new(cop_name)
28
+
29
+ generator.write_source
30
+ generator.write_spec
31
+ generator.inject_require(root_file_path: "lib/rubocop/cop/view_component_cops.rb")
32
+ generator.inject_config(config_file_path: "config/default.yml")
33
+
34
+ puts generator.todo
35
+ end