Module: Shirobai::Dispatch
- Defined in:
- lib/shirobai/dispatch.rb
Overview
Per-file coordinator: computes every cop's offenses in ONE bundled ext call
(Shirobai.check_all), memoized by source. The first participating cop on
a file triggers the bundled run; the rest read their slice from the cache.
The cache key is the raw_source identity (equal?, not ==), so the
autocorrect loop (which re-investigates a freshly built ProcessedSource)
naturally recomputes, and a different file never reuses stale results.
A cop whose per-investigation config/state cannot be represented in the
bundle (see each wrapper's bundle_eligible?) skips this coordinator and
calls its standalone entry point directly instead.
Constant Summary collapse
- ORIGINS =
Origin order of the
Shirobai.check_allresult Array and of the packed config segments: outer index 0 is the core batch, 1 the shirobai-performance plugin, 2 the shirobai-rspec plugin, 3 the shirobai-rails plugin, and so on. Mirrors the origin constants incrates/shirobai-core/src/rules/bundle.rs(ORIGIN_*/N_ORIGINS); adding a plugin means one entry here and one constant there — that pair of one-line edits is the only place plugin batches can conflict. %i[core performance rspec rails].freeze
- SLOTS =
[origin, rule]slot pairs into theShirobai.check_allresult (result[origin][rule]). The rule order within each origin mirrors the order documented onBundleConfigand built bycheck_allinext/shirobai/src/lib.rs; each slot carries the same shape as the cop's standaloneShirobai.check_*return value. Wrappers never index the result themselves — they go throughoffenses_for. { debugger: [0, 0].freeze, block_length: [0, 1].freeze, block_nesting: [0, 2].freeze, complexity: [0, 3].freeze, variable_number: [0, 4].freeze, method_name: [0, 5].freeze, safe_navigation_chain: [0, 6].freeze, multiline_operation: [0, 7].freeze, multiline_method_call: [0, 8].freeze, dot_position: [0, 9].freeze, line_length: [0, 10].freeze, line_length_breakables: [0, 11].freeze, line_end_concatenation: [0, 12].freeze, argument_alignment: [0, 13].freeze, first_argument_indentation: [0, 14].freeze, redundant_self: [0, 15].freeze, indentation_width: [0, 16].freeze, predicate_prefix: [0, 17].freeze, closing_parenthesis_indentation: [0, 18].freeze, first_array_element_indentation: [0, 19].freeze, hash_each_methods: [0, 20].freeze, void: [0, 21].freeze, useless_access_modifier: [0, 22].freeze, empty_lines_around_method_body: [0, 23].freeze, empty_lines_around_class_body: [0, 24].freeze, empty_lines_around_module_body: [0, 25].freeze, empty_lines_around_block_body: [0, 26].freeze, empty_lines_around_begin_body: [0, 27].freeze, empty_lines_around_exception_handling_keywords: [0, 28].freeze, block_delimiters: [0, 29].freeze, abc_size: [0, 30].freeze, indentation_consistency: [0, 31].freeze, empty_line_between_defs: [0, 32].freeze, end_alignment: [0, 33].freeze, block_alignment: [0, 34].freeze, else_alignment: [0, 35].freeze, first_hash_element_indentation: [0, 36].freeze, hash_alignment: [0, 37].freeze, empty_lines_around_arguments: [0, 38].freeze, hash_syntax: [0, 39].freeze, string_literals: [0, 40].freeze, trailing_comma_in_arguments: [0, 41].freeze, string_literals_in_interpolation: [0, 42].freeze, trailing_empty_lines: [0, 43].freeze, space_around_method_call_operator: [0, 44].freeze, space_around_keyword: [0, 45].freeze, space_inside_block_braces: [0, 46].freeze, method_length: [0, 47].freeze, def_end_alignment: [0, 48].freeze, require_parentheses: [0, 49].freeze, self_assignment: [0, 50].freeze, nested_parenthesized_calls: [0, 51].freeze, parentheses_as_grouped_expression: [0, 52].freeze, percent_literal_delimiters: [0, 53].freeze, multiline_method_call_brace_layout: [0, 54].freeze, access_modifier_indentation: [0, 55].freeze, assignment_indentation: [0, 56].freeze, redundant_self_assignment: [0, 57].freeze, colon_method_call: [0, 58].freeze, stabby_lambda_parentheses: [0, 59].freeze, unreachable_code: [0, 60].freeze, hash_transform_keys: [0, 61].freeze, ambiguous_block_association: [0, 62].freeze, empty_line_after_guard_clause: [0, 63].freeze, empty_comment: [0, 64].freeze, empty_line_after_magic_comment: [0, 65].freeze, empty_lines: [0, 66].freeze, leading_empty_lines: [0, 67].freeze, class_length: [0, 68].freeze, module_length: [0, 69].freeze, trailing_comma_in_hash_literal: [0, 70].freeze, trailing_comma_in_array_literal: [0, 71].freeze, space_inside_hash_literal_braces: [0, 72].freeze, space_inside_array_literal_brackets: [0, 73].freeze, space_before_block_braces: [0, 74].freeze, if_unless_modifier: [0, 75].freeze, space_before_comma: [0, 76].freeze, space_after_comma: [0, 77].freeze, space_before_semicolon: [0, 78].freeze, space_after_semicolon: [0, 79].freeze, space_after_colon: [0, 80].freeze, space_before_comment: [0, 81].freeze, space_inside_parens: [0, 82].freeze, space_inside_reference_brackets: [0, 83].freeze, space_before_first_arg: [0, 84].freeze, duplicate_magic_comment: [0, 85].freeze, duplicate_methods: [0, 86].freeze, array_alignment: [0, 87].freeze, file_null: [0, 88].freeze, semicolon: [0, 89].freeze, redundant_freeze: [0, 90].freeze, frozen_string_literal_comment: [0, 91].freeze, arguments_forwarding: [0, 92].freeze, # shirobai-performance plugin slots (origin 1). Always present in the # wire format; the Rust side leaves them empty unless the plugin gem # registered its packed segment (`Dispatch.register_plugin_packer`). perf_detect: [1, 0].freeze, perf_string_include: [1, 1].freeze, perf_end_with: [1, 2].freeze, perf_start_with: [1, 3].freeze, perf_times_map: [1, 4].freeze, # shirobai-rspec plugin slots (origin 2), all filled by the single # RSpecDispatcherRule. The rspec origin is additionally gated per # file (see `register_plugin_packer`'s `gate`): the RSpec department # only runs on spec files, so other files use a dormant-rspec token. rspec_variable_name: [2, 0].freeze, rspec_let_setup: [2, 1].freeze, rspec_variable_definition: [2, 2].freeze, rspec_multiple_memoized_helpers: [2, 3].freeze, rspec_repeated_description: [2, 4].freeze, rspec_repeated_example: [2, 5].freeze, rspec_named_subject: [2, 6].freeze, # R2 metadata family (rspec origin slots 7-12). Slots 9-12 (the four # Metadata-mixin cops) read the same shared metadata-anchor list. rspec_focus: [2, 7].freeze, rspec_pending_without_reason: [2, 8].freeze, rspec_metadata_style: [2, 9].freeze, rspec_duplicated_metadata: [2, 10].freeze, rspec_empty_metadata: [2, 11].freeze, rspec_sort_metadata: [2, 12].freeze, # R2 empty-line family (rspec origin slots 13-17), all filled by the # single RSpecEmptyLineRule. rspec_empty_line_after_example: [2, 13].freeze, rspec_empty_line_after_example_group: [2, 14].freeze, rspec_empty_line_after_final_let: [2, 15].freeze, rspec_empty_line_after_hook: [2, 16].freeze, rspec_empty_line_after_subject: [2, 17].freeze, rspec_empty_example_group: [2, 18].freeze, rspec_described_class: [2, 19].freeze, rspec_scattered_setup: [2, 20].freeze, # shirobai-rails plugin slots (origin 3), all filled by the single # RailsAppVisitor. The rails origin is NOT per-file gated: the # Application* cops run on every Ruby file, so it is always awake once # the plugin gem is loaded. rails_application_record: [3, 0].freeze, rails_application_controller: [3, 1].freeze, rails_application_mailer: [3, 2].freeze, rails_application_job: [3, 3].freeze, # send/block-table cluster (rails origin slots 4-5), each its own rule. rails_unknown_env: [3, 4].freeze, rails_dynamic_find_by: [3, 5].freeze, # Rails Architecture-B cops (origin 3 slots 6-7): the Rust side emits # candidate send ranges here, and the wrapper relocates the parser node # and runs stock detection + autocorrect verbatim (same shape as the # rspec send-candidate cops). The 3-point positional sync (this map, # the `BundleResult` field order in `crates/.../bundle.rs`, the ext # `check_all` push order) moves together. rails_http_positional_arguments: [3, 6].freeze, rails_deprecated_active_model_errors_methods: [3, 7].freeze, rails_pluck: [3, 8].freeze }.freeze
- DORMANT_SEGMENTS =
Dormant packed-config segment per plugin origin: the enable flag (first num) is 0 and the cop settings are placeholders (see the
BundleConfigdocs for each segment's field order). Packed whenever the plugin gem has not registered a packer — or, for a gated origin, whenever the origin's gate says the current file is not relevant; the Rust side then skips that origin's rules and leaves its slots empty. { performance: [[0, 0, 0].freeze, [[].freeze].freeze].freeze, # rspec: enable flag + per-cop nums (VariableName style, # VariableDefinition style, MMH Max, MMH AllowSubject, NamedSubject # style, NamedSubject IgnoreSharedExamples, EmptyLineAfterExample # AllowConsecutiveOneLiners, EmptyLineAfterHook # AllowConsecutiveOneLiners), then the sixteen # RSpec/Language role lists. rspec: [[0, 0, 0, 0, 0, 0, 0, 0, 0].freeze, ([[].freeze] * 16).freeze].freeze, # rails: wake-up flag + UnknownEnv supports_local; four lists # (UnknownEnv Environments + DynamicFindBy AllowedMethods / # AllowedReceivers / Whitelist). rails: [[0, 0].freeze, [[].freeze, [].freeze, [].freeze, [].freeze].freeze].freeze }.freeze
Class Method Summary collapse
-
.bundle_token(config, inactive = EMPTY_INACTIVE) ⇒ Object
The Rust-side token for
configwith the given gated-off origins, registering its packed bundle config on first sight. -
.offenses_for(processed_source, config, cop_key) ⇒ Object
Returns the raw Rust result for
cop_keyon this source, or nil whencop_keybelongs to a gated origin that is inactive for this file (the safety net: the wrapper then takes its standalone entry point, so a gate/relevant_file? disagreement can only cost speed, never offenses). - .plugin_gates ⇒ Object
- .plugin_packers ⇒ Object
-
.register_plugin_packer(origin, gate: nil, &packer) ⇒ Object
Registration point for plugin gems:
originis the ORIGINS key and the block is a callable(config) -> [nums, lists]producing that origin's segment documented onBundleConfig(enable flag first).
Class Method Details
.bundle_token(config, inactive = EMPTY_INACTIVE) ⇒ Object
The Rust-side token for config with the given gated-off origins,
registering its packed bundle config on first sight. Memoized per
(config object identity, inactive origin set): a lint run shares one
Config object across all cops in the team, so a run registers
O(#distinct configs x #distinct gate outcomes) entries (each spec
example registers one; entries are small and never evicted).
264 265 266 267 268 269 |
# File 'lib/shirobai/dispatch.rb', line 264 def bundle_token(config, inactive = EMPTY_INACTIVE) @bundle_tokens ||= {}.compare_by_identity per_config = (@bundle_tokens[config] ||= {}) per_config[inactive] ||= Shirobai.register_bundle_config(*packed_config(config, inactive)) end |
.offenses_for(processed_source, config, cop_key) ⇒ Object
Returns the raw Rust result for cop_key on this source, or nil when
cop_key belongs to a gated origin that is inactive for this file
(the safety net: the wrapper then takes its standalone entry point,
so a gate/relevant_file? disagreement can only cost speed, never
offenses).
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/shirobai/dispatch.rb', line 242 def offenses_for(processed_source, config, cop_key) src = processed_source.raw_source unless defined?(@cached_source) && @cached_source.equal?(src) && @cached_config.equal?(config) inactive = inactive_origins(config, processed_source) result = Shirobai.check_all(src, bundle_token(config, inactive)) @cached_source = src @cached_config = config @cached_result = result @cached_inactive = inactive end origin, rule = SLOTS.fetch(cop_key) return nil if @cached_inactive.include?(ORIGINS.fetch(origin)) @cached_result.fetch(origin).fetch(rule) end |
.plugin_gates ⇒ Object
233 234 235 |
# File 'lib/shirobai/dispatch.rb', line 233 def plugin_gates @plugin_gates ||= {} end |
.plugin_packers ⇒ Object
229 230 231 |
# File 'lib/shirobai/dispatch.rb', line 229 def plugin_packers @plugin_packers ||= {} end |
.register_plugin_packer(origin, gate: nil, &packer) ⇒ Object
Registration point for plugin gems: origin is the ORIGINS key and
the block is a callable (config) -> [nums, lists] producing that
origin's segment documented on BundleConfig (enable flag first).
Called at plugin require time, BEFORE any lint run packs a config.
Configs packed earlier keep their dormant tokens (token memoization
is per Config identity), so requiring a plugin mid-run is not
supported.
gate (optional) makes the origin per-file: a callable
(config, processed_source) -> bool answering "does any of this
origin's cops run on this file?". Department-scoped plugins
(rubocop-rspec's Include: **/*_spec.rb) pass one so non-relevant
files use a token whose segment is dormant and never pay for the
origin's rules. The gate must be a superset of the wrapper cops'
relevant_file? — a wrapper that runs on a gated-off file falls
back to its standalone entry point (offenses_for returns nil).
222 223 224 225 226 227 |
# File 'lib/shirobai/dispatch.rb', line 222 def register_plugin_packer(origin, gate: nil, &packer) raise ArgumentError, "unknown origin #{origin.inspect}" unless ORIGINS.include?(origin) plugin_packers[origin] = packer plugin_gates[origin] = gate if gate end |