tng 0.4.5 → 0.4.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70161861ae22db154e31792a86414ad76d14d56d6a5c7ea14de126e26bf2e7a5
4
- data.tar.gz: ee5bc28ae08666fa789ff0ca21c67735340c01e850b11afcbf34e80fa0d80d3a
3
+ metadata.gz: a4de2429d2ed196847183d8142115e1b99266d474704b0d2b9dfc6a794b1904c
4
+ data.tar.gz: 001e994ec388b5e5bed712a506f6acb3267c0d163377390bff45c87e980fa52b
5
5
  SHA512:
6
- metadata.gz: ebd0fb7dbcbbb795810ae2da51ef4333c0a3e2d226c0cb5f5c5f3de7a965899ef099f7e0874c12f1a040c6f47a8db220b3658d12d5cae8b86b4827d4ade12691
7
- data.tar.gz: 87e2aee29fbd86bb25aa2519d1e602b96bfc45a7d9a63ffd3e91697a9dae5eb4503bdf30c0b345b8de7641b91ee947b8c6b2b48a5b3da3f78a03024bcb636e93
6
+ metadata.gz: 58dd488f1924aa1680f0f3f327042af5133bfc92f2c9eb56540f5b49d89f24905cd20a8baa88deb69d8cbe3bb519693a6b8fea98e14a852bbe5731cb4343d0ed
7
+ data.tar.gz: 1e45869a87e3787f642bdeb245daa1b4776187b73a4a07712e81aa6518b7e9720c41d8ab1747460df5b12d803e158eb778fbc4c96816932aadaf93fe5e729369
data/bin/tng CHANGED
@@ -44,6 +44,10 @@ class CLI
44
44
  example " bundle exec tng app/controllers/users_controller.rb index", ""
45
45
  example " bundle exec tng f=users_controller.rb m=show", ""
46
46
  example " bundle exec tng --file=app/models/user.rb --method=validate", ""
47
+ example ""
48
+ example "Duplicate Detection:", ""
49
+ example " bundle exec tng app/services/pricing_service.rb --clones", ""
50
+ example " bundle exec tng --file=users_controller.rb --clones --level=2", ""
47
51
  end
48
52
 
49
53
  option :file do
@@ -58,6 +62,18 @@ class CLI
58
62
  desc "Method name (for per-method tests)"
59
63
  end
60
64
 
65
+ flag :clones do
66
+ short "-c"
67
+ long "--clones"
68
+ desc "Run in clone detection mode (find code duplication)"
69
+ end
70
+
71
+ option :level do
72
+ short "-l"
73
+ long "--level=LEVEL"
74
+ desc "Analysis level: 1 (Token), 2 (Structural), all (Both)"
75
+ end
76
+
61
77
  flag :audit do
62
78
  short "-a"
63
79
  long "--audit"
@@ -70,6 +86,7 @@ class CLI
70
86
  end
71
87
 
72
88
  flag :json do
89
+ short "-j"
73
90
  long "--json"
74
91
  desc "Output results in JSON format"
75
92
  end
@@ -88,8 +105,8 @@ class CLI
88
105
 
89
106
  def initialize
90
107
  @pastel = Pastel.new
91
- # Check for --json flag raw before parsing
92
- if ARGV.any? { |arg| arg == "--json" }
108
+ # Check for --json or -j flag raw before parsing
109
+ if ARGV.any? { |arg| arg == "--json" || arg == "-j" }
93
110
  require "tng/ui/json_session"
94
111
  @go_ui = Tng::UI::JsonSession.new
95
112
  else
@@ -158,10 +175,14 @@ class CLI
158
175
  normalized << "--audit"
159
176
  when /^(?:--)?(trace)$/
160
177
  normalized << "--trace"
161
- when /^(?:--)?(json)$/
178
+ when /^(?:--)?(json|j)$/
162
179
  normalized << "--json"
163
180
  when /^(?:--)?(fix|x)$/
164
181
  normalized << "--fix"
182
+ when /^(?:--)?(clones|c)$/
183
+ normalized << "--clones"
184
+ when /^(?:--)?(level|l)=(.+)$/
185
+ normalized << "--level=#{::Regexp.last_match(2)}"
165
186
  when /^(help|h)=(.+)$/
166
187
  normalized << "--help=#{::Regexp.last_match(2)}"
167
188
  when /^--file$/, /^-f$/
@@ -219,6 +240,8 @@ class CLI
219
240
  handle_audit_method
220
241
  when "trace"
221
242
  handle_trace_method
243
+ when "clones"
244
+ handle_clone_detection
222
245
  when "stats"
223
246
  user_stats
224
247
  when "about"
@@ -249,6 +272,142 @@ class CLI
249
272
  end
250
273
  end
251
274
 
