Class: Rigor::Plugin::EffectAttribution
- Inherits:
-
Object
- Object
- Rigor::Plugin::EffectAttribution
- Defined in:
- lib/rigor/plugin/effect_attribution.rb
Overview
One row of a plugin's effect_attributions: — "a call to this receiver's method contributes these
effect labels" (ADR-103 WD6 / WD10; design note § 6.6; the contract is
docs/internal-spec/plugin.md § Effect contributions).
A framework method has no body Rigor reads, so someone must colour it. Three channels can, in descending order of preference:
- a
%a{rigor:v1:effect …}annotation in the plugin's ownsignature_paths:RBS — tier 1, read through Effects::EnvelopeIndex's accepted stratum, and the right channel whenever the plugin already ships a signature for the method; - this field, for the methods RBS cannot name per app — association readers,
find_by_*, scopes — and for classes the plugin ships no RBS for at all; - the project's own
effects.attribution:table, which is the user's answer for everything left.
The receiver
receiver is spelled one of two ways, and the spelling picks the matching rule:
- a class name (
"ActiveRecord::Base","I18n") matches the class the call's receiver projects to, through the project's inheritance chain: anActiveRecord::Baserow applies toUser.findbecause the project declaresUser < ApplicationRecord < ActiveRecord::Base. This is the difference from a catalogue row, which matches its exact owner and nothing else — Ruby's core classes are leaves in practice and a framework's base class never is. - a receiver path (
"Rails.cache","Time.zone","Rails.application.credentials") matches the receiver expression as the syntax spells it.Rails.cache.readhas no receiver class the typer can name —Rails.cacheis a call, and its return type is adapter-dependent by design — so the only honest handle on it is the path that was written. - a self path (
"self.session","self.flash.now","self.cookies.encrypted") is the same thing rooted at implicit self, and is what a Rails controller's accessors actually look like:session[:user_id] = idis[]=on the result of a receiver-lesssession. A self-path row MUST name awithin:class, because a receiver-lesssessionin some other project class is a differentsession— the row applies only inside a class whose project ancestry reacheswithin.
on_result: true shifts a class-name row one link outwards: it matches a call on what a call to
that class returned. UserMailer.welcome(u).deliver_now and WelcomeJob.set(wait: 1.hour) .perform_later are the two idioms that need it — the object in the middle is a lazy
MessageDelivery / ConfiguredJob whose type nothing in the project declares, while the class that
produced it is written right there in the source. Without it the send and the enqueue, the two calls
a reviewer most wants coloured, would go unattributed in the spelling Rails actually uses.
Discharge
discharge: true says the label is derived from the framework's own semantics rather than guessed,
so the site is exhaustive rather than tainted. ADR-103 WD6 grants that only to a first-party
bundled plugin (FirstParty), gated by make check-plugins; a third-party plugin's true is
ignored with a load-time warning and the row behaves like the project's effects.attribution:
table — declared, and carrying a plugin-attribution taint.
Either way the labels land in the declared lane, never the proven one. A discharging row is a
trusted claim, exactly like an accepted signature's %a{…}: "this is what it does", not "the
analyzer read the body and saw this".
Constant Summary collapse
- CLASS_NAME =
A receiver spelled as a
Constant::Path— the class-name form. Anything else with a.in it is read as a receiver path. /\A[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*\z/- RECEIVER_PATH =
A receiver path: a constant head followed by one or more sends (
Rails.cache,Rails.application.credentials). /\A[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*(\.[a-z_][A-Za-z0-9_]*)+\z/- SELF_PATH =
A self path: implicit self followed by one or more receiver-less sends (
self.flash.now). /\Aself(\.[a-z_][A-Za-z0-9_]*)+\z/- SELF_HEAD =
What a self path is rooted at, once the
self.head is stripped. "self"- TAINT_CAUSES =
The taint causes a plugin row may name. A closed subset of Effects::TaintCause::ALL: a plugin may say "and there is more here I cannot see", but only for the two reasons a framework model can honestly have — a template it does not read, and a callable whose body is supplied by the application.
%w[template-not-analysed opaque-callable].freeze
Instance Attribute Summary collapse
-
#discharge ⇒ Object
readonly
Returns the value of attribute discharge.
-
#labels ⇒ Object
readonly
Returns the value of attribute labels.
-
#method ⇒ Object
readonly
Returns the value of attribute method.
-
#narrow ⇒ Object
readonly
Returns the value of attribute narrow.
-
#on_result ⇒ Object
readonly
Returns the value of attribute on_result.
-
#receiver ⇒ Object
readonly
Returns the value of attribute receiver.
-
#singleton ⇒ Object
readonly
Returns the value of attribute singleton.
-
#taint ⇒ Object
readonly
Returns the value of attribute taint.
-
#why ⇒ Object
readonly
Returns the value of attribute why.
-
#within ⇒ Object
readonly
Returns the value of attribute within.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
- #hash ⇒ Object
-
#initialize(receiver:, method:, labels:, why:, singleton: false, narrow: nil, discharge: false, within: nil, on_result: false, taint: nil) ⇒ EffectAttribution
constructor
A new instance of EffectAttribution.
-
#key ⇒ Object
The key an origin and a report spell this row as.
-
#receiver_path? ⇒ Boolean
Whether #receiver is a receiver path (
Rails.cache) rather than a class name or a self path. -
#self_path? ⇒ Boolean
Whether #receiver is a self path (
self.flash.now). - #to_h ⇒ Object
Constructor Details
#initialize(receiver:, method:, labels:, why:, singleton: false, narrow: nil, discharge: false, within: nil, on_result: false, taint: nil) ⇒ EffectAttribution
Returns a new instance of EffectAttribution.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 90 def initialize(receiver:, method:, labels:, why:, singleton: false, narrow: nil, discharge: false, # rubocop:disable Metrics/ParameterLists within: nil, on_result: false, taint: nil) @receiver = validate_receiver!(receiver) @method = method.to_sym @singleton = singleton ? true : false @labels = normalize_labels(labels) @narrow = narrow.nil? ? nil : narrow.to_s.dup.freeze @discharge = discharge ? true : false @within = validate_within!(within) @on_result = on_result ? true : false validate_on_result! @taint = validate_taint!(taint) @why = validate_why!(why) freeze end |
Instance Attribute Details
#discharge ⇒ Object (readonly)
Returns the value of attribute discharge.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def discharge @discharge end |
#labels ⇒ Object (readonly)
Returns the value of attribute labels.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def labels @labels end |
#method ⇒ Object (readonly)
Returns the value of attribute method.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def method @method end |
#narrow ⇒ Object (readonly)
Returns the value of attribute narrow.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def narrow @narrow end |
#on_result ⇒ Object (readonly)
Returns the value of attribute on_result.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def on_result @on_result end |
#receiver ⇒ Object (readonly)
Returns the value of attribute receiver.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def receiver @receiver end |
#singleton ⇒ Object (readonly)
Returns the value of attribute singleton.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def singleton @singleton end |
#taint ⇒ Object (readonly)
Returns the value of attribute taint.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def taint @taint end |
#why ⇒ Object (readonly)
Returns the value of attribute why.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def why @why end |
#within ⇒ Object (readonly)
Returns the value of attribute within.
71 72 73 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 71 def within @within end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
132 133 134 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 132 def ==(other) other.is_a?(EffectAttribution) && to_h == other.to_h end |
#hash ⇒ Object
137 138 139 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 137 def hash to_h.hash end |
#key ⇒ Object
The key an origin and a report spell this row as.
117 118 119 120 121 122 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 117 def key return "#{@receiver}.#{@method}" if receiver_path? || self_path? return "#{@receiver}()##{@method}" if @on_result "#{@receiver}#{@singleton ? '.' : '#'}#{@method}" end |
#receiver_path? ⇒ Boolean
Whether #receiver is a receiver path (Rails.cache) rather than a class name or a self path.
107 108 109 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 107 def receiver_path? @receiver.include?(".") && !self_path? end |
#self_path? ⇒ Boolean
Whether #receiver is a self path (self.flash.now).
112 113 114 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 112 def self_path? @receiver.start_with?("#{SELF_HEAD}.") end |
#to_h ⇒ Object
124 125 126 127 128 129 130 |
# File 'lib/rigor/plugin/effect_attribution.rb', line 124 def to_h { "receiver" => @receiver, "method" => @method.to_s, "singleton" => @singleton, "labels" => @labels, "narrow" => @narrow, "discharge" => @discharge, "within" => @within, "on_result" => @on_result, "taint" => @taint } end |