rigortype 0.3.4 → 0.3.5

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/effects/core.yml +18 -1
  4. data/data/effects/registry.yml +31 -3
  5. data/docs/manual/02-cli-reference.md +60 -13
  6. data/docs/manual/03-configuration.md +20 -3
  7. data/docs/manual/04-diagnostics.md +3 -2
  8. data/docs/manual/11-ci.md +37 -0
  9. data/docs/manual/12-caching.md +39 -0
  10. data/docs/manual/16-rbs-extended-annotations.md +15 -2
  11. data/docs/manual/19-effect-labels.md +671 -0
  12. data/docs/manual/README.md +5 -0
  13. data/docs/manual/ci-templates/README.md +9 -0
  14. data/lib/rigor/analysis/rule_catalog.rb +10 -3
  15. data/lib/rigor/analysis/run_cache_probe.rb +69 -1
  16. data/lib/rigor/analysis/runner/declaration_position.rb +8 -24
  17. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +28 -28
  18. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  19. data/lib/rigor/analysis/runner/pool_coordinator.rb +25 -0
  20. data/lib/rigor/analysis/runner/run_snapshots.rb +7 -4
  21. data/lib/rigor/analysis/runner.rb +12 -3
  22. data/lib/rigor/analysis/worker_session.rb +3 -1
  23. data/lib/rigor/cli/effects_command.rb +123 -9
  24. data/lib/rigor/cli/effects_diff_renderer.rb +5 -1
  25. data/lib/rigor/cli/effects_renderer.rb +41 -3
  26. data/lib/rigor/cli/effects_report.rb +116 -6
  27. data/lib/rigor/cli/effects_snapshot_command.rb +23 -4
  28. data/lib/rigor/cli.rb +12 -1
  29. data/lib/rigor/configuration.rb +37 -25
  30. data/lib/rigor/configuration_error.rb +20 -0
  31. data/lib/rigor/effects/collector.rb +38 -1
  32. data/lib/rigor/effects/entry_points.rb +47 -1
  33. data/lib/rigor/effects/file_collection.rb +18 -4
  34. data/lib/rigor/effects/framework_units.rb +68 -13
  35. data/lib/rigor/effects/inline_anchor.rb +134 -0
  36. data/lib/rigor/effects/plugin_facts.rb +62 -13
  37. data/lib/rigor/effects/propagator.rb +79 -19
  38. data/lib/rigor/effects/registry.rb +10 -3
  39. data/lib/rigor/effects/scanner.rb +21 -9
  40. data/lib/rigor/effects/signature_sources.rb +16 -0
  41. data/lib/rigor/effects/snapshot.rb +21 -5
  42. data/lib/rigor/effects/taint_cause.rb +1 -0
  43. data/lib/rigor/effects/unit_scan.rb +87 -12
  44. data/lib/rigor/plugin/base.rb +4 -0
  45. data/lib/rigor/plugin/box.rb +18 -2
  46. data/lib/rigor/plugin/effect_ancestry.rb +80 -0
  47. data/lib/rigor/plugin/manifest.rb +34 -10
  48. data/lib/rigor/plugin/registry.rb +10 -3
  49. data/lib/rigor/rbs_extended.rb +22 -2
  50. data/lib/rigor/version.rb +1 -1
  51. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +17 -2
  52. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +27 -0
  53. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +56 -1
  54. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +75 -0
  55. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +13 -1
  56. metadata +6 -1
@@ -5,6 +5,7 @@ require_relative "protocol_contract"
5
5
  require_relative "additional_initializer"
6
6
  require_relative "effect_attribution"
7
7
  require_relative "effect_edge"
8
+ require_relative "effect_ancestry"
8
9
  require_relative "effect_entry_points"
9
10
  require_relative "first_party"
10
11
 
@@ -46,7 +47,8 @@ module Rigor
46
47
  :heredoc_templates, :nested_class_templates, :trait_registries,
47
48
  :hkt_registrations, :hkt_definitions, :signature_paths, :protocol_contracts,
48
49
  :source_rbs_synthesizer, :additional_initializers,
49
- :effect_root, :effect_labels, :effect_attributions, :effect_edges, :effect_entry_points
50
+ :effect_root, :effect_labels, :effect_attributions, :effect_edges, :effect_entry_points,
51
+ :effect_ancestry
50
52
 
