Module: Ivar

Defined in:
lib/ivar.rb,
lib/ivar/macros.rb,
lib/ivar/checked.rb,
lib/ivar/version.rb,
lib/ivar/manifest.rb,
lib/ivar/policies.rb,
lib/ivar/validation.rb,
lib/ivar/declaration.rb,
lib/ivar/check_policy.rb,
lib/ivar/project_root.rb,
lib/ivar/check_all_manager.rb,
lib/ivar/explicit_declaration.rb,
lib/ivar/checked/class_methods.rb,
lib/ivar/targeted_prism_analysis.rb,
lib/ivar/checked/instance_methods.rb,
lib/ivar/explicit_keyword_declaration.rb,
lib/ivar/explicit_positional_declaration.rb

Defined Under Namespace

Modules: CheckPolicy, Checked, Macros, Validation Classes: CheckAllManager, Declaration, ExplicitDeclaration, ExplicitKeywordDeclaration, ExplicitPositionalDeclaration, IvarCollector, LogPolicy, Manifest, MethodTargetedInstanceVariableReferenceVisitor, NonePolicy, Policy, ProjectRoot, RaisePolicy, TargetedPrismAnalysis, WarnOncePolicy, WarnPolicy

Constant Summary collapse

MUTEX =
Mutex.new
PROJECT_ROOT_FINDER =
ProjectRoot.new
CHECK_ALL_MANAGER =
CheckAllManager.new
INTERNAL_IVAR_PREFIX =

Pattern for internal instance variables

"@__ivar_"
VERSION =
"0.4.7"
POLICY_CLASSES =

Map of policy symbols to policy classes

{
  warn: WarnPolicy,
  warn_once: WarnOncePolicy,
  raise: RaisePolicy,
  log: LogPolicy,
  none: NonePolicy
}.freeze

Class Method Summary collapse

Class Method Details

.check_all(&block) ⇒ void

This method returns an undefined value.

Enables automatic inclusion of Ivar::Checked in all classes and modules defined within the project root.

Parameters:

  • block (Proc)

    Optional block. If provided, auto-checking is only active for the duration of the block. Otherwise, it remains active indefinitely.



154
155
156
157
# File 'lib/ivar.rb', line 154

def self.check_all(&block)
  root = project_root
  CHECK_ALL_MANAGER.enable(root, &block)
end

.check_policySymbol

Get the default check policy

Returns:

  • (Symbol)

    The default check policy



126
127
128
# File 'lib/ivar.rb', line 126

def self.check_policy
  @default_check_policy
end

.check_policy=(policy) ⇒ Object

Set the default check policy

Parameters:

  • policy (Symbol, Policy)

    The default check policy



132
133
134
# File 'lib/ivar.rb', line 132

def self.check_policy=(policy)
  MUTEX.synchronize { @default_check_policy = policy }
end

.class_checked?(klass) ⇒ Boolean

Checks if a class has been validated already Thread-safe: Read-only operation

Parameters:

  • klass (Class)

    The class to check

Returns:

  • (Boolean)

    Whether the class has been validated



75
76
77
# File 'lib/ivar.rb', line 75

def self.class_checked?(klass)
  MUTEX.synchronize { @checked_classes.key?(klass) }
end

.clear_analysis_cacheObject

For testing purposes - allows clearing the cache Thread-safe: Write operation protected by mutex



88
89
90
91
92
93
94
95
# File 'lib/ivar.rb', line 88

def self.clear_analysis_cache
  MUTEX.synchronize do
    @analysis_cache.clear
    @checked_classes.clear
    @manifest_registry.clear
  end
  PROJECT_ROOT_FINDER.clear_cache
end

.disable_check_allvoid

This method returns an undefined value.

Disables automatic inclusion of Ivar::Checked in classes and modules.



161
162
163
# File 'lib/ivar.rb', line 161

def self.disable_check_all
  CHECK_ALL_MANAGER.disable
end

.get_analysis(klass) ⇒ Object

Returns a cached analysis for the given class or module Creates a new analysis if one doesn’t exist in the cache Thread-safe: Multiple readers are allowed, but writers block all other access



63
64
65
66
67
68
69
# File 'lib/ivar.rb', line 63

def self.get_analysis(klass)
  return @analysis_cache[klass] if @analysis_cache.key?(klass)

  MUTEX.synchronize do
    @analysis_cache[klass] ||= TargetedPrismAnalysis.new(klass)
  end
end

.get_ancestral_analyses(klass) ⇒ Object



48
49
50
51
52
# File 'lib/ivar.rb', line 48

def self.get_ancestral_analyses(klass)
  klass
    .ancestors.filter_map { |ancestor| maybe_get_analysis(ancestor) }
    .reverse
end

.get_manifest(klass, create: true) ⇒ Manifest?

Get or create a manifest for a class or module

Parameters:

  • klass (Class, Module)

    The class or module to get a manifest for

  • create (Boolean) (defaults to: true)

    Whether to create a new manifest if one doesn’t exist

Returns:

  • (Manifest, nil)

    The manifest for the class or module, or nil if not found and create_if_missing is false



101
102
103
104
105
106
107
108
# File 'lib/ivar.rb', line 101

def self.get_manifest(klass, create: true)
  return @manifest_registry[klass] if @manifest_registry.key?(klass)
  return nil unless create

  MUTEX.synchronize do
    @manifest_registry[klass] ||= Manifest.new(klass)
  end
