keela 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +111 -5
- data/exe/keela +78 -24
- data/lib/keela/config_file.rb +19 -0
- data/lib/keela/configuration.rb +20 -0
- data/lib/keela/exclusion_validator.rb +160 -0
- data/lib/keela/formatters/base.rb +33 -0
- data/lib/keela/formatters/json.rb +22 -0
- data/lib/keela/formatters/toon.rb +22 -0
- data/lib/keela/formatters.rb +22 -0
- data/lib/keela/reporter.rb +35 -6
- data/lib/keela/scanner.rb +17 -6
- data/lib/keela/strategies/attributes.rb +1 -1
- data/lib/keela/strategies/constants.rb +1 -1
- data/lib/keela/strategies/delegations.rb +3 -7
- data/lib/keela/strategies/i18n_keys.rb +1 -1
- data/lib/keela/strategies/methods.rb +6 -3
- data/lib/keela/strategies/scopes.rb +1 -1
- data/lib/keela/strategy.rb +26 -1
- data/lib/keela/version.rb +1 -1
- data/lib/keela.rb +2 -0
- metadata +20 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3448ca0297d2fb01cbe430bf8d1686ae28871bfa36666815120241664ed95821
|
|
4
|
+
data.tar.gz: 05a96216af6eea62b31ee0e6d6ceb8e82326946ff8eecd35f3eeb07637d220d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc7d5f1e2f1844dc9f67d51e96de17b23e3d040ce767b51529d23c1619f96ae86ea1bf82a028e2e09cea4d074fcba001dab20b1a29725f54f71c366b68a2de17
|
|
7
|
+
data.tar.gz: 177309a39a1fa56d97d6a3693ff674cd94b92cb81e00ea50a6b889823e4d68fd2fdf0601c7d7157e10f48d50f66f940bb92a695808b424d30f4b5ee14bd22623
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-08-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Verbose mode** via `--verbose` flag to show files being scanned, glob patterns, and configuration for debugging ([#53](https://github.com/kerrizor/keela/pull/53))
|
|
15
|
+
- **Source location** via `--source-location` flag to show line numbers in reports for easier navigation ([#55](https://github.com/kerrizor/keela/pull/55))
|
|
16
|
+
- **Exclusion validation** via `--test-exclusions` flag to find stale entries in exclusion files ([#56](https://github.com/kerrizor/keela/pull/56))
|
|
17
|
+
- **TOON output format** via `--format toon` for token-efficient LLM-friendly output ([#58](https://github.com/kerrizor/keela/pull/58))
|
|
18
|
+
- **Configurable definition paths** per strategy via `strategies.<name>.definition_paths` in config file ([#59](https://github.com/kerrizor/keela/pull/59))
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`--quiet` now suppresses all output**, not just the progress bar. Use exit code for success/failure in scripts. ([#54](https://github.com/kerrizor/keela/pull/54))
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Multi-method delegate declarations now detect all methods, not just the first ([#51](https://github.com/kerrizor/keela/pull/51))
|
|
27
|
+
- Class methods (`def self.foo`) are now correctly detected as unused ([#60](https://github.com/kerrizor/keela/pull/60))
|
|
28
|
+
|
|
10
29
|
## [0.3.0] - 2026-08-05
|
|
11
30
|
|
|
12
31
|
### Added
|
data/README.md
CHANGED
|
@@ -66,6 +66,27 @@ If a `.keela_baseline.yml` file exists, Keela compares the current scan against
|
|
|
66
66
|
|
|
67
67
|
This lets you gradually pay down tech debt while preventing new dead code from sneaking in.
|
|
68
68
|
|
|
69
|
+
The baseline file is organized by strategy:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# .keela_baseline.yml
|
|
73
|
+
methods:
|
|
74
|
+
app/models/user.rb:
|
|
75
|
+
- legacy_method
|
|
76
|
+
- old_callback
|
|
77
|
+
app/helpers/application_helper.rb:
|
|
78
|
+
- unused_helper
|
|
79
|
+
scopes:
|
|
80
|
+
app/models/user.rb:
|
|
81
|
+
- inactive
|
|
82
|
+
- archived
|
|
83
|
+
constants:
|
|
84
|
+
app/models/user.rb:
|
|
85
|
+
- OLD_STATUS
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Note:** The baseline file only stores names, not line numbers. This prevents false positives when code moves within a file.
|
|
89
|
+
|
|
69
90
|
### Report Mode
|
|
70
91
|
|
|
71
92
|
If no baseline exists (or you use `--report`), Keela shows all unused code:
|
|
@@ -74,6 +95,32 @@ If no baseline exists (or you use `--report`), Keela shows all unused code:
|
|
|
74
95
|
keela --report
|
|
75
96
|
```
|
|
76
97
|
|
|
98
|
+
## What "Unused" Means
|
|
99
|
+
|
|
100
|
+
Keela defines "unused" as **unused in production code** — not just unused anywhere.
|
|
101
|
+
|
|
102
|
+
By default, Keela scans `app/`, `lib/`, and `config/` directories. It intentionally
|
|
103
|
+
excludes `spec/` and `test/` directories because:
|
|
104
|
+
|
|
105
|
+
1. **Tests aren't usage** — Code that only exists to be tested isn't providing
|
|
106
|
+
application value. If you delete the unused code, you delete its tests too.
|
|
107
|
+
|
|
108
|
+
2. **Tests can cover dead code** — A method with 100% test coverage can still be
|
|
109
|
+
dead if nothing in the application calls it.
|
|
110
|
+
|
|
111
|
+
3. **Cleaner signal** — Including test files would hide genuinely unused code
|
|
112
|
+
behind "but it has tests!" false negatives.
|
|
113
|
+
|
|
114
|
+
If Keela flags something that's only used in tests, consider whether the code
|
|
115
|
+
(and its tests) can be removed entirely. If the code is intentionally test-only
|
|
116
|
+
(e.g., test helpers defined in `app/`), add it to your exclusion file.
|
|
117
|
+
|
|
118
|
+
To include test directories in usage scanning (not recommended), use `--include`:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
keela --include 'test/**/*.rb' --include 'spec/**/*.rb'
|
|
122
|
+
```
|
|
123
|
+
|
|
77
124
|
## Command Line Options
|
|
78
125
|
|
|
79
126
|
```bash
|
|
@@ -114,10 +161,19 @@ keela --config path/to/keela.yml
|
|
|
114
161
|
# Output as JSON (for CI integrations)
|
|
115
162
|
keela --format json
|
|
116
163
|
|
|
117
|
-
# Suppress
|
|
164
|
+
# Suppress all output (useful for CI and scripting, rely on exit code)
|
|
118
165
|
keela --quiet
|
|
119
166
|
keela -q
|
|
120
167
|
|
|
168
|
+
# Show verbose debugging output (files scanned, patterns used)
|
|
169
|
+
keela --verbose
|
|
170
|
+
|
|
171
|
+
# Show source location (file:line) for each unused item
|
|
172
|
+
keela --source-location
|
|
173
|
+
|
|
174
|
+
# Validate exclusion file entries (find stale exclusions)
|
|
175
|
+
keela --test-exclusions
|
|
176
|
+
|
|
121
177
|
# Show version
|
|
122
178
|
keela --version
|
|
123
179
|
```
|
|
@@ -170,10 +226,39 @@ include_patterns:
|
|
|
170
226
|
|
|
171
227
|
excluded_path: ".keela_excluded.yml"
|
|
172
228
|
baseline_path: ".keela_baseline.yml"
|
|
229
|
+
|
|
230
|
+
# Per-strategy configuration
|
|
231
|
+
strategies:
|
|
232
|
+
methods:
|
|
233
|
+
definition_paths:
|
|
234
|
+
- app/helpers
|
|
235
|
+
- app/models
|
|
236
|
+
- lib/
|
|
173
237
|
```
|
|
174
238
|
|
|
175
239
|
Keela automatically loads `keela.yml` or `.keela.yml` from the current directory. Use `--config` to specify a different path.
|
|
176
240
|
|
|
241
|
+
### Customizing Definition Paths Per Strategy
|
|
242
|
+
|
|
243
|
+
By default, each strategy looks for definitions in specific directories (e.g., `methods` looks in `app/helpers` and `app/models`). You can customize this per-strategy:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
# keela.yml
|
|
247
|
+
strategies:
|
|
248
|
+
methods:
|
|
249
|
+
definition_paths:
|
|
250
|
+
- app/helpers
|
|
251
|
+
- app/models
|
|
252
|
+
- lib/
|
|
253
|
+
- ee/app/models
|
|
254
|
+
scopes:
|
|
255
|
+
definition_paths:
|
|
256
|
+
- app/models
|
|
257
|
+
- ee/app/models
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
This is useful when your project has code in non-standard locations (like `lib/` or enterprise edition directories) that you want Keela to check for unused definitions.
|
|
261
|
+
|
|
177
262
|
### Customizing Which Files to Scan
|
|
178
263
|
|
|
179
264
|
There are two approaches:
|
|
@@ -238,14 +323,17 @@ The workflow:
|
|
|
238
323
|
2. **CI runs**: `keela` compares against baseline, fails on new dead code
|
|
239
324
|
3. **After cleanup**: Run `keela --update-baseline` to update the baseline
|
|
240
325
|
|
|
241
|
-
###
|
|
326
|
+
### Structured Output Formats
|
|
242
327
|
|
|
243
|
-
Use `--format
|
|
328
|
+
Use `--format` for machine-readable output:
|
|
244
329
|
|
|
245
330
|
```bash
|
|
246
|
-
keela --format json --report
|
|
331
|
+
keela --format json --report # JSON output
|
|
332
|
+
keela --format toon --report # TOON output (token-efficient for LLMs)
|
|
247
333
|
```
|
|
248
334
|
|
|
335
|
+
#### JSON
|
|
336
|
+
|
|
249
337
|
```json
|
|
250
338
|
{
|
|
251
339
|
"strategies": ["methods", "scopes"],
|
|
@@ -267,7 +355,25 @@ keela --format json --report
|
|
|
267
355
|
}
|
|
268
356
|
```
|
|
269
357
|
|
|
270
|
-
|
|
358
|
+
#### TOON
|
|
359
|
+
|
|
360
|
+
[TOON (Token-Oriented Object Notation)](https://toonformat.dev/) is a compact format optimized for LLM prompts, using ~40-50% fewer tokens than JSON:
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
strategies[2]: methods,scopes
|
|
364
|
+
unused:
|
|
365
|
+
methods:
|
|
366
|
+
"app/models/user.rb"[2]: unused_method,old_helper
|
|
367
|
+
scopes:
|
|
368
|
+
"app/models/post.rb"[1]: inactive
|
|
369
|
+
summary:
|
|
370
|
+
total: 3
|
|
371
|
+
by_strategy:
|
|
372
|
+
methods: 2
|
|
373
|
+
scopes: 1
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
These formats are useful for integrating with other tools, generating reports, processing results programmatically, or including in LLM context windows.
|
|
271
377
|
|
|
272
378
|
## Exclusion File
|
|
273
379
|
|
data/exe/keela
CHANGED
|
@@ -43,7 +43,7 @@ OptionParser.new do |opts|
|
|
|
43
43
|
options[:update_baseline] = true
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
opts.on("--format FORMAT", %i[text json], "Output format: text (default) or
|
|
46
|
+
opts.on("--format FORMAT", %i[text json toon], "Output format: text (default), json, or toon") do |format|
|
|
47
47
|
options[:format] = format
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -82,6 +82,18 @@ OptionParser.new do |opts|
|
|
|
82
82
|
options[:quiet] = true
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
opts.on("--verbose", "Show detailed debugging output (files scanned, patterns used)") do
|
|
86
|
+
options[:verbose] = true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
opts.on("--source-location", "Show file:line for each unused item in reports") do
|
|
90
|
+
options[:source_location] = true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
opts.on("--test-exclusions", "Validate that exclusion file entries match actual definitions") do
|
|
94
|
+
options[:test_exclusions] = true
|
|
95
|
+
end
|
|
96
|
+
|
|
85
97
|
opts.on("-h", "--help", "Show this help") do
|
|
86
98
|
puts opts
|
|
87
99
|
exit
|
|
@@ -99,9 +111,30 @@ end
|
|
|
99
111
|
# Apply quiet mode if requested
|
|
100
112
|
Keela.configuration.show_progress = false if options[:quiet]
|
|
101
113
|
|
|
114
|
+
# Apply verbose mode if requested
|
|
115
|
+
Keela.configuration.verbose = true if options[:verbose]
|
|
116
|
+
|
|
117
|
+
# Apply source location mode if requested
|
|
118
|
+
Keela.configuration.source_location = true if options[:source_location]
|
|
119
|
+
|
|
102
120
|
# Set default baseline path if not specified
|
|
103
121
|
Keela.configuration.baseline_path ||= ".keela_baseline.yml"
|
|
104
122
|
|
|
123
|
+
# Handle --test-exclusions mode
|
|
124
|
+
if options[:test_exclusions]
|
|
125
|
+
excluded_path = Keela.configuration.excluded_path ||
|
|
126
|
+
Keela::Scanner::DEFAULT_EXCLUDED_PATHS.find { |p| File.exist?(p) }
|
|
127
|
+
|
|
128
|
+
unless excluded_path
|
|
129
|
+
warn "Error: No exclusion file found. Specify with --excluded PATH"
|
|
130
|
+
exit 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
validator = Keela::ExclusionValidator.new(excluded_path)
|
|
134
|
+
success = validator.validate
|
|
135
|
+
exit(success ? 0 : 1)
|
|
136
|
+
end
|
|
137
|
+
|
|
105
138
|
STRATEGY_MAP = {
|
|
106
139
|
methods: Keela::Strategies::Methods,
|
|
107
140
|
scopes: Keela::Strategies::Scopes,
|
|
@@ -141,13 +174,46 @@ baseline = Keela::Baseline.new(Keela.configuration.baseline_path)
|
|
|
141
174
|
# Load source files once and share across all strategies
|
|
142
175
|
source_files = Keela::Scanner.load_source_files
|
|
143
176
|
|
|
177
|
+
# Show verbose output if requested
|
|
178
|
+
if options[:verbose]
|
|
179
|
+
config = Keela.configuration
|
|
180
|
+
globs = Keela::Scanner.build_file_globs(config)
|
|
181
|
+
|
|
182
|
+
puts Rainbow("=== Verbose Mode ===").magenta.bright
|
|
183
|
+
puts
|
|
184
|
+
puts Rainbow("Configuration:").yellow
|
|
185
|
+
puts " Extensions: #{config.extensions.join(', ')}"
|
|
186
|
+
puts " Directory patterns: #{config.directory_patterns.join(', ')}"
|
|
187
|
+
puts " Include patterns: #{config.include_patterns.empty? ? '(none)' : config.include_patterns.join(', ')}"
|
|
188
|
+
puts " Exclude patterns: #{config.exclude_patterns.empty? ? '(none)' : config.exclude_patterns.join(', ')}"
|
|
189
|
+
puts
|
|
190
|
+
puts Rainbow("Resolved globs:").yellow
|
|
191
|
+
globs.each { |g| puts " #{g}" }
|
|
192
|
+
puts
|
|
193
|
+
sorted_files = source_files.keys.sort
|
|
194
|
+
file_count = sorted_files.size
|
|
195
|
+
max_display = 50
|
|
196
|
+
|
|
197
|
+
puts Rainbow("Files to scan (#{file_count}):").yellow
|
|
198
|
+
# Show all files when piping (not a TTY), truncate for interactive use
|
|
199
|
+
if file_count <= max_display || !$stdout.tty?
|
|
200
|
+
sorted_files.each { |f| puts " #{f}" }
|
|
201
|
+
else
|
|
202
|
+
sorted_files.first(max_display).each { |f| puts " #{f}" }
|
|
203
|
+
puts " ... and #{file_count - max_display} more files"
|
|
204
|
+
puts " (pipe output to see full list: keela --verbose > files.txt)"
|
|
205
|
+
end
|
|
206
|
+
puts
|
|
207
|
+
end
|
|
208
|
+
|
|
144
209
|
success = true
|
|
145
210
|
results = {}
|
|
146
211
|
|
|
147
|
-
|
|
212
|
+
structured_output = Keela::Formatters.structured_format?(options[:format])
|
|
213
|
+
silent_output = structured_output || options[:quiet]
|
|
148
214
|
|
|
149
215
|
strategies.each_with_index do |strategy, index|
|
|
150
|
-
unless
|
|
216
|
+
unless silent_output
|
|
151
217
|
puts Rainbow("=== Sniffing for unused #{strategy.name} ===").cyan.bright if strategies.size > 1
|
|
152
218
|
end
|
|
153
219
|
|
|
@@ -155,13 +221,13 @@ strategies.each_with_index do |strategy, index|
|
|
|
155
221
|
success &&= scanner.run(
|
|
156
222
|
force_report: options[:force_report],
|
|
157
223
|
update_baseline: options[:update_baseline],
|
|
158
|
-
silent:
|
|
224
|
+
silent: silent_output
|
|
159
225
|
)
|
|
160
226
|
|
|
161
|
-
# Collect results for
|
|
227
|
+
# Collect results for structured output formats
|
|
162
228
|
results[strategy.name] = scanner.unused_collection.transform_values(&:to_a)
|
|
163
229
|
|
|
164
|
-
unless
|
|
230
|
+
unless silent_output
|
|
165
231
|
puts if strategies.size > 1 && index < strategies.size - 1
|
|
166
232
|
end
|
|
167
233
|
end
|
|
@@ -169,26 +235,14 @@ end
|
|
|
169
235
|
# Save baseline after all strategies have run
|
|
170
236
|
if options[:update_baseline]
|
|
171
237
|
baseline.save
|
|
172
|
-
puts Rainbow("Updated #{baseline.path}").green.bright unless
|
|
238
|
+
puts Rainbow("Updated #{baseline.path}").green.bright unless silent_output
|
|
173
239
|
end
|
|
174
240
|
|
|
175
|
-
# Output
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
by_strategy = results.transform_values { |files| files.values.flatten.size }
|
|
181
|
-
|
|
182
|
-
output = {
|
|
183
|
-
strategies: strategies.map(&:name),
|
|
184
|
-
unused: results.reject { |_, v| v.empty? },
|
|
185
|
-
summary: {
|
|
186
|
-
total: total,
|
|
187
|
-
by_strategy: by_strategy.reject { |_, v| v.zero? }
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
puts JSON.pretty_generate(output)
|
|
241
|
+
# Output structured format if requested
|
|
242
|
+
if structured_output
|
|
243
|
+
formatter_class = Keela::Formatters.for(options[:format])
|
|
244
|
+
formatter = formatter_class.new(results: results, strategies: strategies)
|
|
245
|
+
puts formatter.format
|
|
192
246
|
end
|
|
193
247
|
|
|
194
248
|
exit(success ? 0 : 1)
|
data/lib/keela/config_file.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Keela
|
|
|
17
17
|
# - excluded_path: Path to YAML file of excluded items
|
|
18
18
|
# - baseline_path: Path to baseline YAML file
|
|
19
19
|
# - required_directory: Directory that must exist for scanning to proceed
|
|
20
|
+
# - strategies: Per-strategy configuration (see below)
|
|
20
21
|
#
|
|
21
22
|
# Example:
|
|
22
23
|
# # keela.yml
|
|
@@ -29,6 +30,15 @@ module Keela
|
|
|
29
30
|
# - rb
|
|
30
31
|
# - haml
|
|
31
32
|
# - erb
|
|
33
|
+
# strategies:
|
|
34
|
+
# methods:
|
|
35
|
+
# definition_paths:
|
|
36
|
+
# - app/helpers
|
|
37
|
+
# - app/models
|
|
38
|
+
# - lib/
|
|
39
|
+
# scopes:
|
|
40
|
+
# definition_paths:
|
|
41
|
+
# - app/models
|
|
32
42
|
#
|
|
33
43
|
module ConfigFile
|
|
34
44
|
CONFIG_FILENAMES = %w[.keela/config.yml keela.yml .keela.yml].freeze
|
|
@@ -74,6 +84,15 @@ module Keela
|
|
|
74
84
|
value = config[key]
|
|
75
85
|
configuration.public_send("#{key}=", value)
|
|
76
86
|
end
|
|
87
|
+
|
|
88
|
+
apply_strategy_options(config["strategies"]) if config.key?("strategies")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def apply_strategy_options(strategies)
|
|
92
|
+
return unless strategies.is_a?(Hash)
|
|
93
|
+
|
|
94
|
+
configuration = Keela.configuration
|
|
95
|
+
configuration.strategy_options = strategies
|
|
77
96
|
end
|
|
78
97
|
end
|
|
79
98
|
end
|
data/lib/keela/configuration.rb
CHANGED
|
@@ -26,6 +26,18 @@ module Keela
|
|
|
26
26
|
# Additional directory patterns to include (added to directory_patterns)
|
|
27
27
|
attr_accessor :include_patterns
|
|
28
28
|
|
|
29
|
+
# Whether to show verbose debugging output
|
|
30
|
+
attr_accessor :verbose
|
|
31
|
+
|
|
32
|
+
# Whether to show source location (file:line) in reports
|
|
33
|
+
attr_accessor :source_location
|
|
34
|
+
|
|
35
|
+
# Per-strategy configuration options
|
|
36
|
+
# Hash of strategy_name => { option => value }
|
|
37
|
+
# Supported options:
|
|
38
|
+
# - definition_file_pattern: Regex pattern string for files containing definitions
|
|
39
|
+
attr_accessor :strategy_options
|
|
40
|
+
|
|
29
41
|
def initialize
|
|
30
42
|
@extensions = %w[rb haml erb].freeze
|
|
31
43
|
@directory_patterns = %w[
|
|
@@ -39,6 +51,14 @@ module Keela
|
|
|
39
51
|
@show_progress = true
|
|
40
52
|
@exclude_patterns = []
|
|
41
53
|
@include_patterns = []
|
|
54
|
+
@verbose = false
|
|
55
|
+
@source_location = false
|
|
56
|
+
@strategy_options = {}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get options for a specific strategy
|
|
60
|
+
def options_for(strategy_name)
|
|
61
|
+
strategy_options[strategy_name.to_s] || {}
|
|
42
62
|
end
|
|
43
63
|
end
|
|
44
64
|
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "rainbow"
|
|
5
|
+
|
|
6
|
+
module Keela
|
|
7
|
+
class ExclusionValidator
|
|
8
|
+
STRATEGY_MAP = {
|
|
9
|
+
methods: Strategies::Methods,
|
|
10
|
+
scopes: Strategies::Scopes,
|
|
11
|
+
constants: Strategies::Constants,
|
|
12
|
+
delegations: Strategies::Delegations,
|
|
13
|
+
attributes: Strategies::Attributes,
|
|
14
|
+
i18n_keys: Strategies::I18nKeys
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
attr_reader :excluded_path, :results
|
|
18
|
+
|
|
19
|
+
def initialize(excluded_path)
|
|
20
|
+
@excluded_path = excluded_path
|
|
21
|
+
@results = { valid: [], stale: [] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate
|
|
25
|
+
unless File.exist?(excluded_path)
|
|
26
|
+
puts Rainbow("Exclusion file not found: #{excluded_path}").red
|
|
27
|
+
return false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
all_excluded = YAML.load_file(excluded_path, symbolize_names: true) || {}
|
|
31
|
+
|
|
32
|
+
if all_excluded.empty?
|
|
33
|
+
puts Rainbow("Exclusion file is empty: #{excluded_path}").yellow
|
|
34
|
+
return true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "Validating exclusions in #{excluded_path}...\n\n"
|
|
38
|
+
|
|
39
|
+
# Detect format: strategy-aware or legacy flat
|
|
40
|
+
if strategy_aware_format?(all_excluded)
|
|
41
|
+
validate_strategy_aware(all_excluded)
|
|
42
|
+
else
|
|
43
|
+
validate_legacy_flat(all_excluded)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
print_results
|
|
47
|
+
results[:stale].empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def strategy_aware_format?(data)
|
|
53
|
+
data.keys.any? { |k| STRATEGY_MAP.key?(k) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_strategy_aware(all_excluded)
|
|
57
|
+
all_excluded.each do |strategy_name, files|
|
|
58
|
+
next unless STRATEGY_MAP.key?(strategy_name)
|
|
59
|
+
|
|
60
|
+
strategy = STRATEGY_MAP[strategy_name].new
|
|
61
|
+
validate_files(strategy, strategy_name, files || {})
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def validate_legacy_flat(all_excluded)
|
|
66
|
+
# Legacy format applies to all strategies, but we'll check against methods
|
|
67
|
+
strategy = Strategies::Methods.new
|
|
68
|
+
validate_files(strategy, :methods, all_excluded)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def validate_files(strategy, strategy_name, files)
|
|
72
|
+
files.each do |file_path, entries|
|
|
73
|
+
file_str = file_path.to_s
|
|
74
|
+
entries ||= []
|
|
75
|
+
|
|
76
|
+
entries.each do |entry|
|
|
77
|
+
name = entry.keys.first.to_s
|
|
78
|
+
reason = entry.values.first
|
|
79
|
+
|
|
80
|
+
if !File.exist?(file_str)
|
|
81
|
+
results[:stale] << {
|
|
82
|
+
strategy: strategy_name,
|
|
83
|
+
file: file_str,
|
|
84
|
+
name: name,
|
|
85
|
+
reason: reason,
|
|
86
|
+
error: "file not found"
|
|
87
|
+
}
|
|
88
|
+
elsif !definition_exists?(strategy, file_str, name)
|
|
89
|
+
results[:stale] << {
|
|
90
|
+
strategy: strategy_name,
|
|
91
|
+
file: file_str,
|
|
92
|
+
name: name,
|
|
93
|
+
reason: reason,
|
|
94
|
+
error: "no definition found"
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
results[:valid] << {
|
|
98
|
+
strategy: strategy_name,
|
|
99
|
+
file: file_str,
|
|
100
|
+
name: name,
|
|
101
|
+
reason: reason
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def definition_exists?(strategy, file_path, name)
|
|
109
|
+
return false unless File.exist?(file_path)
|
|
110
|
+
|
|
111
|
+
lines = File.readlines(file_path)
|
|
112
|
+
|
|
113
|
+
# Check custom extraction first (for I18n YAML files)
|
|
114
|
+
custom_defs = strategy.extract_definitions_from_file(file_path, lines)
|
|
115
|
+
if custom_defs
|
|
116
|
+
return custom_defs.any? { |d| d[:name] == name }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Default line-by-line extraction
|
|
120
|
+
lines.any? do |line|
|
|
121
|
+
next if strategy.skip_comments? && line.strip.start_with?("#")
|
|
122
|
+
|
|
123
|
+
result = strategy.extract_definition(line)
|
|
124
|
+
Array(result).compact.include?(name)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def print_results
|
|
129
|
+
if results[:valid].any?
|
|
130
|
+
puts Rainbow("✅ Valid exclusions (#{results[:valid].size}):").green.bright
|
|
131
|
+
group_by_strategy(results[:valid]).each do |strategy, entries|
|
|
132
|
+
puts " #{strategy}:"
|
|
133
|
+
entries.each do |e|
|
|
134
|
+
puts " #{e[:file]}:#{e[:name]}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
puts
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if results[:stale].any?
|
|
141
|
+
puts Rainbow("⚠️ Stale exclusions found (#{results[:stale].size}):").yellow.bright
|
|
142
|
+
puts
|
|
143
|
+
group_by_strategy(results[:stale]).each do |strategy, entries|
|
|
144
|
+
puts " #{strategy}:"
|
|
145
|
+
entries.each do |e|
|
|
146
|
+
puts " #{e[:file]}:#{e[:name]} — #{e[:error]}"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
puts
|
|
150
|
+
puts "Consider removing stale exclusions from #{excluded_path}"
|
|
151
|
+
else
|
|
152
|
+
puts Rainbow("All exclusions are valid!").green.bright
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def group_by_strategy(entries)
|
|
157
|
+
entries.group_by { |e| e[:strategy] }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keela
|
|
4
|
+
module Formatters
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :results, :strategies
|
|
7
|
+
|
|
8
|
+
def initialize(results:, strategies:)
|
|
9
|
+
@results = results
|
|
10
|
+
@strategies = strategies
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def format
|
|
14
|
+
raise NotImplementedError, "Subclasses must implement #format"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def total_count
|
|
20
|
+
results.values.flat_map(&:values).flatten.size
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def by_strategy_counts
|
|
24
|
+
results.transform_values { |files| files.values.flatten.size }
|
|
25
|
+
.reject { |_, v| v.zero? }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def non_empty_results
|
|
29
|
+
results.reject { |_, v| v.empty? }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Keela
|
|
6
|
+
module Formatters
|
|
7
|
+
class Json < Base
|
|
8
|
+
def format
|
|
9
|
+
output = {
|
|
10
|
+
strategies: strategies.map(&:name),
|
|
11
|
+
unused: non_empty_results,
|
|
12
|
+
summary: {
|
|
13
|
+
total: total_count,
|
|
14
|
+
by_strategy: by_strategy_counts
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
JSON.pretty_generate(output)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "toon"
|
|
4
|
+
|
|
5
|
+
module Keela
|
|
6
|
+
module Formatters
|
|
7
|
+
class Toon < Base
|
|
8
|
+
def format
|
|
9
|
+
output = {
|
|
10
|
+
"strategies" => strategies.map(&:name),
|
|
11
|
+
"unused" => non_empty_results.transform_keys(&:to_s),
|
|
12
|
+
"summary" => {
|
|
13
|
+
"total" => total_count,
|
|
14
|
+
"by_strategy" => by_strategy_counts.transform_keys(&:to_s)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
::Toon.encode(output)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formatters/base"
|
|
4
|
+
require_relative "formatters/json"
|
|
5
|
+
require_relative "formatters/toon"
|
|
6
|
+
|
|
7
|
+
module Keela
|
|
8
|
+
module Formatters
|
|
9
|
+
REGISTRY = {
|
|
10
|
+
json: Json,
|
|
11
|
+
toon: Toon
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def self.for(format)
|
|
15
|
+
REGISTRY[format]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.structured_format?(format)
|
|
19
|
+
REGISTRY.key?(format)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/keela/reporter.rb
CHANGED
|
@@ -11,12 +11,12 @@ module Keela
|
|
|
11
11
|
@strategy_name = strategy_name
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def print_full_report(unused_collection, elapsed_time)
|
|
14
|
+
def print_full_report(unused_collection, elapsed_time, source_locations: {})
|
|
15
15
|
unused_count = unused_collection.values.flatten.size
|
|
16
16
|
|
|
17
17
|
if unused_count > 0
|
|
18
18
|
puts "\nFound #{unused_count} unused #{strategy_name}:\n\n"
|
|
19
|
-
puts
|
|
19
|
+
puts format_report(unused_collection, source_locations)
|
|
20
20
|
puts "\n"
|
|
21
21
|
else
|
|
22
22
|
puts Rainbow("No unused #{strategy_name} were found.").green.bright
|
|
@@ -25,8 +25,8 @@ module Keela
|
|
|
25
25
|
puts "Finished in #{elapsed_time.round(2)} seconds."
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def print_diff_report(new_unused, removed, excluded_path:, baseline_path:)
|
|
29
|
-
print_new_unused(new_unused, excluded_path) unless new_unused.empty?
|
|
28
|
+
def print_diff_report(new_unused, removed, excluded_path:, baseline_path:, source_locations: {})
|
|
29
|
+
print_new_unused(new_unused, excluded_path, source_locations) unless new_unused.empty?
|
|
30
30
|
|
|
31
31
|
if new_unused.size + removed.size > 0
|
|
32
32
|
puts Rainbow("~" * 80).white.bright
|
|
@@ -36,13 +36,42 @@ module Keela
|
|
|
36
36
|
print_removed(removed, baseline_path) unless removed.empty?
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def format_report(collection, source_locations = {})
|
|
40
|
+
if source_locations.empty?
|
|
41
|
+
format_yaml(collection)
|
|
42
|
+
else
|
|
43
|
+
format_with_locations(collection, source_locations)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
39
47
|
def format_yaml(collection)
|
|
40
48
|
indent_yaml_list_items(collection.sort.to_h.to_yaml)
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
private
|
|
44
52
|
|
|
45
|
-
def
|
|
53
|
+
def format_with_locations(collection, source_locations)
|
|
54
|
+
# Calculate max name length for alignment
|
|
55
|
+
all_names = collection.values.flatten
|
|
56
|
+
max_name_len = all_names.map(&:length).max || 0
|
|
57
|
+
padding = max_name_len + 6 # " - " prefix + 2 spaces
|
|
58
|
+
|
|
59
|
+
lines = ["---"]
|
|
60
|
+
collection.sort.each do |file, names|
|
|
61
|
+
lines << "#{file}:"
|
|
62
|
+
names.each do |name|
|
|
63
|
+
line_num = source_locations["#{file}:#{name}"]
|
|
64
|
+
if line_num
|
|
65
|
+
lines << " - #{name}".ljust(padding) + "#{file}:#{line_num}"
|
|
66
|
+
else
|
|
67
|
+
lines << " - #{name}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
lines.join("\n")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def print_new_unused(new_unused, excluded_path, source_locations = {})
|
|
46
75
|
error = <<~MESSAGE
|
|
47
76
|
We have detected #{new_unused.size} newly unused #{strategy_name}.
|
|
48
77
|
|
|
@@ -50,7 +79,7 @@ module Keela
|
|
|
50
79
|
MESSAGE
|
|
51
80
|
|
|
52
81
|
puts Rainbow(error).red.bright
|
|
53
|
-
puts Rainbow(
|
|
82
|
+
puts Rainbow(format_report(parse_diff(new_unused), source_locations)).red.bright
|
|
54
83
|
end
|
|
55
84
|
|
|
56
85
|
def print_removed(removed, baseline_path)
|
data/lib/keela/scanner.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "yaml"
|
|
|
5
5
|
|
|
6
6
|
module Keela
|
|
7
7
|
class Scanner
|
|
8
|
-
attr_reader :strategy, :configuration, :baseline, :source_files, :unused_collection, :new_unused, :removed
|
|
8
|
+
attr_reader :strategy, :configuration, :baseline, :source_files, :unused_collection, :source_locations, :new_unused, :removed
|
|
9
9
|
|
|
10
10
|
DEFAULT_EXCLUDED_PATHS = [".keela/excluded.yml", "keela_excluded.yml"].freeze
|
|
11
11
|
DEFAULT_BASELINE_PATHS = [".keela/baseline.yml", "keela_baseline.yml"].freeze
|
|
@@ -48,6 +48,7 @@ module Keela
|
|
|
48
48
|
@source_files = source_files || {}
|
|
49
49
|
@source_files_preloaded = !source_files.nil?
|
|
50
50
|
@unused_collection = Hash.new { |hash, key| hash[key] = [] }
|
|
51
|
+
@source_locations = {} # { "file:name" => line_number }
|
|
51
52
|
@new_unused = []
|
|
52
53
|
@removed = []
|
|
53
54
|
end
|
|
@@ -70,7 +71,7 @@ module Keela
|
|
|
70
71
|
|
|
71
72
|
if report_mode
|
|
72
73
|
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
73
|
-
reporter.print_full_report(unused_collection, elapsed) unless silent
|
|
74
|
+
reporter.print_full_report(unused_collection, elapsed, source_locations: source_locations) unless silent
|
|
74
75
|
if update_baseline
|
|
75
76
|
baseline.set(strategy.name, unused_collection)
|
|
76
77
|
# Note: caller is responsible for calling baseline.save after all strategies run
|
|
@@ -85,7 +86,8 @@ module Keela
|
|
|
85
86
|
new_unused,
|
|
86
87
|
removed,
|
|
87
88
|
excluded_path: resolve_excluded_path || ".keela/excluded.yml",
|
|
88
|
-
baseline_path: baseline.path
|
|
89
|
+
baseline_path: baseline.path,
|
|
90
|
+
source_locations: source_locations
|
|
89
91
|
)
|
|
90
92
|
end
|
|
91
93
|
|
|
@@ -155,11 +157,17 @@ module Keela
|
|
|
155
157
|
next custom_definitions if custom_definitions
|
|
156
158
|
|
|
157
159
|
# Default: line-by-line parsing
|
|
158
|
-
|
|
160
|
+
track_lines = configuration.source_location
|
|
161
|
+
lines.each_with_index.flat_map do |line, index|
|
|
159
162
|
next [] if strategy.skip_comments? && line.strip.start_with?("#")
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
result = strategy.extract_definition(line)
|
|
165
|
+
# Support both single name (String) and multiple names (Array)
|
|
166
|
+
Array(result).compact.map do |name|
|
|
167
|
+
definition = { name: name, file: filename }
|
|
168
|
+
definition[:line] = index + 1 if track_lines
|
|
169
|
+
definition
|
|
170
|
+
end
|
|
163
171
|
end
|
|
164
172
|
end
|
|
165
173
|
end
|
|
@@ -211,6 +219,9 @@ module Keela
|
|
|
211
219
|
|
|
212
220
|
unused.each do |unused_def|
|
|
213
221
|
@unused_collection[unused_def[:file]] << unused_def[:name]
|
|
222
|
+
if unused_def[:line]
|
|
223
|
+
@source_locations["#{unused_def[:file]}:#{unused_def[:name]}"] = unused_def[:line]
|
|
224
|
+
end
|
|
214
225
|
end
|
|
215
226
|
end
|
|
216
227
|
|
|
@@ -7,7 +7,7 @@ module Keela
|
|
|
7
7
|
"delegations"
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def default_definition_file_pattern
|
|
11
11
|
# Match app/models/ directories (including concerns), but exclude spec/test
|
|
12
12
|
%r{(?:^|/)(?:ee/)?app/models/}
|
|
13
13
|
end
|
|
@@ -44,12 +44,8 @@ module Keela
|
|
|
44
44
|
methods = methods.map { |m| "#{prefix}_#{m}" }
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# Return
|
|
48
|
-
|
|
49
|
-
# The scanner will create one definition entry per extract_definition call
|
|
50
|
-
# To handle multiple delegations per line, we'd need to change the scanner
|
|
51
|
-
# For now, return just the first method
|
|
52
|
-
methods.first
|
|
47
|
+
# Return all methods (scanner handles both single string and array)
|
|
48
|
+
methods.length == 1 ? methods.first : methods
|
|
53
49
|
end
|
|
54
50
|
|
|
55
51
|
def usage_regex(name)
|
|
@@ -7,7 +7,7 @@ module Keela
|
|
|
7
7
|
"methods"
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def default_definition_file_pattern
|
|
11
11
|
%r{app/helpers|app/models}
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -18,12 +18,15 @@ module Keela
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def usage_regex(name)
|
|
21
|
+
method_name = Regexp.quote(name.sub(/^self\./, ""))
|
|
22
|
+
|
|
21
23
|
if name.end_with?("=")
|
|
22
24
|
# Setter method: match assignment usage
|
|
23
|
-
/(?<!def )#{
|
|
25
|
+
/(?<!def |def self\.)#{method_name.chomp("=")}\W=*/
|
|
24
26
|
else
|
|
25
27
|
# Regular method: match calls
|
|
26
|
-
|
|
28
|
+
# Exclude both "def foo" and "def self.foo" definitions
|
|
29
|
+
/(?<!def |def self\.)#{method_name}\W/
|
|
27
30
|
end
|
|
28
31
|
end
|
|
29
32
|
|
data/lib/keela/strategy.rb
CHANGED
|
@@ -14,8 +14,23 @@ module Keela
|
|
|
14
14
|
|
|
15
15
|
# Regex pattern to match files that may contain definitions
|
|
16
16
|
# (e.g., /app\/models/ for scopes)
|
|
17
|
+
#
|
|
18
|
+
# Can be overridden via configuration:
|
|
19
|
+
# strategies:
|
|
20
|
+
# methods:
|
|
21
|
+
# definition_paths:
|
|
22
|
+
# - app/helpers
|
|
23
|
+
# - app/models
|
|
24
|
+
# - lib/
|
|
25
|
+
#
|
|
17
26
|
def definition_file_pattern
|
|
18
|
-
|
|
27
|
+
configured_pattern || default_definition_file_pattern
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Default pattern when no configuration override is provided.
|
|
31
|
+
# Subclasses should implement this instead of definition_file_pattern.
|
|
32
|
+
def default_definition_file_pattern
|
|
33
|
+
raise NotImplementedError, "#{self.class} must implement #default_definition_file_pattern"
|
|
19
34
|
end
|
|
20
35
|
|
|
21
36
|
# Extract a definition name from a line of code, or nil if no definition found
|
|
@@ -39,5 +54,15 @@ module Keela
|
|
|
39
54
|
def extract_definitions_from_file(_filepath, _lines)
|
|
40
55
|
nil
|
|
41
56
|
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def configured_pattern
|
|
61
|
+
paths = Keela.configuration.options_for(name)["definition_paths"]
|
|
62
|
+
return nil unless paths.is_a?(Array) && paths.any?
|
|
63
|
+
|
|
64
|
+
escaped = paths.map { |p| Regexp.escape(p.to_s) }
|
|
65
|
+
Regexp.new(escaped.join("|"))
|
|
66
|
+
end
|
|
42
67
|
end
|
|
43
68
|
end
|
data/lib/keela/version.rb
CHANGED
data/lib/keela.rb
CHANGED
|
@@ -13,6 +13,8 @@ require_relative "keela/strategies/i18n_keys"
|
|
|
13
13
|
require_relative "keela/reporter"
|
|
14
14
|
require_relative "keela/baseline"
|
|
15
15
|
require_relative "keela/scanner"
|
|
16
|
+
require_relative "keela/exclusion_validator"
|
|
17
|
+
require_relative "keela/formatters"
|
|
16
18
|
|
|
17
19
|
module Keela
|
|
18
20
|
class Error < StandardError; end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keela
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kerri Miller
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '1.11'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: toon-ruby
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.1'
|
|
54
68
|
description: Like the famous CSI dog who found what others missed, Keela detects unused
|
|
55
69
|
methods and scopes in your Ruby codebase.
|
|
56
70
|
email: kerrizor@kerrizor.com
|
|
@@ -67,6 +81,11 @@ files:
|
|
|
67
81
|
- lib/keela/baseline.rb
|
|
68
82
|
- lib/keela/config_file.rb
|
|
69
83
|
- lib/keela/configuration.rb
|
|
84
|
+
- lib/keela/exclusion_validator.rb
|
|
85
|
+
- lib/keela/formatters.rb
|
|
86
|
+
- lib/keela/formatters/base.rb
|
|
87
|
+
- lib/keela/formatters/json.rb
|
|
88
|
+
- lib/keela/formatters/toon.rb
|
|
70
89
|
- lib/keela/reporter.rb
|
|
71
90
|
- lib/keela/scanner.rb
|
|
72
91
|
- lib/keela/strategies/attributes.rb
|