rigortype 0.2.3 → 0.2.5

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: 775a0b6e3050294352d505366428814ab73348d2ced5ccf45842644c38752155
4
- data.tar.gz: 7ec72c4aa56cbb43ae18dd3c1a029da3bb6746a604c4a3d72811b34b5d1c1439
3
+ metadata.gz: ce4071258582638c452079484534e58c6d6b290a88ea51fdce715024d7c09297
4
+ data.tar.gz: 90197cb4711899857c8d242470b063e7da64947e46e240253206179a0e8f6baf
5
5
  SHA512:
6
- metadata.gz: cc0650acb783740184fb76f0c5382fefe3950bd84011c84ccd2e23a60ff703519de94a294b39e96e11198748b2539917fae117972be7366dd06725ee54975935
7
- data.tar.gz: d06b60ff922cfb4ab77ea680b5d624702a4f782b65c1dcb2e3f631793fe90cb4bb8302d50ac5a3656cbd1a1fe79e198cfee9c8641b037acf48906ae1aa63eeb8
6
+ metadata.gz: 4e967fdc0ca679a712860a20da09842c2668727c0068a4ed9657ae4111d0d94a5ab8efa9684c1a75b1ad25dbeb03efd9a4580f85e771aa5a61a05f56f38b6203
7
+ data.tar.gz: 8773e6364390cb36296345c4ec8974cee5bec585f7f9da292950859e4cb754081a68130db00dbc14f62d77c7f396477adace7480500d789bb838777ea268c6a2
@@ -43,9 +43,14 @@ errors_demo.rb:25:1: warning: `t('errors.messages.blank')` is missing from local
43
43
  with a literal first argument. **Lazy keys** — `t('.title')` in a
44
44
  controller — are expanded to `<controller_scope>.<action>.<key>`
45
45
  from the file path and the innermost enclosing `def`, matching
46
- Rails' convention; lazy keys in non-controller files are skipped
47
- (the scope can't be determined statically). Calls with a
48
- non-literal key (`t(some_variable)`) pass through unchecked.
46
+ Rails' convention; lazy keys in non-controller Ruby files (models,
47
+ helpers, mailers) are skipped (the scope can't be determined
48
+ statically at that point). **View template lazy keys** —
49
+ `t('.title')` inside `app/views/setting/index.html.erb` expands
50
+ to `setting.index.title` and is validated for key existence and
51
+ per-locale coverage; ERB, Haml, and Slim templates under
52
+ `view_search_paths` (default `app/views`) are scanned. Calls with
53
+ a non-literal key (`t(some_variable)`) pass through unchecked.
49
54
 
50
55
  Keys under the prefixes Rails and the `rails-i18n` gem ship