end

.get_or_create_manifest(klass) ⇒ Manifest

Alias for get_manifest that makes it clearer that it may create a manifest

Parameters:

  • klass (Class, Module)

    The class or module to get a manifest for

Returns:

  • (Manifest)

    The manifest for the class or module



113
114
115
# File 'lib/ivar.rb', line 113

def self.get_or_create_manifest(klass)
  get_manifest(klass, create: true)
end

.get_policy(policy, **options) ⇒ Policy

Get a policy instance from a symbol or policy object

Parameters:

  • policy (Symbol, Policy, Array)

    The policy to get

  • options (Hash)

    Options to pass to the policy constructor

Returns:

  • (Policy)

    The policy instance

Raises:

  • (ArgumentError)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ivar/policies.rb', line 142

def self.get_policy(policy, **options)
  return policy if policy.is_a?(Policy)

  # Handle the case where policy is an array with [policy_name, options]
  if policy.is_a?(Array) && policy.size == 2 && policy[1].is_a?(Hash)
    policy_name, policy_options = policy
    policy_class = POLICY_CLASSES[policy_name]
    raise ArgumentError, "Unknown policy: #{policy_name}" unless policy_class

    return policy_class.new(**policy_options)
  end

  policy_class = POLICY_CLASSES[policy]
  raise ArgumentError, "Unknown policy: #{policy}" unless policy_class

  policy_class.new(**options)
end

.get_stashed_method(klass, method_name) ⇒ UnboundMethod?

Gets a method from the stash or returns nil if not found

Parameters:

  • klass (Class)

    The class that owns the method

  • method_name (Symbol)

    The name of the method to retrieve

Returns:

  • (UnboundMethod, nil)

    The stashed method or nil if not found



169
170
171
# File 'lib/ivar.rb', line 169

def self.get_stashed_method(klass, method_name)
  (klass.instance_variable_get(:@__ivar_method_impl_stash) || {})[method_name]
end

.internal_ivar?(ivar_name) ⇒ Boolean

Checks if an instance variable name is an internal variable

Parameters:

  • ivar_name (Symbol, String)

    The instance variable name to check

Returns:

  • (Boolean)

    Whether the variable is an internal variable



33
34
35
# File 'lib/ivar.rb', line 33

def self.internal_ivar?(ivar_name)
  ivar_name.to_s.start_with?(INTERNAL_IVAR_PREFIX)
end

.known_internal_ivarsArray<Symbol>

Returns a list of known internal instance variables

Returns:

  • (Array<Symbol>)

    List of known internal instance variables



39
40
41
42
43
44
45
46
# File 'lib/ivar.rb', line 39

def self.known_internal_ivars
  [
    :@__ivar_check_policy,
    :@__ivar_initialized_vars,
    :@__ivar_method_impl_stash,
    :@__ivar_skip_init
  ]
end

.manifest_exists?(klass) ⇒ Boolean

Check if a manifest exists for a class or module

Parameters:

  • klass (Class, Module)

    The class or module to check

Returns:

  • (Boolean)

    Whether a manifest exists for the class or module



120
121
122
# File 'lib/ivar.rb', line 120

def self.manifest_exists?(klass)
  @manifest_registry.key?(klass)
end

.mark_class_checked(klass) ⇒ Object

Marks a class as having been checked Thread-safe: Write operation protected by mutex

Parameters:

  • klass (Class)

    The class to mark as checked



82
83
84
# File 'lib/ivar.rb', line 82

def self.mark_class_checked(klass)
  MUTEX.synchronize { @checked_classes[klass] = true }
end

.maybe_get_analysis(klass) ⇒ Object



54
55
56
57
58
# File 'lib/ivar.rb', line 54

def self.maybe_get_analysis(klass)
  if klass.include?(Validation)
    get_analysis(klass)
  end
end

.project_root(caller_location = nil) ⇒ String

Determines the project root directory based on the caller’s location Delegates to ProjectRoot class

Parameters:

  • caller_location (String, nil) (defaults to: nil)

    Optional file path to start from (defaults to caller’s location)

Returns:

  • (String)

    The absolute path to the project root directory



144
145
146
# File 'lib/ivar.rb', line 144

def self.project_root(caller_location = nil)
  @project_root ||= PROJECT_ROOT_FINDER.find(caller_location)
end

.project_root=(explicit_root) ⇒ Object



136
137
138
# File 'lib/ivar.rb', line 136

def self.project_root=(explicit_root)
  @project_root = explicit_root
end

.stash_method(klass, method_name) ⇒ UnboundMethod?

Stashes a method implementation for a class

Parameters:

  • klass (Class)

    The class that owns the method

  • method_name (Symbol)

    The name of the method to stash

Returns:

  • (UnboundMethod, nil)

    The stashed method or nil if the method doesn’t exist



177
178
179
180
181
182
183
184
# File 'lib/ivar.rb', line 177

def self.stash_method(klass, method_name)
  return nil unless klass.method_defined?(method_name) || klass.private_method_defined?(method_name)

  method_impl = klass.instance_method(method_name)
  stash = klass.instance_variable_get(:@__ivar_method_impl_stash) ||
    klass.instance_variable_set(:@__ivar_method_impl_stash, {})
  stash[method_name] = method_impl
end