275
+ def handle_clone_detection
276
+ # Pick a file type first to narrow down
277
+ choice = @go_ui.show_test_type_menu("clones")
278
+ return if choice == "back"
279
+
280
+ case choice
281
+ when "controller"
282
+ detect_controller_clones
283
+ when "model"
284
+ detect_model_clones
285
+ when "service"
286
+ detect_service_clones
287
+ when "other"
288
+ detect_other_clones
289
+ end
290
+ end
291
+
292
+ def detect_controller_clones
293
+ controllers = nil
294
+ @go_ui.show_spinner("Analyzing controllers...") do
295
+ controllers = Tng::Analyzers::Controller.files_in_dir("app/controllers").map do |file|
296
+ relative_path = file[:path].gsub(%r{^.*app/controllers/}, "").gsub(".rb", "")
297
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
298
+ { name: namespaced_name, path: file[:path] }
299
+ end
300
+ { success: true, message: "Found #{controllers.length} controllers" }
301
+ end
302
+
303
+ return @go_ui.show_no_items("controllers") if controllers.empty?
304
+
305
+ items = controllers.map { |c| { name: c[:name], path: c[:path] } }
306
+ selected = @go_ui.show_list_view("Select Controller to Check Duplicates", items)
307
+ return if selected == "back"
308
+
309
+ choice = controllers.find { |c| c[:name] == selected }
310
+ return unless choice
311
+
312
+ level = @go_ui.show_clone_menu
313
+ return if level == "back"
314
+
315
+ run_clone_detection_for_file(choice, level)
316
+ end
317
+
318
+ def detect_model_clones
319
+ models = nil
320
+ @go_ui.show_spinner("Analyzing models...") do
321
+ models = Tng::Analyzers::Model.files_in_dir("app/models").map do |file|
322
+ relative_path = file[:path].gsub(%r{^.*app/models/}, "").gsub(".rb", "")
323
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
324
+ { name: namespaced_name, path: file[:path] }
325
+ end
326
+ { success: true, message: "Found #{models.length} models" }
327
+ end
328
+
329
+ return @go_ui.show_no_items("models") if models.empty?
330
+
331
+ items = models.map { |m| { name: m[:name], path: m[:path] } }
332
+ selected = @go_ui.show_list_view("Select Model to Check Duplicates", items)
333
+ return if selected == "back"
334
+
335
+ choice = models.find { |m| m[:name] == selected }
336
+ return unless choice
337
+
338
+ level = @go_ui.show_clone_menu
339
+ return if level == "back"
340
+
341
+ run_clone_detection_for_file(choice, level)
342
+ end
343
+
344
+ def detect_service_clones
345
+ services = nil
346
+ @go_ui.show_spinner("Analyzing services...") do
347
+ services = Tng::Analyzers::Service.files_in_dir.map do |file|
348
+ relative_path = file[:path].gsub(%r{^.*app/services?/}, "").gsub(".rb", "")
349
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
350
+ { name: namespaced_name, path: file[:path] }
351
+ end
352
+ { success: true, message: "Found #{services.length} services" }
353
+ end
354
+
355
+ return @go_ui.show_no_items("services") if services.empty?
356
+
357
+ items = services.map { |s| { name: s[:name], path: s[:path] } }
358
+ selected = @go_ui.show_list_view("Select Service to Check Duplicates", items)
359
+ return if selected == "back"
360
+
361
+ choice = services.find { |s| s[:name] == selected }
362
+ return unless choice
363
+
364
+ level = @go_ui.show_clone_menu
365
+ return if level == "back"
366
+
367
+ run_clone_detection_for_file(choice, level)
368
+ end
369
+
370
+ def detect_other_clones
371
+ files = nil
372
+ @go_ui.show_spinner("Analyzing other files...") do
373
+ files = Tng::Analyzers::Other.files_in_dir.map do |file|
374
+ relative_path = file[:relative_path].gsub(".rb", "")
375
+ namespaced_name = relative_path.split("/").map(&:camelize).join("::")
376
+ { name: namespaced_name, path: file[:path], type: file[:type] }
377
+ end
378
+ { success: true, message: "Found #{files.length} other files" }
379
+ end
380
+
381
+ return @go_ui.show_no_items("other files") if files.empty?
382
+
383
+ items = files.map { |f| { name: f[:name], path: f[:path] } }
384
+ selected = @go_ui.show_list_view("Select File to Check Duplicates", items)
385
+ return if selected == "back"
386
+
387
+ choice = files.find { |f| f[:name] == selected }
388
+ return unless choice
389
+
390
+ level = @go_ui.show_clone_menu
391
+ return if level == "back"
392
+
393
+ run_clone_detection_for_file(choice, level)
394
+ end
395
+
396
+ def run_clone_detection_for_file(file_info, level = "all")
397
+ result = nil
398
+ @go_ui.show_progress("Analyzing #{file_info[:name]} for duplicates (Level #{level})") do |progress|
399
+ progress.update("Parsing file structure...", 50)
400
+
401
+ matches = Tng::Analyzer::Context.analyze_clones(Dir.pwd, File.expand_path(file_info[:path]), level)
402
+ result = { matches: matches }
403
+
404
+ progress.update("Analysis complete!", 100)
405
+ { message: "Found #{matches.length} duplicate blocks" }
406
+ end
407
+
408
+ @go_ui.show_clones(file_info[:path], result) if result
409
+ end
410
+
252
411
  def handle_audit_method
