rigortype 0.1.14 → 0.1.16

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -2
  3. data/exe/rigor +19 -0
  4. data/lib/rigor/analysis/check_rules.rb +428 -6
  5. data/lib/rigor/analysis/diagnostic.rb +55 -3
  6. data/lib/rigor/analysis/rule_catalog.rb +80 -0
  7. data/lib/rigor/analysis/runner.rb +71 -2
  8. data/lib/rigor/analysis/worker_session.rb +3 -2
  9. data/lib/rigor/cache/descriptor.rb +6 -2
  10. data/lib/rigor/cli/plugin_command.rb +245 -0
  11. data/lib/rigor/cli/plugins_command.rb +51 -4
  12. data/lib/rigor/cli/plugins_renderer.rb +86 -1
  13. data/lib/rigor/cli.rb +143 -5
  14. data/lib/rigor/configuration/severity_profile.rb +9 -0
  15. data/lib/rigor/environment/rbs_loader.rb +259 -1
  16. data/lib/rigor/environment.rb +8 -2
  17. data/lib/rigor/inference/budget_trace.rb +137 -0
  18. data/lib/rigor/inference/expression_typer.rb +9 -2
  19. data/lib/rigor/inference/hkt_reducer.rb +2 -0
  20. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -6
  21. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +81 -14
  22. data/lib/rigor/inference/method_dispatcher.rb +57 -10
  23. data/lib/rigor/inference/precision_scanner.rb +60 -1
  24. data/lib/rigor/inference/scope_indexer.rb +184 -27
  25. data/lib/rigor/inference/statement_evaluator.rb +13 -8
  26. data/lib/rigor/inference/synthetic_method_index.rb +23 -4
  27. data/lib/rigor/inference/synthetic_method_scanner.rb +148 -14
  28. data/lib/rigor/plugin/additional_initializer.rb +108 -0
  29. data/lib/rigor/plugin/base.rb +321 -2
  30. data/lib/rigor/plugin/box.rb +64 -0
  31. data/lib/rigor/plugin/inflector.rb +121 -0
  32. data/lib/rigor/plugin/isolation.rb +191 -0
  33. data/lib/rigor/plugin/macro/nested_class_template.rb +140 -0
  34. data/lib/rigor/plugin/macro.rb +1 -0
  35. data/lib/rigor/plugin/manifest.rb +120 -23
  36. data/lib/rigor/plugin/node_context.rb +62 -0
  37. data/lib/rigor/plugin/registry.rb +10 -0
  38. data/lib/rigor/plugin.rb +3 -0
  39. data/lib/rigor/scope.rb +27 -1
  40. data/lib/rigor/sig_gen/generator.rb +2 -3
  41. data/lib/rigor/sig_gen/observation_collector.rb +2 -2
  42. data/lib/rigor/source/literals.rb +118 -0
  43. data/lib/rigor/source/node_walker.rb +26 -0
  44. data/lib/rigor/source.rb +1 -0
  45. data/lib/rigor/triage/catalogue.rb +71 -5
  46. data/lib/rigor/type/combinator.rb +6 -1
  47. data/lib/rigor/type/union.rb +65 -1
  48. data/lib/rigor/version.rb +1 -1
  49. data/lib/rigor.rb +1 -0
  50. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +31 -53
  51. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +21 -23
  52. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +38 -59
  53. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +7 -13
  54. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +22 -33
  55. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +298 -413
  56. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +69 -71
  57. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +24 -34
  58. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +18 -16
  59. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +4 -46
  60. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
  61. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +1 -1
  62. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +17 -12
  63. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -8
  64. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +2 -7
  65. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +2 -6
  66. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +4 -3
  67. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
  68. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +40 -45
  69. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +7 -17
  70. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +20 -42
  71. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +7 -4
  72. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +4 -8
  73. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +188 -0
  74. data/plugins/rigor-mangrove/lib/rigor-mangrove.rb +3 -0
  75. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +4 -0
  76. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +24 -8
  77. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +31 -48
  78. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +21 -23
  79. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +54 -82
  80. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +25 -25
  81. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +63 -147
  82. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -17
  83. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +23 -114
  84. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +36 -31
  85. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
  86. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +6 -3
  87. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +4 -2
  88. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +13 -12
  89. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +28 -40
  90. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +44 -47
  91. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +11 -10
  92. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +45 -87
  93. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +11 -12
  94. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +29 -42
  95. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +20 -19
  96. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +73 -0
  97. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +43 -1
  98. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +21 -29
  99. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +36 -96
  100. data/sig/rigor/plugin/access_denied_error.rbs +3 -1
  101. data/sig/rigor/plugin/base.rbs +58 -3
  102. data/sig/rigor/plugin/io_boundary.rbs +3 -0
  103. data/sig/rigor/plugin/manifest.rbs +31 -1
  104. data/sig/rigor/scope.rbs +3 -0
  105. data/sig/rigor/source.rbs +12 -0
  106. data/sig/rigor.rbs +5 -0
  107. data/skills/rigor-plugin-author/SKILL.md +33 -9
  108. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +65 -26
  109. data/skills/rigor-plugin-author/references/02-walker-and-types.md +213 -80
  110. data/skills/rigor-plugin-author/references/03-test-and-ship.md +3 -3
  111. data/skills/rigor-project-init/SKILL.md +72 -7
  112. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +233 -19
  113. metadata +53 -2
  114. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/inflector.rb +0 -114