51
53
  def initialize( # rubocop:disable Metrics/ParameterLists
52
54
  id:, version:,
@@ -57,7 +59,7 @@ module Rigor
57
59
  hkt_registrations: [], hkt_definitions: [], signature_paths: [], protocol_contracts: [],
58
60
  source_rbs_synthesizer: nil, additional_initializers: [],
59
61
  effect_root: nil, effect_labels: [], effect_attributions: [], effect_edges: [],
60
- effect_entry_points: []
62
+ effect_entry_points: [], effect_ancestry: []
61
63
  )
62
64
  validate_id!(id)
63
65
  validate_version!(version)
@@ -76,11 +78,8 @@ module Rigor
76
78
  validate_protocol_contracts!(protocol_contracts)
77
79
  validate_source_rbs_synthesizer!(source_rbs_synthesizer)
78
80
  validate_additional_initializers!(additional_initializers)
79
- validate_effect_root!(effect_root)
80
- validate_effect_labels!(effect_labels)
81
- validate_effect_attributions!(effect_attributions)
82
- validate_effect_edges!(effect_edges)
83
- validate_effect_entry_points!(effect_entry_points)
81
+ validate_effect_fields!(effect_root, effect_labels, effect_attributions, effect_edges,
82
+ effect_entry_points, effect_ancestry)
84
83
 
85
84
  assign_fields(id, version, description, config_schema, produces, consumes, owns_receivers,
86
85
  open_receivers, type_node_resolvers, block_as_methods, heredoc_templates, trait_registries,
@@ -88,7 +87,8 @@ module Rigor
88
87
  source_rbs_synthesizer)
89
88
  assign_nested_class_templates(nested_class_templates)
90
89
  assign_additional_initializers(additional_initializers)
91
- assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges, effect_entry_points)
90
+ assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
91
+ effect_entry_points, effect_ancestry)
92
92
  freeze
93
93
  end
94
94
 
@@ -135,12 +135,26 @@ module Rigor
135
135
 
136
136
  # ADR-103 WD2 / WD6 / WD10 / WD14 — the effect contract's five fields, assigned together and outside
137
137
  # `assign_fields` (which already carries the maximum positional arity).
138
- def assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges, effect_entry_points)
138
+ # The six `effect_*` fields validate together, so {#initialize} carries one call rather than six.
139
+ def validate_effect_fields!(effect_root, effect_labels, effect_attributions, effect_edges,
140
+ effect_entry_points, effect_ancestry)
141
+ validate_effect_root!(effect_root)
142
+ validate_effect_labels!(effect_labels)
143
+ validate_effect_attributions!(effect_attributions)
144
+ validate_effect_edges!(effect_edges)
145
+ validate_effect_entry_points!(effect_entry_points)
146
+ validate_effect_ancestry!(effect_ancestry)
147
+ end
148
+ private :validate_effect_fields!
149
+
150
+ def assign_effect_fields(effect_root, effect_labels, effect_attributions, effect_edges,
151
+ effect_entry_points, effect_ancestry)
139
152
  @effect_root = effect_root.nil? ? nil : effect_root.to_s.dup.freeze
140
153
  @effect_labels = effect_labels.map { |label| label.to_s.dup.freeze }.uniq.sort.freeze
141
154
  @effect_attributions = effect_attributions.dup.freeze
142
155
  @effect_edges = effect_edges.dup.freeze
143
156
  @effect_entry_points = effect_entry_points.dup.freeze
157
+ @effect_ancestry = effect_ancestry.dup.freeze
144
158
  end
145
159
  private :assign_effect_fields
146
160
  # rubocop:enable Metrics/ParameterLists, Metrics/AbcSize
@@ -194,7 +208,8 @@ module Rigor
194
208
  "effect_labels" => effect_labels,
195
209
  "effect_attributions" => effect_attributions.map(&:to_h),
196
210
  "effect_edges" => effect_edges.map(&:to_h),
197
- "effect_entry_points" => effect_entry_points.map(&:to_h)
211
+ "effect_entry_points" => effect_entry_points.map(&:to_h),
212
+ "effect_ancestry" => effect_ancestry.map(&:to_h)
198
213
  }
199
214
  end
200
215
 
@@ -506,6 +521,15 @@ module Rigor
506
521
  end
507
522
  end
508
523
 
