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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/effects/core.yml +18 -1
- data/data/effects/registry.yml +31 -3
- data/docs/manual/02-cli-reference.md +60 -13
- data/docs/manual/03-configuration.md +20 -3
- data/docs/manual/04-diagnostics.md +3 -2
- data/docs/manual/11-ci.md +37 -0
- data/docs/manual/12-caching.md +39 -0
- data/docs/manual/16-rbs-extended-annotations.md +15 -2
- data/docs/manual/19-effect-labels.md +671 -0
- data/docs/manual/README.md +5 -0
- data/docs/manual/ci-templates/README.md +9 -0
- data/lib/rigor/analysis/rule_catalog.rb +10 -3
- data/lib/rigor/analysis/run_cache_probe.rb +69 -1
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -24
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +28 -28
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +25 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -4
- data/lib/rigor/analysis/runner.rb +12 -3
- data/lib/rigor/analysis/worker_session.rb +3 -1
- data/lib/rigor/cli/effects_command.rb +123 -9
- data/lib/rigor/cli/effects_diff_renderer.rb +5 -1
- data/lib/rigor/cli/effects_renderer.rb +41 -3
- data/lib/rigor/cli/effects_report.rb +116 -6
- data/lib/rigor/cli/effects_snapshot_command.rb +23 -4
- data/lib/rigor/cli.rb +12 -1
- data/lib/rigor/configuration.rb +37 -25
- data/lib/rigor/configuration_error.rb +20 -0
- data/lib/rigor/effects/collector.rb +38 -1
- data/lib/rigor/effects/entry_points.rb +47 -1
- data/lib/rigor/effects/file_collection.rb +18 -4
- data/lib/rigor/effects/framework_units.rb +68 -13
- data/lib/rigor/effects/inline_anchor.rb +134 -0
- data/lib/rigor/effects/plugin_facts.rb +62 -13
- data/lib/rigor/effects/propagator.rb +79 -19
- data/lib/rigor/effects/registry.rb +10 -3
- data/lib/rigor/effects/scanner.rb +21 -9
- data/lib/rigor/effects/signature_sources.rb +16 -0
- data/lib/rigor/effects/snapshot.rb +21 -5
- data/lib/rigor/effects/taint_cause.rb +1 -0
- data/lib/rigor/effects/unit_scan.rb +87 -12
- data/lib/rigor/plugin/base.rb +4 -0
- data/lib/rigor/plugin/box.rb +18 -2
- data/lib/rigor/plugin/effect_ancestry.rb +80 -0
- data/lib/rigor/plugin/manifest.rb +34 -10
- data/lib/rigor/plugin/registry.rb +10 -3
- data/lib/rigor/rbs_extended.rb +22 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +17 -2
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +27 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +56 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +75 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +13 -1
- 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
|
-
|
|
80
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
data/lib/rigor/rbs_extended.rb
CHANGED
|
@@ -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
|
-
|
|
781
|
-
"#{
|
|
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
|
@@ -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
|
-
|
|
602
|
-
|
|
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
|
+
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
|