@@ -62,17 +62,14 @@ module Rigor
62
62
  version: "0.1.0",
63
63
  description: "Validates ActionCable broadcast call shape against discovered channels.",
64
64
  config_schema: {
65
- "channel_search_paths" => :array,
66
- "channel_base_classes" => :array
65
+ "channel_search_paths" => { kind: :array, default: ["app/channels"] },
66
+ "channel_base_classes" => {
67
+ kind: :array,
68
+ default: ["ApplicationCable::Channel", "ActionCable::Channel::Base"]
69
+ }
67
70
  }
68
71
  )
69
72
 
70
- DEFAULT_CHANNEL_SEARCH_PATHS = ["app/channels"].freeze
71
- DEFAULT_CHANNEL_BASE_CLASSES = [
72
- "ApplicationCable::Channel",
73
- "ActionCable::Channel::Base"
74
- ].freeze
75
-
76
73
  producer :channel_index do |_params|
77
74
  ChannelDiscoverer.new(
78
75
  io_boundary: io_boundary,
@@ -82,22 +79,30 @@ module Rigor
82
79
  end
83
80
 
84
81
  def init(_services)
85
- @channel_search_paths = Array(
86
- config.fetch("channel_search_paths", DEFAULT_CHANNEL_SEARCH_PATHS)
87
- ).map(&:to_s)
88
- @channel_base_classes = Array(
89
- config.fetch("channel_base_classes", DEFAULT_CHANNEL_BASE_CLASSES)
90
- ).map(&:to_s)
82
+ @channel_search_paths = Array(config.fetch("channel_search_paths")).map(&:to_s)
83
+ @channel_base_classes = Array(config.fetch("channel_base_classes")).map(&:to_s)
91
84
  @channel_index = nil
92
85
  @load_error = nil
93
86
  end
94
87
 
88
+ # File-level only: the load-error emission. Per-call broadcast
89
+ # validation runs over the engine-owned walk via the node_rule
90
+ # below (ADR-37). The channel index is lazily loaded + memoised by
91
+ # channel_index_or_nil, shared by both surfaces.
95
92
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
96
93
  index = channel_index_or_nil
97
94
  return [load_error_diagnostic(path)] if index.nil? && @load_error
98
- return [] if index.nil? || index.empty?
99
95
 
100
- Analyzer.diagnose(path: path, root: root, channel_index: index).map { |diag| build_diagnostic(diag) }
96
+ []
97
+ end
98
+
99
+ node_rule Prism::CallNode do |node, _scope, path|
100
+ index = channel_index_or_nil
101
+ next [] if index.nil? || index.empty?
102
+
103
+ Analyzer.violations_for(call_node: node, channel_index: index).map do |violation|
104
+ diagnostic(node, path: path, message: violation.message, severity: violation.severity, rule: violation.rule)
105
+ end
101
106
  end
102
107
 
103
108
  private
@@ -128,13 +133,6 @@ module Rigor
128
133
  rule: "load-error"
129
134
  )
130
135
  end
131
-
132
- def build_diagnostic(diag)
133
- Rigor::Analysis::Diagnostic.new(
134
- path: diag.path, line: diag.line, column: diag.column,
135
- message: diag.message, severity: diag.severity, rule: diag.rule
136
- )
137
- end
138
136
  end
139
137
 
140
138
  Rigor::Plugin.register(Actioncable)