524
+ # ADR-103 WD17 (#465) — `effect_ancestry:` states an ancestry edge the plugin's own gem introduces
525
+ # and the project's source never writes. Each entry MUST be a {Rigor::Plugin::EffectAncestry}; only
526
+ # a bundled plugin's survives, which `Effects::PluginFacts` enforces.
527
+ def validate_effect_ancestry!(entries)
528
+ validate_array_of!("effect_ancestry", entries, "Rigor::Plugin::EffectAncestry instances") do |e|
529
+ e.is_a?(EffectAncestry)
530
+ end
531
+ end
532
+
509
533
  # ADR-103 WD14 — `effect_entry_points:` names the `effects.snapshot.reach:` presets this plugin
510
534
  # supplies. Each entry MUST be a {Rigor::Plugin::EffectEntryPoints}.
511
535
  def validate_effect_entry_points!(entries)
@@ -262,9 +262,15 @@ module Rigor
262
262
  # plugin may open, and `discharge_allowed`, whether its attributions may discharge — so nothing
263
263
  # downstream has to re-derive who is first-party.
264
264
  Contribution = Data.define(:id, :owner, :requested_root, :discharge_allowed, :labels, :attributions,
265
- :edges, :entry_points) do
265
+ :edges, :entry_points, :ancestry) do
266
+ # `ancestry:` defaults so a caller written before #465 — and every spec that constructs one by
267
+ # hand — keeps working; a plugin that declares none contributes none.
268
+ def initialize(ancestry: [], **rest)
269
+ super
270
+ end
271
+
266
272
  def empty?
267
- labels.empty? && attributions.empty? && edges.empty? && entry_points.empty?
273
+ labels.empty? && attributions.empty? && edges.empty? && entry_points.empty? && ancestry.empty?
268
274
  end
269
275
  end
270
276
 
@@ -422,7 +428,8 @@ module Rigor
422
428
  id: manifest.id, owner: manifest.effect_owner, requested_root: manifest.effect_root,
423
429
  discharge_allowed: manifest.effect_discharge_allowed?,
424
430
  labels: plugin.effect_labels, attributions: plugin.effect_attributions,
425
- edges: plugin.effect_edges, entry_points: plugin.effect_entry_points
431
+ edges: plugin.effect_edges, entry_points: plugin.effect_entry_points,
432
+ ancestry: plugin.effect_ancestry
426
433
  )
427
434
  contribution.empty? ? nil : contribution
428
435
  rescue StandardError
@@ -4,6 +4,7 @@ require_relative "type"
4
4
  require_relative "builtins/imported_refinements"
5
5
  require_relative "flow_contribution"
6
6
  require_relative "effects/envelope"
7
+ require_relative "effects/inline_anchor"
7
8
  require_relative "effects/label"
8
9
  require_relative "effects/label_set"
9
10
  require_relative "effects/signature_sources"
@@ -769,6 +770,12 @@ module Rigor
769
770
 
770
771
  # `path:line` for the annotation, project-relative when it sits under the working directory, so a
771
772
  # diagnostic message names `sig/foo.rbs:12` rather than an absolute path.
773
+ #
774
+ # A synthesized buffer is re-anchored here rather than at any of the surfaces that render it
775
+ # ({Effects::InlineAnchor}; #432). The buffer's own line numbers describe a document the author
776
+ # never saw, so an envelope carrying one is wrong for every consumer at once — the diagnostic, the
777
+ # `effect.unknown-label` position, `rigor explain`, the JSON formatter, LSP hover. Correcting it
778
+ # where the value is *built* is what makes them agree without each learning the mapping.
772
779
  def render_annotation_location(annotation)
773
780
  location = annotation_location(annotation)
774
781
  return nil if location.nil?
@@ -777,12 +784,25 @@ module Rigor
777
784
  name = buffer.respond_to?(:name) ? buffer.name.to_s : nil
778
785
  return nil if name.nil? || name.empty?
779
786
 
780
- line = location.respond_to?(:start_line) ? location.start_line : 1
781
- "#{relative_annotation_path(name)}:#{line}"
787
+ path = relative_annotation_path(name)
788
+ "#{path}:#{annotation_line(annotation, location, buffer, path)}"
782
789
  rescue StandardError
783
790
  nil
784
791
  end
785
792
 
