evilution 0.22.4 → 0.22.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64e44290d350e92d6c70bdd99a4a982fa8f0a33cf36062e3fa8f0fa5eea553a8
4
- data.tar.gz: 31c5dbe19058f73d8c8ae2301649ec44127b1b0487955c8b26b2732e1e69f967
3
+ metadata.gz: f88a719ecd12ca2576f1254f3ef1771359f64345eea28e3537e0d9dae38e2c78
4
+ data.tar.gz: 01c65258b5a9d496a566437b74460ff77175d0dbea9988643d048df3b2e18171
5
5
  SHA512:
6
- metadata.gz: 24d6294bd7f0ef343fb7b3d4573e1217389e3c1e38ac5a4056da51ac3cda0a298dbd33abae25d2bdd927f82f07d0079f1d7b3530e65ff2efb8acc96778631e46
7
- data.tar.gz: d767b3b380fcfed64b64915e177931d72939d5fbb6a5498676c989daed2f34df969843e1a2c5f04be83e0498bfba2e4198cc65eb04a9904d654db49758904e99
6
+ metadata.gz: 136e6f94f55a9e312b777573bd73391d6ec7d05ac8c7455817341589edf018002836db6e550255ff16deb974e6a320a8e818e08661b7918bd2ac6e2f4e85aea1
7
+ data.tar.gz: 8e48faca42c452ba1d8ba1b8800deaf1eafc89d264997f34dc04840324296d2431fdc7e9ccb31f2cff5127862adeb818c15a10da5d060301bdcae0b0352d0cf4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.22.6] - 2026-04-12
4
+
5
+ ### Fixed
6
+
7
+ - **Zeitwerk re-autoloads original file during mutation load** — Zeitwerk's `const_added` hook re-autoloaded the original source when a module constant was reopened from a temp-dir copy, re-setting `@_included_block` after `clear_concern_state` already removed it; now `pin_autoloaded_constants` resolves all module/class constants from the source via `Object.const_get` before loading, preventing the autoloader from re-triggering (#680)
8
+
9
+ ## [0.22.5] - 2026-04-12
10
+
11
+ ### Fixed
12
+
13
+ - **`ActiveSupport::Concern` modules: all mutations error with `MultipleIncludedBlocks`** — re-evaluating a mutated concern file triggered Rails' guard because `@_included_block` (and `@_prepended_block`) was already set from the original load with a different `source_location`; now `clear_concern_state` removes these instance variables from affected concern modules before `require`/`load`, matching both original file paths and temp-dir copies via subpath suffix so consecutive mutations of the same concern also succeed (#676, PR #678)
14
+
3
15
  ## [0.22.4] - 2026-04-12
4
16
 
5
17
  ### Fixed
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "prism"
4
5
  require "tmpdir"
5
6
  require_relative "../integration"
6
7
  require_relative "../temp_dir_tracker"
@@ -87,6 +88,8 @@ class Evilution::Integration::Base
87
88
  File.write(dest, mutation.mutated_source)
88
89
  $LOAD_PATH.unshift(@temp_dir)
89
90
  displace_loaded_feature(mutation.file_path)
91
+ pin_autoloaded_constants(mutation.original_source)
92
+ clear_concern_state(mutation.file_path)
90
93
  require(subpath.delete_suffix(".rb"))
91
94
  end
92
95
 
@@ -95,6 +98,8 @@ class Evilution::Integration::Base
95
98
  dest = File.join(@temp_dir, absolute)
96
99
  FileUtils.mkdir_p(File.dirname(dest))
97
100
  File.write(dest, mutation.mutated_source)
101
+ pin_autoloaded_constants(mutation.original_source)
102
+ clear_concern_state(mutation.file_path)
98
103
  load(dest)
99
104
  end
100
105
 
@@ -110,6 +115,56 @@ class Evilution::Integration::Base
110
115
  @temp_dir = nil
111
116
  end
112
117
 
118
+ def pin_autoloaded_constants(source)
119
+ collect_constant_names(Prism.parse(source).value).each do |name|
120
+ Object.const_get(name) if Object.const_defined?(name, false)
121
+ rescue NameError # :nodoc:
122
+ nil
123
+ end
124
+ end
125
+
126
+ def collect_constant_names(node, nesting = [])
127
+ names = []
128
+ case node
129
+ when Prism::ModuleNode, Prism::ClassNode
130
+ const = node.constant_path.full_name
131
+ qualified = nesting.any? && !const.include?("::") ? "#{nesting.join("::")}::#{const}" : const
132
+ names << qualified
133
+ names.concat(collect_constant_names(node.body, nesting + [const])) if node.body
134
+ when Prism::ProgramNode
135
+ names.concat(collect_constant_names(node.statements, nesting)) if node.statements
136
+ when Prism::StatementsNode
137
+ node.body.each { |child| names.concat(collect_constant_names(child, nesting)) }
138
+ end
139
+ names
140
+ end
141
+
142
+ def clear_concern_state(file_path)
143
+ return unless defined?(ActiveSupport::Concern)
144
+
145
+ absolute = File.expand_path(file_path)
146
+ subpath = resolve_require_subpath(file_path)
147
+
148
+ ObjectSpace.each_object(Module) do |mod|
149
+ next unless mod.singleton_class.ancestors.include?(ActiveSupport::Concern)
150
+
151
+ %i[@_included_block @_prepended_block].each do |ivar|
152
+ next unless mod.instance_variable_defined?(ivar)
153
+
154
+ block = mod.instance_variable_get(ivar)
155
+ block_file = block.source_location&.first
156
+ next unless block_file
157
+
158
+ expanded = File.expand_path(block_file)
159
+ mod.remove_instance_variable(ivar) if source_matches?(expanded, absolute, subpath)
160
+ end
161
+ end
162
+ end
163
+
164
+ def source_matches?(block_path, absolute, subpath)
165
+ block_path == absolute || (subpath && block_path.end_with?("/#{subpath}"))
166
+ end
167
+
113
168
  def resolve_require_subpath(file_path)
114
169
  absolute = File.expand_path(file_path)
115
170
  best_subpath = nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "0.22.4"
4
+ VERSION = "0.22.6"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evilution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.4
4
+ version: 0.22.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Kiselev