henitai 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +21 -0
- data/README.md +182 -0
- data/assets/schema/henitai.schema.json +123 -0
- data/exe/henitai +6 -0
- data/lib/henitai/arid_node_filter.rb +97 -0
- data/lib/henitai/cli.rb +341 -0
- data/lib/henitai/configuration.rb +132 -0
- data/lib/henitai/configuration_validator.rb +293 -0
- data/lib/henitai/coverage_bootstrapper.rb +75 -0
- data/lib/henitai/coverage_formatter.rb +112 -0
- data/lib/henitai/equivalence_detector.rb +85 -0
- data/lib/henitai/execution_engine.rb +174 -0
- data/lib/henitai/git_diff_analyzer.rb +82 -0
- data/lib/henitai/integration.rb +417 -0
- data/lib/henitai/mutant/activator.rb +234 -0
- data/lib/henitai/mutant.rb +68 -0
- data/lib/henitai/mutant_generator.rb +158 -0
- data/lib/henitai/mutant_history_store.rb +279 -0
- data/lib/henitai/operator.rb +96 -0
- data/lib/henitai/operators/arithmetic_operator.rb +46 -0
- data/lib/henitai/operators/array_declaration.rb +52 -0
- data/lib/henitai/operators/assignment_expression.rb +78 -0
- data/lib/henitai/operators/block_statement.rb +31 -0
- data/lib/henitai/operators/boolean_literal.rb +70 -0
- data/lib/henitai/operators/conditional_expression.rb +184 -0
- data/lib/henitai/operators/equality_operator.rb +41 -0
- data/lib/henitai/operators/hash_literal.rb +66 -0
- data/lib/henitai/operators/logical_operator.rb +84 -0
- data/lib/henitai/operators/method_expression.rb +56 -0
- data/lib/henitai/operators/pattern_match.rb +66 -0
- data/lib/henitai/operators/range_literal.rb +40 -0
- data/lib/henitai/operators/return_value.rb +105 -0
- data/lib/henitai/operators/safe_navigation.rb +34 -0
- data/lib/henitai/operators/string_literal.rb +64 -0
- data/lib/henitai/operators.rb +25 -0
- data/lib/henitai/parser_current.rb +7 -0
- data/lib/henitai/reporter.rb +432 -0
- data/lib/henitai/result.rb +170 -0
- data/lib/henitai/runner.rb +183 -0
- data/lib/henitai/sampling_strategy.rb +33 -0
- data/lib/henitai/scenario_execution_result.rb +71 -0
- data/lib/henitai/source_parser.rb +41 -0
- data/lib/henitai/static_filter.rb +186 -0
- data/lib/henitai/stillborn_filter.rb +34 -0
- data/lib/henitai/subject.rb +71 -0
- data/lib/henitai/subject_resolver.rb +232 -0
- data/lib/henitai/syntax_validator.rb +16 -0
- data/lib/henitai/test_prioritizer.rb +55 -0
- data/lib/henitai/unparse_helper.rb +24 -0
- data/lib/henitai/version.rb +5 -0
- data/lib/henitai/warning_silencer.rb +16 -0
- data/lib/henitai.rb +51 -0
- data/sig/configuration_validator.rbs +29 -0
- data/sig/henitai.rbs +594 -0
- data/sig/unparser.rbs +3 -0
- metadata +153 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 586e777c1eb78b5345eedde4f010ab262fe1ced6e0ac3714e21dbd9aa7cc63d3
|
|
4
|
+
data.tar.gz: 7d6d5d3939777b28ab127133dc4f2d9663c29c812f6f4a77af4705a3f36ef973
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0fd445f2506b0dec762413e3e992af6b93797728c4253a86de2e1ce0f60fcd189ac2d2f342033ac8d16653064ce3fc3cd885c69ad34026e9781c60ca88d85de4
|
|
7
|
+
data.tar.gz: 9d00b7d9f3237b70460306aae62729d38e0142fbf7b954c2b4528feda3a4e596f08bd373c9ed448fd0ff12de7c5c1fb867c3ea9f8cf8582792a9c3a454d2bfd5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial gem scaffold with Ruby 4.0.2 support
|
|
12
|
+
- Dev Container configuration (official `ruby:4.0.2-alpine` base image, Codex CLI preinstalled)
|
|
13
|
+
- CI pipeline (RuboCop + RSpec + incremental mutation testing on PRs)
|
|
14
|
+
- `.henitai.yml` configuration schema
|
|
15
|
+
- Module structure: `Configuration`, `Subject`, `Mutant`, `Operator`, `Runner`, `Reporter`, `Integration`, `Result`
|
|
16
|
+
- CLI critical path: `henitai run` now executes the full pipeline, supports `--since`, returns CI-friendly exit codes, and `henitai version` prints `Henitai::VERSION`
|
|
17
|
+
- RSpec per-test coverage output: `henitai/coverage_formatter` now writes `coverage/henitai_per_test.json`
|
|
18
|
+
|
|
19
|
+
[Unreleased]: https://github.com/martinotten/henitai/commits/main
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martin Otten
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# hen’itai
|
|
2
|
+
|
|
3
|
+
変異体 (へんいたい, hen’itai)
|
|
4
|
+
Pronunciation: he-n-i-ta-i (4 morae; no stress accent)
|
|
5
|
+
Meaning: mutant, mutated organism, or variant entity
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
A Ruby mutation testing framework
|
|
9
|
+
|
|
10
|
+
[](https://github.com/martinotten/henitai/actions/workflows/ci.yml)
|
|
11
|
+
[](https://badge.fury.io/rb/henitai)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Maturaty
|
|
16
|
+
|
|
17
|
+
- This is alpha software, there will be bugs
|
|
18
|
+
- Henitai tests itself
|
|
19
|
+
- Stryker Dashboard support is untested
|
|
20
|
+
- Minitest support is untested
|
|
21
|
+
- Lots of code has been carefully crafted by AI agents, not everything has been reviewed by humans (yet)
|
|
22
|
+
|
|
23
|
+
## What is mutation testing?
|
|
24
|
+
|
|
25
|
+
Mutation testing answers the question that code coverage cannot: **does your test suite actually verify the behaviour of your code?**
|
|
26
|
+
|
|
27
|
+
A mutation testing tool makes small, systematic changes — *mutants* — to your source code (e.g. replacing `>` with `>=`, removing a `return` statement, flipping a boolean) and then runs your tests. A mutant that causes at least one test to fail is *killed*. A mutant that passes all tests is *survived* — evidence that your tests are not covering that behaviour.
|
|
28
|
+
|
|
29
|
+
The ratio of killed mutants to total mutants is the **Mutation Score** (MS). A high mutation score is a strong quality signal.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Add to your `Gemfile`:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
gem "henitai", group: :development
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install globally:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
gem install henitai
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Requires Ruby 4.0.2+**
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
# Run mutation testing on the entire project
|
|
51
|
+
bundle exec henitai run
|
|
52
|
+
|
|
53
|
+
# Run only on subjects changed since main (CI-friendly)
|
|
54
|
+
bundle exec henitai run --since origin/main
|
|
55
|
+
|
|
56
|
+
# Run on a specific subject pattern
|
|
57
|
+
bundle exec henitai run 'MyClass#my_method'
|
|
58
|
+
bundle exec henitai run 'MyNamespace*'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Configuration lives in `.henitai.yml`:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
# yaml-language-server: $schema=./assets/schema/henitai.schema.json
|
|
65
|
+
integration:
|
|
66
|
+
name: rspec
|
|
67
|
+
|
|
68
|
+
includes:
|
|
69
|
+
- lib
|
|
70
|
+
|
|
71
|
+
mutation:
|
|
72
|
+
operators: light # light | full
|
|
73
|
+
timeout: 10.0
|
|
74
|
+
max_mutants_per_line: 1
|
|
75
|
+
max_flaky_retries: 3
|
|
76
|
+
sampling:
|
|
77
|
+
ratio: 0.05
|
|
78
|
+
strategy: stratified
|
|
79
|
+
reports_dir: reports
|
|
80
|
+
|
|
81
|
+
thresholds:
|
|
82
|
+
high: 80
|
|
83
|
+
low: 60
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Henitai warns on unknown config keys and aborts with `Henitai::ConfigurationError`
|
|
87
|
+
when a value is invalid.
|
|
88
|
+
|
|
89
|
+
CLI flags override the corresponding values from `.henitai.yml`.
|
|
90
|
+
|
|
91
|
+
Before mutation testing starts, Henitai checks whether the current coverage data
|
|
92
|
+
covers the configured source files. If not, Henitai runs the configured test
|
|
93
|
+
suite once to bootstrap a usable coverage baseline. If coverage is still
|
|
94
|
+
unavailable for the current sources, `henitai run` aborts with
|
|
95
|
+
`Henitai::CoverageError`.
|
|
96
|
+
|
|
97
|
+
Surviving mutants are retried up to `mutation.max_flaky_retries` times before
|
|
98
|
+
they are classified as survivors. The default retry budget is 3.
|
|
99
|
+
|
|
100
|
+
Per-test coverage reporting is currently wired through the RSpec child runner.
|
|
101
|
+
Minitest integration reuses the same selection and execution flow, but does not
|
|
102
|
+
yet enable the per-test coverage formatter.
|
|
103
|
+
|
|
104
|
+
By default, Henitai keeps child test output out of the live terminal. Each
|
|
105
|
+
baseline or mutant run writes captured stdout/stderr to `reports/mutation-logs/`
|
|
106
|
+
and the terminal only shows progress plus a concise summary. Pass
|
|
107
|
+
`--all-logs` (or `--verbose`) to print every captured child log.
|
|
108
|
+
|
|
109
|
+
`henitai version` prints the installed version. `henitai run` exits with `0`
|
|
110
|
+
when the mutation score meets the low threshold, `1` when it does not, and `2`
|
|
111
|
+
for framework errors.
|
|
112
|
+
|
|
113
|
+
The repository ships a JSON Schema at [`assets/schema/henitai.schema.json`](/workspaces/henitai/assets/schema/henitai.schema.json) for editor autocompletion.
|
|
114
|
+
|
|
115
|
+
## Operator sets
|
|
116
|
+
|
|
117
|
+
**Light** (default) — high-signal, low-noise operators covering the majority of real-world defects:
|
|
118
|
+
|
|
119
|
+
- `ArithmeticOperator` — `+` ↔ `-`, `*` ↔ `/`
|
|
120
|
+
- `EqualityOperator` — `==` ↔ `!=`, `>` ↔ `<`, etc.
|
|
121
|
+
- `LogicalOperator` — `&&` ↔ `||`
|
|
122
|
+
- `BooleanLiteral` — `true` ↔ `false`, `!expr`
|
|
123
|
+
- `ConditionalExpression` — remove branch bodies
|
|
124
|
+
- `StringLiteral` — empty string replacement
|
|
125
|
+
- `ReturnValue` — mutate return expressions
|
|
126
|
+
|
|
127
|
+
**Full** — adds lower-signal operators:
|
|
128
|
+
|
|
129
|
+
- `ArrayDeclaration`, `HashLiteral`, `RangeLiteral`
|
|
130
|
+
- `SafeNavigation` — `&.` → `.`
|
|
131
|
+
- `PatternMatch` — case/in arm removal
|
|
132
|
+
- `BlockStatement` — remove blocks
|
|
133
|
+
- `MethodExpression` — remove calls
|
|
134
|
+
- `AssignmentExpression` — mutate compound assignment
|
|
135
|
+
|
|
136
|
+
## Stryker Dashboard integration (untested)
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
# .henitai.yml
|
|
140
|
+
reporters:
|
|
141
|
+
- terminal
|
|
142
|
+
- html
|
|
143
|
+
- json
|
|
144
|
+
- dashboard
|
|
145
|
+
|
|
146
|
+
dashboard:
|
|
147
|
+
project: "github.com/your-org/your-repo"
|
|
148
|
+
base_url: "https://dashboard.stryker-mutator.io"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Set `STRYKER_DASHBOARD_API_KEY` in your CI environment to publish reports.
|
|
152
|
+
|
|
153
|
+
JSON reports are written to `reports/mutation-report.json` by default. Set
|
|
154
|
+
`reports_dir` to change the output directory.
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
git clone https://github.com/martinotten/henitai
|
|
160
|
+
cd henitai
|
|
161
|
+
bundle install
|
|
162
|
+
bundle exec rspec # run tests
|
|
163
|
+
bundle exec rubocop # lint
|
|
164
|
+
bundle exec henitai run # dogfood
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
A Dev Container configuration is included (`.devcontainer/`) for VS Code with the official `ruby:4.0.2-alpine` image and the Codex CLI preinstalled.
|
|
168
|
+
|
|
169
|
+
## Architecture
|
|
170
|
+
|
|
171
|
+
See [`docs/architecture/architecture.md`](docs/architecture/architecture.md) for the full design document, including:
|
|
172
|
+
|
|
173
|
+
- Phase-Gate pipeline (5 gates)
|
|
174
|
+
- AST-based operator implementation
|
|
175
|
+
- Fork isolation model
|
|
176
|
+
- Stryker JSON schema integration
|
|
177
|
+
- Architecture decisions in [`docs/architecture/adr/`](docs/architecture/adr/)
|
|
178
|
+
- Three-phase roadmap
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
[MIT License](LICENSE) — © 2026 Martin Otten
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/martinotten/henitai/assets/schema/henitai.schema.json",
|
|
4
|
+
"title": "Henitai configuration",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"integration": {
|
|
9
|
+
"oneOf": [
|
|
10
|
+
{
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"properties": {
|
|
17
|
+
"name": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"includes": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "string"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"mutation": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"properties": {
|
|
34
|
+
"operators": {
|
|
35
|
+
"enum": ["light", "full"]
|
|
36
|
+
},
|
|
37
|
+
"timeout": {
|
|
38
|
+
"type": "number"
|
|
39
|
+
},
|
|
40
|
+
"max_mutants_per_line": {
|
|
41
|
+
"type": "integer",
|
|
42
|
+
"minimum": 1
|
|
43
|
+
},
|
|
44
|
+
"max_flaky_retries": {
|
|
45
|
+
"type": "integer",
|
|
46
|
+
"minimum": 0
|
|
47
|
+
},
|
|
48
|
+
"ignore_patterns": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"sampling": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"required": ["ratio", "strategy"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"ratio": {
|
|
60
|
+
"type": "number",
|
|
61
|
+
"minimum": 0,
|
|
62
|
+
"maximum": 1
|
|
63
|
+
},
|
|
64
|
+
"strategy": {
|
|
65
|
+
"enum": ["stratified"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"coverage_criteria": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"properties": {
|
|
75
|
+
"test_result": {
|
|
76
|
+
"type": "boolean"
|
|
77
|
+
},
|
|
78
|
+
"timeout": {
|
|
79
|
+
"type": "boolean"
|
|
80
|
+
},
|
|
81
|
+
"process_abort": {
|
|
82
|
+
"type": "boolean"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"thresholds": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": false,
|
|
89
|
+
"properties": {
|
|
90
|
+
"high": {
|
|
91
|
+
"type": "integer"
|
|
92
|
+
},
|
|
93
|
+
"low": {
|
|
94
|
+
"type": "integer"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"reporters": {
|
|
99
|
+
"type": "array",
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "string"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"reports_dir": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
107
|
+
"dashboard": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"additionalProperties": false,
|
|
110
|
+
"properties": {
|
|
111
|
+
"project": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
},
|
|
114
|
+
"base_url": {
|
|
115
|
+
"type": "string"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"jobs": {
|
|
120
|
+
"type": ["integer", "null"]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
data/exe/henitai
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Henitai
|
|
4
|
+
# Suppresses AST nodes that are unlikely to produce useful mutants.
|
|
5
|
+
class AridNodeFilter
|
|
6
|
+
DIRECT_OUTPUT_METHODS = %i[puts p pp warn].freeze
|
|
7
|
+
DIRECT_DEBUG_METHODS = %i[byebug debugger].freeze
|
|
8
|
+
BINDING_DEBUG_METHODS = %i[pry].freeze
|
|
9
|
+
LOGGER_METHODS = %i[debug info warn error fatal].freeze
|
|
10
|
+
INVARIANT_METHODS = %i[is_a? respond_to? kind_of?].freeze
|
|
11
|
+
DSL_METHODS = %i[let subject before after].freeze
|
|
12
|
+
|
|
13
|
+
def suppressed?(node, config)
|
|
14
|
+
custom_pattern_match?(node, config) || catalog_match?(node)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def custom_pattern_match?(node, config)
|
|
20
|
+
source = node.location&.expression&.source
|
|
21
|
+
return false unless source
|
|
22
|
+
|
|
23
|
+
compiled_ignore_patterns(config).any? do |pattern|
|
|
24
|
+
pattern.match?(source)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def compiled_ignore_patterns(config)
|
|
29
|
+
patterns = Array(config&.ignore_patterns).dup.freeze
|
|
30
|
+
@compiled_ignore_patterns ||= {}
|
|
31
|
+
@compiled_ignore_patterns[patterns] ||= patterns.map { |pattern| Regexp.new(pattern) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def catalog_match?(node)
|
|
35
|
+
case node.type
|
|
36
|
+
when :send
|
|
37
|
+
send_match?(node)
|
|
38
|
+
when :block
|
|
39
|
+
block_match?(node)
|
|
40
|
+
when :or_asgn
|
|
41
|
+
true
|
|
42
|
+
else
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def send_match?(node)
|
|
48
|
+
receiver, method_name, = node.children
|
|
49
|
+
method_name = method_name&.to_sym
|
|
50
|
+
|
|
51
|
+
return true if direct_output_call?(receiver, method_name)
|
|
52
|
+
return true if direct_debug_call?(receiver, method_name)
|
|
53
|
+
return true if binding_debug_call?(receiver, method_name)
|
|
54
|
+
return true if rails_logger_call?(receiver, method_name)
|
|
55
|
+
|
|
56
|
+
invariant_call?(method_name)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def block_match?(node)
|
|
60
|
+
send_node = node.children.first
|
|
61
|
+
return false unless send_node&.type == :send
|
|
62
|
+
|
|
63
|
+
receiver, method_name, = send_node.children
|
|
64
|
+
receiver.nil? && DSL_METHODS.include?(method_name.to_sym)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def direct_output_call?(receiver, method_name)
|
|
68
|
+
receiver.nil? && DIRECT_OUTPUT_METHODS.include?(method_name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def direct_debug_call?(receiver, method_name)
|
|
72
|
+
receiver.nil? && DIRECT_DEBUG_METHODS.include?(method_name)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def binding_debug_call?(receiver, method_name)
|
|
76
|
+
send_call?(receiver, :binding) && BINDING_DEBUG_METHODS.include?(method_name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def rails_logger_call?(receiver, method_name)
|
|
80
|
+
logger_receiver?(receiver) && LOGGER_METHODS.include?(method_name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def invariant_call?(method_name)
|
|
84
|
+
INVARIANT_METHODS.include?(method_name)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def logger_receiver?(node)
|
|
88
|
+
send_call?(node, :logger) &&
|
|
89
|
+
node.children.first&.type == :const &&
|
|
90
|
+
node.children.first.children.last == :Rails
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def send_call?(node, method_name)
|
|
94
|
+
node&.type == :send && node.children[1] == method_name
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|