793
+ # The line a reader can open. For a real `.rbs` that is the parser's own answer; for the
794
+ # `virtual:rbs-inline:…rb` buffer the writer produced, the annotation is found again in the Ruby
795
+ # source by its own spelling.
796
+ def annotation_line(annotation, location, buffer, path)
797
+ line = location.respond_to?(:start_line) ? location.start_line : 1
798
+ content = buffer.respond_to?(:content) ? buffer.content : nil
799
+ return line if content.nil?
800
+
801
+ Effects::InlineAnchor.ruby_line(
802
+ path: path, buffer: content, buffer_line: line, spelling: "%a{#{annotation.string}}"
803
+ )
804
+ end
805
+
786
806
  # The buffer-name → readable-path rule lives with the walk that produces the buffers
787
807
  # ({Effects::SignatureSources}), so a `virtual:` name is stripped identically wherever it surfaces.
788
808
  def relative_annotation_path(name)
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.3.4"
4
+ VERSION = "0.3.5"
5
5
  end
@@ -598,8 +598,23 @@ class Date
598
598
  %a{pure}
599
599
  def advance: (untyped options) -> Date
600
600
  def all_day: () -> Range[Time]
601
- %a{pure}
602
- def to_time: (?Symbol form) -> Time
601
+ # NOTE: `Date#to_time` is an OVERLOAD CONTINUATION (`| ...`), not a plain
602
+ # declaration — stdlib `date` already types `to_time: () -> Time`, and a
603
+ # second full declaration of the same method from a second signature source
604
+ # makes `RBS::DefinitionBuilder` raise `DuplicatedMethodDefinitionError`,
605
+ # which collapses `Date` AND `DateTime` to `Dynamic[top]` for every project
606
+ # that activates this plugin (#437 — the same failure the `DateTime#to_time`
607
+ # note below records). The row cannot simply be DROPPED the way
608
+ # `DateTime#to_time` was: ActiveSupport genuinely widens the arity, and
609
+ # without the row `date.to_time(:utc)` — correct Rails code — would draw an
610
+ # arity diagnostic. `| ...` appends this overload ahead of the stdlib one, so
611
+ # both arities resolve and the `%a{pure}` envelope stays attached to the
612
+ # ActiveSupport overload (which is the one a no-argument call selects).
613
+ # `| ...` requires the base declaration to exist, which `Environment.for_project`
614
+ # guarantees: it merges `DEFAULT_LIBRARIES` — including `date` — into every
615
+ # environment that loads a plugin's `signature_paths`.
616
+ %a{pure}
617
+ def to_time: (?Symbol form) -> Time | ...
603
618
  end
604
619
 
605
620
  # ---------------------------------------------------------------
@@ -82,6 +82,33 @@ module Rigor
82
82
  },
83
83
  always_included: ["Devise::Models::Authenticatable"]
84
84
  )
85
+ ],
86
+ # ADR-103 WD17 (#465) — the ancestry Devise introduces and a project's own source never writes.
87
+ #
88
+ # `class UserMailer < Devise::Mailer` and `class Auth::SessionsController <
89
+ # Devise::SessionsController` are what a Devise application looks like, and the walk that reaches
90
+ # a plugin row climbs the project's own `class … <` lines only — so it stopped at the gem class
91
+ # and `rigor-actionmailer`'s and `rigor-actionpack`'s rows never arrived. Measured on Mastodon:
92
+ # six classes, five of them controllers, which are entry points.
93
+ #
94
+ # Every `parent:` below is a **true ancestor and not the immediate one**, deliberately. Devise's
95
+ # controllers descend through `DeviseController`, whose own parent is `Devise.parent_controller`
96
+ # — configurable per project, defaulting to the application's own `ApplicationController`. Naming
97
+ # the intermediate links would state something a project can make false; naming a true ancestor
98
+ # loses nothing, because no plugin row is ever keyed on a project class.
99
+ effect_ancestry: [
100
+ Rigor::Plugin::EffectAncestry.new(
101
+ child: "Devise::Mailer", parent: "ActionMailer::Base",
102
+ why: "Devise::Mailer is an ActionMailer; a true ancestor, skipping Devise's own intermediates"
103
+ ),
104
+ *%w[SessionsController RegistrationsController PasswordsController ConfirmationsController
105
+ UnlocksController OmniauthCallbacksController].map do |controller|
106
+ Rigor::Plugin::EffectAncestry.new(
107
+ child: "Devise::#{controller}", parent: "ActionController::Base",
108
+ why: "a Devise controller is an ActionController; a true ancestor, skipping DeviseController " \
109
+ "and `Devise.parent_controller`, which a project may configure"
110
+ )
111
+ end
85
112
  ]