@@ -45,53 +45,44 @@ module Rigor
45
45
  method instance_method methods
46
46
  ].freeze
47
47
 
48
- Diagnostic = Struct.new(:path, :line, :column, :severity, :rule, :message, keyword_init: true)
48
+ # One mailer-call observation. Carries no path/location the
49
+ # caller (the `node_rule` block) positions it via
50
+ # `Plugin::Base#diagnostic`.
51
+ Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
49
52
 
50
53
  module_function
51
54
 
52
- # @param path [String]
53
- # @param root [Prism::Node]
55
+ # The mailer-call violations for a single call node (0..2), or
56
+ # `[]` when it is not a `<Mailer>.action(...)` call on a known
57
+ # mailer. ADR-37: the engine owns the walk.
58
+ #
59
+ # @param call_node [Prism::Node]
54
60
  # @param mailer_index [MailerIndex]
55
- # @return [Array<Diagnostic>]
56
- def diagnose(path:, root:, mailer_index:)
57
- diagnostics = []
58
- walk(root) do |call_node|
59
- class_name = mailer_class_for_call(call_node)
60
- next if class_name.nil?
61
- next if RESERVED_CLASS_METHODS.include?(call_node.name)
62
-
63
- class_entry = mailer_index.find(class_name) || mailer_index.find("::#{class_name}")
64
- next if class_entry.nil?
65
-
66
- action_entry = class_entry.find_action(call_node.name)
67
- if action_entry.nil?
68
- # Skip `unknown-action` when the mailer's include
69
- # set has any unresolved module — the unresolved
70
- # module may legitimately define the action
71
- # (gem-shipped concern, dynamically loaded
72
- # mailer extension). Mirrors the same predicate
73
- # `rigor-actionpack` uses for unknown-filter-method.
74
- next if class_entry.unresolved_includes?
75
-
76
- diagnostics << unknown_action_diagnostic(path, call_node, class_entry)
77
- next
78
- end
79
-
80
- diagnostics << action_call_info(path, call_node, class_entry, action_entry)
81
- arity_diag = arity_check(path, call_node, class_entry, action_entry)
82
- diagnostics << arity_diag if arity_diag
83
- end
84
- diagnostics
85
- end
61
+ # @return [Array<Violation>]
62
+ def violations_for(call_node:, mailer_index:)
63
+ return [] unless call_node.is_a?(Prism::CallNode) && action_call_candidate?(call_node)
64
+
65
+ class_name = mailer_class_for_call(call_node)
66
+ return [] if class_name.nil?
67
+ return [] if RESERVED_CLASS_METHODS.include?(call_node.name)
86
68
 
87
- # Walks the tree yielding every CallNode whose receiver
88
- # resolves (directly or through `.with(...)`) to a
89
- # constant.
90
- def walk(node, &)
91
- return unless node.is_a?(Prism::Node)
69
+ class_entry = mailer_index.find(class_name) || mailer_index.find("::#{class_name}")
70
+ return [] if class_entry.nil?
71
+
72
+ action_entry = class_entry.find_action(call_node.name)
73
+ if action_entry.nil?
74
+ # Skip `unknown-action` when the mailer's include set has any
75
+ # unresolved module — it may legitimately define the action
76
+ # (gem-shipped concern, dynamically loaded extension).
77
+ return [] if class_entry.unresolved_includes?
78
+
79
+ return [unknown_action_violation(call_node, class_entry)]
80
+ end
92
81
 
93
- yield node if node.is_a?(Prism::CallNode) && action_call_candidate?(node)
94
- node.compact_child_nodes.each { |child| walk(child, &) }
82
+ violations = [action_call_info(call_node, class_entry, action_entry)]
83
+ arity = arity_violation(call_node, class_entry, action_entry)
84
+ violations << arity if arity
85
+ violations
95
86
  end
96
87
 
97
88
  def action_call_candidate?(node)
@@ -120,12 +111,8 @@ module Rigor
120
111
  end
121
112
  end
122
113
 
