rigortype 0.2.4 → 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 +4 -4
- data/docs/manual/plugins/rigor-rails-i18n.md +22 -3
- data/lib/rigor/analysis/incremental_session.rb +7 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +52 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +123 -8
- metadata +23 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce4071258582638c452079484534e58c6d6b290a88ea51fdce715024d7c09297
|
|
4
|
+
data.tar.gz: 90197cb4711899857c8d242470b063e7da64947e46e240253206179a0e8f6baf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
47
|
-
(the scope can't be determined
|
|
48
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
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
|
data/lib/rigor/version.rb
CHANGED
|
@@ -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-
|
|
65
|
-
#
|
|
66
|
-
# `
|
|
67
|
-
version: "0.
|
|
80
|
+
# Bumped 2026-06-23 — view 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
|
|
100
|
-
# over the engine-owned walk via the
|
|
101
|
-
#
|
|
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.
|
|
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-
|
|
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:
|
|
957
|
+
rubygems_version: 4.0.10
|
|
938
958
|
specification_version: 4
|
|
939
959
|
summary: Inference-first static analysis for Ruby.
|
|
940
960
|
test_files: []
|