86
113
  )
87
114
  end
@@ -50,6 +50,19 @@ module Rigor
50
50
  # space-free spelling is affected; `# :nodoc:` never reaches upstream's annotation grammar.
51
51
  RDOC_DIRECTIVE_COMMENT = /\A#:[a-z_][\w-]*:/
52
52
 
53
+ # An `%a{…}` the author wrote above a `class` / `module`, as upstream's writer echoes it: an ordinary
54
+ # comment, because the writer emits no annotation on a declaration
55
+ # ([#452](https://github.com/rigortype/rigor/issues/452)).
56
+ ECHOED_ANNOTATION = /\A\s*#\s*@rbs\s+(%a\{[^}]*\})\s*\z/
57
+
58
+ # The declaration the pending annotations belong to. `class`/`module` and the whitespace after the
59
+ # keyword, so `class_eval` is not one and the indentation to re-emit at is captured.
60
+ DECLARATION_LINE = /\A(\s*)(?:class|module)\s/
61
+
62
+ COMMENT_LINE = /\A\s*#/
63
+
64
+ private_constant :ECHOED_ANNOTATION, :DECLARATION_LINE, :COMMENT_LINE
65
+
53
66
  # @param require_magic_comment [Boolean] when `false` (the default since ADR-93 WD1), the magic
54
67
  # comment is not required and the file is processed only if it actually carries an annotation — see
55
68
  # {#annotated?}. When `true` (the old ADR-32 WD2 gate), only files opening with
@@ -82,7 +95,7 @@ module Rigor
82
95
  return nil if parsed.nil?
83
96
 
84
97
  uses, decls, rbs_decls = parsed
85
- rendered = ::RBS::Inline::Writer.write(uses, decls, rbs_decls)
98
+ rendered = reattach_declaration_annotations(::RBS::Inline::Writer.write(uses, decls, rbs_decls))
86
99
  return nil if rendered.nil? || rendered.strip.empty?
87
100
 
88
101
  notices = unhonoured_annotations(result)
@@ -96,6 +109,48 @@ module Rigor
96
109
 
97
110
  private
98
111
 
112
+ # Re-attaches a class- or module-level `%a{…}` that upstream's writer dropped (#452).
113
+ #
114
+ # `RBS::Inline::Writer` emits a member's annotations as real annotation lines and a *declaration's*
115
+ # as nothing at all — it echoes the author's comment block above `class Foo` and stops there. So the
116
+ # cheapest bound in the feature, one line above a class, reached the envelope reader as a comment:
117
+ # no bound, no diagnostic, and a clean run that had checked nothing. The `.rbs` lane, writing the
118
+ # same `%a{pure}` above the same `class`, worked. Both lanes now answer the same.
119
+ #
120
+ # The rewrite reads only what the writer itself emitted, in one pass, and is deliberately literal:
121
+ # an annotation the author spelled in a comment block that the writer echoed is re-emitted, at the
122
+ # declaration's own indentation, on the line the RBS grammar wants it on. Three properties make that
123
+ # safe rather than clever:
124
+ #
125
+ # - a comment block is echoed **only when its member is emitted**, so a detached annotation (one a
126
+ # blank line separates from the declaration, which upstream drops) is never echoed and so never
127
+ # re-attached — the two lanes agree about that too;
128
+ # - any non-comment line clears the pending set, so a member's own annotation — which the writer
129
+ # *does* emit, on the line between the echo and the `def` — ends the run before a declaration can
130
+ # claim it, and no annotation is ever attached twice;
131
+ # - the same property makes this forward-compatible: if upstream some day emits the declaration
132
+ # annotation itself, that line clears the pending set and this contributes nothing.
133
+ def reattach_declaration_annotations(rendered)
134
+ return rendered if rendered.nil? || !rendered.include?("%a{")
135
+
136
+ pending = []
137
+ rendered.lines.flat_map do |line|
138
+ if (annotation = ECHOED_ANNOTATION.match(line))
139
+ pending << annotation[1]
140
+ line
141
+ elsif COMMENT_LINE.match?(line)
142
+ line
143
+ elsif (declaration = DECLARATION_LINE.match(line)) && !pending.empty?
144
+ emitted = pending.map { |spelling| "#{declaration[1]}#{spelling}\n" } << line
145
+ pending = []
146
+ emitted
147
+ else
148
+ pending = []
149
+ line
150
+ end
151
+ end.join
152
+ end
153
+
99
154
  # True when the file carries at least one rbs-inline annotation. Gates the magic-comment-free mode,
