hone 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.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/.standard.yml +8 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +201 -0
  6. data/Rakefile +10 -0
  7. data/examples/.hone/harness.rb +41 -0
  8. data/examples/README.md +22 -0
  9. data/examples/allocation_patterns.rb +66 -0
  10. data/examples/cpu_patterns.rb +50 -0
  11. data/examples/jit_patterns.rb +69 -0
  12. data/exe/hone +7 -0
  13. data/lib/hone/adapters/base.rb +35 -0
  14. data/lib/hone/adapters/fasterer.rb +38 -0
  15. data/lib/hone/adapters/rubocop_performance.rb +85 -0
  16. data/lib/hone/analyzer.rb +258 -0
  17. data/lib/hone/cli.rb +247 -0
  18. data/lib/hone/config.rb +93 -0
  19. data/lib/hone/correlator.rb +250 -0
  20. data/lib/hone/exit_codes.rb +10 -0
  21. data/lib/hone/finding.rb +64 -0
  22. data/lib/hone/finding_filter.rb +57 -0
  23. data/lib/hone/formatters/base.rb +25 -0
  24. data/lib/hone/formatters/filterable.rb +31 -0
  25. data/lib/hone/formatters/github.rb +71 -0
  26. data/lib/hone/formatters/json.rb +75 -0
  27. data/lib/hone/formatters/junit.rb +154 -0
  28. data/lib/hone/formatters/sarif.rb +179 -0
  29. data/lib/hone/formatters/tsv.rb +49 -0
  30. data/lib/hone/harness.rb +57 -0
  31. data/lib/hone/harness_generator.rb +128 -0
  32. data/lib/hone/harness_runner.rb +172 -0
  33. data/lib/hone/method_map.rb +140 -0
  34. data/lib/hone/patterns/README.md +174 -0
  35. data/lib/hone/patterns/array_compact.rb +105 -0
  36. data/lib/hone/patterns/array_include_set.rb +34 -0
  37. data/lib/hone/patterns/base.rb +90 -0
  38. data/lib/hone/patterns/block_to_proc.rb +109 -0
  39. data/lib/hone/patterns/bsearch_vs_find.rb +80 -0
  40. data/lib/hone/patterns/chars_map_ord.rb +42 -0
  41. data/lib/hone/patterns/chars_to_variable.rb +136 -0
  42. data/lib/hone/patterns/chars_to_variable_tainted.rb +136 -0
  43. data/lib/hone/patterns/constant_regexp.rb +74 -0
  44. data/lib/hone/patterns/count_vs_size.rb +35 -0
  45. data/lib/hone/patterns/divmod.rb +92 -0
  46. data/lib/hone/patterns/dynamic_ivar.rb +44 -0
  47. data/lib/hone/patterns/dynamic_ivar_get.rb +33 -0
  48. data/lib/hone/patterns/each_with_index.rb +116 -0
  49. data/lib/hone/patterns/each_with_object.rb +63 -0
  50. data/lib/hone/patterns/flatten_once.rb +28 -0
  51. data/lib/hone/patterns/gsub_to_tr.rb +48 -0
  52. data/lib/hone/patterns/hash_each_key.rb +41 -0
  53. data/lib/hone/patterns/hash_each_value.rb +31 -0
  54. data/lib/hone/patterns/hash_keys_include.rb +30 -0
  55. data/lib/hone/patterns/hash_merge_bang.rb +33 -0
  56. data/lib/hone/patterns/hash_values_include.rb +31 -0
  57. data/lib/hone/patterns/inject_sum.rb +48 -0
  58. data/lib/hone/patterns/kernel_loop.rb +27 -0
  59. data/lib/hone/patterns/lazy_ivar.rb +39 -0
  60. data/lib/hone/patterns/map_compact.rb +32 -0
  61. data/lib/hone/patterns/map_flatten.rb +31 -0
  62. data/lib/hone/patterns/map_select_chain.rb +32 -0
  63. data/lib/hone/patterns/parallel_assignment.rb +127 -0
  64. data/lib/hone/patterns/positive_predicate.rb +27 -0
  65. data/lib/hone/patterns/range_include.rb +34 -0
  66. data/lib/hone/patterns/redundant_string_chars.rb +82 -0
  67. data/lib/hone/patterns/regexp_match.rb +126 -0
  68. data/lib/hone/patterns/reverse_each.rb +30 -0
  69. data/lib/hone/patterns/reverse_first.rb +40 -0
  70. data/lib/hone/patterns/select_count.rb +32 -0
  71. data/lib/hone/patterns/select_first.rb +31 -0
  72. data/lib/hone/patterns/select_map.rb +32 -0
  73. data/lib/hone/patterns/shuffle_first.rb +30 -0
  74. data/lib/hone/patterns/slice_with_length.rb +48 -0
  75. data/lib/hone/patterns/sort_by_first.rb +31 -0
  76. data/lib/hone/patterns/sort_by_last.rb +31 -0
  77. data/lib/hone/patterns/sort_first.rb +52 -0
  78. data/lib/hone/patterns/sort_last.rb +30 -0
  79. data/lib/hone/patterns/sort_reverse.rb +53 -0
  80. data/lib/hone/patterns/string_casecmp.rb +54 -0
  81. data/lib/hone/patterns/string_chars_each.rb +56 -0
  82. data/lib/hone/patterns/string_concat_in_loop.rb +116 -0
  83. data/lib/hone/patterns/string_delete_prefix.rb +53 -0
  84. data/lib/hone/patterns/string_delete_suffix.rb +53 -0
  85. data/lib/hone/patterns/string_empty.rb +64 -0
  86. data/lib/hone/patterns/string_end_with.rb +81 -0
  87. data/lib/hone/patterns/string_shovel.rb +75 -0
  88. data/lib/hone/patterns/string_start_with.rb +80 -0
  89. data/lib/hone/patterns/taint_tracking_base.rb +230 -0
  90. data/lib/hone/patterns/times_map.rb +38 -0
  91. data/lib/hone/patterns/uniq_by.rb +32 -0
  92. data/lib/hone/patterns/yield_vs_block.rb +72 -0
  93. data/lib/hone/profilers/base.rb +162 -0
  94. data/lib/hone/profilers/factory.rb +31 -0
  95. data/lib/hone/profilers/memory_profiler.rb +213 -0
  96. data/lib/hone/profilers/stackprof.rb +99 -0
  97. data/lib/hone/profilers/vernier.rb +147 -0
  98. data/lib/hone/reporter.rb +371 -0
  99. data/lib/hone/scanner.rb +75 -0
  100. data/lib/hone/suggestion_generator.rb +23 -0
  101. data/lib/hone/version.rb +5 -0
  102. data/lib/hone.rb +108 -0
  103. data/logo.png +0 -0
  104. data/sig/hone.rbs +4 -0
  105. metadata +176 -0
