Class: RuboCop::Cop::Apartment::NoDirectCurrentWrite

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/apartment/no_direct_current_write.rb

Overview

Bans direct assignment to Apartment::Current attributes. Application code must change tenant context through the block-form switch, which guarantees restore via ensure.

Covers plain assignment (‘=`) and operator assignment (`||=`, `&&=`, `+=`). Known syntactic limitations (deliberately not chased — they signal deliberate evasion and are out of scope for a lint nudge): multiple assignment (`a, Apartment::Current.tenant = …`), safe navigation (`Apartment::Current&.tenant = …`), dynamic dispatch (`Apartment::Current.public_send(:tenant=, …)`), bulk mutators (`Apartment::Current.set(…)` / `.reset`), and aliased receivers.

Examples:

# bad
Apartment::Current.tenant = 'acme'
Apartment::Current.tenant ||= 'acme'

# good
Apartment::Tenant.switch('acme') { ... }

Constant Summary collapse

MSG =
'Do not write `Apartment::Current.%<attr>s` directly; use the ' \
'block-form `Apartment::Tenant.switch(tenant) { ... }`.'
RESTRICT_ON_SEND =

Only invoke on_send for these setters — keeps the cop off the hot path (RuboCop would otherwise call on_send for every method call linted).

%i[tenant= previous_tenant=].freeze

Instance Method Summary collapse

Instance Method Details

#current_attr_lhs?(node) ⇒ Object

The reader-shaped LHS of an operator-assignment (‘tenant ||= x` parses as an or_asgn around `(send recv :tenant)`, not a `:tenant=` send).



41
42
43
# File 'lib/rubocop/cop/apartment/no_direct_current_write.rb', line 41

def_node_matcher :current_attr_lhs?, <<~PATTERN
  (send (const (const {nil? cbase} :Apartment) :Current) {:tenant :previous_tenant})
PATTERN

#current_attr_write?(node) ⇒ Object



34
35
36
# File 'lib/rubocop/cop/apartment/no_direct_current_write.rb', line 34

def_node_matcher :current_attr_write?, <<~PATTERN
  (send (const (const {nil? cbase} :Apartment) :Current) {:tenant= :previous_tenant=} _)
PATTERN

#on_op_asgn(node) ⇒ Object



59
60
61
# File 'lib/rubocop/cop/apartment/no_direct_current_write.rb', line 59

def on_op_asgn(node)
  check_op_assign(node.children.first)
end

#on_or_asgn(node) ⇒ Object Also known as: on_and_asgn

‘||=` and `&&=` are or_asgn / and_asgn; `+=` etc. are op_asgn. The LHS is always the first child (a reader send). RESTRICT_ON_SEND does not apply to these callbacks, so the matcher does the filtering.



54
55
56
# File 'lib/rubocop/cop/apartment/no_direct_current_write.rb', line 54

def on_or_asgn(node)
  check_op_assign(node.children.first)
end

#on_send(node) ⇒ Object



45
46
47
48
49
# File 'lib/rubocop/cop/apartment/no_direct_current_write.rb', line 45

def on_send(node)
  return unless current_attr_write?(node)

  register(node, node.method_name.to_s.delete_suffix('='))
end