100
155
  # and is the difference between "honour annotations wherever they are" and "fabricate signatures for
101
156
  # code nobody annotated" — upstream's opt-out mode does the latter, emitting a full
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rigor/plugin"
4
+
5
+ module Rigor
6
+ module Plugin
7
+ class Sidekiq < Rigor::Plugin::Base
8
+ # rigor-sidekiq's effect contract (ADR-103 WD4 / WD10; [#456](https://github.com/rigortype/rigor/issues/456)).
9
+ #
10
+ # `job.enqueue` shipped in the vocabulary with nothing producing it. Across two Rails applications
11
+ # the corpus census found zero instances of it, while Mastodon alone writes 219 `perform_async`-shaped
12
+ # call sites with this plugin loaded — so the label a policy would actually name ("nothing on this
13
+ # path may enqueue work") could not be written against anything.
14
+ #
15
+ # ## Why the rows key on a module
16
+ #
17
+ # A Sidekiq worker has no base class: it is `class TriggerWebhookWorker; include Sidekiq::Job`. Every
18
+ # other first-party plugin's rows key on a base class and reach a project subclass through the
19
+ # project's own `class … <` lines, which is a walk that cannot see an `include` — so until #456 taught
20
+ # `Effects::PluginFacts#ancestry` to walk included modules beside the superclass, no spelling of these
21
+ # rows could have matched. The marker modules are the plugin's existing `worker_marker_modules:`
22
+ # config, so a project that includes its own concern instead names it once and gets both the arity
23
+ # check and these rows.
24
+ #
25
+ # ## What is deliberately not here
26
+ #
27
+ # `perform_inline` / `perform_sync` run the job on the caller's stack. They are not an enqueue and
28
+ # must not carry `job.enqueue`; the honest reading is an edge into the worker's own `#perform`, which
29
+ # {Rigor::Plugin::EffectEdge::TARGETS} spells only for the ActiveJob shape today. A row claiming the
30
+ # enqueue for them would be worse than the silence.
31
+ module Effects
32
+ # Redis is the transport, and unlike ActiveJob's adapter there is nothing to read: a project that
33
+ # loads Sidekiq has chosen it. `io.net` rather than `io` because the round trip is a socket in every
34
+ # deployment worth naming — a unix socket is still `io.net`'s subsystem, not the filesystem's.
35
+ TRANSPORT = ["io.net"].freeze
36
+
37
+ # The meaning half, and the reason this file exists: what a policy names.
38
+ MEANING = ["job.enqueue"].freeze
39
+
40
+ LABELS = (TRANSPORT + MEANING).freeze
41
+
42
+ # The class-side enqueues. `perform_bulk` takes an array of argument arrays and enqueues each.
43
+ SINGLETON_ENQUEUES = %w[perform_async perform_in perform_at perform_bulk].freeze
44
+
45
+ # `Worker.set(queue: "low").perform_async(…)` — `set` returns a lazy `Setter` that has enqueued
46
+ # nothing, so the enqueue is one link further out, keyed on the class that produced the Setter.
47
+ RESULT_ENQUEUES = %w[perform_async perform_in perform_at perform_bulk].freeze
48
+
49
+ WHY = "Sidekiq's enqueue is a Redis round trip; `job.enqueue` is the meaning a policy names. " \
50
+ "The row is keyed on the marker module because a Sidekiq worker includes rather than " \
51
+ "inherits."
52
+
53
+ module_function
54
+
55
+ # @param marker_modules [Array<String>] the modules a worker includes — the plugin's own
56
+ # `worker_marker_modules:` config, so one project setting drives the arity check and these rows.
57
+ def attributions(marker_modules)
58
+ marker_modules.flat_map do |marker|
59
+ SINGLETON_ENQUEUES.map do |selector|
60
+ EffectAttribution.new(receiver: marker, method: selector, labels: LABELS, singleton: true,
61
+ discharge: true, why: WHY)
62
+ end +
63
+ RESULT_ENQUEUES.map do |selector|
64
+ EffectAttribution.new(
65
+ receiver: marker, method: selector, labels: LABELS, on_result: true, discharge: true,
66
+ why: "#{WHY} Matched on the RESULT of a call to the worker class, which is the shape " \
67
+ "`Worker.set(queue: \"low\").perform_async(…)` takes."
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -6,6 +6,7 @@ require_relative "sidekiq/worker_index"
6
6
  require_relative "sidekiq/worker_discoverer"
7
7
  require_relative "sidekiq/schedule_scan"
8
8
  require_relative "sidekiq/analyzer"
9
+ require_relative "sidekiq/effects"
9
10
 
10
11
  module Rigor
11
12
  module Plugin
@@ -62,9 +63,20 @@ module Rigor
62
63
  # these are file paths, not directories, because a schedule is a named document rather than a tree.
63
64
  "schedule_paths" => { kind: :array, default: ["config/schedule.yml", "config/sidekiq.yml"] }
64
65
  },