@@ -0,0 +1,371 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hone
4
+ class Reporter
5
+ include Formatters::Filterable
6
+
7
+ # ANSI color codes
8
+ COLORS = {
9
+ reset: "\e[0m",
10
+ bold: "\e[1m",
11
+ dim: "\e[2m",
12
+ red: "\e[31m",
13
+ green: "\e[32m",
14
+ yellow: "\e[33m",
15
+ cyan: "\e[36m",
16
+ white: "\e[37m",
17
+ bright_red: "\e[91m",
18
+ gray: "\e[90m"
19
+ }.freeze
20
+
21
+ # Priority display configuration
22
+ PRIORITY_CONFIG = {
23
+ hot_cpu: {label: "High impact", color: %i[bold bright_red], metric: :cpu},
24
+ hot_alloc: {label: "High impact", color: %i[bold red], metric: :alloc},
25
+ jit_unfriendly: {label: "JIT issue", color: %i[bold yellow], metric: nil},
26
+ warm: {label: "Moderate", color: [:yellow], metric: nil},
27
+ cold: {label: "Low", color: %i[dim gray], metric: nil},
28
+ unknown: {label: "Unknown", color: [:dim], metric: nil}
29
+ }.freeze
30
+
31
+ SEPARATOR_WIDTH = 72
32
+ CONTEXT_LINES = 2
33
+
34
+ def initialize(findings, options = {})
35
+ @findings = findings
36
+ @show_cold = options.fetch(:show_cold, false)
37
+ @quiet = options.fetch(:quiet, false)
38
+ @verbose = options.fetch(:verbose, false)
39
+ @color = options.fetch(:color, $stdout.tty? && !ENV["NO_COLOR"])
40
+ @profile_path = options[:profile_path]
41
+ @file_path = options[:file_path]
42
+ @hot_threshold = options.fetch(:hot_threshold, Correlator::HOT_THRESHOLD)
43
+ @warm_threshold = options.fetch(:warm_threshold, Correlator::WARM_THRESHOLD)
44
+ @source_cache = {}
45
+ end
46
+
47
+ def format
48
+ output = []
49
+ output << header unless @quiet
50
+ output << findings_output
51
+ output << summary
52
+ output.flatten.compact.join("\n")
53
+ end
54
+
55
+ alias_method :report, :format
56
+
57
+ private
58
+
59
+ def header
60
+ lines = []
61
+ lines << colorize("Hone v#{VERSION}", :bold)
62
+ lines << ""
63
+
64
+ context_parts = []
65
+ context_parts << "Analyzing #{@file_path}" if @file_path
66
+ context_parts << "with profile: #{@profile_path}" if @profile_path
67
+
68
+ lines << context_parts.join(" ") unless context_parts.empty?
69
+ lines
70
+ end
71
+
72
+ def findings_output
73
+ visible_findings.map { |finding| format_finding(finding) }
74
+ end
75
+
76
+ def visible_findings
77
+ filter_cold(@findings, show_cold: @show_cold)
78
+ end
79
+
80
+ def format_finding(finding)
81
+ if @quiet
82
+ format_finding_quiet(finding)
83
+ elsif @verbose
84
+ format_finding_verbose(finding)
85
+ else
86
+ format_finding_default(finding)
87
+ end
88
+ end
89
+
90
+ # Quiet mode: single line per finding
91
+ def format_finding_quiet(finding)
92
+ location = "#{finding.file}:#{finding.line}"
93
+ metric = format_primary_metric(finding)
94
+ original = finding.code&.strip&.split("\n")&.first&.strip || ""
95
+ suggestion = SuggestionGenerator.generate(finding.pattern_id, original)
96
+
97
+ if suggestion && suggestion != original
98
+ # Shorten for display
99
+ original_short = truncate(original, 30)
100
+ suggestion_short = truncate(suggestion, 30)
101
+ transform = "#{original_short} → #{suggestion_short}"
102
+ else
103
+ transform = truncate(original, 40)
104
+ end
105
+
106
+ "#{location} #{metric} #{transform}"
107
+ end
108
+
109
+ # Default mode: balanced detail with source context
110
+ def format_finding_default(finding)
111
+ lines = []
112
+ lines << separator
113
+ lines << ""
114
+
115
+ # Location and method
116
+ location = colorize("#{finding.file}:#{finding.line}", :cyan)
117
+ method_info = finding.method_name ? " in `#{finding.method_name}`" : ""
118
+ lines << "#{location}#{method_info}"
119
+
120
+ # Metrics with impact label
121
+ lines << format_metrics_line(finding)
122
+ lines << ""
123
+
124
+ # Source context with line numbers
125
+ lines.concat(format_source_context(finding))
126
+ lines << ""
127
+
128
+ # Fix suggestion
129
+ lines.concat(format_fix_suggestion(finding))
130
+ lines << ""
131
+
132
+ lines
133
+ end
134
+
135
+ # Verbose mode: full detail with extended explanation
136
+ def format_finding_verbose(finding)
137
+ lines = []
138
+ lines << separator
139
+ lines << ""
140
+
141
+ # Location, method, and pattern info
142
+ location = colorize("#{finding.file}:#{finding.line}", :cyan)
143
+ method_info = finding.method_name ? " in `#{finding.method_name}`" : ""
144
+ lines << "#{location}#{method_info}"
145
+
146
+ # Metrics
147
+ lines << format_metrics_line(finding)
148
+
149
+ # Pattern metadata
150
+ pattern_info = "Pattern: #{finding.pattern_id}"
151
+ pattern_info += " Type: #{finding.optimization_type}" if finding.optimization_type
152
+ lines << colorize(pattern_info, :dim)
153
+ lines << ""
154
+
155
+ # Source context (more lines in verbose)
156
+ lines.concat(format_source_context(finding, context_lines: 3))
157
+ lines << ""
158
+
159
+ # Fix suggestion with full explanation
160
+ lines.concat(format_fix_suggestion(finding, include_explanation: true))
161
+ lines << ""
162
+
163
+ lines
164
+ end
165
+
166
+ def format_metrics_line(finding)
167
+ priority = finding.priority || :unknown
168
+ config = PRIORITY_CONFIG[priority]
169
+
170
+ parts = []
171
+
172
+ # Primary metric
173
+ if finding.alloc_percent && finding.alloc_percent > 0
174
+ parts << "#{format_percent(finding.alloc_percent)} of allocations"
175
+ end
176
+ if finding.cpu_percent && finding.cpu_percent > 0
177
+ parts << "#{format_percent(finding.cpu_percent)} of CPU"
178
+ end
179
+
180
+ metric_str = parts.any? ? parts.join(", ") : nil
181
+
182
+ # Impact label
183
+ impact = colorize_multi(config[:label], config[:color])
184
+
185
+ if metric_str
186
+ "#{metric_str} — #{impact}"
187
+ else
188
+ impact
189
+ end
190
+ end
191
+
192
+ def format_source_context(finding, context_lines: CONTEXT_LINES)
193
+ lines = []
194
+ source_lines = read_source_lines(finding.file)
195
+ return lines if source_lines.empty?
196
+
197
+ target_line = finding.line
198
+ start_line = [target_line - context_lines, 1].max
199
+ end_line = [target_line + context_lines, source_lines.size].min
200
+
201
+ # Calculate line number width for alignment
202
+ width = end_line.to_s.length
203
+
204
+ (start_line..end_line).each do |line_num|
205
+ line_content = source_lines[line_num - 1] || ""
206
+ line_content = line_content.chomp
207
+
208
+ if line_num == target_line
209
+ # Highlight the target line
210
+ prefix = colorize("#{line_num.to_s.rjust(width)} │ ", :dim)
211
+ lines << "#{prefix}#{line_content}"
212
+
213
+ # Add caret marker
214
+ if finding.column && finding.column > 0
215
+ caret_padding = " " * (width + 3 + finding.column)
216
+ code_length = [finding.code&.split("\n")&.first&.length || 5, 3].max
217
+ carets = colorize("^" * [code_length, 40].min, :bright_red)
218
+ lines << "#{caret_padding}#{carets}"
219
+ end
220
+ else
221
+ prefix = colorize("#{line_num.to_s.rjust(width)} │ ", :dim)
222
+ lines << colorize("#{prefix}#{line_content}", :dim)
223
+ end
224
+ end
225
+
226
+ lines
227
+ end
228
+
229
+ def format_fix_suggestion(finding, include_explanation: false)
230
+ lines = []
231
+ original_code = finding.code&.strip&.split("\n")&.first&.strip || ""
232
+ suggested_code = SuggestionGenerator.generate(finding.pattern_id, original_code)
233
+
234
+ if suggested_code && suggested_code != original_code
235
+ lines << "#{colorize("Fix:", :bold)} #{colorize(suggested_code, :green)}"
236
+ end
237
+
238
+ # Explanation (merged Why/Speedup into single line)
239
+ explanation = finding.message
240
+ if explanation
241
+ lines << " #{explanation}"
242
+ end
243
+
244
+ lines
245
+ end
246
+
247
+ def format_primary_metric(finding)
248
+ if finding.alloc_percent && finding.alloc_percent >= 1
249
+ "#{format_percent(finding.alloc_percent)} alloc"
250
+ elsif finding.cpu_percent && finding.cpu_percent >= 1
251
+ "#{format_percent(finding.cpu_percent)} CPU"
252
+ elsif finding.alloc_percent
253
+ "#{format_percent(finding.alloc_percent)} alloc"
254
+ elsif finding.cpu_percent
255
+ "#{format_percent(finding.cpu_percent)} CPU"
256
+ else
257
+ "?"
258
+ end
259
+ end
260
+
261
+ def summary
262
+ stats = calculate_stats
263
+ build_summary_output(stats)
264
+ end
265
+
266
+ def calculate_stats
267
+ counts = @findings.map { |f| f.priority || :unknown }.tally
268
+ {
269
+ total: @findings.size,
270
+ hot_cpu: counts[:hot_cpu] || 0,
271
+ hot_alloc: counts[:hot_alloc] || 0,
272
+ jit_unfriendly: counts[:jit_unfriendly] || 0,
273
+ warm: counts[:warm] || 0,
274
+ cold: counts[:cold] || 0,
275
+ unknown: counts[:unknown] || 0
276
+ }
277
+ end
278
+
279
+ def build_summary_output(stats)
280
+ lines = []
281
+ lines << separator
282
+ lines << ""
283
+
284
+ total = stats[:total]
285
+ high_impact = stats[:hot_cpu] + stats[:hot_alloc]
286
+
287
+ if @quiet
288
+ parts = []
289
+ parts << "#{high_impact} high" if high_impact > 0
290
+ parts << "#{stats[:jit_unfriendly]} jit" if stats[:jit_unfriendly] > 0
291
+ parts << "#{stats[:warm]} moderate" if stats[:warm] > 0
292
+ parts << "#{stats[:cold]} low" if stats[:cold] > 0
293
+ lines << "#{total} findings: #{parts.join(", ")}"
294
+ return lines
295
+ end
296
+
297
+ lines << colorize("Summary: #{total} #{(total == 1) ? "finding" : "findings"}", :bold)
298
+ lines << ""
299
+
300
+ if high_impact > 0
301
+ label = colorize_multi("#{high_impact} high impact", PRIORITY_CONFIG[:hot_cpu][:color])
302
+ arrow = colorize("← fix these first", :bold)
303
+ lines << " #{label} (>#{@hot_threshold.to_i}% of CPU or allocations) #{arrow}"
304
+ end
305
+
306
+ if stats[:jit_unfriendly] > 0
307
+ label = colorize_multi("#{stats[:jit_unfriendly]} JIT issues", PRIORITY_CONFIG[:jit_unfriendly][:color])
308
+ lines << " #{label} (may hurt YJIT optimization)"
309
+ end
310
+
311
+ if stats[:warm] > 0
312
+ label = colorize_multi("#{stats[:warm]} moderate", PRIORITY_CONFIG[:warm][:color])
313
+ lines << " #{label} (#{@warm_threshold.to_i}-#{@hot_threshold.to_i}%)"
314
+ end
315
+
316
+ if stats[:cold] > 0
317
+ label = colorize_multi("#{stats[:cold]} low", PRIORITY_CONFIG[:cold][:color])
318
+ hint = @show_cold ? "" : " (use --show-cold to display)"
319
+ lines << " #{label} (<#{@warm_threshold.to_i}%)#{hint}"
320
+ end
321
+
322
+ if stats[:unknown] > 0
323
+ label = colorize_multi("#{stats[:unknown]} unknown", PRIORITY_CONFIG[:unknown][:color])
324
+ lines << " #{label} (no profile data)"
325
+ end
326
+
327
+ if @verbose
328
+ lines << ""
329
+ lines << colorize("Percentages show share of total profiled CPU time or allocations.", :dim)
330
+ end
331
+
332
+ lines << ""
333
+ lines
334
+ end
335
+
336
+ def read_source_lines(file_path)
337
+ return @source_cache[file_path] if @source_cache.key?(file_path)
338
+
339
+ @source_cache[file_path] = if File.exist?(file_path)
340
+ File.readlines(file_path)
341
+ else
342
+ []
343
+ end
344
+ end
345
+
346
+ def separator
347
+ colorize("─" * SEPARATOR_WIDTH, :dim)
348
+ end
349
+
350
+ def truncate(str, max_length)
351
+ return str if str.length <= max_length
352
+ "#{str[0, max_length - 3]}..."
353
+ end
354
+
355
+ def colorize(text, *colors)
356
+ return text unless @color
357
+
358
+ codes = colors.flatten.map { |c| COLORS[c] }.compact.join
359
+ "#{codes}#{text}#{COLORS[:reset]}"
360
+ end
361
+
362
+ def colorize_multi(text, colors)
363
+ colorize(text, *colors)
364
+ end
365
+
366
+ def format_percent(value)
367
+ return "0%" if value.nil? || value == 0
368
+ value < 0.1 ? "<0.1%" : "#{value.round(1)}%"
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Hone
6
+ # Scans Ruby files for optimization patterns using Prism AST analysis.
7
+ # Coordinates multiple pattern matchers and collects their findings.
8
+ class Scanner
9
+ # Default glob pattern for finding Ruby files
10
+ DEFAULT_RUBY_GLOB = "**/*.rb"
11
+
12
+ def initialize(patterns: Patterns.registered, rails: false)
13
+ @rails = rails
14
+ @patterns = patterns.select do |pattern|
15
+ # Skip abstract base classes (no pattern_id)
16
+ next false if pattern.pattern_id.nil?
17
+
18
+ if @rails
19
+ true
20
+ else
21
+ pattern.scope.nil? || pattern.scope == :ruby
22
+ end
23
+ end
24
+ end
25
+
26
+ # Scan a single file with all patterns
27
+ # Returns an array of findings from all pattern matchers
28
+ def scan_file(path)
29
+ result = Prism.parse_file(path)
30
+
31
+ if result.errors.any?
32
+ warn "Parse errors in #{path}:"
33
+ result.errors.each { |e| warn " #{e.message}" }
34
+ return []
35
+ end
36
+
37
+ @patterns.flat_map do |pattern_class|
38
+ matcher = pattern_class.new(path)
39
+ result.value.accept(matcher)
40
+ matcher.findings
41
+ end
42
+ end
43
+
44
+ # Scan all Ruby files in a directory
45
+ # Returns an array of findings from all files
46
+ def scan_directory(path, pattern: DEFAULT_RUBY_GLOB)
47
+ files = Dir.glob(File.join(path, pattern))
48
+
49
+ files.flat_map do |file|
50
+ scan_file(file)
51
+ end
52
+ end
53
+
54
+ # Scan multiple files
55
+ # Returns an array of findings from all files
56
+ def scan_files(paths)
57
+ paths.flat_map { |path| scan_file(path) }
58
+ end
59
+
60
+ # Group findings by file
61
+ def self.group_by_file(findings)
62
+ findings.group_by(&:file)
63
+ end
64
+
65
+ # Group findings by pattern type
66
+ def self.group_by_pattern(findings)
67
+ findings.group_by(&:pattern_id)
68
+ end
69
+
70
+ # Group findings by optimization type (:cpu, :allocation, :jit)
71
+ def self.group_by_optimization_type(findings)
72
+ findings.group_by(&:optimization_type)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hone
4
+ class SuggestionGenerator
5
+ TRANSFORMS = {
6
+ positive_predicate: ->(code) { code.gsub(".positive?", " > 0") },
7
+ negative_predicate: ->(code) { code.gsub(".negative?", " < 0") },
8
+ zero_predicate: ->(code) { code.gsub(".zero?", " == 0") },
9
+ map_select_chain: ->(code) { code.gsub(/\.map\s*[({].*?[)}]\s*\.select/, ".filter_map") },
10
+ kernel_loop: ->(code) { code.gsub(/\bloop\s*(?:do|\{)/, "while true do") },
11
+ chars_map_ord: ->(code) { code.gsub(/\.chars\.map\s*\(&:ord\)/, ".bytes") },
12
+ dynamic_ivar: ->(code) { code },
13
+ dynamic_ivar_get: ->(code) { code },
14
+ lazy_ivar: ->(code) { code.gsub(/defined\?\(@\w+\)\s*\?\s*@(\w+)\s*:/, '@\1 ||=') },
15
+ slice_with_length: ->(code) { code.gsub(/\[(\d+),\s*\w+\.(?:length|size)\]/, '[\1..]') }
16
+ }.freeze
17
+
18
+ def self.generate(pattern_id, code)
19
+ transform = TRANSFORMS[pattern_id]
20
+ transform ? transform.call(code) : code
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hone
4
+ VERSION = "0.1.0"
5
+ end
data/lib/hone.rb ADDED
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hone/version"
4
+ require_relative "hone/exit_codes"
5
+ require_relative "hone/finding"
6
+ require_relative "hone/method_map"
7
+
8
+ # Patterns
9
+ require_relative "hone/patterns/base"
10
+ require_relative "hone/patterns/positive_predicate"
11
+ require_relative "hone/patterns/kernel_loop"
12
+ require_relative "hone/patterns/slice_with_length"
13
+ require_relative "hone/patterns/chars_map_ord"
14
+ require_relative "hone/patterns/map_select_chain"
15
+ require_relative "hone/patterns/dynamic_ivar"
16
+ require_relative "hone/patterns/dynamic_ivar_get"
17
+ require_relative "hone/patterns/lazy_ivar"
18
+ require_relative "hone/patterns/each_with_index"
19
+ require_relative "hone/patterns/string_concat_in_loop"
20
+ require_relative "hone/patterns/reverse_each"
21
+ require_relative "hone/patterns/hash_each_key"
22
+ require_relative "hone/patterns/shuffle_first"
23
+ require_relative "hone/patterns/sort_first"
24
+ require_relative "hone/patterns/count_vs_size"
25
+ require_relative "hone/patterns/map_flatten"
26
+ require_relative "hone/patterns/inject_sum"
27
+ require_relative "hone/patterns/gsub_to_tr"
28
+ require_relative "hone/patterns/hash_keys_include"
29
+ require_relative "hone/patterns/select_count"
30
+ require_relative "hone/patterns/range_include"
31
+ require_relative "hone/patterns/select_first"
32
+ require_relative "hone/patterns/string_chars_each"
33
+ require_relative "hone/patterns/redundant_string_chars"
34
+ require_relative "hone/patterns/parallel_assignment"
35
+ require_relative "hone/patterns/string_shovel"
36
+ require_relative "hone/patterns/yield_vs_block"
37
+ require_relative "hone/patterns/sort_by_first"
38
+ require_relative "hone/patterns/sort_last"
39
+ require_relative "hone/patterns/sort_by_last"
40
+ require_relative "hone/patterns/hash_values_include"
41
+ require_relative "hone/patterns/hash_each_value"
42
+ require_relative "hone/patterns/hash_merge_bang"
43
+ require_relative "hone/patterns/string_delete_prefix"
44
+ require_relative "hone/patterns/string_delete_suffix"
45
+ require_relative "hone/patterns/string_casecmp"
46
+ require_relative "hone/patterns/string_start_with"
47
+ require_relative "hone/patterns/string_end_with"
48
+ require_relative "hone/patterns/string_empty"
49
+ require_relative "hone/patterns/map_compact"
50
+ require_relative "hone/patterns/select_map"
51
+ require_relative "hone/patterns/reverse_first"
52
+ require_relative "hone/patterns/times_map"
53
+ require_relative "hone/patterns/each_with_object"
54
+ require_relative "hone/patterns/block_to_proc"
55
+ require_relative "hone/patterns/regexp_match"
56
+ require_relative "hone/patterns/constant_regexp"
57
+ require_relative "hone/patterns/sort_reverse"
58
+ require_relative "hone/patterns/array_compact"
59
+ require_relative "hone/patterns/bsearch_vs_find"
60
+ require_relative "hone/patterns/divmod"
61
+ require_relative "hone/patterns/flatten_once"
62
+ require_relative "hone/patterns/uniq_by"
63
+ require_relative "hone/patterns/array_include_set"
64
+ require_relative "hone/patterns/taint_tracking_base"
65
+ require_relative "hone/patterns/chars_to_variable"
66
+ require_relative "hone/patterns/chars_to_variable_tainted"
67
+
68
+ require_relative "hone/scanner"
69
+
70
+ # Adapters
71
+ require_relative "hone/adapters/base"
72
+ require_relative "hone/adapters/fasterer"
73
+ require_relative "hone/adapters/rubocop_performance"
74
+
75
+ # Profilers
76
+ require_relative "hone/profilers/base"
77
+ require_relative "hone/profilers/stackprof"
78
+ require_relative "hone/profilers/vernier"
79
+ require_relative "hone/profilers/memory_profiler"
80
+ require_relative "hone/profilers/factory"
81
+
82
+ # Correlation
83
+ require_relative "hone/correlator"
84
+ require_relative "hone/finding_filter"
85
+
86
+ # Reporting
87
+ require_relative "hone/suggestion_generator"
88
+ require_relative "hone/formatters/filterable"
89
+ require_relative "hone/formatters/base"
90
+ require_relative "hone/reporter"
91
+ require_relative "hone/formatters/json"
92
+ require_relative "hone/formatters/github"
93
+ require_relative "hone/formatters/sarif"
94
+ require_relative "hone/formatters/junit"
95
+ require_relative "hone/formatters/tsv"
96
+
97
+ # Orchestration
98
+ require_relative "hone/analyzer"
99
+
100
+ # Harness
101
+ require_relative "hone/harness"
102
+ require_relative "hone/harness_runner"
103
+ require_relative "hone/harness_generator"
104
+ require_relative "hone/config"
105
+
106
+ module Hone
107
+ class Error < StandardError; end
108
+ end
data/logo.png ADDED
Binary file
data/sig/hone.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Hone
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end