keela 0.2.3 → 0.3.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 +14 -0
- data/README.md +31 -6
- data/exe/keela +19 -7
- data/lib/keela/scanner.rb +46 -3
- data/lib/keela/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4fcf07cf3f0adee173c2dfaef9e79f256f6a6318c5dd843935b6113fede4dea4
|
|
4
|
+
data.tar.gz: 454fa3bc9a9839dcf7d5f40a6d2fa2160daeb3a2d113d2f372da6ce15f9a49b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9105a9cc47b95443d527772bd1dc44b5578d3b88829ec0144109e1ce82cc03d3414be62de9c8e4f8a7ab308b742e3beea343e6aefd2b4e3184f694847bba711
|
|
7
|
+
data.tar.gz: b583a1e03b6b460d4393ed687ef8796315c6fc4e8f9e4afde3a4edce26581536ef0b72dbef7215237b97835263b12a52b55879f391d901efe5409007ae407982
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-08-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Strategy-aware exclusion file format** - Exclusion files can now be organized by strategy (methods, scopes, etc.) to match the baseline file format. This allows excluding the same name in one strategy while flagging it in another. The legacy flat format is still supported for backward compatibility. ([#35](https://github.com/kerrizor/keela/pull/35))
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Performance: Share source files across strategies** - When scanning multiple strategies, source files are now loaded once and shared, reducing scan time by ~33% on large codebases. ([#37](https://github.com/kerrizor/keela/pull/37))
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- CLI options now correctly override config file settings ([#36](https://github.com/kerrizor/keela/pull/36))
|
|
23
|
+
|
|
10
24
|
## [0.2.3] - 2026-07-31
|
|
11
25
|
|
|
12
26
|
### Fixed
|
data/README.md
CHANGED
|
@@ -271,21 +271,46 @@ This is useful for integrating with other tools, generating reports, or processi
|
|
|
271
271
|
|
|
272
272
|
## Exclusion File
|
|
273
273
|
|
|
274
|
-
Some code appears unused but is actually called dynamically. Exclude it:
|
|
274
|
+
Some code appears unused but is actually called dynamically. Exclude it using the **strategy-aware format** (recommended):
|
|
275
275
|
|
|
276
276
|
```yaml
|
|
277
|
-
# .
|
|
277
|
+
# .keela/excluded.yml (strategy-aware format)
|
|
278
|
+
methods:
|
|
279
|
+
app/models/user.rb:
|
|
280
|
+
- legacy_method: "Called via metaprogramming"
|
|
281
|
+
- callback_method: "Used as ActiveRecord callback"
|
|
282
|
+
app/helpers/application_helper.rb:
|
|
283
|
+
- helper_method: "Called from views dynamically"
|
|
284
|
+
scopes:
|
|
285
|
+
app/models/user.rb:
|
|
286
|
+
- active: "Called dynamically via send()"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
This format mirrors the baseline file structure and allows you to exclude the same name in one strategy while flagging it in another. For example, if you have both `scope :active` and `def active` in the same file, you can exclude them independently.
|
|
290
|
+
|
|
291
|
+
### Legacy Flat Format
|
|
292
|
+
|
|
293
|
+
The flat format is still supported for backward compatibility:
|
|
294
|
+
|
|
295
|
+
```yaml
|
|
296
|
+
# .keela_excluded.yml (legacy flat format)
|
|
278
297
|
app/models/user.rb:
|
|
279
298
|
- legacy_method: "Called via metaprogramming"
|
|
280
299
|
- callback_method: "Used as ActiveRecord callback"
|
|
281
|
-
app/helpers/application_helper.rb:
|
|
282
|
-
- helper_method: "Called from views dynamically"
|
|
283
300
|
```
|
|
284
301
|
|
|
285
|
-
|
|
302
|
+
**Note:** With the flat format, an exclusion applies to ALL strategies. If you exclude `active`, both the method and scope named `active` will be excluded.
|
|
303
|
+
|
|
304
|
+
### Usage
|
|
286
305
|
|
|
287
306
|
```bash
|
|
288
|
-
keela --excluded .
|
|
307
|
+
keela --excluded .keela/excluded.yml
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Or configure in `keela.yml`:
|
|
311
|
+
|
|
312
|
+
```yaml
|
|
313
|
+
excluded_path: ".keela/excluded.yml"
|
|
289
314
|
```
|
|
290
315
|
|
|
291
316
|
## Ruby API
|
data/exe/keela
CHANGED
|
@@ -13,6 +13,9 @@ options = {
|
|
|
13
13
|
quiet: false
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
# Store CLI overrides separately so they can be applied after config file loads
|
|
17
|
+
cli_overrides = {}
|
|
18
|
+
|
|
16
19
|
OptionParser.new do |opts|
|
|
17
20
|
opts.banner = "Usage: keela [options]"
|
|
18
21
|
|
|
@@ -45,23 +48,25 @@ OptionParser.new do |opts|
|
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
opts.on("--excluded PATH", "Path to YAML file of excluded items") do |path|
|
|
48
|
-
|
|
51
|
+
cli_overrides[:excluded_path] = path
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
opts.on("--baseline PATH", "Path to YAML baseline file (default: .keela_baseline.yml)") do |path|
|
|
52
|
-
|
|
55
|
+
cli_overrides[:baseline_path] = path
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
opts.on("--extensions EXTS", "Comma-separated file extensions to scan (default: rb,haml,erb)") do |exts|
|
|
56
|
-
|
|
59
|
+
cli_overrides[:extensions] = exts.split(",").map(&:strip)
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
opts.on("--exclude PATTERN", "Glob pattern for files to exclude (can be used multiple times)") do |pattern|
|
|
60
|
-
|
|
63
|
+
cli_overrides[:exclude_patterns] ||= []
|
|
64
|
+
cli_overrides[:exclude_patterns] << pattern
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
opts.on("--include PATTERN", "Additional glob pattern to scan (can be used multiple times)") do |pattern|
|
|
64
|
-
|
|
68
|
+
cli_overrides[:include_patterns] ||= []
|
|
69
|
+
cli_overrides[:include_patterns] << pattern
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
opts.on("--config PATH", "-c", "Path to config file (default: keela.yml or .keela.yml)") do |path|
|
|
@@ -84,9 +89,13 @@ OptionParser.new do |opts|
|
|
|
84
89
|
end.parse!
|
|
85
90
|
|
|
86
91
|
# Load config file (keela.yml or .keela.yml) if present
|
|
87
|
-
# CLI options override config file settings
|
|
88
92
|
Keela::ConfigFile.load(path: options[:config_path])
|
|
89
93
|
|
|
94
|
+
# Apply CLI overrides (these take precedence over config file)
|
|
95
|
+
cli_overrides.each do |key, value|
|
|
96
|
+
Keela.configuration.public_send("#{key}=", value)
|
|
97
|
+
end
|
|
98
|
+
|
|
90
99
|
# Apply quiet mode if requested
|
|
91
100
|
Keela.configuration.show_progress = false if options[:quiet]
|
|
92
101
|
|
|
@@ -129,6 +138,9 @@ strategies = build_strategies(options[:types])
|
|
|
129
138
|
# Share a single baseline across all strategies
|
|
130
139
|
baseline = Keela::Baseline.new(Keela.configuration.baseline_path)
|
|
131
140
|
|
|
141
|
+
# Load source files once and share across all strategies
|
|
142
|
+
source_files = Keela::Scanner.load_source_files
|
|
143
|
+
|
|
132
144
|
success = true
|
|
133
145
|
results = {}
|
|
134
146
|
|
|
@@ -139,7 +151,7 @@ strategies.each_with_index do |strategy, index|
|
|
|
139
151
|
puts Rainbow("=== Sniffing for unused #{strategy.name} ===").cyan.bright if strategies.size > 1
|
|
140
152
|
end
|
|
141
153
|
|
|
142
|
-
scanner = Keela::Scanner.new(strategy: strategy, baseline: baseline)
|
|
154
|
+
scanner = Keela::Scanner.new(strategy: strategy, baseline: baseline, source_files: source_files)
|
|
143
155
|
success &&= scanner.run(
|
|
144
156
|
force_report: options[:force_report],
|
|
145
157
|
update_baseline: options[:update_baseline],
|
data/lib/keela/scanner.rb
CHANGED
|
@@ -10,11 +10,43 @@ module Keela
|
|
|
10
10
|
DEFAULT_EXCLUDED_PATHS = [".keela/excluded.yml", "keela_excluded.yml"].freeze
|
|
11
11
|
DEFAULT_BASELINE_PATHS = [".keela/baseline.yml", "keela_baseline.yml"].freeze
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# Load source files once for sharing across multiple Scanner instances.
|
|
14
|
+
# Returns a hash of { filename => [lines] }.
|
|
15
|
+
def self.load_source_files(configuration: Keela.configuration)
|
|
16
|
+
source_files = {}
|
|
17
|
+
file_globs = build_file_globs(configuration)
|
|
18
|
+
|
|
19
|
+
Dir.glob(file_globs).each do |filename|
|
|
20
|
+
next if excluded_file?(filename, configuration)
|
|
21
|
+
|
|
22
|
+
source_files[filename] = File.readlines(filename)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
source_files
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.build_file_globs(configuration)
|
|
29
|
+
all_patterns = configuration.directory_patterns + configuration.include_patterns
|
|
30
|
+
|
|
31
|
+
configuration.extensions.flat_map do |ext|
|
|
32
|
+
all_patterns.map { |pattern| format(pattern, ext: ext) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.excluded_file?(filename, configuration)
|
|
37
|
+
return false if configuration.exclude_patterns.empty?
|
|
38
|
+
|
|
39
|
+
configuration.exclude_patterns.any? do |pattern|
|
|
40
|
+
File.fnmatch?(pattern, filename, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def initialize(strategy:, configuration: Keela.configuration, baseline: nil, source_files: nil)
|
|
14
45
|
@strategy = strategy
|
|
15
46
|
@configuration = configuration
|
|
16
47
|
@baseline = baseline || Baseline.new(resolve_baseline_path)
|
|
17
|
-
@source_files = {}
|
|
48
|
+
@source_files = source_files || {}
|
|
49
|
+
@source_files_preloaded = !source_files.nil?
|
|
18
50
|
@unused_collection = Hash.new { |hash, key| hash[key] = [] }
|
|
19
51
|
@new_unused = []
|
|
20
52
|
@removed = []
|
|
@@ -97,6 +129,8 @@ module Keela
|
|
|
97
129
|
end
|
|
98
130
|
|
|
99
131
|
def load_source_files
|
|
132
|
+
return if @source_files_preloaded
|
|
133
|
+
|
|
100
134
|
Dir.glob(file_globs).each do |filename|
|
|
101
135
|
next if excluded_file?(filename)
|
|
102
136
|
|
|
@@ -134,7 +168,16 @@ module Keela
|
|
|
134
168
|
path = resolve_excluded_path
|
|
135
169
|
return definitions unless path
|
|
136
170
|
|
|
137
|
-
|
|
171
|
+
all_excluded = YAML.load_file(path, symbolize_names: true) || {}
|
|
172
|
+
|
|
173
|
+
# Support both formats:
|
|
174
|
+
# 1. Strategy-aware (new): { methods: { "file.rb": [{ name: "reason" }] } }
|
|
175
|
+
# 2. Flat (legacy): { "file.rb": [{ name: "reason" }] }
|
|
176
|
+
excluded = if all_excluded.key?(strategy.name.to_sym)
|
|
177
|
+
all_excluded[strategy.name.to_sym] || {}
|
|
178
|
+
else
|
|
179
|
+
all_excluded # Legacy flat format
|
|
180
|
+
end
|
|
138
181
|
|
|
139
182
|
definitions.reject do |h|
|
|
140
183
|
excluded_for_file = excluded[h[:file].to_sym]
|
data/lib/keela/version.rb
CHANGED