65
- produces: [:reachability_roots]
66
+ produces: [:reachability_roots],
67
+ # ADR-103 WD4 / WD10 (#456). The rows themselves are NOT here: they key on the project's own
68
+ # `worker_marker_modules:` setting, so they are built in `#effect_attributions` below.
69
+ effect_labels: []
66
70
  )
67
71
 
72
+ # ADR-103 WD10 — the enqueue rows, keyed on whichever modules this project's workers include.
73
+ # Overridden rather than declared on the manifest because the marker set is configuration;
74
+ # `Plugin::Registry#effect_contributions` is lazy and asks once per process, and only on a run with
75
+ # collection on.
76
+ def effect_attributions
77
+ Effects.attributions(@worker_marker_modules || Array(config.fetch("worker_marker_modules")).map(&:to_s))
78
+ end
79
+
68
80
  producer :worker_index, watch: -> { [[@worker_search_paths, "**/*.rb"]] } do |_params|
69
81
  WorkerDiscoverer.new(
70
82
  io_boundary: io_boundary,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -360,6 +360,7 @@ files:
360
360
  - docs/manual/16-rbs-extended-annotations.md
361
361
  - docs/manual/17-driving-improvement.md
362
362
  - docs/manual/18-removing-dead-code.md
363
+ - docs/manual/19-effect-labels.md
363
364
  - docs/manual/README.md
364
365
  - docs/manual/ci-templates/README.md
365
366
  - docs/manual/plugins/README.md
@@ -533,6 +534,7 @@ files:
533
534
  - lib/rigor/configuration.rb
534
535
  - lib/rigor/configuration/dependencies.rb
535
536
  - lib/rigor/configuration/severity_profile.rb
537
+ - lib/rigor/configuration_error.rb
536
538
  - lib/rigor/effects.rb
537
539
  - lib/rigor/effects/attribution.rb
538
540
  - lib/rigor/effects/catalog.rb
@@ -547,6 +549,7 @@ files:
547
549
  - lib/rigor/effects/file_collection.rb
548
550
  - lib/rigor/effects/framework_units.rb
549
551
  - lib/rigor/effects/identity.rb
552
+ - lib/rigor/effects/inline_anchor.rb
550
553
  - lib/rigor/effects/label.rb
551
554
  - lib/rigor/effects/label_intent.rb
552
555
  - lib/rigor/effects/label_set.rb
@@ -708,6 +711,7 @@ files:
708
711
  - lib/rigor/plugin/base.rb
709
712
  - lib/rigor/plugin/blueprint.rb
710
713
  - lib/rigor/plugin/box.rb
714
+ - lib/rigor/plugin/effect_ancestry.rb
711
715
  - lib/rigor/plugin/effect_attribution.rb
712
716
  - lib/rigor/plugin/effect_edge.rb
713
717
  - lib/rigor/plugin/effect_entry_points.rb
@@ -937,6 +941,7 @@ files:
937
941
  - plugins/rigor-sidekiq/lib/rigor-sidekiq.rb
938
942
  - plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb
939
943
  - plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb
944
+ - plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb
940
945
  - plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb
941
946
  - plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb
942
947
  - plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb