Class: Moult::RailsConventions

Inherits:
Object
  • Object
show all
Defined in:
lib/moult/rails_conventions.rb

Overview

Models the Rails entrypoint conventions that make framework-invoked code look unused. In Rails most "uncalled" methods are reached by convention (controller actions via routing, jobs via #perform) or by symbol-based DSLs (before_action :authenticate) that no static call site references.

This layer never hides a finding (per Moult's core principle, metaprogramming/conventions must lower confidence, never silently hide). It only emits Signals that the :rails_entrypoint confidence rule turns into a strong, explained downward adjustment. A genuinely dead controller action therefore still appears — just sorted low — rather than being asserted alive.

Scope (Tier A): controller/mailer actions, helpers, job #perform, symbol- DSL callbacks, serializers, initializers. Classes/modules are not flagged at all (only methods and non-class constants are candidates), which sidesteps STI/Zeitwerk false positives for this slice. Route-file and view-template resolution are deferred.

Defined Under Namespace

Classes: Signal

Constant Summary collapse

CONTROLLER =
%r{(\A|/)app/controllers/.+_controller\.rb\z}
MAILER =
%r{(\A|/)app/mailers/.+\.rb\z}
HELPER =
%r{(\A|/)app/helpers/.+\.rb\z}
JOB =
%r{(\A|/)app/jobs/.+\.rb\z}
SERIALIZER =
%r{(\A|/)app/serializers/.+\.rb\z}
INITIALIZER =
%r{(\A|/)config/initializers/.+\.rb\z}
PERFORM_METHODS =
%w[perform perform_async perform_later perform_now].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rails:, dsl_references: Set.new) ⇒ RailsConventions

Returns a new instance of RailsConventions.

Parameters:

  • rails (Boolean)

    whether the project is a Rails app

  • dsl_references (Set<String>) (defaults to: Set.new)

    method names referenced via DSL symbols



68
69
70
71
# File 'lib/moult/rails_conventions.rb', line 68

def initialize(rails:, dsl_references: Set.new)
  @rails = rails
  @dsl_references = dsl_references
end

Class Method Details

.build(root:, files:) ⇒ RailsConventions

Parameters:

  • root (String)

    absolute analysis root

  • files (Array<String>)

    absolute Ruby file paths (for DSL scan)

Returns:



38
39
40
41
42
# File 'lib/moult/rails_conventions.rb', line 38

def build(root:, files:)
  rails = rails_app?(root)
  refs = rails ? collect_dsl_references(files) : Set.new
  new(rails: rails, dsl_references: refs)
end

.collect_dsl_references(files) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/moult/rails_conventions.rb', line 57

def collect_dsl_references(files)
  files.each_with_object(Set.new) do |path, set|
    SymbolScanner.scan_file(path).each { |name| set << name }
  rescue
    next
  end
end

.gemfile_mentions_rails?(root) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
52
53
54
55
# File 'lib/moult/rails_conventions.rb', line 49

def gemfile_mentions_rails?(root)
  gemfile = File.join(root, "Gemfile")
  return false unless File.file?(gemfile)
  File.foreach(gemfile).any? { |line| line =~ /^\s*gem\s+["'](rails|railties)["']/ }
rescue
  false
end

.rails_app?(root) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
47
# File 'lib/moult/rails_conventions.rb', line 44

def rails_app?(root)
  return true if File.file?(File.join(root, "config", "application.rb"))
  File.directory?(File.join(root, "app")) && gemfile_mentions_rails?(root)
end

Instance Method Details

#rails?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/moult/rails_conventions.rb', line 73

def rails?
  @rails
end

#signals_for(definition) ⇒ Array<Signal>

Returns matched entrypoint conventions (empty if none / not Rails).

Parameters:

Returns:

  • (Array<Signal>)

    matched entrypoint conventions (empty if none / not Rails)



79
80
81
82
83
# File 'lib/moult/rails_conventions.rb', line 79

def signals_for(definition)
  return [] unless @rails

  [path_signal(definition), symbol_signal(definition)].compact
end