51
56
  themselves (`date.` / `time.` / `datetime.` / `number.` /
@@ -62,11 +67,14 @@ plugins:
62
67
  config:
63
68
  locale_search_paths: ["config/locales"] # default
64
69
  configured_locales: ["en"] # default
70
+ view_search_paths: ["app/views"] # default
65
71
  ```
66
72
 
67
73
  `configured_locales` is the set of locales the project ships;
68
74
  setting it to `["en", "ja"]` turns on `missing-locale` warnings
69
75
  whenever a key resolves in one but not the other.
76
+ `view_search_paths` controls which directories are scanned for
77
+ view templates containing lazy `t('.key')` calls.
70
78
 
71
79
  ## Limitations
72
80
 
@@ -74,6 +82,17 @@ whenever a key resolves in one but not the other.
74
82
  - **Lazy keys outside controllers are skipped** — the
75
83
  controller/action scope `t('.x')` depends on isn't derivable in
76
84
  a model / helper / mailer.
85
+ - **View template lazy keys** (`t('.key')` inside ERB / Haml /
86
+ Slim) are validated for key existence and per-locale coverage.
87
+ Interpolation validation is skipped for templates — the hash
88
+ may come from controller instance variables not visible in the
89
+ template source. Configure `view_search_paths:` to override the
90
+ default `["app/views"]`.
91
+ - **View diagnostics duplicate under `--workers`** — the view
92
+ scan is a project-wide pass surfaced through the per-file
93
+ diagnostic hook, so each fork-pool worker re-emits the full set
94
+ (the same once-per-run limitation the `load-error` diagnostics
95
+ carry). Default `rigor check` (sequential) is unaffected.
77
96
  - **Pluralization is recognised but not validated** — `count:` is
78
97
  treated as a reserved option; whether the locale defines
79
98
  `:zero` / `:one` / `:other` is not checked.
@@ -47,6 +47,9 @@ module Rigor
47
47
  def initialize(path:, line:, column:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
48
48
  source_family: DEFAULT_SOURCE_FAMILY,
49
49
  receiver_type: nil, method_name: nil, project_definition_site: nil)
50
+ raise ArgumentError, "line must be >= 1, got #{line}" if line < 1
51
+ raise ArgumentError, "column must be >= 1, got #{column}" if column < 1
52
+
50
53
  @path = path
51
54
  @line = line
52
55
  @column = column
@@ -35,9 +35,14 @@ module Rigor
35
35
 
36
36
  # @param paths [Array<String>, nil] explicit analysis roots; nil
37
37
  # (the default) uses the configuration's `paths:`.
38
- def initialize(configuration:, paths: nil)
38
+ # @param environment [Rigor::Environment, nil] optional shared
39
+ # environment to thread into each internal Runner. Long-lived
40
+ # callers and specs can use this to avoid rebuilding the same
41
+ # RBS universe for every baseline / recheck / oracle run.
42
+ def initialize(configuration:, paths: nil, environment: nil)
39
43
  @configuration = configuration
40
44
  @paths = paths
45
+ @environment = environment
41
46
  @cache = {} # analyzed path => [Diagnostic]
42
47
  @sources = {} # analyzed path => Set<source path it read from>
43
48
  @digests = {} # analyzed path => content digest at last analysis
@@ -312,7 +317,7 @@ module Rigor
312
317
  end
313
318
 
314
319
  def build_runner(**)
315
- Runner.new(configuration: @configuration, cache_store: nil, **)
320
+ Runner.new(configuration: @configuration, cache_store: nil, environment: @environment, **)
316
321
  end
317
322
 
318
323
  # Run the runner over the session's explicit paths (or, when none were
@@ -208,8 +208,14 @@ module Rigor
208
208
  ["rigor-rbs-setup", "your gems ship no community RBS yet — install it so Rigor stops typing them as Dynamic."]
209
209
  elsif state.fetch(:ci) != :wired
210
210
  ["rigor-ci-setup", "Rigor is configured but not wired into CI — lock in the regression guard."]
211
- elsif state.fetch(:baseline)
212
- ["rigor-baseline-reduce", "a baseline is in place work it down rule by rule."]
211
+ # A present baseline is deliberately NOT a recommendation trigger.
212
+ # A baseline is a healthy, finished onboarding state, not a problem to
213
+ # work off; pushing every baselined project to "reduce it" turns a
214
+ # working build into a chore and tempts scattering `# rigor:disable`
215
+ # through the code to make a number go down — means over ends.
216
+ # `rigor-baseline-reduce` stays in the catalogue for the intermediate
217
+ # user who *chooses* to invest in it; the headline routes to genuinely
218
+ # additive steps instead.
213
219
  elsif state.fetch(:editor) == :unwired
214
220
  ["rigor-editor-setup",
215
221
  "you have an editor config but no Rigor LSP — wire `rigor lsp` for live diagnostics and hover types."]
@@ -297,7 +303,6 @@ module Rigor
297
303
  The recommendation above is from a presence-only probe — it does not run
298
304
  `rigor check`. If you have run (or now run) `rigor check`, let its findings
299
305
  refine the choice:
300
- - errors present and no baseline yet → rigor-baseline-reduce
301
306
  - a `call.unresolved-toplevel` / `call.undefined-method` cluster on the
302
307
  project's own monkey-patches → rigor-monkeypatch-resolve
303
308
  - framework calls (ActiveRecord, routes, i18n …) typing as Dynamic with no
@@ -170,7 +170,7 @@ module Rigor
170
170
  source = missing.map { |name| "module #{name}\nend\n" }.join
171
171
  buffer = ::RBS::Buffer.new(name: SYNTHETIC_NAMESPACE_BUFFER, content: source)
172
172
  _, directives, decls = ::RBS::Parser.parse_signature(buffer)
173
- env.add_source(::RBS::Source::RBS.new(buffer, directives || [], decls || []))
173
+ add_parsed_decls(env, buffer, directives, decls)
174
174
  rescue ::RBS::BaseError
175
175
  # Fail-soft: synthesis is an opportunistic uplift, never a
176
176
  # hard requirement. A parse failure here just leaves the env
@@ -232,11 +232,52 @@ module Rigor
232
232
  missing.uniq
233
233
  end
234
234
 
235
+ # Normalises a `class_decls` entry's representative declaration
236
+ # across the gemspec's supported RBS range (`rbs >= 3.0, < 5.0`).
237
+ # RBS 4.x exposes it as `entry.primary_decl` (the AST declaration
238
+ # directly); RBS 3.x exposes `entry.primary` (a wrapper whose
239
+ # `#decl` is the AST declaration). Returns the AST declaration, or
240
+ # nil when neither accessor is present. Without this guard,
241
+ # `class_decl_paths` crashed under RBS 3.x with
242
+ # `undefined method 'primary_decl'`.
243
+ def primary_decl_for(entry)
244
+ if entry.respond_to?(:primary_decl)
245
+ entry.primary_decl
246
+ elsif entry.respond_to?(:primary)
247
+ primary = entry.primary
248
+ primary.respond_to?(:decl) ? primary.decl : primary
249
+ end
250
+ end
251
+
252
+ # Appends freshly-parsed declarations to an `RBS::Environment`
253
+ # across the gemspec's supported RBS range (`rbs >= 3.0, < 5.0`).
254
+ # RBS 4.x wraps the declarations in an `RBS::Source::RBS` and
255
+ # takes them through `env.add_source`; RBS 3.x has neither
256
+ # `RBS::Source` nor `add_source` and instead registers them with
257
+ # `env.add_signature(buffer:, directives:, decls:)` (a bare
258
+ # `env << decl` is NOT enough — it skips the `signatures` table
259
+ # that `resolve_type_names` rebuilds from, so the synthesized
260
+ # declarations silently vanish on resolve). Without this guard
261
+ # the synthesis paths (`synthesize_missing_namespaces`,
262
+ # `append_stub_declarations`, `add_virtual_rbs`) crashed under
263
+ # RBS 3.x with `uninitialized constant RBS::Source`.
264
+ def add_parsed_decls(env, buffer, directives, decls)
265
+ decls ||= []
266
+ directives ||= []
267
+ if env.respond_to?(:add_source)
268
+ env.add_source(::RBS::Source::RBS.new(buffer, directives, decls))
269
+ elsif env.respond_to?(:add_signature)
270
+ env.add_signature(buffer: buffer, directives: directives, decls: decls)
271
+ else
272
+ decls.each { |decl| env << decl }
273
+ end
274
+ end
275
+
235
276
  # True when a `class_decls` entry was declared in one of the
236
277
  # project's own signature files (by declaration location), so
237
278
  # the sweep skips the bundled stdlib / vendored universe.
238
279
  def project_entry?(entry, project_files)
239
- decl = entry.respond_to?(:primary_decl) ? entry.primary_decl : nil
280
+ decl = primary_decl_for(entry)
240
281
  location = decl&.location
241
282
  buffer_name = location&.buffer&.name
242
283
  return false unless buffer_name
@@ -262,7 +303,7 @@ module Rigor
262
303
  end.join
263
304
  buffer = ::RBS::Buffer.new(name: SYNTHETIC_STUB_BUFFER, content: source)
264
305
  _, directives, decls = ::RBS::Parser.parse_signature(buffer)
265
- base_env.add_source(::RBS::Source::RBS.new(buffer, directives || [], decls || []))
306
+ add_parsed_decls(base_env, buffer, directives, decls)
266
307
  rescue ::RBS::BaseError
267
308
  nil
268
309
  end
@@ -284,8 +325,7 @@ module Rigor
284
325
 
285
326
  buffer = ::RBS::Buffer.new(name: filename.to_s, content: content.to_s)
286
327
  _, directives, decls = ::RBS::Parser.parse_signature(buffer)
287
- source = ::RBS::Source::RBS.new(buffer, directives || [], decls || [])
288
- env.add_source(source)
328
+ add_parsed_decls(env, buffer, directives, decls)
289
329
  rescue ::RBS::BaseError
290
330
  # WD6 fail-soft: a single broken virtual RBS contribution
291
331
  # does not pull the whole env down. The plugin layer
@@ -589,7 +629,7 @@ module Rigor
589
629
 
590
630
  result = {}
591
631
  env.class_decls.each do |rbs_name, entry|
592
- decl = entry.primary_decl
632
+ decl = self.class.primary_decl_for(entry)
593
633
  next if decl.nil?
594
634
 
595
635
  location = decl.location
@@ -911,12 +951,17 @@ module Rigor
911
951
  end
912
952
 
913
953
  # Collects the AST declaration nodes behind a `class_decls`
914
- # entry. RBS 4's `ModuleEntry` / `ClassEntry` expose `each_decl`;
915
- # the older single-`decl` shape is handled defensively so the
916
- # loader survives an rbs-gem minor bump.
954
+ # entry across the supported RBS range (`rbs >= 3.0, < 5.0`).
955
+ # RBS 4's `ModuleEntry` / `ClassEntry` expose `each_decl` yielding
956
+ # bare AST declarations; RBS 3.x exposes `decls`, an array of
957
+ # `MultiEntry::D` wrappers whose `#decl` is the AST declaration.
958
+ # The single-`decl` shape is handled defensively so the loader
959
+ # survives an rbs-gem minor bump.
917
960
  def entry_declarations(entry)
918
961
  if entry.respond_to?(:each_decl)
919
962
  [].tap { |acc| entry.each_decl { |decl| acc << decl } }
963
+ elsif entry.respond_to?(:decls)
964
+ entry.decls.map { |d| d.respond_to?(:decl) ? d.decl : d }
920
965
  elsif entry.respond_to?(:decl)
921
966
  [entry.decl]
922
967
  else
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.5"
5
5
  end
@@ -42,6 +42,32 @@ module Rigor
42
42
  # `_controller.rb` (e.g. `users`, `admin/users`).
43
43
  CONTROLLER_PATH_RE = %r{(?:^|/)controllers/(.+)_controller\.rb$}
44
44
 
45
+ # Matches Rails view-template file paths to derive the
46
+ # I18n "virtual path" — the scope that Rails uses for
47
+ # lazy `t('.key')` lookups inside a template.
48
+ #
49
+ # Captures the path segment between `views/` and the
50
+ # format+variant+handler suffix, then strips a leading
51
+ # underscore from each segment — Rails templates resolve
52
+ # `_form.html.erb` as "form", not "_form".
53
+ #
54
+ # app/views/setting/index.html.erb → setting.index
55
+ # app/views/admin/users/new.html.erb → admin.users.new
56
+ # app/views/home/index.html+mobile.erb → home.index
57
+ # app/views/users/_form.html.erb → users.form
58
+ #
59
+ # The view-scope lazy expansion replaces the action
60
+ # part with the view's virtual path:
61
+ # `<%= t('.title') %>` → `setting.index.title`
62
+ VIEW_SCOPE_RE = %r{views/(.+?)\.(?:\w+)(?:\+\w+)?\.\w+\z}
63
+
64
+ # Regex to extract lazy-key arguments from ERB / HTML
65
+ # template content. Matches `t('.key')`, `t(".key")`,
66
+ # `I18n.t('.key')`, and `I18n.translate('.key')` with a
67
+ # leading dot on the key string. Captures only the key
68
+ # part after the dot.
69
+ LAZY_T_KEY_RE = /\b(?:I18n\.)?(?:t|translate)\s*\(\s*(?:"\.([^"\\]*)"|'\.([^'\\]*)')/
70
+
45
71
  # Reserved option keys — these are recognised by I18n
46
72
  # itself and not treated as interpolation variables.
47
73
  RESERVED_OPTION_KEYS = %i[
@@ -146,6 +172,32 @@ module Rigor
146
172
  m[1].tr("/", ".")
147
173
  end
148
174
 
175
+ def view_scope_from_path(path)
176
+ m = VIEW_SCOPE_RE.match(path.to_s)
177
+ return nil unless m
178
+
179
+ m[1].split("/").map { |seg| seg.sub(/\A_/, "") }.join(".")
180
+ end
181
+
182
+ def extract_lazy_keys_from_erb(content)
183
+ content.scan(LAZY_T_KEY_RE).map { |dq, sq| dq || sq }.uniq
184
+ end
185
+
186
+ def validate_view_key(key, locale_index:, configured_locales:)
187
+ entry = locale_index.find(key)
188
+ if entry.nil?
189
+ return [] if locale_index.pluralization_namespace?(key)
190
+ return [] if rails_shipped_key?(key)
191
+
192
+ return [unknown_key_violation(key, locale_index)]
193
+ end
194
+
195
+ violations = [translation_call_info(key, entry)]
196
+ missing = locale_index.missing_locales_for(key, configured_locales: configured_locales)
197
+ violations << missing_locale_violation(key, missing) unless missing.empty?
198
+ violations
199
+ end
200
+
149
201
  # Expands a lazy key (starting with `.`) to its full
150
202
  # dotted path using the controller scope and action name.
151
203
  # Returns the raw key unchanged for absolute keys.
@@ -26,6 +26,7 @@ module Rigor
26
26
  # config:
27
27
  # locale_search_paths: ["config/locales"] # default; optional
28
28
  # configured_locales: ["en"] # default; optional — locales the project ships
29
+ # view_search_paths: ["app/views"] # default; optional — view template search roots
29
30
  #
30
31
  # ## What it checks
31
32
  #
@@ -39,6 +40,11 @@ module Rigor
39
40
  # `%{var}` placeholders must match the call's keyword
40
41
  # arguments. Missing placeholders are errors; extra
41
42
  # arguments are warnings.
43
+ # 4. **View template lazy keys** — `t('.title')` inside
44
+ # `app/views/setting/index.html.erb` expands to
45
+ # `setting.index.title` and is validated against the
46
+ # locale index. ERB, Haml, and Slim templates are
47
+ # scanned under `view_search_paths`.
42
48
  #
43
49
  # ## Limitations
44
50
  #
@@ -51,6 +57,16 @@ module Rigor
51
57
  # Lazy keys in non-controller `.rb` files (models, helpers,
52
58
  # mailers, …) are silently skipped — the controller/action
53
59
  # scope cannot be statically determined there.
60
+ # - View template lazy keys (`t('.key')` inside ERB / Haml /
61
+ # Slim) are validated for key existence and per-locale
62
+ # coverage. Interpolation variable validation is skipped
63
+ # for view templates (the hash may come from controller
64
+ # instance variables not visible in the template).
65
+ # The view scan is a project-wide pass surfaced through the
66
+ # per-file diagnostic hook, so under `--workers` each
67
+ # fork-pool worker re-emits the full set (the same
68
+ # once-per-run limitation the `load-error` path carries);
69
+ # sequential `rigor check` is unaffected.
54
70
  # - Pluralization (`t('errors.messages.too_short',
55
71
  # count: n)`) is recognised at the call site but the
56
72
  # `count` key is not used to validate the locale's
@@ -61,14 +77,15 @@ module Rigor
61
77
  class RailsI18n < Rigor::Plugin::Base
62
78
  manifest(
63
79
  id: "rails-i18n",
64
- # Bumped 2026-05-28skip `unknown-key` on Rails / rails-
65
- # i18n shipped defaults (`date.order`, `time.am`,
66
- # `support.array.*`, `errors.format`, …).
67
- version: "0.2.0",
80
+ # Bumped 2026-06-23view template lazy-key scanning
81
+ # (`t('.key')` inside ERB / Haml / Slim under
82
+ # `view_search_paths`).
83
+ version: "0.3.0",
68
84
  description: "Validates I18n `t(key)` calls against `config/locales/*.yml`.",
69
85
  config_schema: {
70
86
  "locale_search_paths" => { kind: :array, default: ["config/locales"] },
71
- "configured_locales" => { kind: :array, default: ["en"] }
87
+ "configured_locales" => { kind: :array, default: ["en"] },
88
+ "view_search_paths" => { kind: :array, default: ["app/views"] }
72
89
  }
73
90
  )
74
91
 
@@ -88,22 +105,49 @@ module Rigor
88
105
  index
89
106
  end
90
107
 
108
+ # Scans view templates under `view_search_paths` for lazy
109
+ # `t('.key')` / `I18n.translate('.key')` calls and validates
110
+ # each expanded key against the locale index. Interpolation
111
+ # validation is skipped — the hash may come from controller
112
+ # instance variables not visible in the template source.
113
+ #
114
+ # Watches `**/*.{erb,haml,slim}` under each search root so
115
+ # the cache invalidates when templates are edited.
116
+ producer :view_diagnostics, watch: -> { [[@view_search_paths, "**/*.erb", "**/*.haml", "**/*.slim"]] } do |_params|
117
+ index = producer_value(:locale_index)
118
+ next [] if index.nil? || index.empty?
119
+
120
+ scan_view_files(index)
121
+ end
122
+
91
123
  def init(_services)
92
124
  @locale_search_paths = Array(config.fetch("locale_search_paths")).map(&:to_s)
125
+ @view_search_paths = Array(config.fetch("view_search_paths")).map(&:to_s)
93
126
  @configured_locales = Array(config.fetch("configured_locales")).map(&:to_s)
94
127
  @load_errors = []
95
128
  @load_errors_emitted = false
129
+ @view_diagnostics_emitted = false
96
130
  end
97
131
 
98
132
  # File-level only: the once-per-run YAML load errors + the
99
- # runtime (cache-load) error. Per-call `t('key')` validation runs
100
- # over the engine-owned walk via the node_rule below (ADR-37). The
101
- # locale index is lazily loaded + memoised by `producer_value`.
133
+ # runtime (cache-load) error + the view-template scan. Per-call
134
+ # `t('key')` validation runs over the engine-owned walk via the
135
+ # node_rule below (ADR-37). The locale index and view diagnostics
136
+ # are lazily loaded + memoised by `producer_value`.
102
137
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
103
138
  index = producer_value(:locale_index)
104
139
  diagnostics = []
105
140
  diagnostics.concat(consume_load_error_diagnostics(path)) unless @load_errors.empty?
106
141
  diagnostics << runtime_error_diagnostic(path) if index.nil? && producer_error(:locale_index)
142
+ unless @view_diagnostics_emitted
143
+ view_diags = producer_value(:view_diagnostics) || []
144
+ if (view_err = producer_error(:view_diagnostics))
145
+ diagnostics << view_runtime_error_diagnostic(path, view_err)
146
+ else
147
+ diagnostics.concat(view_diags)
148
+ end
149
+ @view_diagnostics_emitted = true
150
+ end
107
151
  diagnostics
108
152
  end
109
153
 
@@ -126,6 +170,68 @@ module Rigor
126
170
 
127
171
  private
128
172
 
173
+ def scan_view_files(index)
174
+ view_files.flat_map do |view_path|
175
+ scan_view_file(view_path, index)
176
+ end
177
+ end
178
+
179
+ def scan_view_file(view_path, index)
180
+ content = read_view_template(view_path) or return []
181
+ scope = Analyzer.view_scope_from_path(view_path) or return []
182
+
183
+ display_path = relative_view_path(view_path)
184
+ Analyzer.extract_lazy_keys_from_erb(content).flat_map do |key|
185
+ full_key = "#{scope}.#{key}"
186
+ Analyzer.validate_view_key(
187
+ full_key, locale_index: index, configured_locales: @configured_locales
188
+ ).map { |v| view_diagnostic(display_path, v) }
189
+ end
190
+ rescue StandardError => e
191
+ [Rigor::Analysis::Diagnostic.new(
192
+ path: relative_view_path(view_path), line: 1, column: 1,
193
+ message: "rigor-rails-i18n: failed to scan view template: #{e.class}: #{e.message}",
194
+ severity: :warning,
195
+ rule: "load-error"
196
+ )]
197
+ end
198
+
199
+ def view_files
200
+ @view_search_paths.flat_map do |root|
201
+ absolute = File.expand_path(root)
202
+ next [] unless File.directory?(absolute)
203
+
204
+ Dir.glob(File.join(absolute, "**", "*.{erb,haml,slim}"))
205
+ end.sort
206
+ end
207
+
208
+ # Diagnostics anchor on a path relative to the working
209
+ # directory — the same base `File.expand_path` globbed the
210
+ # view files against — so view diagnostics render and match
211
+ # baselines like every other diagnostic (which carry
212
+ # project-relative paths). Falls back to the absolute path
213
+ # for a view outside the working tree.
214
+ def relative_view_path(absolute_path)
215
+ Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
216
+ rescue ArgumentError
217
+ absolute_path
218
+ end
219
+
220
+ def read_view_template(path)
221
+ io_boundary.read_file(path)
222
+ rescue Plugin::AccessDeniedError
223
+ nil
224
+ end
225
+
226
+ def view_diagnostic(view_path, violation)
227
+ Rigor::Analysis::Diagnostic.new(
228
+ path: view_path, line: 1, column: 1,
229
+ message: violation.message,
230
+ severity: violation.severity,
231
+ rule: violation.rule
232
+ )
233
+ end
234
+
129
235
  # The runner only invokes `diagnostics_for_file` for
130
236
  # Ruby files (`paths:` is filtered to `.rb`). YAML
131
237
  # parse errors therefore can't be anchored on the
@@ -155,6 +261,15 @@ module Rigor
155
261
  rule: "load-error"
156
262
  )
157
263
  end
264
+
265
+ def view_runtime_error_diagnostic(path, error)
266
+ Rigor::Analysis::Diagnostic.new(
267
+ path: path, line: 1, column: 1,
268
+ message: "rigor-rails-i18n: failed to scan view templates: #{error.class}: #{error.message}",
269
+ severity: :warning,
270
+ rule: "load-error"
271
+ )
272
+ end
158
273
  end
159
274
 
160
275
  Rigor::Plugin.register(RailsI18n)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: language_server-protocol
@@ -109,6 +109,26 @@ dependencies:
109
109
  - - "<"
110
110
  - !ruby/object:Gem::Version
111
111
  version: '4.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: binpacker
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 0.1.0
119
+ - - "<"
120
+ - !ruby/object:Gem::Version
121
+ version: '1.0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: 0.1.0
129
+ - - "<"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
112
132
  - !ruby/object:Gem::Dependency
113
133
  name: parallel_tests
114
134
  requirement: !ruby/object:Gem::Requirement
@@ -934,7 +954,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
934
954
  - !ruby/object:Gem::Version
935
955
  version: '0'
936
956
  requirements: []
937
- rubygems_version: 3.7.2
957
+ rubygems_version: 4.0.10
938
958
  specification_version: 4
939
959
  summary: Inference-first static analysis for Ruby.
940
960
  test_files: []