evilution 0.16.1 → 0.17.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/.migration-hint-ts +1 -1
  3. data/.beads/issues.jsonl +19 -18
  4. data/CHANGELOG.md +17 -0
  5. data/docs/ast_pattern_syntax.md +210 -0
  6. data/lib/evilution/ast/pattern/filter.rb +25 -0
  7. data/lib/evilution/ast/pattern/matcher.rb +107 -0
  8. data/lib/evilution/ast/pattern/parser.rb +185 -0
  9. data/lib/evilution/ast/pattern.rb +4 -0
  10. data/lib/evilution/cli.rb +13 -1
  11. data/lib/evilution/config.rb +35 -2
  12. data/lib/evilution/hooks/loader.rb +35 -0
  13. data/lib/evilution/hooks/registry.rb +60 -0
  14. data/lib/evilution/hooks.rb +58 -0
  15. data/lib/evilution/integration/base.rb +4 -0
  16. data/lib/evilution/integration/rspec.rb +6 -2
  17. data/lib/evilution/isolation/fork.rb +5 -0
  18. data/lib/evilution/mutator/base.rb +4 -1
  19. data/lib/evilution/mutator/operator/index_assignment_removal.rb +18 -0
  20. data/lib/evilution/mutator/operator/index_to_dig.rb +58 -0
  21. data/lib/evilution/mutator/operator/index_to_fetch.rb +30 -0
  22. data/lib/evilution/mutator/operator/mixin_removal.rb +2 -1
  23. data/lib/evilution/mutator/operator/pattern_matching_alternative.rb +46 -0
  24. data/lib/evilution/mutator/operator/pattern_matching_array.rb +97 -0
  25. data/lib/evilution/mutator/operator/pattern_matching_guard.rb +44 -0
  26. data/lib/evilution/mutator/operator/superclass_removal.rb +2 -1
  27. data/lib/evilution/mutator/registry.rb +9 -3
  28. data/lib/evilution/parallel/pool.rb +3 -1
  29. data/lib/evilution/reporter/cli.rb +1 -0
  30. data/lib/evilution/reporter/html.rb +7 -0
  31. data/lib/evilution/reporter/json.rb +1 -0
  32. data/lib/evilution/reporter/suggestion.rb +87 -1
  33. data/lib/evilution/result/summary.rb +3 -2
  34. data/lib/evilution/runner.rb +21 -9
  35. data/lib/evilution/session/store.rb +5 -2
  36. data/lib/evilution/version.rb +1 -1
  37. data/lib/evilution.rb +12 -0
  38. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '009f01f1d0ff623a8dc2730f05d23f5564118f0996e1d207d1c898a719579632'
4
- data.tar.gz: 89481ac5c9d0b8b344d14d2acbd320382abb5d0fa4fe3de2fea8a715154e9e0e
3
+ metadata.gz: adc5fa5ff465865f50dc8faf28531463e2ecf931db42f9c7f43d736d8ae03165
4
+ data.tar.gz: 03c3c1ebebd3c3316f453049f7bd9625ac4f4217f94ad27655a86dd80c8b4a1a
5
5
  SHA512:
