shirobai-rails 2026.0709.0000
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 +7 -0
- data/LICENSE.txt +21 -0
- data/lib/shirobai/cop/rails/application_controller.rb +50 -0
- data/lib/shirobai/cop/rails/application_job.rb +57 -0
- data/lib/shirobai/cop/rails/application_mailer.rb +51 -0
- data/lib/shirobai/cop/rails/application_record.rb +65 -0
- data/lib/shirobai/cop/rails/candidate_support.rb +56 -0
- data/lib/shirobai/cop/rails/deprecated_active_model_errors_methods.rb +171 -0
- data/lib/shirobai/cop/rails/dynamic_find_by.rb +66 -0
- data/lib/shirobai/cop/rails/http_positional_arguments.rb +160 -0
- data/lib/shirobai/cop/rails/pluck.rb +61 -0
- data/lib/shirobai/cop/rails/unknown_env.rb +88 -0
- data/lib/shirobai/rails/version.rb +9 -0
- data/lib/shirobai-rails.rb +95 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 59377ca996914ce8807076603ac3a1b9ea4e36f99122b84ab54d021aa19e5ddc
|
|
4
|
+
data.tar.gz: 63686f3c441b593525971c307bca3b35cf38f14eab0fa6c8988bed3316466268
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 71cb62d85e9f072a0f79bd5fd41148b685a44e09088be5e0d4d88e4885ce0082ce4830ae9807116ffadea432de54f6baab4a1516480059fbf7a2892f5f15e140
|
|
7
|
+
data.tar.gz: f75adf3a6314b9173d52822852b44ab59b9a0fb62c6fd5d991bbab2b2b0c170393d3e668acc3a3ab2a94891ea4ae6a38442a549f94a5e176feba3a6d1ded5628
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fusagiko / takayamaki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/ApplicationController`
|
|
7
|
+
# (rubocop-rails 2.35.5). See `ApplicationRecord` for the shared
|
|
8
|
+
# `EnforceSuperclass` semantics and offense/autocorrect ranges.
|
|
9
|
+
#
|
|
10
|
+
# Unlike Record / Mailer / Job this cop has NO `TargetRailsVersion`
|
|
11
|
+
# gate — stock does not declare one, so it runs on every Rails version.
|
|
12
|
+
class ApplicationController < RuboCop::Cop::Base
|
|
13
|
+
include Shirobai::Cop::BundleEligible
|
|
14
|
+
extend RuboCop::Cop::AutoCorrector
|
|
15
|
+
|
|
16
|
+
MSG = "Controllers should subclass `ApplicationController`."
|
|
17
|
+
SUPERCLASS = "ApplicationController"
|
|
18
|
+
|
|
19
|
+
def self.cop_name = "Rails/ApplicationController"
|
|
20
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
21
|
+
|
|
22
|
+
def self.bundle_args(_config)
|
|
23
|
+
[]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_new_investigation
|
|
27
|
+
buffer = processed_source.buffer
|
|
28
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
29
|
+
off = SourceOffsets.for(source)
|
|
30
|
+
resolved_offenses.each do |start_offset, end_offset|
|
|
31
|
+
range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
|
|
32
|
+
add_offense(range, message: MSG) do |corrector|
|
|
33
|
+
corrector.replace(range, self.class::SUPERCLASS)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def resolved_offenses
|
|
41
|
+
if bundle_eligible?
|
|
42
|
+
Dispatch.offenses_for(processed_source, config, :rails_application_controller)
|
|
43
|
+
else
|
|
44
|
+
Shirobai.check_rails_application_controller(processed_source.buffer.source)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/ApplicationJob`
|
|
7
|
+
# (rubocop-rails 2.35.5). See `ApplicationRecord` for the shared
|
|
8
|
+
# `EnforceSuperclass` semantics and offense/autocorrect ranges. Gated on
|
|
9
|
+
# `requires_gem('railties', '>= 5.0')` like stock.
|
|
10
|
+
#
|
|
11
|
+
# (Stock defines an explicit `autocorrect(node)` returning
|
|
12
|
+
# `corrector.replace(node, SUPERCLASS)`; the other three cops reach the
|
|
13
|
+
# same replacement through the `AutoCorrector` block. Both collapse to
|
|
14
|
+
# "replace the offense range with the superclass name", which is what
|
|
15
|
+
# the shared wrapper corrector does here.)
|
|
16
|
+
class ApplicationJob < RuboCop::Cop::Base
|
|
17
|
+
include Shirobai::Cop::BundleEligible
|
|
18
|
+
extend RuboCop::Cop::AutoCorrector
|
|
19
|
+
extend RuboCop::Cop::TargetRailsVersion
|
|
20
|
+
|
|
21
|
+
minimum_target_rails_version 5.0
|
|
22
|
+
|
|
23
|
+
MSG = "Jobs should subclass `ApplicationJob`."
|
|
24
|
+
SUPERCLASS = "ApplicationJob"
|
|
25
|
+
|
|
26
|
+
def self.cop_name = "Rails/ApplicationJob"
|
|
27
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
28
|
+
|
|
29
|
+
def self.bundle_args(_config)
|
|
30
|
+
[]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def on_new_investigation
|
|
34
|
+
buffer = processed_source.buffer
|
|
35
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
36
|
+
off = SourceOffsets.for(source)
|
|
37
|
+
resolved_offenses.each do |start_offset, end_offset|
|
|
38
|
+
range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
|
|
39
|
+
add_offense(range, message: MSG) do |corrector|
|
|
40
|
+
corrector.replace(range, self.class::SUPERCLASS)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def resolved_offenses
|
|
48
|
+
if bundle_eligible?
|
|
49
|
+
Dispatch.offenses_for(processed_source, config, :rails_application_job)
|
|
50
|
+
else
|
|
51
|
+
Shirobai.check_rails_application_job(processed_source.buffer.source)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/ApplicationMailer`
|
|
7
|
+
# (rubocop-rails 2.35.5). See `ApplicationRecord` for the shared
|
|
8
|
+
# `EnforceSuperclass` semantics and offense/autocorrect ranges. Gated on
|
|
9
|
+
# `requires_gem('railties', '>= 5.0')` like stock.
|
|
10
|
+
class ApplicationMailer < RuboCop::Cop::Base
|
|
11
|
+
include Shirobai::Cop::BundleEligible
|
|
12
|
+
extend RuboCop::Cop::AutoCorrector
|
|
13
|
+
extend RuboCop::Cop::TargetRailsVersion
|
|
14
|
+
|
|
15
|
+
minimum_target_rails_version 5.0
|
|
16
|
+
|
|
17
|
+
MSG = "Mailers should subclass `ApplicationMailer`."
|
|
18
|
+
SUPERCLASS = "ApplicationMailer"
|
|
19
|
+
|
|
20
|
+
def self.cop_name = "Rails/ApplicationMailer"
|
|
21
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
22
|
+
|
|
23
|
+
def self.bundle_args(_config)
|
|
24
|
+
[]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def on_new_investigation
|
|
28
|
+
buffer = processed_source.buffer
|
|
29
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
30
|
+
off = SourceOffsets.for(source)
|
|
31
|
+
resolved_offenses.each do |start_offset, end_offset|
|
|
32
|
+
range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
|
|
33
|
+
add_offense(range, message: MSG) do |corrector|
|
|
34
|
+
corrector.replace(range, self.class::SUPERCLASS)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def resolved_offenses
|
|
42
|
+
if bundle_eligible?
|
|
43
|
+
Dispatch.offenses_for(processed_source, config, :rails_application_mailer)
|
|
44
|
+
else
|
|
45
|
+
Shirobai.check_rails_application_mailer(processed_source.buffer.source)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/ApplicationRecord`
|
|
7
|
+
# (rubocop-rails 2.35.5).
|
|
8
|
+
#
|
|
9
|
+
# Rust replicates stock's `EnforceSuperclass` mixin: `class X <
|
|
10
|
+
# ActiveRecord::Base` (unless `X`'s terminal name is `ApplicationRecord`)
|
|
11
|
+
# and `Class.new(ActiveRecord::Base)` (unless it is the direct value of
|
|
12
|
+
# an `ApplicationRecord =` constant write, covering the `do..end` / `{}`
|
|
13
|
+
# block forms). The offense range is the superclass / argument const node
|
|
14
|
+
# (leading `::` included); the wrapper replaces it with the bare
|
|
15
|
+
# `ApplicationRecord` name — byte for byte with stock's autocorrect.
|
|
16
|
+
#
|
|
17
|
+
# `TargetRailsVersion`: like stock this cop is gated on
|
|
18
|
+
# `requires_gem('railties', '>= 5.0')`, so it stays silent on Rails <
|
|
19
|
+
# 5.0 or without railties in the target bundle. The `Exclude:
|
|
20
|
+
# db/**/*.rb` from the merged default.yml is resolved by RuboCop through
|
|
21
|
+
# this wrapper's badge, exactly as for the stock cop.
|
|
22
|
+
class ApplicationRecord < RuboCop::Cop::Base
|
|
23
|
+
include Shirobai::Cop::BundleEligible
|
|
24
|
+
extend RuboCop::Cop::AutoCorrector
|
|
25
|
+
extend RuboCop::Cop::TargetRailsVersion
|
|
26
|
+
|
|
27
|
+
minimum_target_rails_version 5.0
|
|
28
|
+
|
|
29
|
+
MSG = "Models should subclass `ApplicationRecord`."
|
|
30
|
+
SUPERCLASS = "ApplicationRecord"
|
|
31
|
+
|
|
32
|
+
def self.cop_name = "Rails/ApplicationRecord"
|
|
33
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
34
|
+
|
|
35
|
+
# No behavioral config: the segment is a wake-up flag only, so
|
|
36
|
+
# `bundle_args` contributes nothing.
|
|
37
|
+
def self.bundle_args(_config)
|
|
38
|
+
[]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def on_new_investigation
|
|
42
|
+
buffer = processed_source.buffer
|
|
43
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
44
|
+
off = SourceOffsets.for(source)
|
|
45
|
+
resolved_offenses.each do |start_offset, end_offset|
|
|
46
|
+
range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
|
|
47
|
+
add_offense(range, message: MSG) do |corrector|
|
|
48
|
+
corrector.replace(range, self.class::SUPERCLASS)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def resolved_offenses
|
|
56
|
+
if bundle_eligible?
|
|
57
|
+
Dispatch.offenses_for(processed_source, config, :rails_application_record)
|
|
58
|
+
else
|
|
59
|
+
Shirobai.check_rails_application_record(processed_source.buffer.source)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Shared harness for the Architecture-B Rails cops
|
|
7
|
+
# (`HttpPositionalArguments`, `DeprecatedActiveModelErrorsMethods`).
|
|
8
|
+
#
|
|
9
|
+
# Rust identifies candidate SEND ranges once on the shared walk; this
|
|
10
|
+
# module relocates each parser send node (`Shirobai::NodeLocator`) and
|
|
11
|
+
# hands it to the cop's `investigate_send`, which is stock's `on_send`
|
|
12
|
+
# copied verbatim (renamed so the Commissioner never dispatches a
|
|
13
|
+
# per-node `on_send`). Every guard, matcher, receiver walk and
|
|
14
|
+
# autocorrect runs on the real parser AST, so detection and `-A` bytes
|
|
15
|
+
# match stock exactly; the Rust prefilter only narrows which nodes are
|
|
16
|
+
# visited.
|
|
17
|
+
#
|
|
18
|
+
# The candidate set is a superset — false positives (e.g. a routing-block
|
|
19
|
+
# `get`, a non-model bare `errors`) are dropped by the stock matchers the
|
|
20
|
+
# wrapper re-runs.
|
|
21
|
+
module CandidateSupport
|
|
22
|
+
include Shirobai::Cop::BundleEligible
|
|
23
|
+
|
|
24
|
+
def on_new_investigation
|
|
25
|
+
ranges = resolved_candidates
|
|
26
|
+
return if ranges.nil? || ranges.empty?
|
|
27
|
+
|
|
28
|
+
source = bundle_eligible? ? processed_source.raw_source : processed_source.buffer.source
|
|
29
|
+
off = SourceOffsets.for(source)
|
|
30
|
+
keys = ranges.map { |(start, fin)| [off[start], off[fin]] }
|
|
31
|
+
located = Shirobai::NodeLocator.locate(processed_source, keys)
|
|
32
|
+
|
|
33
|
+
keys.each do |key|
|
|
34
|
+
node = located[key]
|
|
35
|
+
investigate_send(node) if node&.send_type?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Bundle path only when raw_source and the parser buffer agree byte for
|
|
42
|
+
# byte; a gated-off file (nil from Dispatch) falls back to the
|
|
43
|
+
# standalone entry scanning `buffer.source`. The rails origin has no
|
|
44
|
+
# per-file gate, so on a bundle-eligible file the bundle path always
|
|
45
|
+
# wins.
|
|
46
|
+
def resolved_candidates
|
|
47
|
+
if bundle_eligible?
|
|
48
|
+
ranges = Dispatch.offenses_for(processed_source, config, candidate_slot)
|
|
49
|
+
return ranges unless ranges.nil?
|
|
50
|
+
end
|
|
51
|
+
fallback_candidates
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Shirobai
|
|
6
|
+
module Cop
|
|
7
|
+
module Rails
|
|
8
|
+
# Drop-in Rust-backed reimplementation of
|
|
9
|
+
# `Rails/DeprecatedActiveModelErrorsMethods` (rubocop-rails 2.35.5),
|
|
10
|
+
# Architecture B.
|
|
11
|
+
#
|
|
12
|
+
# Rust supplies candidate SEND ranges (the five errors-chain shapes:
|
|
13
|
+
# `errors[...]`/`errors.messages[...]`/`errors.details[...]` manipulation
|
|
14
|
+
# and assignment, and `errors.{keys,values,to_h,to_xml}`); this wrapper
|
|
15
|
+
# relocates each parser send node and runs stock's `on_send` (renamed
|
|
16
|
+
# `investigate_send`) plus autocorrect VERBATIM. The receiver-model
|
|
17
|
+
# heuristic (`model_file?`), the Rails <= 6.0 gate on the incompatible
|
|
18
|
+
# methods, `skip_autocorrect?` (uncorrectable `details << ...`) and the
|
|
19
|
+
# receiver-walk offense range all run on the parser AST, so offenses and
|
|
20
|
+
# `-A` bytes match stock exactly.
|
|
21
|
+
class DeprecatedActiveModelErrorsMethods < RuboCop::Cop::Base
|
|
22
|
+
include RuboCop::Cop::RangeHelp
|
|
23
|
+
include Shirobai::Cop::Rails::CandidateSupport
|
|
24
|
+
extend RuboCop::Cop::AutoCorrector
|
|
25
|
+
|
|
26
|
+
MSG = 'Avoid manipulating ActiveModel errors as hash directly.'
|
|
27
|
+
AUTOCORRECTABLE_METHODS = %i[<< clear keys].freeze
|
|
28
|
+
INCOMPATIBLE_METHODS = %i[keys values to_h to_xml].freeze
|
|
29
|
+
|
|
30
|
+
MANIPULATIVE_METHODS = Set[
|
|
31
|
+
*%i[
|
|
32
|
+
<< append clear collect! compact! concat
|
|
33
|
+
delete delete_at delete_if drop drop_while fill filter! keep_if
|
|
34
|
+
flatten! insert map! pop prepend push reject! replace reverse!
|
|
35
|
+
rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift
|
|
36
|
+
]
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
def self.cop_name = "Rails/DeprecatedActiveModelErrorsMethods"
|
|
40
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
41
|
+
|
|
42
|
+
def self.bundle_args(_config)
|
|
43
|
+
[]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def_node_matcher :receiver_matcher_outside_model, '{send ivar lvar}'
|
|
47
|
+
def_node_matcher :receiver_matcher_inside_model, '{nil? send ivar lvar}'
|
|
48
|
+
|
|
49
|
+
def_node_matcher :any_manipulation?, <<~PATTERN
|
|
50
|
+
{
|
|
51
|
+
#root_manipulation?
|
|
52
|
+
#root_assignment?
|
|
53
|
+
#errors_deprecated?
|
|
54
|
+
#messages_details_manipulation?
|
|
55
|
+
#messages_details_assignment?
|
|
56
|
+
}
|
|
57
|
+
PATTERN
|
|
58
|
+
|
|
59
|
+
def_node_matcher :root_manipulation?, <<~PATTERN
|
|
60
|
+
(send
|
|
61
|
+
(send
|
|
62
|
+
(send #receiver_matcher :errors) :[] ...)
|
|
63
|
+
MANIPULATIVE_METHODS
|
|
64
|
+
...
|
|
65
|
+
)
|
|
66
|
+
PATTERN
|
|
67
|
+
|
|
68
|
+
def_node_matcher :root_assignment?, <<~PATTERN
|
|
69
|
+
(send
|
|
70
|
+
(send #receiver_matcher :errors)
|
|
71
|
+
:[]=
|
|
72
|
+
...)
|
|
73
|
+
PATTERN
|
|
74
|
+
|
|
75
|
+
def_node_matcher :errors_deprecated?, <<~PATTERN
|
|
76
|
+
(send
|
|
77
|
+
(send #receiver_matcher :errors)
|
|
78
|
+
{:keys :values :to_h :to_xml})
|
|
79
|
+
PATTERN
|
|
80
|
+
|
|
81
|
+
def_node_matcher :messages_details_manipulation?, <<~PATTERN
|
|
82
|
+
(send
|
|
83
|
+
(send
|
|
84
|
+
(send
|
|
85
|
+
(send #receiver_matcher :errors)
|
|
86
|
+
{:messages :details})
|
|
87
|
+
:[]
|
|
88
|
+
...)
|
|
89
|
+
MANIPULATIVE_METHODS
|
|
90
|
+
...)
|
|
91
|
+
PATTERN
|
|
92
|
+
|
|
93
|
+
def_node_matcher :messages_details_assignment?, <<~PATTERN
|
|
94
|
+
(send
|
|
95
|
+
(send
|
|
96
|
+
(send #receiver_matcher :errors)
|
|
97
|
+
{:messages :details})
|
|
98
|
+
:[]=
|
|
99
|
+
...)
|
|
100
|
+
PATTERN
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def candidate_slot = :rails_deprecated_active_model_errors_methods
|
|
105
|
+
|
|
106
|
+
def fallback_candidates
|
|
107
|
+
Shirobai.check_rails_deprecated_active_model_errors_methods(processed_source.buffer.source)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# --- stock's methods, copied verbatim (rubocop-rails 2.35.5);
|
|
111
|
+
# `on_send` renamed to `investigate_send` so it is not a node callback.
|
|
112
|
+
|
|
113
|
+
def investigate_send(node)
|
|
114
|
+
any_manipulation?(node) do
|
|
115
|
+
next if target_rails_version <= 6.0 && INCOMPATIBLE_METHODS.include?(node.method_name)
|
|
116
|
+
|
|
117
|
+
add_offense(node) do |corrector|
|
|
118
|
+
next if skip_autocorrect?(node)
|
|
119
|
+
|
|
120
|
+
autocorrect(corrector, node)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def skip_autocorrect?(node)
|
|
126
|
+
return true unless AUTOCORRECTABLE_METHODS.include?(node.method_name)
|
|
127
|
+
return false unless (receiver = node.receiver.receiver)
|
|
128
|
+
|
|
129
|
+
receiver.send_type? && receiver.method?(:details) && node.method?(:<<)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def autocorrect(corrector, node)
|
|
133
|
+
receiver = node.receiver
|
|
134
|
+
|
|
135
|
+
range = offense_range(node, receiver)
|
|
136
|
+
replacement = replacement(node, receiver)
|
|
137
|
+
|
|
138
|
+
corrector.replace(range, replacement)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def offense_range(node, receiver)
|
|
142
|
+
receiver = receiver.receiver while receiver.send_type? && !receiver.method?(:errors) && receiver.receiver
|
|
143
|
+
range_between(receiver.source_range.end_pos, node.source_range.end_pos)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def replacement(node, receiver)
|
|
147
|
+
return '.attribute_names' if node.method?(:keys)
|
|
148
|
+
|
|
149
|
+
key = receiver.first_argument.source
|
|
150
|
+
|
|
151
|
+
case node.method_name
|
|
152
|
+
when :<<
|
|
153
|
+
value = node.first_argument.source
|
|
154
|
+
|
|
155
|
+
".add(#{key}, #{value})"
|
|
156
|
+
when :clear
|
|
157
|
+
".delete(#{key})"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def receiver_matcher(node)
|
|
162
|
+
model_file? ? receiver_matcher_inside_model(node) : receiver_matcher_outside_model(node)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def model_file?
|
|
166
|
+
processed_source.file_path.include?('/models/')
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/DynamicFindBy`
|
|
7
|
+
# (rubocop-rails 2.35.5).
|
|
8
|
+
#
|
|
9
|
+
# Rust replicates the `on_send` / `on_csend` detection (the
|
|
10
|
+
# `/^find_by_(.+?)(!)?$/` name match, the argument count / no-splat /
|
|
11
|
+
# no-hash rules, the `AllowedMethods` / `AllowedReceivers` / `Whitelist`
|
|
12
|
+
# suppressions, and the receiverless-inside-ActiveRecord class check) and
|
|
13
|
+
# the full autocorrect: replace the selector with `find_by` / `find_by!`
|
|
14
|
+
# and insert each `col: ` keyword before its argument. The wrapper owns
|
|
15
|
+
# only the fixed `MSG` (the method name comes from the selector range).
|
|
16
|
+
class DynamicFindBy < RuboCop::Cop::Base
|
|
17
|
+
include Shirobai::Cop::BundleEligible
|
|
18
|
+
extend RuboCop::Cop::AutoCorrector
|
|
19
|
+
|
|
20
|
+
MSG = "Use `%<static_name>s` instead of dynamic `%<method>s`."
|
|
21
|
+
|
|
22
|
+
def self.cop_name = "Rails/DynamicFindBy"
|
|
23
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
24
|
+
|
|
25
|
+
# Contributes `[nums, lists]` pieces to the rails segment:
|
|
26
|
+
# `[[], [AllowedMethods, AllowedReceivers, Whitelist]]`.
|
|
27
|
+
def self.bundle_args(config)
|
|
28
|
+
cop = config.for_badge(badge)
|
|
29
|
+
[[], [cop["AllowedMethods"] || [], cop["AllowedReceivers"] || [], cop["Whitelist"] || []]]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def on_new_investigation
|
|
33
|
+
buffer = processed_source.buffer
|
|
34
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
35
|
+
off = SourceOffsets.for(source)
|
|
36
|
+
resolved_offenses.each do |start_o, end_o, static_name, sel_s, sel_e, inserts|
|
|
37
|
+
range = Parser::Source::Range.new(buffer, off[start_o], off[end_o])
|
|
38
|
+
sel_range = Parser::Source::Range.new(buffer, off[sel_s], off[sel_e])
|
|
39
|
+
message = format(MSG, static_name: static_name, method: sel_range.source)
|
|
40
|
+
add_offense(range, message: message) do |corrector|
|
|
41
|
+
corrector.replace(sel_range, static_name)
|
|
42
|
+
inserts.each do |arg_start, keyword|
|
|
43
|
+
pos = off[arg_start]
|
|
44
|
+
corrector.insert_before(Parser::Source::Range.new(buffer, pos, pos), keyword)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def resolved_offenses
|
|
53
|
+
if bundle_eligible?
|
|
54
|
+
Dispatch.offenses_for(processed_source, config, :rails_dynamic_find_by)
|
|
55
|
+
else
|
|
56
|
+
Shirobai.check_rails_dynamic_find_by(
|
|
57
|
+
processed_source.buffer.source,
|
|
58
|
+
cop_config["AllowedMethods"] || [], cop_config["AllowedReceivers"] || [],
|
|
59
|
+
cop_config["Whitelist"] || []
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust-backed reimplementation of `Rails/HttpPositionalArguments`
|
|
7
|
+
# (rubocop-rails 2.35.5), Architecture B.
|
|
8
|
+
#
|
|
9
|
+
# Rust supplies candidate SEND ranges (bare-receiver HTTP verb calls with
|
|
10
|
+
# an action plus a data argument); this wrapper relocates each parser
|
|
11
|
+
# send node and runs stock's `on_send` (renamed `investigate_send`) plus
|
|
12
|
+
# autocorrect VERBATIM. The routing-block / rack-test guards, the
|
|
13
|
+
# `http_request?` / `needs_conversion?` matchers (kwsplat / forwarded /
|
|
14
|
+
# already-keyword hashes) and the full-node source rebuild all run on the
|
|
15
|
+
# parser AST, so offenses and `-A` bytes match stock exactly.
|
|
16
|
+
#
|
|
17
|
+
# `TargetRailsVersion`: like stock, gated on
|
|
18
|
+
# `requires_gem('railties', '>= 5.0')` — silent on Rails < 5.0 or without
|
|
19
|
+
# railties in the target bundle. The `Include: **/spec/**, **/test/**`
|
|
20
|
+
# from the merged default.yml resolves through this wrapper's badge,
|
|
21
|
+
# exactly as for the stock cop.
|
|
22
|
+
class HttpPositionalArguments < RuboCop::Cop::Base
|
|
23
|
+
include RuboCop::Cop::RangeHelp
|
|
24
|
+
include Shirobai::Cop::Rails::CandidateSupport
|
|
25
|
+
extend RuboCop::Cop::AutoCorrector
|
|
26
|
+
extend RuboCop::Cop::TargetRailsVersion
|
|
27
|
+
|
|
28
|
+
MSG = 'Use keyword arguments instead of positional arguments for http call: `%<verb>s`.'
|
|
29
|
+
KEYWORD_ARGS = %i[method params session body flash xhr as headers env to].freeze
|
|
30
|
+
ROUTING_METHODS = %i[draw routes].freeze
|
|
31
|
+
RESTRICT_ON_SEND = %i[get post put patch delete head].freeze
|
|
32
|
+
|
|
33
|
+
minimum_target_rails_version 5.0
|
|
34
|
+
|
|
35
|
+
def self.cop_name = "Rails/HttpPositionalArguments"
|
|
36
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
37
|
+
|
|
38
|
+
# No behavioral config: the candidate list is a wake-up flag only.
|
|
39
|
+
def self.bundle_args(_config)
|
|
40
|
+
[]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def_node_matcher :http_request?, <<~PATTERN
|
|
44
|
+
(send nil? {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} !nil? $_ ...)
|
|
45
|
+
PATTERN
|
|
46
|
+
|
|
47
|
+
def_node_matcher :kwsplat_hash?, <<~PATTERN
|
|
48
|
+
(hash (kwsplat _))
|
|
49
|
+
PATTERN
|
|
50
|
+
|
|
51
|
+
def_node_matcher :forwarded_kwrestarg?, <<~PATTERN
|
|
52
|
+
(hash (forwarded-kwrestarg))
|
|
53
|
+
PATTERN
|
|
54
|
+
|
|
55
|
+
def_node_matcher :include_rack_test_methods?, <<~PATTERN
|
|
56
|
+
(send nil? :include
|
|
57
|
+
(const
|
|
58
|
+
(const
|
|
59
|
+
(const {nil? cbase} :Rack) :Test) :Methods))
|
|
60
|
+
PATTERN
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def candidate_slot = :rails_http_positional_arguments
|
|
65
|
+
|
|
66
|
+
def fallback_candidates
|
|
67
|
+
Shirobai.check_rails_http_positional_arguments(processed_source.buffer.source)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# --- stock's methods, copied verbatim (rubocop-rails 2.35.5);
|
|
71
|
+
# `on_send` renamed to `investigate_send` so it is not a node callback.
|
|
72
|
+
|
|
73
|
+
def investigate_send(node)
|
|
74
|
+
return if in_routing_block?(node) || use_rack_test_methods?
|
|
75
|
+
|
|
76
|
+
http_request?(node) do |data|
|
|
77
|
+
return unless needs_conversion?(data)
|
|
78
|
+
|
|
79
|
+
message = format(MSG, verb: node.method_name)
|
|
80
|
+
|
|
81
|
+
add_offense(highlight_range(node), message: message) do |corrector|
|
|
82
|
+
corrector.replace(node, correction(node))
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def in_routing_block?(node)
|
|
88
|
+
!!node.each_ancestor(:block).detect { |block| ROUTING_METHODS.include?(block.method_name) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def use_rack_test_methods?
|
|
92
|
+
processed_source.ast.each_descendant(:send).any? do |node|
|
|
93
|
+
include_rack_test_methods?(node)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
98
|
+
def needs_conversion?(data)
|
|
99
|
+
return false if data.forwarded_args_type? || forwarded_kwrestarg?(data)
|
|
100
|
+
return true unless data.hash_type?
|
|
101
|
+
return false if kwsplat_hash?(data)
|
|
102
|
+
|
|
103
|
+
data.each_pair.none? do |pair|
|
|
104
|
+
special_keyword_arg?(pair.key) || (format_arg?(pair.key) && data.pairs.one?)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
108
|
+
|
|
109
|
+
def special_keyword_arg?(node)
|
|
110
|
+
node.sym_type? && KEYWORD_ARGS.include?(node.value)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def format_arg?(node)
|
|
114
|
+
node.sym_type? && node.value == :format
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def highlight_range(node)
|
|
118
|
+
_http_path, *data = *node.arguments
|
|
119
|
+
|
|
120
|
+
range_between(data.first.source_range.begin_pos, data.last.source_range.end_pos)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def convert_hash_data(data, type)
|
|
124
|
+
return '' if data.hash_type? && data.empty?
|
|
125
|
+
|
|
126
|
+
hash_data = if data.hash_type?
|
|
127
|
+
format('{ %<data>s }', data: data.pairs.map(&:source).join(', '))
|
|
128
|
+
else
|
|
129
|
+
# user supplies an object,
|
|
130
|
+
# no need to surround with braces
|
|
131
|
+
data.source
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
format(', %<type>s: %<hash_data>s', type: type, hash_data: hash_data)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def correction(node)
|
|
138
|
+
http_path, *data = *node.arguments
|
|
139
|
+
|
|
140
|
+
controller_action = http_path.source
|
|
141
|
+
params = convert_hash_data(data.first, 'params')
|
|
142
|
+
session = convert_hash_data(data.last, 'session') if data.size > 1
|
|
143
|
+
|
|
144
|
+
format(correction_template(node), name: node.method_name,
|
|
145
|
+
action: controller_action,
|
|
146
|
+
params: params,
|
|
147
|
+
session: session)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def correction_template(node)
|
|
151
|
+
if parentheses?(node)
|
|
152
|
+
'%<name>s(%<action>s%<params>s%<session>s)'
|
|
153
|
+
else
|
|
154
|
+
'%<name>s %<action>s%<params>s%<session>s'
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/Pluck`
|
|
7
|
+
# (rubocop-rails 2.35.5).
|
|
8
|
+
#
|
|
9
|
+
# Rust detects `map { |x| x[:key] }` / `collect { |x| x[:key] }`
|
|
10
|
+
# patterns replaceable with `pluck(:key)`, including numblock (`_1`)
|
|
11
|
+
# and itblock (`it`) forms. The ancestor block-with-receiver guard
|
|
12
|
+
# (N+1 query risk) and the regexp key / block-arg-in-key exclusions
|
|
13
|
+
# are all handled Rust-side.
|
|
14
|
+
#
|
|
15
|
+
# Autocorrect: replace from the selector (map/collect) through the
|
|
16
|
+
# block end with `pluck(<key source>)`.
|
|
17
|
+
class Pluck < RuboCop::Cop::Base
|
|
18
|
+
include Shirobai::Cop::BundleEligible
|
|
19
|
+
extend RuboCop::Cop::AutoCorrector
|
|
20
|
+
extend RuboCop::Cop::TargetRailsVersion
|
|
21
|
+
|
|
22
|
+
MSG = "Prefer `%<replacement>s` over `%<current>s`."
|
|
23
|
+
|
|
24
|
+
minimum_target_rails_version 5.0
|
|
25
|
+
|
|
26
|
+
def self.cop_name = "Rails/Pluck"
|
|
27
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
28
|
+
|
|
29
|
+
# No behavioral config to pack into the segment.
|
|
30
|
+
def self.bundle_args(_config)
|
|
31
|
+
[[], []]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_new_investigation
|
|
35
|
+
buffer = processed_source.buffer
|
|
36
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
37
|
+
off = SourceOffsets.for(source)
|
|
38
|
+
resolved_offenses.each do |sel_start, block_end, key_src_start, key_src_end|
|
|
39
|
+
offense_range = Parser::Source::Range.new(buffer, off[sel_start], off[block_end])
|
|
40
|
+
key_source = source.byteslice(key_src_start, key_src_end - key_src_start)
|
|
41
|
+
replacement = "pluck(#{key_source})"
|
|
42
|
+
message = format(MSG, replacement: replacement, current: offense_range.source)
|
|
43
|
+
add_offense(offense_range, message: message) do |corrector|
|
|
44
|
+
corrector.replace(offense_range, replacement)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def resolved_offenses
|
|
52
|
+
if bundle_eligible?
|
|
53
|
+
Dispatch.offenses_for(processed_source, config, :rails_pluck)
|
|
54
|
+
else
|
|
55
|
+
Shirobai.check_rails_pluck(processed_source.buffer.source)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Drop-in Rust reimplementation of `Rails/UnknownEnv`
|
|
7
|
+
# (rubocop-rails 2.35.5).
|
|
8
|
+
#
|
|
9
|
+
# Rust detects the three `Rails.env` shapes (predicate / comparison /
|
|
10
|
+
# `case`) and emits `[start, end, name]` per unknown environment. The
|
|
11
|
+
# message — including the `DidYouMean` spell suggestion, which must stay
|
|
12
|
+
# Ruby-side — is built here from this cop's own `Environments` config, so
|
|
13
|
+
# the suggestion text is byte-for-byte stock. No autocorrect (stock has
|
|
14
|
+
# none).
|
|
15
|
+
#
|
|
16
|
+
# The `supports_local` view (`local` is a known environment for the
|
|
17
|
+
# predicate form only, and only on Rails >= 7.1) is packed into the
|
|
18
|
+
# segment so the Rust filter matches; the comparison and `case` forms
|
|
19
|
+
# never allow `local`, mirroring stock.
|
|
20
|
+
class UnknownEnv < RuboCop::Cop::Base
|
|
21
|
+
include Shirobai::Cop::BundleEligible
|
|
22
|
+
|
|
23
|
+
MSG = "Unknown environment `%<name>s`."
|
|
24
|
+
MSG_SIMILAR = "Unknown environment `%<name>s`. Did you mean `%<similar>s`?"
|
|
25
|
+
|
|
26
|
+
def self.cop_name = "Rails/UnknownEnv"
|
|
27
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
28
|
+
|
|
29
|
+
# Contributes `[nums, lists]` pieces to the rails segment:
|
|
30
|
+
# `[[supports_local], [environments]]`.
|
|
31
|
+
def self.bundle_args(config)
|
|
32
|
+
cop = config.for_badge(badge)
|
|
33
|
+
environments = cop["Environments"] || []
|
|
34
|
+
supports_local = config.target_rails_version >= 7.1
|
|
35
|
+
[[supports_local ? 1 : 0], [environments]]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def on_new_investigation
|
|
39
|
+
buffer = processed_source.buffer
|
|
40
|
+
source = bundle_eligible? ? processed_source.raw_source : buffer.source
|
|
41
|
+
off = SourceOffsets.for(source)
|
|
42
|
+
resolved_offenses.each do |start_offset, end_offset, name|
|
|
43
|
+
range = Parser::Source::Range.new(buffer, off[start_offset], off[end_offset])
|
|
44
|
+
add_offense(range, message: message(name))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def message(name)
|
|
51
|
+
name = name.to_s.chomp("?")
|
|
52
|
+
|
|
53
|
+
# DidYouMean::SpellChecker is not available in all versions of Ruby;
|
|
54
|
+
# feature-check first, exactly like stock.
|
|
55
|
+
similar_names = if defined?(DidYouMean::SpellChecker)
|
|
56
|
+
DidYouMean::SpellChecker.new(dictionary: environments).correct(name)
|
|
57
|
+
else
|
|
58
|
+
[]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if similar_names.empty?
|
|
62
|
+
format(MSG, name: name)
|
|
63
|
+
else
|
|
64
|
+
format(MSG_SIMILAR, name: name, similar: similar_names.join(", "))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def environments
|
|
69
|
+
cop_config["Environments"] || []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def supports_local?
|
|
73
|
+
target_rails_version >= 7.1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def resolved_offenses
|
|
77
|
+
if bundle_eligible?
|
|
78
|
+
Dispatch.offenses_for(processed_source, config, :rails_unknown_env)
|
|
79
|
+
else
|
|
80
|
+
Shirobai.check_rails_unknown_env(
|
|
81
|
+
processed_source.buffer.source, environments, supports_local?
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Load order matters, and this file owns it so users don't have to:
|
|
4
|
+
#
|
|
5
|
+
# 1. `shirobai` first. The core gem loads the native extension, replaces
|
|
6
|
+
# the core cops, and defines `Shirobai::Dispatch` — the registration
|
|
7
|
+
# point for this gem's packed-config segment.
|
|
8
|
+
# 2. `rubocop-rails` second. Stock Rails cop classes must be enlisted in
|
|
9
|
+
# RuboCop's registry BEFORE the wrappers below:
|
|
10
|
+
# `Registry#clear_enrollment_queue` resolves same-badge collisions by
|
|
11
|
+
# last-write-wins, so whoever is defined later owns the badge.
|
|
12
|
+
# Requiring it here (the gemspec pins the exact version) makes the
|
|
13
|
+
# replacement order independent of `.rubocop.yml` require order.
|
|
14
|
+
# 3. Wrapper cop classes last. Defining each class auto-enlists it
|
|
15
|
+
# (`RuboCop::Cop::Base.inherited`) and replaces the stock cop under
|
|
16
|
+
# the same badge.
|
|
17
|
+
#
|
|
18
|
+
# Requiring rubocop-rails here does NOT merge its config/default.yml into
|
|
19
|
+
# RuboCop's default configuration — that is the plugin system's job. Users
|
|
20
|
+
# still declare `plugins: [rubocop-rails]` in `.rubocop.yml` (or legacy
|
|
21
|
+
# `require:`, which RuboCop auto-promotes to a plugin with a deprecation
|
|
22
|
+
# warning) and add `require: [shirobai-rails]`.
|
|
23
|
+
require "shirobai"
|
|
24
|
+
require "rubocop-rails"
|
|
25
|
+
|
|
26
|
+
require_relative "shirobai/rails/version"
|
|
27
|
+
require_relative "shirobai/cop/rails/application_record"
|
|
28
|
+
require_relative "shirobai/cop/rails/application_controller"
|
|
29
|
+
require_relative "shirobai/cop/rails/application_mailer"
|
|
30
|
+
require_relative "shirobai/cop/rails/application_job"
|
|
31
|
+
require_relative "shirobai/cop/rails/unknown_env"
|
|
32
|
+
require_relative "shirobai/cop/rails/dynamic_find_by"
|
|
33
|
+
require_relative "shirobai/cop/rails/pluck"
|
|
34
|
+
require_relative "shirobai/cop/rails/candidate_support"
|
|
35
|
+
require_relative "shirobai/cop/rails/http_positional_arguments"
|
|
36
|
+
require_relative "shirobai/cop/rails/deprecated_active_model_errors_methods"
|
|
37
|
+
|
|
38
|
+
module Shirobai
|
|
39
|
+
# Glue for the shirobai-rails plugin gem: the packed-config segment (just a
|
|
40
|
+
# wake-up flag — the Application* cluster carries no behavioral config).
|
|
41
|
+
#
|
|
42
|
+
# Unlike shirobai-rspec there is NO per-file gate: rubocop-rails cops run on
|
|
43
|
+
# every Ruby file (no department Include like RSpec's `**/*_spec.rb`), so
|
|
44
|
+
# once this gem is loaded the rails origin is always awake. The core packs
|
|
45
|
+
# this origin's segment into every config from now on.
|
|
46
|
+
module Rails
|
|
47
|
+
# Wrapper cop classes, appended as cops land. Kept for parity with the
|
|
48
|
+
# sibling plugins and for plumbing specs.
|
|
49
|
+
COP_CLASSES = [
|
|
50
|
+
Shirobai::Cop::Rails::ApplicationRecord,
|
|
51
|
+
Shirobai::Cop::Rails::ApplicationController,
|
|
52
|
+
Shirobai::Cop::Rails::ApplicationMailer,
|
|
53
|
+
Shirobai::Cop::Rails::ApplicationJob,
|
|
54
|
+
Shirobai::Cop::Rails::UnknownEnv,
|
|
55
|
+
Shirobai::Cop::Rails::DynamicFindBy,
|
|
56
|
+
Shirobai::Cop::Rails::Pluck,
|
|
57
|
+
Shirobai::Cop::Rails::HttpPositionalArguments,
|
|
58
|
+
Shirobai::Cop::Rails::DeprecatedActiveModelErrorsMethods
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
# The rails origin's `[nums, lists]` segment for `config`. The wake-up
|
|
63
|
+
# flag is always `1` once the gem is loaded; the rest is each config-
|
|
64
|
+
# bearing cop's own `bundle_args` (the single source of its config).
|
|
65
|
+
#
|
|
66
|
+
# Segment layout (crates/shirobai-core/src/rules/rails_config.rs):
|
|
67
|
+
# nums = [enabled, unknown_env_supports_local]
|
|
68
|
+
# lists = [unknown_env_environments,
|
|
69
|
+
# dynamic_find_by_allowed_methods,
|
|
70
|
+
# dynamic_find_by_allowed_receivers,
|
|
71
|
+
# dynamic_find_by_whitelist]
|
|
72
|
+
#
|
|
73
|
+
# Per-cop gating that DOES vary (the `Rails/ApplicationRecord`
|
|
74
|
+
# `Exclude: db/**/*.rb`, the `TargetRailsVersion` gates, and each cop's
|
|
75
|
+
# `Enabled`) is NOT in this segment: RuboCop resolves it through each
|
|
76
|
+
# wrapper's own cop config exactly as for the stock cop, so a file a
|
|
77
|
+
# wrapper does not run on simply drops that cop's Rust-computed slot
|
|
78
|
+
# while the other cops' slots (from the same shared walk) are consumed.
|
|
79
|
+
def segment(config)
|
|
80
|
+
ue = Shirobai::Cop::Rails::UnknownEnv.bundle_args(config)
|
|
81
|
+
dfb = Shirobai::Cop::Rails::DynamicFindBy.bundle_args(config)
|
|
82
|
+
nums = [1, *ue[0]]
|
|
83
|
+
lists = [*ue[1], *dfb[1]]
|
|
84
|
+
[nums, lists]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Wake up the rails origin in the shared bundle: from now on every packed
|
|
91
|
+
# config carries this origin's segment with `enabled=1` (segment layout:
|
|
92
|
+
# crates/shirobai-core/src/rules/bundle.rs BundleConfig). No gate — the
|
|
93
|
+
# Application* cops run on every Ruby file. Without this gem the core packs
|
|
94
|
+
# the dormant segment and the Rust side skips the Rails rules entirely.
|
|
95
|
+
Shirobai::Dispatch.register_plugin_packer(:rails) { |config| Shirobai::Rails.segment(config) }
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: shirobai-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 2026.0709.0000
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- fusagiko / takayamaki
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: shirobai
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 2026.0709.0000
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 2026.0709.0000
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubocop-rails
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - '='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 2.35.5
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - '='
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 2.35.5
|
|
40
|
+
executables: []
|
|
41
|
+
extensions: []
|
|
42
|
+
extra_rdoc_files: []
|
|
43
|
+
files:
|
|
44
|
+
- LICENSE.txt
|
|
45
|
+
- lib/shirobai-rails.rb
|
|
46
|
+
- lib/shirobai/cop/rails/application_controller.rb
|
|
47
|
+
- lib/shirobai/cop/rails/application_job.rb
|
|
48
|
+
- lib/shirobai/cop/rails/application_mailer.rb
|
|
49
|
+
- lib/shirobai/cop/rails/application_record.rb
|
|
50
|
+
- lib/shirobai/cop/rails/candidate_support.rb
|
|
51
|
+
- lib/shirobai/cop/rails/deprecated_active_model_errors_methods.rb
|
|
52
|
+
- lib/shirobai/cop/rails/dynamic_find_by.rb
|
|
53
|
+
- lib/shirobai/cop/rails/http_positional_arguments.rb
|
|
54
|
+
- lib/shirobai/cop/rails/pluck.rb
|
|
55
|
+
- lib/shirobai/cop/rails/unknown_env.rb
|
|
56
|
+
- lib/shirobai/rails/version.rb
|
|
57
|
+
homepage: https://github.com/takayamaki/shirobai
|
|
58
|
+
licenses:
|
|
59
|
+
- MIT
|
|
60
|
+
metadata: {}
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.1'
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 4.0.10
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: Drop-in Rust replacement for rubocop-rails cops
|
|
78
|
+
test_files: []
|