253
412
  choice = @go_ui.show_test_type_menu("audit")
254
413
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
data/binaries/tng.bundle CHANGED
Binary file
@@ -28,11 +28,10 @@ module Tng
28
28
  file_path = @params[:file]
29
29
  method_name = @params[:method]
30
30
 
31
- unless file_path && method_name
32
- @go_ui.display_error(@pastel.red("❌ Both file and method parameters are required"))
31
+ unless file_path && (method_name || @params[:clones])
32
+ @go_ui.display_error(@pastel.red("❌ File parameter is required#{@params[:clones] ? "" : " along with method"}"))
33
33
  @go_ui.display_warning(@pastel.yellow("Usage: bundle exec tng app/controllers/users_controller.rb index"))
34
- @go_ui.display_warning(@pastel.yellow(" or: bundle exec tng --file=users_controller.rb --method=index"))
35
- @go_ui.display_warning(@pastel.yellow(" or: bundle exec tng f=users_controller.rb m=index"))
34
+ @go_ui.display_warning(@pastel.yellow(" or: bundle exec tng --file=users_controller.rb --clones [--level=1|2|all]"))
36
35
  return
37
36
  end
38
37
 
@@ -45,7 +44,12 @@ module Tng
45
44
  end
46
45
 
47
46
  type = FileTypeDetector.detect_type(resolved_path)
48
- generate_test_for_file(resolved_path, method_name, type)
47
+
48
+ if @params[:clones]
49
+ run_clones_for_file(resolved_path, type)
50
+ else
51
+ generate_test_for_file(resolved_path, method_name, type)
52
+ end
49
53
  end
50
54
 
51
55
  def suggest_similar_files(file_path)
@@ -114,6 +118,21 @@ module Tng
114
118
  end
115
119
  end
116
120
 
121
+ def run_clones_for_file(file_path, type)
122
+ file_object = FileTypeDetector.build_file_object(file_path, type)
123
+ @go_ui.display_info(@pastel.bright_white("🎯 Analyzing clones in #{file_object[:name]}..."))
124
+
125
+ result = @go_ui.show_progress("Processing...") do |progress|
126
+ perform_clone_analysis(file_object, progress)
127
+ end
128
+
129
+ if result&.dig(:error)
130
+ @go_ui.display_error(@pastel.red("❌ Clone analysis failed: #{result[:message]}"))
131
+ else
132
+ @go_ui.show_clones(file_object[:path], result)
133
+ end
134
+ end
135
+
117
136
  def extract_methods_for_type(file_object, type)
118
137
  case type
119
138
  when "controller" then extract_controller_methods(file_object)
@@ -145,6 +164,22 @@ module Tng
145
164
  end
146
165
  end
147
166
 
167
+ def perform_clone_analysis(file_object, progress)
168
+ progress.update("Analyzing file for duplicates...", 50)
169
+
170
+ project_root = Dir.pwd
171
+ path = File.expand_path(file_object[:path])
172
+
173
+ level = @params[:level] || "all"
174
+ begin
175
+ matches = Tng::Analyzer::Context.analyze_clones(project_root, path, level)
176
+ progress.update("Analysis complete!", 100)
177
+ { matches: matches }
178
+ rescue => e
179
+ { error: true, message: e.message }
180
+ end
181
+ end
182
+
148
183
  def perform_trace_analysis(file_object, method_info, progress)
149
184
  progress.update("Tracing method execution...", 50)
150
185
 
@@ -65,6 +65,25 @@ module Tng
65
65
  File.unlink(output_path) if File.exist?(output_path)
66
66
  end
67
67
  end
68
+ # Returns: "1", "2", "all", "back"
69
+ def show_clone_menu
70
+ output_file = Tempfile.new(["go_ui_clone_menu", ".json"])
71
+ output_path = output_file.path
72
+ output_file.close
73
+
74
+ begin
75
+ system(@binary_path, "clone-menu", "--output", output_path)
76
+
77
+ return "back" unless File.exist?(output_path) && File.size(output_path) > 0
78
+
79
+ choice = File.read(output_path).strip
80
+ choice.empty? ? "back" : choice
81
+ rescue StandardError
82
+ "back"
83
+ ensure
84
+ File.unlink(output_path) if File.exist?(output_path)
85
+ end
86
+ end
68
87
 