6
- metadata.gz: 9de110591c9c23a86d20d521aa3002c7aa1743898ae7e4364f5c3ddfc7060a6907153cb050582bb2a3a7f07804f50f1bce1801e7e70df9ca892906065be0c9be
7
- data.tar.gz: d658b860c63f7226ae274db15c5abd417b41208043ed113fb0f484f335f377642999fadd2fd33190d24313d98498b75b3df3d2124a0620dc4859a73f3b5a97cb
6
+ metadata.gz: 5f2e6afc1b39ef3befd736860cb1c1d63cb25c53018021202cec45823bca3e804d1df495053845959d423b760fc2c1f894433ca069de11b622ebaa1bb055d543
7
+ data.tar.gz: 58802ed6dfee68bfb3dfc3b867444322598e7f2af8b085a5d39ac03c7d99859351bfe62d5643b251203e0009dab28ac5001e918999f1a4432907fec03ecd08c2
@@ -1 +1 @@
1
- 1774720771
1
+ 1774807809
data/.beads/issues.jsonl CHANGED
@@ -26,7 +26,7 @@
26
26
  {"id":"EV-116","title":"Implement zsuper removal mutator","description":"Create a mutator for implicit super calls (zsuper — super with no parens). Mutations: remove the super call entirely. Prism forwarding_super_node.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:00.030638552+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T14:22:27.928183609+07:00","dependencies":[{"issue_id":"EV-116","depends_on_id":"EV-112","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
27
27
  {"id":"EV-117","title":"Implement rescue body replacement mutator","description":"Create a mutator that replaces rescue clause bodies with nil or re-raise. Tests whether the rescue handler logic is actually tested.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:07.741756241+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T09:46:31.420660224+07:00","dependencies":[{"issue_id":"EV-117","depends_on_id":"EV-89","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
28
28
  {"id":"EV-118","title":"Add compact/flatten and zip/product method swap pairs","description":"Add method swap pairs: compact↔flatten (remove nils vs flatten nesting), zip↔product (pairwise vs cartesian). Register in the collection method swap operator.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:09.488783588+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T13:07:37.800200558+07:00","dependencies":[{"issue_id":"EV-118","depends_on_id":"EV-96","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
29
- {"id":"EV-119","title":"Design hook registry and configuration API","description":"Design the hook system API: how hooks are registered (config file, Ruby API), what data they receive, and how errors are handled. Support at minimum: worker_process_start, mutation_insert_pre/post, and setup_integration_pre/post hook points.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:09.630400111+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:09.630400111+07:00","dependencies":[{"issue_id":"EV-119","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
29
+ {"id":"EV-119","title":"Design hook registry and configuration API","description":"Design the hook system API: how hooks are registered (config file, Ruby API), what data they receive, and how errors are handled. Support at minimum: worker_process_start, mutation_insert_pre/post, and setup_integration_pre/post hook points.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:09.630400111+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T21:23:56.689185157+07:00","dependencies":[{"issue_id":"EV-119","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
30
30
  {"id":"EV-12","title":"Resolve suggestion gap","description":"The workflow section instructs agents to read a suggestion field from survived[], but the JSON reporter output does not include suggestion (lib/evilution/reporter/json.rb only emits operator/file/line/status/duration/diff). Either add suggestion to the JSON output/schema or update the workflow steps to match the actual report fields. See GitHub issue #12.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-05T12:33:23.674094791+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T13:05:48.785616141+07:00","closed_at":"2026-03-05T13:05:48.785616141+07:00","close_reason":"Wired Suggestion into JSON reporter for survived mutations, updated README schema, added specs"}
31
31
  {"id":"EV-120","title":"Implement explicit super argument mutations","description":"Create a mutator for explicit super(args) calls. Mutations: remove arguments (super() with no args), remove individual arguments, replace super with zsuper. Prism super_node.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:10.245605293+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T14:27:56.442898048+07:00","dependencies":[{"issue_id":"EV-120","depends_on_id":"EV-112","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
32
32
  {"id":"EV-121","title":"Add RSpec suggestion templates for bitwise operator mutations","description":"Add concrete RSpec suggestion templates for survived bitwise operator mutations to the SuggestionReporter.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:12.673234672+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T14:16:14.791646944+07:00","dependencies":[{"issue_id":"EV-121","depends_on_id":"EV-105","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
@@ -34,43 +34,43 @@
34
34
  {"id":"EV-123","title":"Fix version mismatch between CLI and MCP JSON output","description":"CLI reports 0.12.0 but MCP JSON version field says 0.11.2. The version string in the MCP server response is hardcoded or reading from a stale source. Should read from Evilution::VERSION consistently. Identified in real-world comparison testing feedback.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:19.056996262+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:34:16.40564094+07:00","closed_at":"2026-03-23T11:34:16.40564094+07:00","close_reason":"Already fixed — all version references (MCP server, JSON reporter, HTML reporter) use Evilution::VERSION consistently. The 0.11.2 seen in feedback was likely a stale gem installation."}
35
35
  {"id":"EV-124","title":"Add first/last and keys/values method swap pairs","description":"Add orthogonal method swap pairs: first↔last (Array endpoints), keys↔values (Hash extraction). Register in the collection method swap operator.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:19.366299705+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T13:11:43.391612732+07:00","dependencies":[{"issue_id":"EV-124","depends_on_id":"EV-96","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
36
36
  {"id":"EV-125","title":"Add RSpec suggestion templates for super mutations","description":"Add concrete RSpec suggestion templates for survived super mutations to the SuggestionReporter.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:20.933060824+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T14:36:21.649380826+07:00","dependencies":[{"issue_id":"EV-125","depends_on_id":"EV-112","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
37
- {"id":"EV-126","title":"Implement hook registry module","description":"Create Evilution::Hooks::Registry that stores and dispatches hook callbacks. Support registering hooks by name, running hooks with context data, and error isolation (one failing hook shouldn't crash the run).","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:21.393514581+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:21.393514581+07:00","dependencies":[{"issue_id":"EV-126","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
37
+ {"id":"EV-126","title":"Implement hook registry module","description":"Create Evilution::Hooks::Registry that stores and dispatches hook callbacks. Support registering hooks by name, running hooks with context data, and error isolation (one failing hook shouldn't crash the run).","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:21.393514581+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T22:54:34.384022121+07:00","dependencies":[{"issue_id":"EV-126","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
38
38
  {"id":"EV-127","title":"Implement ensure clause removal mutator","description":"Create a mutator that removes ensure blocks from begin/ensure expressions. Tests whether cleanup code is actually necessary. Prism ensure_node.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:27.334061542+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T10:37:25.904594009+07:00","dependencies":[{"issue_id":"EV-127","depends_on_id":"EV-89","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
39
39
  {"id":"EV-128","title":"Visual progress bar for TTY execution","description":"Add a visual TTY progress bar during mutation testing showing mutation count, kills, elapsed time, and ETA. Mutant has had this since v0.14.2. Important for user experience during long-running mutation testing sessions.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:30.526136549+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:30.526136549+07:00"}
40
40
  {"id":"EV-129","title":"Add bang vs non-bang method mutations","description":"Create mutations that swap bang methods with their non-bang equivalents and vice versa (e.g., save! ↔ save, sort! ↔ sort, map! ↔ map, uniq! ↔ uniq). Tests whether the in-place vs copy semantics matter.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:30.959721048+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T13:16:36.661424217+07:00","dependencies":[{"issue_id":"EV-129","depends_on_id":"EV-100","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
41
41
  {"id":"EV-13","title":"Enable true per-mutation isolation with temp file copies","description":"Replace direct file writes with temp-dir + $LOAD_PATH isolation so multiple workers can mutate the same file in parallel. Remove per-file grouping from Pool#partition.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-05T13:42:04.711580397+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T13:44:34.779951959+07:00","closed_at":"2026-03-05T13:44:34.779951959+07:00","close_reason":"Implemented temp file isolation via $LOAD_PATH and round-robin partition"}
42
- {"id":"EV-130","title":"Add worker_process_start hook point","description":"Fire the worker_process_start hook when a parallel worker process starts (after fork). This is the most important hook for Rails database isolation — users need to re-establish database connections after fork.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:31.360903708+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:31.360903708+07:00","dependencies":[{"issue_id":"EV-130","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
42
+ {"id":"EV-130","title":"Add worker_process_start hook point","description":"Fire the worker_process_start hook when a parallel worker process starts (after fork). This is the most important hook for Rails database isolation — users need to re-establish database connections after fork.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:31.360903708+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T23:07:00.267178228+07:00","dependencies":[{"issue_id":"EV-130","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
43
43
  {"id":"EV-131","title":"Epic: Index access operator mutations ([] / []=)","description":"Add mutations for index access operators. Mutant mutates [] to fetch/dig and []=. These surface real semantic differences (e.g., KeyError vs nil on missing key). Specifically called out in feedback as important.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:33.13561849+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:33.13561849+07:00"}
44
44
  {"id":"EV-132","title":"Add RSpec suggestion templates for exception handling mutations","description":"Add concrete RSpec suggestion templates for survived exception handling mutations to the SuggestionReporter.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:38.53663204+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T10:48:10.93836753+07:00","dependencies":[{"issue_id":"EV-132","depends_on_id":"EV-89","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
45
45
  {"id":"EV-133","title":"Implement TTY progress bar renderer","description":"Create a progress bar component that renders to TTY showing: [=====> ] 45/100 mutations | 38 killed | 2 survived | 00:23 elapsed | ~00:28 remaining. Detect TTY vs piped output and only show in TTY mode.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:40.177839247+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T15:05:19.015910008+07:00","dependencies":[{"issue_id":"EV-133","depends_on_id":"EV-128","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
46
- {"id":"EV-134","title":"Implement [] to fetch mutation","description":"Create a mutator that replaces hash/array [] access with .fetch(). This surfaces whether code handles missing keys properly. The [] vs fetch distinction was specifically called out in comparison feedback.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:42.107226515+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:42.107226515+07:00","dependencies":[{"issue_id":"EV-134","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
46
+ {"id":"EV-134","title":"Implement [] to fetch mutation","description":"Create a mutator that replaces hash/array [] access with .fetch(). This surfaces whether code handles missing keys properly. The [] vs fetch distinction was specifically called out in comparison feedback.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:42.107226515+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T20:25:10.80923459+07:00","dependencies":[{"issue_id":"EV-134","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
47
47
  {"id":"EV-135","title":"Add enumerable reduction method swaps","description":"Add method swap mutations for enumerable reductions: sum↔inject, count↔length↔size, detect↔find, select↔filter, collect↔map. Some may already exist; add the missing ones.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:42.397830812+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T13:31:17.723196272+07:00","dependencies":[{"issue_id":"EV-135","depends_on_id":"EV-100","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
48
- {"id":"EV-136","title":"Add mutation_insert_pre/post hook points","description":"Fire hooks before and after each mutation is inserted into source. Provides the mutation details (operator, location, original/mutated code) to the hook callback.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:44.656504163+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:44.656504163+07:00","dependencies":[{"issue_id":"EV-136","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
48
+ {"id":"EV-136","title":"Add mutation_insert_pre/post hook points","description":"Fire hooks before and after each mutation is inserted into source. Provides the mutation details (operator, location, original/mutated code) to the hook callback.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:44.656504163+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T00:28:05.530739933+07:00","dependencies":[{"issue_id":"EV-136","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
49
49
  {"id":"EV-137","title":"Integrate progress bar with Runner","description":"Wire the progress bar into Runner so it updates after each mutation result. Support both sequential and parallel execution modes. Ensure it doesn't interfere with verbose output.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:50.780537596+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T19:24:43.716640415+07:00","dependencies":[{"issue_id":"EV-137","depends_on_id":"EV-128","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
50
- {"id":"EV-138","title":"Implement [] to dig mutation","description":"Create a mutator that replaces nested [] access with .dig(). Tests whether nil propagation through nested access is handled.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:50.859337664+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:50.859337664+07:00","dependencies":[{"issue_id":"EV-138","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
50
+ {"id":"EV-138","title":"Implement [] to dig mutation","description":"Create a mutator that replaces nested [] access with .dig(). Tests whether nil propagation through nested access is handled.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:50.859337664+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T20:29:30.381299754+07:00","dependencies":[{"issue_id":"EV-138","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
51
51
  {"id":"EV-139","title":"Add to_s/to_i/to_f/to_a/to_h conversion method swaps","description":"Add mutations that swap type conversion methods with each other (e.g., to_s↔to_i, to_a↔to_h). Tests whether the correct type conversion is used.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:52.403463105+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T13:44:39.03275591+07:00","dependencies":[{"issue_id":"EV-139","depends_on_id":"EV-100","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
52
52
  {"id":"EV-14","title":"Add changelog_uri to gemspec metadata","description":"The published gem on RubyGems is missing the Changelog link. Add changelog_uri to spec.metadata in evilution.gemspec pointing to CHANGELOG.md on master.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-06T11:46:12.648668594+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-06T11:47:41.23691915+07:00","closed_at":"2026-03-06T11:47:41.23691915+07:00","close_reason":"Added changelog_uri to gemspec"}
53
- {"id":"EV-140","title":"Add setup_integration_pre/post hook points","description":"Fire hooks before and after the test integration is set up. Allows custom environment configuration before tests run.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:53.595178841+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:17:53.595178841+07:00","dependencies":[{"issue_id":"EV-140","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
53
+ {"id":"EV-140","title":"Add setup_integration_pre/post hook points","description":"Fire hooks before and after the test integration is set up. Allows custom environment configuration before tests run.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:17:53.595178841+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T00:45:12.894118119+07:00","dependencies":[{"issue_id":"EV-140","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
54
54
  {"id":"EV-141","title":"Epic: Pattern matching mutations (case/in)","description":"Add mutation operators for Ruby 3.x+ pattern matching expressions (case/in). Mutant mutates guards, alternatives, and predicates in pattern matching. Important as pattern matching adoption grows.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:00.138078298+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:00.138078298+07:00"}
55
- {"id":"EV-142","title":"Implement []= removal mutation","description":"Create a mutator that removes []= (index assignment) statements. Tests whether the assignment is actually necessary.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:01.34824668+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:01.34824668+07:00","dependencies":[{"issue_id":"EV-142","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
55
+ {"id":"EV-142","title":"Implement []= removal mutation","description":"Create a mutator that removes []= (index assignment) statements. Tests whether the assignment is actually necessary.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:01.34824668+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T20:47:29.117134657+07:00","dependencies":[{"issue_id":"EV-142","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
56
56
  {"id":"EV-143","title":"Add --no-progress flag to disable progress bar","description":"Add CLI flag and config option to disable the progress bar for CI/piped environments where TTY detection might not work correctly.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:01.773000995+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T19:30:09.471623389+07:00","dependencies":[{"issue_id":"EV-143","depends_on_id":"EV-128","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
57
57
  {"id":"EV-144","title":"Add send vs public_send method swap","description":"Add a mutation that swaps send with public_send and vice versa. Tests whether the method visibility bypass is intentional.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:03.825429179+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T13:54:46.379099536+07:00","dependencies":[{"issue_id":"EV-144","depends_on_id":"EV-100","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
58
- {"id":"EV-145","title":"Add hooks configuration to .evilution.yml","description":"Allow hooks to be specified in .evilution.yml config file. Support both inline Ruby blocks and file paths to Ruby scripts. Example: hooks: { worker_process_start: 'config/evilution_hooks.rb' }.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:04.207200619+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:04.207200619+07:00","dependencies":[{"issue_id":"EV-145","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
59
- {"id":"EV-146","title":"Add RSpec suggestion templates for index access mutations","description":"Add concrete RSpec suggestion templates for survived index access mutations to the SuggestionReporter.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:11.229320728+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:11.229320728+07:00","dependencies":[{"issue_id":"EV-146","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
60
- {"id":"EV-147","title":"Implement pattern matching guard mutation","description":"Mutate guard clauses in pattern matching (in pattern if guard). Mutations: remove guard (always match), negate guard. Prism in_node with guard.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:12.368560689+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:12.368560689+07:00","dependencies":[{"issue_id":"EV-147","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
58
+ {"id":"EV-145","title":"Add hooks configuration to .evilution.yml","description":"Allow hooks to be specified in .evilution.yml config file. Support both inline Ruby blocks and file paths to Ruby scripts. Example: hooks: { worker_process_start: 'config/evilution_hooks.rb' }.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:04.207200619+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T00:57:29.643350761+07:00","dependencies":[{"issue_id":"EV-145","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
59
+ {"id":"EV-146","title":"Add RSpec suggestion templates for index access mutations","description":"Add concrete RSpec suggestion templates for survived index access mutations to the SuggestionReporter.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:11.229320728+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T21:10:50.330156341+07:00","dependencies":[{"issue_id":"EV-146","depends_on_id":"EV-131","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
60
+ {"id":"EV-147","title":"Implement pattern matching guard mutation","description":"Mutate guard clauses in pattern matching (in pattern if guard). Mutations: remove guard (always match), negate guard. Prism in_node with guard.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:12.368560689+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T10:27:48.916121107+07:00","dependencies":[{"issue_id":"EV-147","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
61
61
  {"id":"EV-148","title":"Session recording and history","description":"Store JSON results per run in .evilution/results/ with CLI commands to list, show, and garbage-collect past sessions. Mutant v1.0.0 has this. Enables trend analysis and regression detection across runs.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:13.658761141+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-28T01:20:56.545069251+07:00","closed_at":"2026-03-28T01:20:56.545069251+07:00","close_reason":"GH #294 closed by user"}
62
- {"id":"EV-149","title":"Add RSpec tests for hooks system","description":"Comprehensive test coverage for the hooks system: registration, dispatch, error isolation, configuration loading, all hook points firing at correct times.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:16.004153638+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:16.004153638+07:00","dependencies":[{"issue_id":"EV-149","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
62
+ {"id":"EV-149","title":"Add RSpec tests for hooks system","description":"Comprehensive test coverage for the hooks system: registration, dispatch, error isolation, configuration loading, all hook points firing at correct times.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:16.004153638+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T01:21:30.147192237+07:00","dependencies":[{"issue_id":"EV-149","depends_on_id":"EV-110","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
63
63
  {"id":"EV-15","title":"Add line-range targeting (file:line-line syntax)","description":"Parse line-range syntax in CLI, store in Config, filter subjects in Runner. GH #29.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-08T00:36:29.164188342+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-08T00:38:02.739833414+07:00","closed_at":"2026-03-08T00:38:02.739833414+07:00","close_reason":"Implemented line-range targeting in CLI, Config, and Runner with full test coverage"}
64
64
  {"id":"EV-150","title":"Implement session result storage","description":"After each mutation run (when --save-session is passed), save a JSON file to .evilution/results/<timestamp>-<hash>.json containing: version, timestamp, git context (branch, commit SHA), summary statistics, and survived mutation details.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:23.433170933+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-24T00:32:49.095176663+07:00","dependencies":[{"issue_id":"EV-150","depends_on_id":"EV-148","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
65
- {"id":"EV-151","title":"Implement pattern matching alternative mutation","description":"Mutate pattern alternatives (in pat1 | pat2). Mutations: remove each alternative individually, swap order. Prism alternation_pattern_node.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:23.447046732+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:23.447046732+07:00","dependencies":[{"issue_id":"EV-151","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
65
+ {"id":"EV-151","title":"Implement pattern matching alternative mutation","description":"Mutate pattern alternatives (in pat1 | pat2). Mutations: remove each alternative individually, swap order. Prism alternation_pattern_node.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:23.447046732+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T10:45:02.759245169+07:00","dependencies":[{"issue_id":"EV-151","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
66
66
  {"id":"EV-152","title":"Epic: Type-aware return value mutations","description":"Add mutations that replace return values with type-appropriate empty/zero values: Array→[], Hash→{}, String→\"\", Integer→0, Float→0.0, true→false. Mutant does this extensively; Evilution only does body→nil.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:23.546834696+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:23.546834696+07:00"}
67
67
  {"id":"EV-153","title":"Epic: Dynamic work allocation for parallel execution","description":"Replace static batch-based work distribution with a shared work queue for dynamic load balancing. Mutant uses a shared queue where workers pull the next mutation as they complete work. Evilution's current static batching can lead to uneven utilization when mutation execution times vary.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:26.946371704+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:26.946371704+07:00"}
68
68
  {"id":"EV-154","title":"Implement type-aware return mutations for collection types","description":"When a method body returns an Array literal or Hash literal, also generate mutations returning the empty version ([] or {}). Detect by return value node type.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:31.525962391+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:31.525962391+07:00","dependencies":[{"issue_id":"EV-154","depends_on_id":"EV-152","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
69
- {"id":"EV-155","title":"Implement pattern matching find/array pattern mutations","description":"Mutate find and array patterns: remove individual pattern elements, replace with wildcard (_). Prism find_pattern_node, array_pattern_node.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:33.136069529+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:33.136069529+07:00","dependencies":[{"issue_id":"EV-155","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
69
+ {"id":"EV-155","title":"Implement pattern matching find/array pattern mutations","description":"Mutate find and array patterns: remove individual pattern elements, replace with wildcard (_). Prism find_pattern_node, array_pattern_node.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:33.136069529+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T10:49:55.941063007+07:00","dependencies":[{"issue_id":"EV-155","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
70
70
  {"id":"EV-156","title":"Add 'evilution session list' command","description":"Add a CLI subcommand that lists past session results with date, target, mutation count, score, and duration. Support --limit and --since filters.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:35.453549801+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-24T09:01:22.324682217+07:00","dependencies":[{"issue_id":"EV-156","depends_on_id":"EV-148","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
71
71
  {"id":"EV-157","title":"Implement shared work queue with pipe-based IPC","description":"Create a work queue that distributes mutations to worker processes dynamically. Use pipe-based IPC (or DRb) so workers pull the next mutation when ready, rather than receiving a pre-assigned batch.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:35.963687077+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:35.963687077+07:00","dependencies":[{"issue_id":"EV-157","depends_on_id":"EV-153","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
72
72
  {"id":"EV-158","title":"Implement type-aware return mutations for scalar types","description":"When a method body returns a String, Integer, or Float literal, also generate mutations returning the zero/empty value ('', 0, 0.0). Detect by return value node type.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:43.59763546+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:43.59763546+07:00","dependencies":[{"issue_id":"EV-158","depends_on_id":"EV-152","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
73
- {"id":"EV-159","title":"Add RSpec suggestion templates for pattern matching mutations","description":"Add concrete RSpec suggestion templates for survived pattern matching mutations.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:45.083995741+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:45.083995741+07:00","dependencies":[{"issue_id":"EV-159","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
73
+ {"id":"EV-159","title":"Add RSpec suggestion templates for pattern matching mutations","description":"Add concrete RSpec suggestion templates for survived pattern matching mutations.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:45.083995741+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T11:33:37.260966394+07:00","dependencies":[{"issue_id":"EV-159","depends_on_id":"EV-141","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
74
74
  {"id":"EV-16","title":"Remove file-discovery logic from Integration::RSpec (GH #33)","description":"Integration::RSpec has detect_test_files, spec_file_candidates, and fallback_spec_dir that guess which specs to run. With precise targeting, agents can pass spec files directly. Simplify or remove this guessing logic, possibly adding a --spec flag.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-08T19:17:27.268579626+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-09T18:13:46.899195957+07:00","closed_at":"2026-03-09T18:13:46.899195957+07:00","close_reason":"Completed via PR #38 (tracked as EV-17)"}
75
75
  {"id":"EV-160","title":"Add 'evilution session show' command","description":"Add a CLI subcommand that displays the full report for a specific past session, including all survived mutations with diffs.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:45.851348485+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-24T12:24:53.120184549+07:00","dependencies":[{"issue_id":"EV-160","depends_on_id":"EV-148","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
76
76
  {"id":"EV-161","title":"Add work-stealing for idle workers","description":"When a worker's local queue is empty, allow it to pull work from the shared queue immediately rather than waiting. Track per-worker completion rates for monitoring.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:47.426119432+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:47.426119432+07:00","dependencies":[{"issue_id":"EV-161","depends_on_id":"EV-153","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
@@ -78,17 +78,17 @@
78
78
  {"id":"EV-163","title":"Add 'evilution session gc' command","description":"Add a CLI subcommand to garbage-collect old session results. Support --older-than flag (e.g., --older-than 30d). Default to keeping all results.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:56.398453511+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-24T13:45:03.75407328+07:00","dependencies":[{"issue_id":"EV-163","depends_on_id":"EV-148","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
79
79
  {"id":"EV-164","title":"Epic: AST pattern language for ignore_patterns","description":"Implement a pattern language for ignore_patterns configuration, similar to Mutant's (e.g., send{selector=log}, send{receiver=send{selector=logger}}). Allows precise, semantic exclusion of mutations on logging, debugging, or infrastructure code without requiring file/line targeting.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:56.583664516+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:56.583664516+07:00"}
80
80
  {"id":"EV-165","title":"Update Parallel::Pool to use dynamic queue","description":"Refactor Parallel::Pool to use the new dynamic work queue instead of static batch distribution. Maintain backward compatibility with the existing API (results should be identical, just better distributed).","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:18:57.644578151+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:18:57.644578151+07:00","dependencies":[{"issue_id":"EV-165","depends_on_id":"EV-153","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
81
- {"id":"EV-166","title":"Design AST pattern language syntax","description":"Design the pattern matching DSL syntax for ignore_patterns. Support at minimum: node type matching, attribute matching (selector, receiver), nested patterns, and wildcards. Document the syntax with examples.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:04.486430975+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:04.486430975+07:00","dependencies":[{"issue_id":"EV-166","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
81
+ {"id":"EV-166","title":"Design AST pattern language syntax","description":"Design the pattern matching DSL syntax for ignore_patterns. Support at minimum: node type matching, attribute matching (selector, receiver), nested patterns, and wildcards. Document the syntax with examples.","design":"See docs/ast_pattern_syntax.md for full syntax spec. Key decisions: Prism-native node type names, unquoted attribute values, alternatives with |, nested patterns for structural matching, _ and ** wildcards, negation with !, AND across attributes / OR within values. Grammar in EBNF included.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:04.486430975+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T11:45:10.017653608+07:00","dependencies":[{"issue_id":"EV-166","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
82
82
  {"id":"EV-167","title":"Add efficiency metrics to summary output","description":"Add killtime/runtime efficiency percentage and mutations/second rate to the summary output. Mutant reports these. Helps users understand mutation testing overhead and optimize configuration. Calculate: efficiency = (time spent in test execution / total runtime) * 100%, rate = total_mutations / total_seconds.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:10.074542778+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:10.074542778+07:00"}
83
83
  {"id":"EV-168","title":"Add parallel execution metrics","description":"Track and report per-worker mutation counts, idle time, and utilization percentage. Include in verbose output to help users tune their --jobs setting.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:11.84738201+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:11.84738201+07:00","dependencies":[{"issue_id":"EV-168","depends_on_id":"EV-153","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
84
- {"id":"EV-169","title":"Implement AST pattern parser","description":"Parse the pattern language strings into a matcher object that can be evaluated against Prism AST nodes. Support: node type (send, lvar), attribute filters ({selector=log}), nested matchers.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:15.548202079+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:15.548202079+07:00","dependencies":[{"issue_id":"EV-169","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
84
+ {"id":"EV-169","title":"Implement AST pattern parser","description":"Parse the pattern language strings into a matcher object that can be evaluated against Prism AST nodes. Support: node type (send, lvar), attribute filters ({selector=log}), nested matchers.","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:15.548202079+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T12:04:08.595004056+07:00","dependencies":[{"issue_id":"EV-169","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
85
85
  {"id":"EV-17","title":"Remove file-discovery logic from Integration::RSpec","description":"Add --spec flag, simplify RSpec integration by removing detect_test_files heuristics. GH #33","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-09T00:41:29.880486333+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-09T00:43:21.229172712+07:00","closed_at":"2026-03-09T00:43:21.229172712+07:00","close_reason":"Removed ~60 lines of file-discovery logic from Integration::RSpec, added --spec CLI flag, spec_files config, and wired through Runner"}
86
86
  {"id":"EV-170","title":"Environment diagnostic commands","description":"Add diagnostic/inspection commands similar to Mutant's environment tools. Helps users understand what Evilution sees before running a full mutation test.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:19.537979519+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:19.537979519+07:00"}
87
87
  {"id":"EV-171","title":"Integrate AST pattern filter with mutation generation","description":"Before generating mutations, check if the target AST node matches any ignore_pattern. Skip matching nodes. Report skipped count in summary.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:24.909224128+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:24.909224128+07:00","dependencies":[{"issue_id":"EV-171","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
88
88
  {"id":"EV-172","title":"Support # evilution:disable source comments","description":"Allow users to add # evilution:disable comments in source code to skip mutations for specific methods or lines. Mutant supports # mutant:disable for per-method opt-out. Should support: method-level (comment before def), line-level (inline comment), and block-level (disable/enable pairs).","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:25.957514857+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:25.957514857+07:00"}
89
89
  {"id":"EV-173","title":"Add 'evilution environment show' command","description":"Display the resolved configuration: loaded config file, CLI overrides, effective values for all settings (jobs, target, integration, etc.).","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:30.633766429+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:30.633766429+07:00","dependencies":[{"issue_id":"EV-173","depends_on_id":"EV-170","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
90
90
  {"id":"EV-174","title":"Implement comment-based disable detection","description":"Scan source files for # evilution:disable comments. Support three forms: method-level (comment on line before def), line-level (inline # evilution:disable), and range (# evilution:disable / # evilution:enable pairs). Store disabled ranges per file.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:35.174813233+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:35.174813233+07:00","dependencies":[{"issue_id":"EV-174","depends_on_id":"EV-172","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
91
- {"id":"EV-175","title":"Add ignore_patterns to .evilution.yml config","description":"Add ignore_patterns as a config key in .evilution.yml. Accept an array of pattern strings. Example: ignore_patterns: ['send{selector=log}', 'send{receiver=send{selector=logger}}'].","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:35.758598835+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:35.758598835+07:00","dependencies":[{"issue_id":"EV-175","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
91
+ {"id":"EV-175","title":"Add ignore_patterns to .evilution.yml config","description":"Add ignore_patterns as a config key in .evilution.yml. Accept an array of pattern strings. Example: ignore_patterns: ['send{selector=log}', 'send{receiver=send{selector=logger}}'].","status":"in_progress","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:35.758598835+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-30T13:57:11.398457981+07:00","dependencies":[{"issue_id":"EV-175","depends_on_id":"EV-164","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
92
92
  {"id":"EV-176","title":"Add 'evilution subjects' command","description":"List all subjects (classes/methods) that would be mutated for the given target. Shows what mutations would be generated without actually running tests.","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:44.530116644+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:44.530116644+07:00","dependencies":[{"issue_id":"EV-176","depends_on_id":"EV-170","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
93
93
  {"id":"EV-177","title":"Filter mutations against disabled ranges","description":"Before generating mutations, check if the target source location falls within a disabled range. Skip mutation generation for disabled locations. Report skipped count in summary.","status":"open","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:46.006587202+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:46.006587202+07:00","dependencies":[{"issue_id":"EV-177","depends_on_id":"EV-172","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
94
94
  {"id":"EV-178","title":"Epic: Rich subject expression language","description":"Implement a rich expression language for subject matching similar to Mutant's: namespace wildcards (Foo::Bar*), method-type selectors (Foo# for instance, Foo. for class methods), descendant matching (descendants:Foo). Currently Evilution supports method names and file paths but lacks wildcard and type selectors.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:19:48.801608223+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:19:48.801608223+07:00"}
@@ -144,6 +144,7 @@
144
144
  {"id":"EV-210","title":"Implement defined? check mutations","description":"Add mutation for defined?(expr) → true. Tests whether the defined? check is actually needed. Prism defined_node.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:23:22.128405637+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:23:22.128405637+07:00"}
145
145
  {"id":"EV-211","title":"Implement regex capture reference (, ) mutations","description":"Add mutations for numbered regex capture references ($1, $2, etc.). Mutations: swap capture numbers ($1↔$2), replace with nil. Prism numbered_reference_read_node.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:23:30.659728763+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:23:30.659728763+07:00"}
146
146
  {"id":"EV-212","title":"Implement self reference mutations","description":"Add mutations for self references. Mutations: remove self where it's used as an explicit receiver (self.foo → foo). Prism self_node.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-23T11:23:42.173027795+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-23T11:23:42.173027795+07:00"}
147
+ {"id":"EV-213","title":"Critical: SourceSurgeon crashes on multi-byte UTF-8 source files","description":"SourceSurgeon.apply uses String#[]= with Prism byte offsets, but Ruby String#[]= interprets indices as character offsets for UTF-8 strings. This causes IndexError on files containing multi-byte characters (e.g. Cyrillic, CJK, emoji). Fix: use source.b to force ASCII-8BIT before byte-offset operations, or use byteslice-based replacement. Reported by end user on 2026-03-29 against v0.16.0.","status":"open","priority":0,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-29T23:19:47.803570297+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-29T23:19:47.803570297+07:00"}
147
148
  {"id":"EV-22","title":"Add --fail-fast flag with survivor threshold","description":"Add --fail-fast [N] CLI flag and fail_fast config option to stop mutation testing after N surviving mutants (default N=1 when flag given without value). Agents fix gaps iteratively, so discovering all survivors upfront is wasted work. A threshold gives control over the speed/thoroughness tradeoff: --fail-fast 1 for quick checks, --fail-fast 5 for CI gates, omit for full scans. Runner#call should stop running mutations and return a partial Summary once the threshold is reached. JSON output should include a 'truncated: true' field when fail-fast triggered.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:28.018733235+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:11:01.311500554+07:00","closed_at":"2026-03-16T10:11:01.311500554+07:00","close_reason":"Already merged and released in v0.4.0"}
148
149
  {"id":"EV-23","title":"Per-mutation spec targeting","description":"Instead of running the full spec suite for every mutation, map each mutated source file to its relevant spec file(s) using convention-based resolution (e.g. lib/foo/bar.rb -> spec/foo/bar_spec.rb) and only run those. This dramatically reduces per-mutation test time. Depends on convention-based spec file resolution being implemented first.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:28.98620973+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T14:49:13.616876819+07:00","closed_at":"2026-03-16T14:49:13.616876819+07:00","close_reason":"Fixed and merged","dependencies":[{"issue_id":"EV-23","depends_on_id":"EV-34","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
149
150
  {"id":"EV-24","title":"Epic: JSON Output Improvements","description":"Make JSON output fully machine-parseable in all scenarios, including errors. Add diagnostic fields that help agents debug failures.","status":"closed","priority":1,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:37.450686472+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T11:15:24.900944562+07:00","closed_at":"2026-03-16T11:15:24.900944562+07:00","close_reason":"All children complete: structured errors, test_command in JSON, noise suppression","dependencies":[{"issue_id":"EV-24","depends_on_id":"EV-25","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-26","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-40","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.17.0] - 2026-03-30
4
+
5
+ ### Added
6
+
7
+ - **Hooks system** — lifecycle hooks for mutation testing pipeline: `worker_process_start` for parallel workers, `mutation_insert_pre`/`post` for RSpec integration, `setup_integration_pre`/`post` for test setup; hook registry with registration, dispatch, and error isolation; `.evilution.yml` hooks configuration (#265, #272, #277, #282, #286, #290)
8
+ - **Index access mutation operators** — `IndexToFetch` replaces `[]` with `.fetch()`, `IndexToDig` replaces `[]` chains with `.dig()`, `IndexAssignmentRemoval` removes `[]=` assignments (#280, #283, #288)
9
+ - **Pattern matching mutation operators** — `PatternMatchingGuard` removes or negates guard clauses in `case/in` patterns; `PatternMatchingAlternative` removes, reorders alternatives in `pat1 | pat2` patterns; `PatternMatchingArray` removes or wildcards elements in array and find patterns (#293, #297, #301)
10
+ - **AST pattern language** — custom DSL for `ignore_patterns` configuration: node type matching, attribute constraints, nested patterns, wildcards (`_`, `*`, `**`), negation, and alternatives; recursive descent parser producing matcher objects; syntax spec in `docs/ast_pattern_syntax.md` (#312, #315)
11
+ - **AST pattern filter integration** — mutations matching `ignore_patterns` are skipped during generation; skipped count reported in CLI, JSON, HTML reporters and session data (#317)
12
+ - **`ignore_patterns` config** — new `.evilution.yml` key accepting an array of AST pattern strings to exclude mutations on logging/debug/infrastructure code (#320)
13
+ - **Suggestion templates** — concrete RSpec suggestions for index access mutations (`index_to_fetch`, `index_to_dig`, `index_assignment_removal`) and pattern matching mutations (`pattern_matching_guard`, `pattern_matching_alternative`, `pattern_matching_array`) (#292, #305)
14
+
15
+ ### Changed
16
+
17
+ - **Operator count** — 52 operators (up from 46), with new index access, pattern matching, and hooks integration
18
+ - **Hooks wiring** — Runner passes hooks through to Fork isolator, RSpec integration, and Parallel::Pool; comprehensive test coverage for hooks lifecycle (#295)
19
+
3
20
  ## [0.16.1] - 2026-03-30
4
21
 
5
22
  ### Fixed
@@ -0,0 +1,210 @@
1
+ # AST Pattern Language Syntax
2
+
3
+ Design document for the `ignore_patterns` configuration in Evilution.
4
+
5
+ ## Overview
6
+
7
+ The AST pattern language allows precise, semantic exclusion of mutations. Instead of
8
+ file/line targeting, patterns match Prism AST node structures so that mutations on
9
+ logging, debugging, or infrastructure code can be suppressed declaratively.
10
+
11
+ Patterns are specified in `.evilution.yml` under the `ignore_patterns` key:
12
+
13
+ ```yaml
14
+ ignore_patterns:
15
+ - "call{name=log}"
16
+ - "call{receiver=call{name=logger}}"
17
+ - "call{name=puts|warn|pp}"
18
+ ```
19
+
20
+ ## Syntax
21
+
22
+ ### Node Type Matching
23
+
24
+ A pattern starts with a **node type** name. Node types are lowercased, underscore-separated
25
+ names derived from Prism node classes with the `Node` suffix stripped:
26
+
27
+ | Pattern type | Prism class |
28
+ |-----------------|---------------------------|
29
+ | `call` | `Prism::CallNode` |
30
+ | `string` | `Prism::StringNode` |
31
+ | `integer` | `Prism::IntegerNode` |
32
+ | `if` | `Prism::IfNode` |
33
+ | `def` | `Prism::DefNode` |
34
+ | `constant_read` | `Prism::ConstantReadNode` |
35
+
36
+ A bare node type matches any node of that type:
37
+
38
+ ```
39
+ call # matches any CallNode
40
+ def # matches any DefNode
41
+ ```
42
+
43
+ ### Attribute Matching
44
+
45
+ Curly braces after the node type specify **attribute constraints**. Attributes
46
+ correspond to Prism node accessor methods:
47
+
48
+ ```
49
+ call{name=log} # CallNode where name == :log
50
+ def{name=initialize} # DefNode where name == :initialize
51
+ constant_read{name=Logger} # ConstantReadNode where name == :Logger
52
+ ```
53
+
54
+ Multiple attributes are separated by commas (AND logic):
55
+
56
+ ```
57
+ call{name=info, receiver=call{name=logger}}
58
+ ```
59
+
60
+ ### Attribute Values
61
+
62
+ **Symbol/string values** are unquoted. The value is matched against the node attribute
63
+ after calling `.to_s` on both sides:
64
+
65
+ ```
66
+ call{name=log} # matches when node.name.to_s == "log"
67
+ ```
68
+
69
+ **Alternatives** use `|` (OR logic) within a single attribute value:
70
+
71
+ ```
72
+ call{name=debug|info|warn|error|fatal} # matches any log level
73
+ call{name=puts|p|pp|print} # matches common debug output
74
+ ```
75
+
76
+ **Wildcard** `*` matches any value (useful for requiring an attribute exists):
77
+
78
+ ```
79
+ call{receiver=*} # matches any call with a receiver (not bare function calls)
80
+ ```
81
+
82
+ ### Nested Patterns
83
+
84
+ Attribute values can be **nested patterns**, enabling structural matching:
85
+
86
+ ```
87
+ call{receiver=call{name=logger}}
88
+ # Matches: logger.info("msg"), logger.debug(data)
89
+ # Does NOT match: info("msg"), foo.info("msg")
90
+ ```
91
+
92
+ Nesting can go arbitrarily deep:
93
+
94
+ ```
95
+ call{receiver=call{receiver=constant_read{name=Rails}, name=logger}}
96
+ # Matches: Rails.logger.info("msg")
97
+ ```
98
+
99
+ ### Wildcards
100
+
101
+ The `_` pattern matches **any single node**:
102
+
103
+ ```
104
+ call{receiver=_} # any call with any receiver
105
+ call{name=log, receiver=_} # .log() called on anything
106
+ ```
107
+
108
+ The `**` pattern matches **any subtree** (zero or more levels):
109
+
110
+ ```
111
+ call{receiver=**, name=log}
112
+ # Matches: log(), foo.log(), foo.bar.log(), a.b.c.log()
113
+ ```
114
+
115
+ ### Negation
116
+
117
+ Prefix `!` negates a pattern or value:
118
+
119
+ ```
120
+ call{name=!log} # any call except log
121
+ call{receiver=!call{name=logger}} # calls whose receiver is not logger
122
+ ```
123
+
124
+ ## YAML Configuration
125
+
126
+ ```yaml
127
+ ignore_patterns:
128
+ # Suppress mutations on all logging calls
129
+ - "call{name=debug|info|warn|error|fatal, receiver=call{name=logger}}"
130
+
131
+ # Suppress mutations on debug output
132
+ - "call{name=puts|p|pp|print}"
133
+
134
+ # Suppress mutations on Rails logger at any depth
135
+ - "call{receiver=call{receiver=constant_read{name=Rails}, name=logger}}"
136
+
137
+ # Suppress mutations inside any method named `to_s`
138
+ - "def{name=to_s}"
139
+
140
+ # Suppress mutations on constant `ENV` reads
141
+ - "call{receiver=constant_read{name=ENV}}"
142
+ ```
143
+
144
+ ## Pattern Matching Semantics
145
+
146
+ 1. Each pattern is tested against the **mutation's AST node** and its **ancestors**.
147
+ 2. A mutation is ignored if **any** pattern in `ignore_patterns` matches.
148
+ 3. For `def` patterns, the match is against the enclosing method node, not the
149
+ mutated node itself. This allows ignoring all mutations within a method.
150
+ 4. Attribute matching is **exact** (after `.to_s` coercion) unless alternatives (`|`)
151
+ or wildcards (`*`, `_`, `**`) are used.
152
+ 5. Unspecified attributes are unconstrained (implicit wildcard).
153
+
154
+ ## Grammar (EBNF)
155
+
156
+ ```ebnf
157
+ pattern = node_type [ "{" attributes "}" ]
158
+ node_type = identifier | "_" | "**"
159
+ attributes = attribute { "," attribute }
160
+ attribute = identifier "=" value
161
+ value = "!" value
162
+ | nested_pattern
163
+ | alternatives
164
+ | "*"
165
+ nested_pattern = node_type "{" attributes "}"
166
+ alternatives = atom { "|" atom }
167
+ atom = identifier | "*"
168
+ identifier = [a-zA-Z_] [a-zA-Z0-9_]*
169
+ ```
170
+
171
+ A bare `identifier` without `{` is parsed as a scalar value (matched via `.to_s`).
172
+ An `identifier` followed by `{` is parsed as a nested pattern.
173
+
174
+ ## Examples
175
+
176
+ | Pattern | Matches | Does NOT match |
177
+ |---------|---------|----------------|
178
+ | `call` | Any method call | Literals, assignments |
179
+ | `call{name=log}` | `log()`, `x.log()` | `logger()`, `x.debug()` |
180
+ | `call{name=debug\|info}` | `debug()`, `x.info()` | `warn()`, `error()` |
181
+ | `call{receiver=call{name=logger}}` | `logger.info()` | `info()`, `foo.info()` |
182
+ | `call{receiver=_}` | `x.foo()`, `obj.bar()` | `foo()` (no receiver) |
183
+ | `call{receiver=**}` | `foo()`, `x.foo()`, `a.b.foo()` | *(matches all)* |
184
+ | `def{name=to_s}` | `def to_s; ... end` | `def to_str; ... end` |
185
+ | `call{name=!log}` | `debug()`, `info()` | `log()` |
186
+
187
+ ## Design Decisions
188
+
189
+ 1. **Prism-native naming**: Node types map directly to Prism class names (lowercased,
190
+ `Node` suffix stripped). No abstraction layer — users who inspect AST output can
191
+ write patterns immediately.
192
+
193
+ 2. **Unquoted values**: Attribute values don't require quotes. This keeps YAML clean
194
+ and avoids escaping issues. The tradeoff is that values cannot contain `{`, `}`,
195
+ `,`, `=`, `|`, or `!` — these are reserved syntax characters.
196
+
197
+ 3. **Implicit wildcards for unspecified attributes**: `call{name=log}` matches
198
+ regardless of receiver, arguments, etc. Only specified attributes constrain the
199
+ match.
200
+
201
+ 4. **OR within attributes, AND across attributes**: `name=a|b` is OR;
202
+ `name=a, receiver=x` is AND. This covers the common cases without needing
203
+ explicit boolean operators.
204
+
205
+ 5. **`**` for deep matching**: Inspired by glob syntax. Enables "match .log() at any
206
+ call depth" without enumerating receiver chains.
207
+
208
+ 6. **No regex support**: Exact match + alternatives covers the practical use cases.
209
+ Regex would complicate parsing and make patterns harder to read. Can be added later
210
+ if needed.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "parser"
4
+
5
+ class Evilution::AST::Pattern::Filter
6
+ attr_reader :skipped_count
7
+
8
+ def initialize(patterns)
9
+ @matchers = patterns.map { |p| Evilution::AST::Pattern::Parser.new(p).parse }
10
+ @skipped_count = 0
11
+ end
12
+
13
+ def skip?(node)
14
+ if @matchers.any? { |m| m.match?(node) }
15
+ @skipped_count += 1
16
+ true
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ def reset_count!
23
+ @skipped_count = 0
24
+ end
25
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../pattern"
4
+
5
+ module Evilution::AST::Pattern
6
+ class NodeMatcher
7
+ attr_reader :node_type, :attributes
8
+
9
+ def initialize(node_type, attributes)
10
+ @node_type = node_type
11
+ @prism_class = resolve_prism_class(node_type)
12
+ @attributes = attributes
13
+ end
14
+
15
+ def match?(node)
16
+ return false unless node.is_a?(@prism_class)
17
+
18
+ @attributes.all? { |attr_name, value_matcher| match_attribute?(node, attr_name, value_matcher) }
19
+ end
20
+
21
+ private
22
+
23
+ def resolve_prism_class(type_name)
24
+ class_name = "#{type_name.split("_").map(&:capitalize).join}Node"
25
+ Prism.const_get(class_name)
26
+ rescue NameError
27
+ raise Evilution::ConfigError, "unknown AST node type: #{type_name}"
28
+ end
29
+
30
+ def match_attribute?(node, attr_name, value_matcher)
31
+ return false unless node.respond_to?(attr_name)
32
+
33
+ attr_value = node.public_send(attr_name)
34
+
35
+ if value_matcher.respond_to?(:match?)
36
+ value_matcher.match?(attr_value)
37
+ else
38
+ value_matcher.match_value?(attr_value)
39
+ end
40
+ end
41
+ end
42
+
43
+ class AnyNodeMatcher
44
+ def match?(node)
45
+ !node.nil?
46
+ end
47
+ end
48
+
49
+ class DeepWildcardMatcher
50
+ def match?(_node)
51
+ true
52
+ end
53
+ end
54
+
55
+ class ValueMatcher
56
+ def initialize(value)
57
+ @value = value
58
+ end
59
+
60
+ def match_value?(actual)
61
+ actual.to_s == @value
62
+ end
63
+
64
+ def match?(node)
65
+ match_value?(node)
66
+ end
67
+ end
68
+
69
+ class AlternativesMatcher
70
+ def initialize(values)
71
+ @values = values
72
+ end
73
+
74
+ def match_value?(actual)
75
+ actual_str = actual.to_s
76
+ @values.any? { |v| actual_str == v }
77
+ end
78
+
79
+ def match?(node)
80
+ match_value?(node)
81
+ end
82
+ end
83
+
84
+ class WildcardValueMatcher
85
+ def match_value?(_actual)
86
+ true
87
+ end
88
+
89
+ def match?(node)
90
+ !node.nil?
91
+ end
92
+ end
93
+
94
+ class NegationMatcher
95
+ def initialize(inner)
96
+ @inner = inner
97
+ end
98
+
99
+ def match_value?(actual)
100
+ !@inner.match_value?(actual)
101
+ end
102
+
103
+ def match?(node)
104
+ !@inner.match?(node)
105
+ end
106
+ end
107
+ end