123
- def action_call_info(path, call_node, class_entry, action_entry)
124
- location = call_node.location
125
- Diagnostic.new(
126
- path: path,
127
- line: location.start_line,
128
- column: location.start_column + 1,
114
+ def action_call_info(_call_node, class_entry, action_entry)
115
+ Violation.new(
129
116
  severity: :info,
130
117
  rule: "mailer-call",
131
118
  message: "`#{class_entry.class_name}.#{action_entry.method_name}` " \
@@ -133,7 +120,7 @@ module Rigor
133
120
  )
134
121
  end
135
122
 
136
- def arity_check(path, call_node, class_entry, action_entry)
123
+ def arity_violation(call_node, class_entry, action_entry)
137
124
  args = call_node.arguments&.arguments || []
138
125
  actual = args.size
139
126
  return nil if action_entry.accepts?(actual)
@@ -147,11 +134,7 @@ module Rigor
147
134
  # carrying calls don't surface as wrong-arity.
148
135
  return nil if args.last.is_a?(Prism::KeywordHashNode) && action_entry.accepts?(actual - 1)
149
136
 
150
- location = call_node.location
151
- Diagnostic.new(
152
- path: path,
153
- line: location.start_line,
154
- column: location.start_column + 1,
137
+ Violation.new(
155
138
  severity: :error,
156
139
  rule: "wrong-arity",
157
140
  message: "`#{class_entry.class_name}.#{action_entry.method_name}` " \
@@ -159,14 +142,10 @@ module Rigor
159
142
  )
160
143
  end
161
144
 
162
- def unknown_action_diagnostic(path, call_node, class_entry)
163
- location = call_node.location
145
+ def unknown_action_violation(call_node, class_entry)
164
146
  known = class_entry.actions.keys.sort.join(", ")
165
147
  known_part = known.empty? ? "no actions defined" : "known actions: #{known}"
166
- Diagnostic.new(
167
- path: path,
168
- line: location.start_line,
169
- column: location.start_column + 1,
148
+ Violation.new(
170
149
  severity: :error,
171
150
  rule: "unknown-action",
172
151
  message: "`#{class_entry.class_name}.#{call_node.name}` is not a defined " \
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "prism"
4
+ require "rigor/source/literals"
4
5
 
5
6
  require_relative "mailer_index"
6
7
 
@@ -264,7 +265,7 @@ module Rigor
264
265
  next unless node.is_a?(Prism::CallNode) && node.receiver.nil?
265
266
 
266
267
  args = (node.arguments&.arguments || []).filter_map do |arg|
267
- arg.is_a?(Prism::SymbolNode) ? arg.unescaped.to_sym : nil
268
+ Rigor::Source::Literals.symbol(arg)
268
269
  end
269
270
  next if args.empty?
270
271
 
@@ -371,7 +372,11 @@ module Rigor
371
372
  # denied) are swallowed.
372
373
  def view_exists?(class_name, action_name)
373
374
  views_root_absolute = File.expand_path(@views_root)
374
- underscore_path = underscore(class_name.delete_prefix("::"))
375
+ # ADR-39: the real ActiveSupport::Inflector resolves the mailer's
376
+ # view directory (`Foo::BarMailer` → `foo/bar_mailer`), so a
377
+ # divergence from Rails' real underscore can't point the
378
+ # missing-view check at the wrong directory.
379
+ underscore_path = Rigor::Plugin::Inflector.underscore(class_name.delete_prefix("::"))
375
380
  mailer_dir = File.join(views_root_absolute, underscore_path)
376
381
 
377
382
  VIEW_FORMATS.any? do |format|
@@ -381,17 +386,6 @@ module Rigor
381
386
  end
382
387
  end
383
388
  end
384
-
385
- # Convert `Foo::BarMailer` → `foo/bar_mailer`. Mirrors
386
- # ActiveSupport's String#underscore for ASCII-only
387
- # constant names; we don't try to be inflector-perfect
388
- # here.
389
- def underscore(name)
390
- name.gsub("::", "/")
391
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
392
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
393
- .downcase
394
- end
395
389
  end
396
390
  end
397
391
  end
@@ -59,16 +59,12 @@ module Rigor
59
59
  version: "0.4.0",
60
60
  description: "Validates ActionMailer call shape and view template existence.",
61
61
  config_schema: {
62
- "mailer_search_paths" => :array,
63
- "mailer_base_classes" => :array,
64
- "views_root" => :string
62
+ "mailer_search_paths" => { kind: :array, default: ["app/mailers"] },
63
+ "mailer_base_classes" => { kind: :array, default: %w[ApplicationMailer ActionMailer::Base] },
64
+ "views_root" => { kind: :string, default: "app/views" }
65
65
  }
66
66
  )
67
67
 
68
- DEFAULT_MAILER_SEARCH_PATHS = ["app/mailers"].freeze
69
- DEFAULT_MAILER_BASE_CLASSES = %w[ApplicationMailer ActionMailer::Base].freeze
70
- DEFAULT_VIEWS_ROOT = "app/views"
71
-
72
68
  producer :mailer_index do |_params|
73
69
  MailerDiscoverer.new(
74
70
  io_boundary: io_boundary,
@@ -79,22 +75,33 @@ module Rigor
79
75
  end
80
76
 
81
77
  def init(_services)
82
- @mailer_search_paths = Array(config.fetch("mailer_search_paths", DEFAULT_MAILER_SEARCH_PATHS)).map(&:to_s)
83
- @mailer_base_classes = Array(config.fetch("mailer_base_classes", DEFAULT_MAILER_BASE_CLASSES)).map(&:to_s)
84
- @views_root = config.fetch("views_root", DEFAULT_VIEWS_ROOT).to_s
78
+ @mailer_search_paths = Array(config.fetch("mailer_search_paths")).map(&:to_s)
79
+ @mailer_base_classes = Array(config.fetch("mailer_base_classes")).map(&:to_s)
80
+ @views_root = config.fetch("views_root").to_s
85
81
  @mailer_index = nil
86
82
  @load_error = nil
87
83
  end
88
84
 
85
+ # File-level: load-error + the missing-view check (anchored on the
86
+ # mailer's own source file, so it is genuinely per-file, not
87
+ # per-call). The per-call mailer validation (action existence /
88
+ # arity) runs over the engine-owned walk via the node_rule below
89
+ # (ADR-37). The mailer index is lazily loaded + memoised, shared.
89
90
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
90
91
  index = mailer_index_or_nil
91
92
  return [load_error_diagnostic(path)] if index.nil? && @load_error
92
93
  return [] if index.nil? || index.empty?
93
94
 
94
- diagnostics = []
95
- diagnostics.concat(call_site_diagnostics(path, root, index))
96
- diagnostics.concat(missing_view_diagnostics(path, index))
97
- diagnostics
95
+ missing_view_diagnostics(path, index)
96
+ end
97
+
98
+ node_rule Prism::CallNode do |node, _scope, path|
99
+ index = mailer_index_or_nil
100
+ next [] if index.nil? || index.empty?
101
+
102
+ Analyzer.violations_for(call_node: node, mailer_index: index).map do |violation|
103
+ diagnostic(node, path: path, message: violation.message, severity: violation.severity, rule: violation.rule)
104
+ end
98
105
  end
99
106
 
100
107
  private
@@ -121,10 +128,6 @@ module Rigor
121
128
  nil
122
129
  end
123
130
 
124
- def call_site_diagnostics(path, root, index)
125
- Analyzer.diagnose(path: path, root: root, mailer_index: index).map { |diag| build_diagnostic(diag) }
126
- end
127
-
128
131
  # Anchors `missing-view` diagnostics on the mailer file
129
132
  # itself: when the file currently being analysed is the
130
133
  # mailer's source file, emit one diagnostic per missing
@@ -143,7 +146,7 @@ module Rigor
143
146
  severity: :warning,
144
147
  rule: "missing-view",
145
148
  message: "`#{class_entry.class_name}##{action_name}` has no view template " \
146
- "under `#{@views_root}/#{underscore(class_entry.class_name.delete_prefix('::'))}/`"
149
+ "under `#{@views_root}/#{Rigor::Plugin::Inflector.underscore(class_entry.class_name.delete_prefix('::'))}/`"
147
150
  )
148
151
  end
149
152
  end
@@ -154,13 +157,6 @@ module Rigor
154
157
  File.expand_path(path)
155
158
  end
156
159
 
157
- def underscore(name)
158
- name.gsub("::", "/")
159
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
160
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
161
- .downcase
162
- end
163
-
164
160
  def load_error_diagnostic(path)
165
161
  Rigor::Analysis::Diagnostic.new(
166
162
  path: path, line: 1, column: 1,
@@ -169,13 +165,6 @@ module Rigor
169
165
  rule: "load-error"
170
166
  )
171
167
  end
172
-
173
- def build_diagnostic(diag)
174
- Rigor::Analysis::Diagnostic.new(
175
- path: diag.path, line: diag.line, column: diag.column,
176
- message: diag.message, severity: diag.severity, rule: diag.rule
177
- )
178
- end
179
168
  end
180
169
 
181
170
  Rigor::Plugin.register(Actionmailer)