Class: Mbeditor::UnusedMethodsService

Inherits:
Object
  • Object
show all
Defined in:
app/services/mbeditor/unused_methods_service.rb

Overview

Finds method definitions in a file that have no call-sites anywhere in the workspace. Uses a single ripgrep (or grep) subprocess with an alternation pattern so cost is O(1) subprocesses regardless of how many methods the file contains.

Results are cached per-file; entries are invalidated when the file’s mtime changes OR when the entry is older than CACHE_TTL_SECONDS (handles edits to other files that may add or remove call-sites).

Usage:

UnusedMethodsService.call(workspace_root, abs_path,
                          excluded_dirnames: [], excluded_paths: [])
→ [{ name: "my_method", line: 42 }, ...]

Constant Summary collapse

CACHE_TTL_SECONDS =
30
RG_TIMEOUT =
10
GREP_TIMEOUT =
30

Class Method Summary collapse

Class Method Details

.call(workspace_root, file_path, excluded_dirnames: [], excluded_paths: []) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'app/services/mbeditor/unused_methods_service.rb', line 29

def call(workspace_root, file_path, excluded_dirnames: [], excluded_paths: [])
  file_path = file_path.to_s

  begin
    mtime = File.mtime(file_path).to_f
  rescue StandardError
    return []
  end

  # Return cached result if file mtime matches and the entry is fresh.
  cached = @cache_mutex.synchronize { @cache[file_path] }
  if cached && cached[:mtime] == mtime &&
     (Process.clock_gettime(Process::CLOCK_MONOTONIC) - cached[:ts]) < CACHE_TTL_SECONDS
    return cached[:result]
  end

  # Ensure the file is indexed (populates module_names / include_calls too).
  defs = RubyDefinitionService.defs_in_file(file_path)
  if defs.empty?
    # File not yet in cache; trigger a parse of just this one file by
    # calling the definition service with a dummy symbol.
    RubyDefinitionService.call(workspace_root, "__mbeditor_warmup__",
                               excluded_dirnames: excluded_dirnames,
                               excluded_paths:    excluded_paths)
    defs = RubyDefinitionService.defs_in_file(file_path)
  end
  return [] if defs.empty?

  method_names = defs.keys
  counts       = count_occurrences(method_names, workspace_root.to_s,
                                   excluded_dirnames: excluded_dirnames,
                                   excluded_paths:    excluded_paths)

  # A method with ≤1 total occurrence has only its own `def` line; no call-sites.
  unused = method_names.select { |n| counts.fetch(n, 0) <= 1 }
  result = unused.filter_map do |name|
    entries = defs[name]
    next unless entries&.any?
    { name: name, line: entries.first[:line] }
  end

  ts = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @cache_mutex.synchronize do
    @cache[file_path] = { mtime: mtime, ts: ts, result: result }
  end

  result
rescue StandardError
  []
end

.clear_cache!Object

Exposed for tests.



81
82
83
# File 'app/services/mbeditor/unused_methods_service.rb', line 81

def clear_cache!
  @cache_mutex.synchronize { @cache.clear }
end