69
88
  # @return [String] Selected item name or "back"
70
89
  def show_list_view(title, items)
@@ -261,6 +280,25 @@ module Tng
261
280
  puts "Trace results error: #{e.message}"
262
281
  end
263
282
 
283
+ # Show clone detection results
284
+ # @param file_path [String] Original file path analyzed
285
+ # @param results [Hash] Clone detection results { matches: [...] }
286
+ def show_clones(file_path, results)
287
+ data = { file_path: file_path, matches: results[:matches] }
288
+ data_json = JSON.generate(data)
289
+
290
+ input_file = Tempfile.new(["go_ui_clones", ".json"])
291
+ input_path = input_file.path
292
+ File.write(input_path, data_json)
293
+ input_file.close
294
+
295
+ system(@binary_path, "clones", "--file", input_path)
296
+ rescue StandardError => e
297
+ puts "Clones results error: #{e.message}"
298
+ ensure
299
+ File.unlink(input_path) if input_path && File.exist?(input_path)
300
+ end
301
+
264
302
  # Show authentication error
265
303
  # @param message [String] Error message to display
266
304
  def show_auth_error(message = "Authentication failed")
@@ -334,7 +372,8 @@ module Tng
334
372
  { cmd: "bundle exec tng", desc: "Interactive mode" },
335
373
  { cmd: "bundle exec tng --file=FILE --method=METHOD", desc: "Direct test generation" },
336
374
  { cmd: "bundle exec tng --file=FILE --method=METHOD --audit", desc: "Direct audit mode" },
337
- { cmd: "bundle exec tng --file=FILE --method=METHOD --trace", desc: "Symbolic trace mode" }
375
+ { cmd: "bundle exec tng --file=FILE --method=METHOD --trace", desc: "Symbolic trace mode" },
376
+ { cmd: "bundle exec tng --file=FILE --clones", desc: "Check for code duplicates" }
338
377
  ],
339
378
  features: [
340
379
  "Controllers, Models, Services",
@@ -342,6 +381,7 @@ module Tng
342
381
  "Per-method test generation",
343
382
  "Code audit for issues & behaviors",
344
383
  "Symbolic execution traces",
384
+ "Duplicate code detection (Clones)",
345
385
  "Searchable method lists"
346
386
  ],
347
387
  options: [
@@ -349,13 +389,15 @@ module Tng
349
389
  { flag: "--file=PATH", desc: "Target file path (enables direct CLI mode)" },
350
390
  { flag: "--method=NAME", desc: "Method name to generate test for" },
351
391
  { flag: "--audit", desc: "Run audit mode instead of test generation" },
352
- { flag: "--trace", desc: "Run symbolic trace mode" }
392
+ { flag: "--trace", desc: "Run symbolic trace mode" },
393
+ { flag: "--clones", desc: "Run clone detection mode" },
394
+ { flag: "--level=1|2|all", desc: "Set clone detection level (default: all)" }
353
395
  ],
354
396
  how_to: [
355
- "Run 'bundle exec tng' to start the interactive interface",
356
- "Select Controller, Model, Service, or Other to test",
357
- "Choose a specific method from the list",
358
- "Use search/filter to find methods quickly"
397
+ "Run 'bundle exec tng' for interactive mode",
398
+ "Run 'tng [file] --clones' for duplicate detection",
399
+ "Select Controller, Model, or Service to start",
400
+ "Choose a method to generate/audit or select 'Check Duplicates'"
359
401
  ],
360
402
  footer: "Happy testing! "
361
403
  }
@@ -92,6 +92,10 @@ module Tng
92
92
  emit_event("result", audit_result)
93
93
  end
94
94
 
95
+ def show_clones(file_path, results)
96
+ emit_event("clones", { file_path: file_path, matches: results[:matches] })
97
+ end
98
+
95
99
  def show_test_results(title, passed, failed, errors, total, results = [])
96
100
  emit_event("test_results", {
97
101
  title: title,
data/lib/tng/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tng
4
- VERSION = "0.4.5"
4
+ VERSION = "0.4.6"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tng
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - ralucab
@@ -222,7 +222,7 @@ post_install_message: "┌ TNG ────────────────
222
222
  \ │\n│ • bundle exec
223
223
  tng --help - Show help information │\n│ │\n│
224
224
  \ \U0001F4A1 Generate tests for individual methods with precision │\n└────────────────────────────────────────────────────────────
225
- v0.4.5 ┘\n"
225
+ v0.4.6 ┘\n"
226
226
  rdoc_options: []
227
227
  require_paths:
228
228
  - lib