rigortype 0.1.3 → 0.1.4
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/README.md +125 -31
- data/lib/rigor/analysis/check_rules.rb +10 -18
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
- data/lib/rigor/analysis/diagnostic.rb +0 -2
- data/lib/rigor/analysis/fact_store.rb +11 -3
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/runner.rb +114 -3
- data/lib/rigor/builtins/imported_refinements.rb +360 -55
- data/lib/rigor/cache/descriptor.rb +1 -1
- data/lib/rigor/cache/store.rb +1 -1
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +173 -0
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_renderer.rb +1 -1
- data/lib/rigor/cli/type_scan_report.rb +2 -2
- data/lib/rigor/cli.rb +9 -1
- data/lib/rigor/configuration/dependencies.rb +2 -2
- data/lib/rigor/configuration.rb +2 -2
- data/lib/rigor/environment.rb +35 -4
- data/lib/rigor/flow_contribution/conflict.rb +2 -2
- data/lib/rigor/flow_contribution/element.rb +1 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution/merge_result.rb +1 -1
- data/lib/rigor/flow_contribution/merger.rb +3 -3
- data/lib/rigor/flow_contribution.rb +2 -2
- data/lib/rigor/inference/block_parameter_binder.rb +0 -2
- data/lib/rigor/inference/coverage_scanner.rb +1 -1
- data/lib/rigor/inference/expression_typer.rb +67 -11
- data/lib/rigor/inference/fallback.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +0 -12
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +118 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +6 -11
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +0 -4
- data/lib/rigor/inference/method_dispatcher.rb +146 -2
- data/lib/rigor/inference/method_parameter_binder.rb +1 -3
- data/lib/rigor/inference/narrowing.rb +2 -4
- data/lib/rigor/inference/rbs_type_translator.rb +0 -2
- data/lib/rigor/inference/scope_indexer.rb +14 -9
- data/lib/rigor/inference/statement_evaluator.rb +7 -7
- data/lib/rigor/plugin/io_boundary.rb +0 -2
- data/lib/rigor/plugin/loader.rb +2 -2
- data/lib/rigor/plugin/manifest.rb +30 -9
- data/lib/rigor/plugin/registry.rb +11 -0
- data/lib/rigor/plugin/services.rb +1 -1
- data/lib/rigor/plugin/type_node_resolver.rb +52 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/reporter.rb +91 -0
- data/lib/rigor/rbs_extended.rb +131 -32
- data/lib/rigor/scope.rb +25 -8
- data/lib/rigor/sig_gen/classification.rb +36 -0
- data/lib/rigor/sig_gen/generator.rb +1048 -0
- data/lib/rigor/sig_gen/layout_index.rb +108 -0
- data/lib/rigor/sig_gen/method_candidate.rb +62 -0
- data/lib/rigor/sig_gen/observation_collector.rb +391 -0
- data/lib/rigor/sig_gen/observed_call.rb +62 -0
- data/lib/rigor/sig_gen/path_mapper.rb +116 -0
- data/lib/rigor/sig_gen/renderer.rb +157 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
- data/lib/rigor/sig_gen/write_result.rb +48 -0
- data/lib/rigor/sig_gen/writer.rb +530 -0
- data/lib/rigor/sig_gen.rb +25 -0
- data/lib/rigor/type/bound_method.rb +79 -0
- data/lib/rigor/type/combinator.rb +195 -2
- data/lib/rigor/type/constant.rb +13 -0
- data/lib/rigor/type/hash_shape.rb +0 -2
- data/lib/rigor/type/union.rb +20 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +62 -0
- data/lib/rigor/type_node/identifier.rb +30 -0
- data/lib/rigor/type_node/indexed_access.rb +41 -0
- data/lib/rigor/type_node/integer_literal.rb +29 -0
- data/lib/rigor/type_node/name_scope.rb +52 -0
- data/lib/rigor/type_node/resolver_chain.rb +56 -0
- data/lib/rigor/type_node/string_literal.rb +29 -0
- data/lib/rigor/type_node/symbol_literal.rb +28 -0
- data/lib/rigor/type_node/union.rb +42 -0
- data/lib/rigor/type_node.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -0
- data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
- data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
- data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
- data/sig/rigor/cli/diff_command.rbs +4 -0
- data/sig/rigor/cli/explain_command.rbs +4 -0
- data/sig/rigor/cli/sig_gen_command.rbs +4 -0
- data/sig/rigor/cli/type_scan_command.rbs +3 -0
- data/sig/rigor/environment.rbs +5 -2
- data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
- data/sig/rigor/inference/builtins.rbs +2 -0
- data/sig/rigor/plugin/access_denied_error.rbs +3 -0
- data/sig/rigor/plugin/base.rbs +6 -0
- data/sig/rigor/plugin/fact_store.rbs +11 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/load_error.rbs +6 -0
- data/sig/rigor/plugin/loader.rbs +20 -0
- data/sig/rigor/plugin/manifest.rbs +9 -0
- data/sig/rigor/plugin/registry.rbs +3 -0
- data/sig/rigor/plugin/services.rbs +3 -0
- data/sig/rigor/plugin/trust_policy.rbs +4 -0
- data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
- data/sig/rigor/plugin.rbs +8 -0
- data/sig/rigor/scope.rbs +4 -2
- data/sig/rigor/type.rbs +28 -6
- metadata +52 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8142401ce01630e0adcc3e1d59dedfc0a123ae247617b7bf91d33cbffe4cb193
|
|
4
|
+
data.tar.gz: ca747bb44214c0bf7dca8203f1c2fcf84657b8a639dbdc5448ede1f32b3f48ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02ef30047bcbf17ee716634315664522449610396c0645cf2e9f289fab7b1bc5667ac60a3cbe4069b59c7435bc1ad2ff9142f35f525df019b439ef74ad58191b
|
|
7
|
+
data.tar.gz: 6e6fe48ffce033b46a1f44be6b2eff62c3e11733f6fd5a583a17e631e18a752d3b77464783da88e9c9f5f0ee345440ca5c751d05e3d77d5253334f3a988bbcf4
|
data/README.md
CHANGED
|
@@ -79,6 +79,12 @@ bundle exec rigor type-of lib/foo.rb:10:5
|
|
|
79
79
|
# Report Scope#type_of coverage across a tree (handy when
|
|
80
80
|
# diagnosing why a particular call site reads as `untyped`).
|
|
81
81
|
bundle exec rigor type-scan lib
|
|
82
|
+
|
|
83
|
+
# Emit RBS skeletons from inference results — review with
|
|
84
|
+
# `--diff`, write to `sig/` with `--write`. ADR-14 sig-gen.
|
|
85
|
+
bundle exec rigor sig-gen --print lib/foo.rb
|
|
86
|
+
bundle exec rigor sig-gen --diff lib/foo.rb
|
|
87
|
+
bundle exec rigor sig-gen --write lib/foo.rb
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
### Sample output
|
|
@@ -145,6 +151,7 @@ out of the box, without you writing a single annotation.
|
|
|
145
151
|
| **Intersection** (`Type::Intersection`) | Composition of multiple refinements | `non-empty-lowercase-string = non-empty-string ∩ lowercase-string` |
|
|
146
152
|
| **Tuple / HashShape** | Heterogeneous arrays / known-key hashes that carry per-position / per-key types | `[1, "two", :three]` types as `Tuple[Constant<1>, Constant<"two">, Constant<:three>]`; `{name: "Alice", age: 30}` as `HashShape{name: Constant<"Alice">, age: Constant<30>}` |
|
|
147
153
|
| **Union** (`Type::Union`) | "One of these literal values" — finite enums Rigor can enumerate | `Constant<:zero> \| Constant<:small> \| Constant<:large>` |
|
|
154
|
+
| **`Method` binding** (`Type::BoundMethod`) | The receiver / method-name pair `Object#method(:sym)` produces, so `.call` / `.()` / `[]` recover the precise backing dispatch | `"1".method(:to_i).call` resolves to `Constant<1>` instead of `untyped` |
|
|
148
155
|
| **`Dynamic[T]`** | The gradual carrier — wraps a static facet with a "could be anything" admission | `Dynamic[Top]` is the conservative fallback Rigor uses when it cannot prove a narrower type |
|
|
149
156
|
|
|
150
157
|
Each refinement / range / literal carrier **erases to its base
|
|
@@ -188,6 +195,12 @@ label = case n
|
|
|
188
195
|
label # Constant<:zero> | Constant<:small>
|
|
189
196
|
# | Constant<:large>
|
|
190
197
|
|
|
198
|
+
# Method bindings keep their receiver — `.method(:sym).call`
|
|
199
|
+
# round-trips through the original dispatch.
|
|
200
|
+
[:to_i, :to_f, :to_sym].map { |m| "1".method(m).call }
|
|
201
|
+
# Tuple[Constant<1>, Constant<1.0>, Constant<:"1">]
|
|
202
|
+
# — per-element fold + BoundMethod backward fold
|
|
203
|
+
|
|
191
204
|
# RBS::Extended directives let you tighten beyond what RBS expresses.
|
|
192
205
|
class Slug
|
|
193
206
|
%a{rigor:v1:return: non-empty-string}
|
|
@@ -353,6 +366,10 @@ sees `id` as `non-empty-string` (so `id.empty?` reduces to
|
|
|
353
366
|
optional policies, per-element block fold over
|
|
354
367
|
`map`, `select`, `filter_map`, `flat_map`, `find` /
|
|
355
368
|
`find_index`, `count`, `any?` / `all?` / `none?`, `zip`.
|
|
369
|
+
`&:symbol` block-pass on these methods is treated as
|
|
370
|
+
`{ |x| x.symbol }` and dispatches against the element type
|
|
371
|
+
so `Hash#transform_values(&:freeze)` returns `Hash[K, V]`
|
|
372
|
+
instead of `Enumerator[...]`.
|
|
356
373
|
- **Constant folding** — aggressive arithmetic / string /
|
|
357
374
|
Symbol / Tuple-shaped `divmod` folding, cartesian fold over
|
|
358
375
|
`Union[Constant…]`, integer-range arithmetic
|
|
@@ -370,26 +387,48 @@ sees `id` as `non-empty-string` (so `id.empty?` reduces to
|
|
|
370
387
|
- **Refinement carriers** — `Type::Difference`,
|
|
371
388
|
`Type::Refined`, `Type::Intersection` provide the
|
|
372
389
|
imported-built-in catalogue end-to-end through
|
|
373
|
-
`Builtins::ImportedRefinements`.
|
|
390
|
+
`Builtins::ImportedRefinements`. The parser accepts Symbol
|
|
391
|
+
/ String literals and `|`-unions at type-arg position
|
|
392
|
+
(`pick_of[Shape, :a | :b]`, `Pick[T, "name" | "email"]`).
|
|
393
|
+
- **`Method` carrier (`Type::BoundMethod`)** —
|
|
394
|
+
`Object#method(:sym)` lifts into a binding carrier so
|
|
395
|
+
`.call` / `.()` / `[]` recover the precise dispatch
|
|
396
|
+
(`"1".method(:to_i).call` resolves to `Constant<1>`).
|
|
397
|
+
Reflective Method members (`#owner` / `#name` / `#arity`)
|
|
398
|
+
still resolve via the Method RBS sig.
|
|
374
399
|
- **`RBS::Extended` directive routes** — `return:`, `param:`
|
|
375
400
|
(call-site + body-side), `assert:` /
|
|
376
401
|
`predicate-if-(true|false)` accept refinement payloads, and
|
|
377
402
|
roll up into a single `Rigor::FlowContribution` bundle per
|
|
378
403
|
method (the v0.1.0 plugin contribution merger reads bundles
|
|
379
404
|
directly).
|
|
405
|
+
- **Opt-in gem-source inference (ADR-10)** — gems listed under
|
|
406
|
+
`dependencies.source_inference:` have their `lib/` walked.
|
|
407
|
+
Per-gem budget, per-gem-version cache slice,
|
|
408
|
+
`dynamic.dependency-source.*` diagnostic family covering
|
|
409
|
+
gem-not-found / budget-exceeded / config-conflict /
|
|
410
|
+
boundary-cross (the last surfaces RBS+gem-source overlap
|
|
411
|
+
on `mode: :full` gems for audit).
|
|
380
412
|
|
|
381
413
|
The full per-release surface lives in
|
|
382
414
|
[`CHANGELOG.md`](CHANGELOG.md). The internal contracts the
|
|
383
415
|
analyzer guarantees live under
|
|
384
416
|
[`docs/internal-spec/`](docs/internal-spec/).
|
|
385
417
|
|
|
386
|
-
## Plugins
|
|
418
|
+
## Plugins
|
|
419
|
+
|
|
420
|
+
`v0.1.0` introduced the extension API; `v0.1.x` rounds it out
|
|
421
|
+
with the [ADR-9](docs/adr/9-cross-plugin-api.md) cross-plugin
|
|
422
|
+
fact channel (one plugin publishes a fact like `:model_index`,
|
|
423
|
+
another consumes it), [ADR-11](docs/adr/11-sorbet-input-adapter.md)
|
|
424
|
+
Sorbet ingestion, and [ADR-13](docs/adr/13-typenode-resolver-plugin.md)
|
|
425
|
+
plugin-supplied type-vocabulary resolvers. **Nineteen worked
|
|
426
|
+
examples** ship under [`examples/`](examples/) — each is a
|
|
427
|
+
fully-shaped plugin gem with a runnable demo and an end-to-end
|
|
428
|
+
integration spec.
|
|
387
429
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
[`examples/`](examples/) — each is a fully-shaped plugin gem
|
|
391
|
-
with a runnable demo and an end-to-end integration spec, and
|
|
392
|
-
each spotlights a different facet of the plugin contract:
|
|
430
|
+
**Plugin-contract teaching examples** (focus on a single
|
|
431
|
+
extension-point):
|
|
393
432
|
|
|
394
433
|
- [`rigor-deprecations`](examples/rigor-deprecations/) —
|
|
395
434
|
smallest possible plugin (~80 lines); config-driven rules.
|
|
@@ -403,21 +442,45 @@ each spotlights a different facet of the plugin contract:
|
|
|
403
442
|
- [`rigor-units`](examples/rigor-units/) — local-variable flow
|
|
404
443
|
tracking through arithmetic.
|
|
405
444
|
- [`rigor-routes`](examples/rigor-routes/) — `Plugin::IoBoundary`
|
|
406
|
-
reads under `TrustPolicy` plus cache producers
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
445
|
+
reads under `TrustPolicy` plus cache producers.
|
|
446
|
+
- [`rigor-typescript-utility-types`](examples/rigor-typescript-utility-types/)
|
|
447
|
+
— `Plugin::TypeNodeResolver` chain wiring TS-canonical names
|
|
448
|
+
(`Pick` / `Omit` / `Partial` / `Required` / `Readonly`) onto
|
|
449
|
+
Rigor's shape-projection type functions.
|
|
450
|
+
|
|
451
|
+
**Rails ecosystem plugins** (Tier 1 + Tier 2 + Tier 3 + Sorbet):
|
|
452
|
+
|
|
453
|
+
- Tier 1: [`rigor-rails-routes`](examples/rigor-rails-routes/),
|
|
454
|
+
[`rigor-rails-i18n`](examples/rigor-rails-i18n/),
|
|
455
|
+
[`rigor-actionmailer`](examples/rigor-actionmailer/),
|
|
456
|
+
[`rigor-activejob`](examples/rigor-activejob/).
|
|
457
|
+
- Tier 2: [`rigor-actionpack`](examples/rigor-actionpack/)
|
|
458
|
+
(4 phases — routes / filters / renders / strong-params),
|
|
459
|
+
[`rigor-factorybot`](examples/rigor-factorybot/),
|
|
460
|
+
[`rigor-activerecord`](examples/rigor-activerecord/) —
|
|
461
|
+
publishes `:model_index` via ADR-9 for the other two
|
|
462
|
+
to consume.
|
|
463
|
+
- Tier 3: [`rigor-pundit`](examples/rigor-pundit/),
|
|
464
|
+
[`rigor-sidekiq`](examples/rigor-sidekiq/),
|
|
465
|
+
[`rigor-rspec`](examples/rigor-rspec/),
|
|
466
|
+
[`rigor-actioncable`](examples/rigor-actioncable/).
|
|
467
|
+
- Parallel: [`rigor-sorbet`](examples/rigor-sorbet/) — ingests
|
|
468
|
+
Sorbet `sig` / `T.let` / `T.cast` / `T.must` / `T.bind` /
|
|
469
|
+
`T.assert_type!` / `T.reveal_type` / `T.absurd` and RBI
|
|
470
|
+
files as type sources.
|
|
413
471
|
|
|
414
472
|
[`examples/README.md`](examples/README.md) is the plugin
|
|
415
473
|
authoring landing page — comparison table, recommended reading
|
|
416
474
|
order, and the architectural map of which surface each example
|
|
417
475
|
exercises. The binding contract for the plugin API lives in
|
|
418
|
-
[`docs/adr/2-extension-api.md`](docs/adr/2-extension-api.md)
|
|
419
|
-
|
|
420
|
-
[`docs/internal-spec/plugin*.md`](docs/internal-spec/)
|
|
476
|
+
[`docs/adr/2-extension-api.md`](docs/adr/2-extension-api.md);
|
|
477
|
+
the slice-by-slice normative specs are under
|
|
478
|
+
[`docs/internal-spec/plugin*.md`](docs/internal-spec/); the
|
|
479
|
+
sibling ADRs that extend it ride the same surface
|
|
480
|
+
([ADR-9](docs/adr/9-cross-plugin-api.md) cross-plugin facts,
|
|
481
|
+
[ADR-11](docs/adr/11-sorbet-input-adapter.md) Sorbet adapter,
|
|
482
|
+
[ADR-13](docs/adr/13-typenode-resolver-plugin.md) TypeNode
|
|
483
|
+
resolver).
|
|
421
484
|
|
|
422
485
|
## Configuration
|
|
423
486
|
|
|
@@ -447,22 +510,53 @@ Common knobs the file exposes:
|
|
|
447
510
|
|
|
448
511
|
## Status
|
|
449
512
|
|
|
450
|
-
Current released version: **`v0.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
[`docs/MILESTONES.md`](docs/MILESTONES.md); release-by-release
|
|
513
|
+
Current released version: **`v0.1.2`**. The analyzer is usable
|
|
514
|
+
on real Ruby code today; the rule catalogue is deliberately
|
|
515
|
+
narrow — Rigor's stance is to surface zero false positives
|
|
516
|
+
while the inference surface stabilises. The roadmap is tracked
|
|
517
|
+
in [`docs/MILESTONES.md`](docs/MILESTONES.md); release-by-release
|
|
456
518
|
detail lives in [`CHANGELOG.md`](CHANGELOG.md).
|
|
457
519
|
|
|
458
|
-
`v0.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
520
|
+
`v0.1.4` is the active development cluster on `master` and
|
|
521
|
+
delivers:
|
|
522
|
+
|
|
523
|
+
- **[ADR-10](docs/adr/10-dependency-source-inference.md) closed
|
|
524
|
+
end-to-end** — opt-in gem-source inference, per-gem budget,
|
|
525
|
+
cache slice, and the `dynamic.dependency-source.boundary-cross`
|
|
526
|
+
`:info` diagnostic that surfaces RBS / gem-source overlap
|
|
527
|
+
under `mode: :full`.
|
|
528
|
+
- **[ADR-11](docs/adr/11-sorbet-input-adapter.md) primary surface
|
|
529
|
+
+ per-call-site assertion gating** — `rigor-sorbet` ingests
|
|
530
|
+
Sorbet `sig { ... }` blocks, `T.let` / `T.cast` / `T.must` /
|
|
531
|
+
`T.bind` / `T.assert_type!` / `T.reveal_type` / `T.absurd`,
|
|
532
|
+
and RBI files. Per-call-site `enforce_sigil` gates assertion
|
|
533
|
+
recognisers by the caller file's `# typed:` sigil.
|
|
534
|
+
- **[ADR-13](docs/adr/13-typenode-resolver-plugin.md) plugin
|
|
535
|
+
TypeNode resolver + TypeScript-utility-type adapter** —
|
|
536
|
+
`Plugin::TypeNodeResolver` extension point + five
|
|
537
|
+
Rigor-canonical shape-projection type functions
|
|
538
|
+
(`pick_of` / `omit_of` / `partial_of` / `required_of` /
|
|
539
|
+
`readonly_of`) + the opt-in `rigor-typescript-utility-types`
|
|
540
|
+
plugin mapping TS spellings onto the core functions.
|
|
541
|
+
`Pick[T, :a | :b]` round-trips through the directive grammar.
|
|
542
|
+
- **[ADR-14](docs/adr/14-rbs-sig-generation.md) — `rigor sig-gen`
|
|
543
|
+
CLI** — emits RBS from inference results across five
|
|
544
|
+
classifications (`new-file` / `new-method` / `tighter-return`
|
|
545
|
+
/ `equivalent` / `skipped`); `--params=untyped` default,
|
|
546
|
+
`--params=observed` opt-in via `--observe=PATH`.
|
|
547
|
+
- **`Method` carrier (`Type::BoundMethod`)** —
|
|
548
|
+
`Object#method(:sym).call` / `.()` / `[]` round-trip with
|
|
549
|
+
full precision instead of collapsing to `untyped`.
|
|
550
|
+
- **Rails ecosystem (Tier 1 + Tier 2)** — `rigor-rails-routes`,
|
|
551
|
+
`rigor-rails-i18n`, `rigor-actionmailer`, `rigor-activejob`,
|
|
552
|
+
`rigor-actionpack` (4 phases), `rigor-factorybot`, and
|
|
553
|
+
`rigor-activerecord` publishing `:model_index` via the
|
|
554
|
+
ADR-9 cross-plugin fact channel.
|
|
555
|
+
|
|
556
|
+
Nineteen worked plugin examples now ship under
|
|
557
|
+
[`examples/`](examples/) — see
|
|
558
|
+
[`examples/README.md`](examples/README.md) for the comparison
|
|
559
|
+
table.
|
|
466
560
|
|
|
467
561
|
## Contributing
|
|
468
562
|
|
|
@@ -262,7 +262,7 @@ module Rigor
|
|
|
262
262
|
# errors, internal analyzer errors) are NEVER
|
|
263
263
|
# suppressed — they represent failures the user cannot
|
|
264
264
|
# silence away.
|
|
265
|
-
def filter_suppressed(diagnostics, comments:, disabled_rules:)
|
|
265
|
+
def filter_suppressed(diagnostics, comments:, disabled_rules:)
|
|
266
266
|
line_suppressions, file_suppressions = parse_suppression_comments(comments)
|
|
267
267
|
disabled = expand_rule_tokens(disabled_rules)
|
|
268
268
|
|
|
@@ -329,7 +329,7 @@ module Rigor
|
|
|
329
329
|
class << self
|
|
330
330
|
private
|
|
331
331
|
|
|
332
|
-
def undefined_method_diagnostic(path, call_node, scope_index)
|
|
332
|
+
def undefined_method_diagnostic(path, call_node, scope_index)
|
|
333
333
|
return nil if call_node.receiver.nil?
|
|
334
334
|
|
|
335
335
|
scope = scope_index[call_node]
|
|
@@ -429,7 +429,7 @@ module Rigor
|
|
|
429
429
|
# by `undefined_method_diagnostic`; it returns nil
|
|
430
430
|
# when the call's receiver / RBS coverage / call shape
|
|
431
431
|
# disqualifies the rule.
|
|
432
|
-
# rubocop:disable Metrics/
|
|
432
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
433
433
|
def wrong_arity_diagnostic(path, call_node, scope_index)
|
|
434
434
|
return nil if call_node.receiver.nil?
|
|
435
435
|
return nil unless plain_positional_call?(call_node)
|
|
@@ -459,7 +459,7 @@ module Rigor
|
|
|
459
459
|
|
|
460
460
|
build_arity_diagnostic(path, call_node, class_name, min, max, actual)
|
|
461
461
|
end
|
|
462
|
-
# rubocop:enable Metrics/
|
|
462
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
463
463
|
|
|
464
464
|
def plain_positional_call?(call_node)
|
|
465
465
|
arguments = call_node.arguments
|
|
@@ -534,7 +534,7 @@ module Rigor
|
|
|
534
534
|
# and union receivers where every member already
|
|
535
535
|
# disqualifies the call (avoid duplicating the
|
|
536
536
|
# undefined-method diagnostic).
|
|
537
|
-
def nil_receiver_diagnostic(path, call_node, scope_index)
|
|
537
|
+
def nil_receiver_diagnostic(path, call_node, scope_index)
|
|
538
538
|
return nil if call_node.receiver.nil?
|
|
539
539
|
# Safe-navigation calls (`recv&.method`) already
|
|
540
540
|
# short-circuit on nil at runtime, so a nil-bearing
|
|
@@ -615,7 +615,7 @@ module Rigor
|
|
|
615
615
|
# The diagnostic does NOT count toward `Result#error_count`
|
|
616
616
|
# so a fixture peppered with `dump_type` calls still
|
|
617
617
|
# passes `rigor check`.
|
|
618
|
-
def dump_type_diagnostic(path, call_node, scope_index)
|
|
618
|
+
def dump_type_diagnostic(path, call_node, scope_index)
|
|
619
619
|
return nil unless rigor_testing_call?(call_node, :dump_type)
|
|
620
620
|
return nil if call_node.arguments.nil? || call_node.arguments.arguments.empty?
|
|
621
621
|
|
|
@@ -644,7 +644,7 @@ module Rigor
|
|
|
644
644
|
# is emitted; matching calls produce no output. This
|
|
645
645
|
# lets a fixture document its expected types inline:
|
|
646
646
|
# subsequent `rigor check` runs flag any drift.
|
|
647
|
-
def assert_type_diagnostic(path, call_node, scope_index)
|
|
647
|
+
def assert_type_diagnostic(path, call_node, scope_index)
|
|
648
648
|
return nil unless rigor_testing_call?(call_node, :assert_type)
|
|
649
649
|
return nil if call_node.arguments.nil? || call_node.arguments.arguments.size < 2
|
|
650
650
|
|
|
@@ -972,7 +972,6 @@ module Rigor
|
|
|
972
972
|
)
|
|
973
973
|
end
|
|
974
974
|
|
|
975
|
-
# rubocop:disable Metrics/ParameterLists
|
|
976
975
|
def build_ivar_write_mismatch_diagnostic(path, node, class_name, ivar_name, first_class, other_class)
|
|
977
976
|
location = node.name_loc || node.location
|
|
978
977
|
Diagnostic.new(
|
|
@@ -985,7 +984,6 @@ module Rigor
|
|
|
985
984
|
severity: :error
|
|
986
985
|
)
|
|
987
986
|
end
|
|
988
|
-
# rubocop:enable Metrics/ParameterLists
|
|
989
987
|
|
|
990
988
|
# Returns the dead-branch node for a literal-predicate
|
|
991
989
|
# if/unless, or nil when no observable branch is dead.
|
|
@@ -1034,7 +1032,6 @@ module Rigor
|
|
|
1034
1032
|
# (no splat / kw / block-pass / forwarded).
|
|
1035
1033
|
# - Per-argument: skip when EITHER side is `Dynamic`
|
|
1036
1034
|
# (the call cannot be statically refuted).
|
|
1037
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
1038
1035
|
def argument_type_diagnostic(path, call_node, scope_index)
|
|
1039
1036
|
return nil if call_node.receiver.nil?
|
|
1040
1037
|
return nil unless plain_positional_call?(call_node)
|
|
@@ -1059,15 +1056,13 @@ module Rigor
|
|
|
1059
1056
|
return nil if method_def.nil? || method_def == true
|
|
1060
1057
|
return nil unless method_def.method_types.size == 1
|
|
1061
1058
|
|
|
1062
|
-
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def)
|
|
1059
|
+
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
1063
1060
|
mismatch = first_argument_mismatch(method_def.method_types.first, call_node, scope, param_overrides)
|
|
1064
1061
|
return nil if mismatch.nil?
|
|
1065
1062
|
|
|
1066
1063
|
build_argument_type_diagnostic(path, call_node, class_name, mismatch)
|
|
1067
1064
|
end
|
|
1068
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
1069
1065
|
|
|
1070
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
1071
1066
|
def first_argument_mismatch(method_type, call_node, scope, param_overrides)
|
|
1072
1067
|
function = method_type.type
|
|
1073
1068
|
return nil unless argument_check_eligible?(function)
|
|
@@ -1094,7 +1089,6 @@ module Rigor
|
|
|
1094
1089
|
end
|
|
1095
1090
|
nil
|
|
1096
1091
|
end
|
|
1097
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
1098
1092
|
|
|
1099
1093
|
def argument_check_eligible?(function)
|
|
1100
1094
|
# See `arity_eligible?`: `UntypedFunction` lacks
|
|
@@ -1132,7 +1126,6 @@ module Rigor
|
|
|
1132
1126
|
)
|
|
1133
1127
|
end
|
|
1134
1128
|
|
|
1135
|
-
# rubocop:disable Metrics/ParameterLists
|
|
1136
1129
|
def build_arity_diagnostic(path, call_node, class_name, min, max, actual)
|
|
1137
1130
|
location = call_node.message_loc || call_node.location
|
|
1138
1131
|
range = min == max ? min.to_s : "#{min}..#{max}"
|
|
@@ -1147,7 +1140,6 @@ module Rigor
|
|
|
1147
1140
|
severity: :error
|
|
1148
1141
|
)
|
|
1149
1142
|
end
|
|
1150
|
-
# rubocop:enable Metrics/ParameterLists
|
|
1151
1143
|
|
|
1152
1144
|
def build_undefined_method_diagnostic(path, call_node, receiver_type)
|
|
1153
1145
|
location = call_node.message_loc || call_node.location
|
|
@@ -1186,7 +1178,7 @@ module Rigor
|
|
|
1186
1178
|
# :maybe → emit at :warning. Promoted to :error
|
|
1187
1179
|
# under `severity_profile: strict` per
|
|
1188
1180
|
# ADR-8 § "Severity profile".
|
|
1189
|
-
def return_type_mismatch_diagnostic(path, def_node, scope_index)
|
|
1181
|
+
def return_type_mismatch_diagnostic(path, def_node, scope_index)
|
|
1190
1182
|
return nil if def_node.body.nil?
|
|
1191
1183
|
|
|
1192
1184
|
last_expr = body_last_expression(def_node.body)
|
|
@@ -1255,7 +1247,7 @@ module Rigor
|
|
|
1255
1247
|
end
|
|
1256
1248
|
return nil if method_def.nil?
|
|
1257
1249
|
|
|
1258
|
-
override = Rigor::RbsExtended.read_return_type_override(method_def)
|
|
1250
|
+
override = Rigor::RbsExtended.read_return_type_override(method_def, environment: scope.environment)
|
|
1259
1251
|
return override if override
|
|
1260
1252
|
|
|
1261
1253
|
declared_return_union(method_def, scope.environment)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Analysis
|
|
5
|
+
module DependencySourceInference
|
|
6
|
+
# ADR-10 slice 5c — per-run accumulator for the
|
|
7
|
+
# `dynamic.dependency-source.boundary-cross` `:info`
|
|
8
|
+
# diagnostic.
|
|
9
|
+
#
|
|
10
|
+
# The diagnostic fires when both an authoritative source
|
|
11
|
+
# (RBS today; plugins later) AND a `mode: :full` opt-in
|
|
12
|
+
# gem's source catalog resolve the same `(class_name,
|
|
13
|
+
# method_name)`. The dispatcher takes the authoritative
|
|
14
|
+
# source's answer (per ADR-10's tier order), but records
|
|
15
|
+
# the boundary crossing so the user can audit whether RBS
|
|
16
|
+
# and the gem source have drifted.
|
|
17
|
+
#
|
|
18
|
+
# The accumulator deduplicates per `(class_name,
|
|
19
|
+
# method_name, gem_name)` so a method called from many
|
|
20
|
+
# files yields one diagnostic.
|
|
21
|
+
#
|
|
22
|
+
# Used in the same pattern as
|
|
23
|
+
# {Rigor::RbsExtended::Reporter}: the dispatcher writes
|
|
24
|
+
# events into the per-run instance; {Rigor::Analysis::Runner}
|
|
25
|
+
# drains it at end-of-run into a flat
|
|
26
|
+
# `Rigor::Analysis::Diagnostic` list.
|
|
27
|
+
class BoundaryCrossReporter
|
|
28
|
+
Entry = Data.define(:class_name, :method_name, :gem_name, :rbs_display)
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@entries = []
|
|
32
|
+
@mutex = Mutex.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Array<Entry>] frozen snapshot of the recorded
|
|
36
|
+
# boundary-cross events. Each entry is a Data with
|
|
37
|
+
# `class_name` (String), `method_name` (Symbol),
|
|
38
|
+
# `gem_name` (String), and `rbs_display` (String —
|
|
39
|
+
# the authoritative-side type's human-facing form,
|
|
40
|
+
# embedded into the diagnostic message).
|
|
41
|
+
def entries
|
|
42
|
+
@mutex.synchronize { @entries.dup.freeze }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def empty?
|
|
46
|
+
@mutex.synchronize { @entries.empty? }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Records one boundary-cross event. Deduplicates on the
|
|
50
|
+
# `(class_name, method_name, gem_name)` triple — the
|
|
51
|
+
# diagnostic per-receiver-per-method-per-owning-gem is
|
|
52
|
+
# the actionable unit.
|
|
53
|
+
def record(class_name:, method_name:, gem_name:, rbs_display:)
|
|
54
|
+
entry = Entry.new(
|
|
55
|
+
class_name: class_name, method_name: method_name,
|
|
56
|
+
gem_name: gem_name, rbs_display: rbs_display
|
|
57
|
+
)
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
return if @entries.any? { |existing| same_key?(existing, entry) }
|
|
60
|
+
|
|
61
|
+
@entries << entry
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def same_key?(existing, entry)
|
|
68
|
+
existing.class_name == entry.class_name &&
|
|
69
|
+
existing.method_name == entry.method_name &&
|
|
70
|
+
existing.gem_name == entry.gem_name
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -23,37 +23,63 @@ module Rigor
|
|
|
23
23
|
|
|
24
24
|
# @param dependencies [Rigor::Configuration::Dependencies]
|
|
25
25
|
# @return [Index]
|
|
26
|
-
def build(dependencies)
|
|
26
|
+
def build(dependencies)
|
|
27
27
|
return Index::EMPTY if dependencies.empty?
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
unresolvable = []
|
|
31
|
-
catalog = {}
|
|
32
|
-
class_to_gem = {}
|
|
33
|
-
budget_exceeded = []
|
|
34
|
-
budget = dependencies.budget_per_gem
|
|
35
|
-
|
|
29
|
+
state = BuildState.new
|
|
36
30
|
dependencies.source_inference.each do |entry|
|
|
37
31
|
next if entry.disabled?
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
state.absorb(GemResolver.resolve(entry), dependencies.budget_per_gem, self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
state.to_index(dependencies)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Per-build mutable accumulator. The original inline
|
|
40
|
+
# variables (`resolved` / `unresolvable` / `catalog` /
|
|
41
|
+
# `class_to_gem` / `budget_exceeded` / `gem_modes`)
|
|
42
|
+
# pushed the method past the AbcSize budget; the
|
|
43
|
+
# struct collects the same fields, narrows
|
|
44
|
+
# `absorb`'s branching, and yields one
|
|
45
|
+
# `Index.new(...)` call from `to_index`.
|
|
46
|
+
class BuildState
|
|
47
|
+
def initialize
|
|
48
|
+
@resolved = []
|
|
49
|
+
@unresolvable = []
|
|
50
|
+
@catalog = {}
|
|
51
|
+
@class_to_gem = {}
|
|
52
|
+
@budget_exceeded = []
|
|
53
|
+
@gem_modes = {}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def absorb(outcome, budget, builder)
|
|
40
57
|
case outcome
|
|
41
58
|
when GemResolver::Resolved
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
record_class_to_gem(walked.catalog, outcome.gem_name, class_to_gem)
|
|
46
|
-
budget_exceeded << outcome.gem_name if walked.truncated?
|
|
47
|
-
when GemResolver::Unresolvable then unresolvable << outcome
|
|
59
|
+
absorb_resolved(outcome, budget, builder)
|
|
60
|
+
when GemResolver::Unresolvable
|
|
61
|
+
@unresolvable << outcome
|
|
48
62
|
end
|
|
49
63
|
end
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
def absorb_resolved(resolved, budget, builder)
|
|
66
|
+
@resolved << resolved
|
|
67
|
+
@gem_modes[resolved.gem_name] = resolved.mode
|
|
68
|
+
walked = builder.walker_outcome_for(resolved, budget)
|
|
69
|
+
@catalog.merge!(walked.catalog)
|
|
70
|
+
builder.record_class_to_gem(walked.catalog, resolved.gem_name, @class_to_gem)
|
|
71
|
+
@budget_exceeded << resolved.gem_name if walked.truncated?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def to_index(dependencies)
|
|
75
|
+
Index.new(
|
|
76
|
+
resolved_gems: @resolved, unresolvable: @unresolvable,
|
|
77
|
+
method_catalog: @catalog, budget_exceeded: @budget_exceeded,
|
|
78
|
+
class_to_gem: @class_to_gem,
|
|
79
|
+
budget_overrun_strategy: dependencies.budget_overrun_strategy,
|
|
80
|
+
gem_modes: @gem_modes
|
|
81
|
+
)
|
|
82
|
+
end
|
|
57
83
|
end
|
|
58
84
|
|
|
59
85
|
# ADR-10 5b — per-class reverse-lookup table (β budget
|
|
@@ -21,7 +21,7 @@ module Rigor
|
|
|
21
21
|
# a String so it round-trips into cache descriptors
|
|
22
22
|
# (slice 3) without leaking a `Gem::Version` instance
|
|
23
23
|
# through public surfaces.
|
|
24
|
-
Resolved
|
|
24
|
+
class Resolved < Data.define(:gem_name, :version, :gem_dir, :mode, :roots)
|
|
25
25
|
def descriptor_key
|
|
26
26
|
[gem_name, version, mode].freeze
|
|
27
27
|
end
|
|
@@ -16,7 +16,7 @@ module Rigor
|
|
|
16
16
|
# by walking the resolved gems' `roots:`.
|
|
17
17
|
class Index
|
|
18
18
|
attr_reader :resolved_gems, :unresolvable, :method_catalog, :budget_exceeded,
|
|
19
|
-
:class_to_gem, :budget_overrun_strategy
|
|
19
|
+
:class_to_gem, :budget_overrun_strategy, :gem_modes
|
|
20
20
|
|
|
21
21
|
# @param method_catalog [Hash{[String, Symbol] => Symbol}]
|
|
22
22
|
# the flat `(class_name, method_name) → :instance | :singleton`
|
|
@@ -38,10 +38,19 @@ module Rigor
|
|
|
38
38
|
# budget-exceeded gem's classes degrade to
|
|
39
39
|
# `Dynamic[top]` instead of falling through to the
|
|
40
40
|
# user-class fallback.
|
|
41
|
-
|
|
41
|
+
# @param gem_modes [Hash<String, Symbol>] per-gem mode
|
|
42
|
+
# table (`gem_name → :disabled | :when_missing |
|
|
43
|
+
# :full`). ADR-10 slice 5c consults this through
|
|
44
|
+
# {#mode_for} to identify call sites where gem-source
|
|
45
|
+
# and RBS both contribute under `mode: :full`. The map
|
|
46
|
+
# is keyed on `gem_name` (not class) because re-opened
|
|
47
|
+
# classes belong to the first gem they appeared in
|
|
48
|
+
# per `class_to_gem`; `mode_for(class_name)` chains
|
|
49
|
+
# the two lookups.
|
|
50
|
+
def initialize(
|
|
42
51
|
resolved_gems: [], unresolvable: [], method_catalog: {},
|
|
43
52
|
budget_exceeded: [], class_to_gem: {},
|
|
44
|
-
budget_overrun_strategy: :walker_cap
|
|
53
|
+
budget_overrun_strategy: :walker_cap, gem_modes: {}
|
|
45
54
|
)
|
|
46
55
|
@resolved_gems = resolved_gems.freeze
|
|
47
56
|
@unresolvable = unresolvable.freeze
|
|
@@ -49,6 +58,7 @@ module Rigor
|
|
|
49
58
|
@budget_exceeded = budget_exceeded.freeze
|
|
50
59
|
@class_to_gem = class_to_gem.freeze
|
|
51
60
|
@budget_overrun_strategy = budget_overrun_strategy
|
|
61
|
+
@gem_modes = gem_modes.freeze
|
|
52
62
|
freeze
|
|
53
63
|
end
|
|
54
64
|
|
|
@@ -59,6 +69,25 @@ module Rigor
|
|
|
59
69
|
@class_to_gem[class_name]
|
|
60
70
|
end
|
|
61
71
|
|
|
72
|
+
# ADR-10 slice 5c — per-class mode lookup. Chains
|
|
73
|
+
# `class_to_gem` + `gem_modes`; returns `nil` when the
|
|
74
|
+
# class isn't owned by any opt-in gem in this run.
|
|
75
|
+
def mode_for(class_name)
|
|
76
|
+
gem_name = @class_to_gem[class_name]
|
|
77
|
+
return nil if gem_name.nil?
|
|
78
|
+
|
|
79
|
+
@gem_modes[gem_name]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# ADR-10 slice 5c — true when the receiver class belongs
|
|
83
|
+
# to a gem the user opted into `mode: :full` for. The
|
|
84
|
+
# dispatcher consults this AFTER an authoritative-source
|
|
85
|
+
# (RBS / plugin) dispatch resolves so it can record the
|
|
86
|
+
# boundary-crossing for audit.
|
|
87
|
+
def full_mode?(class_name)
|
|
88
|
+
mode_for(class_name) == :full
|
|
89
|
+
end
|
|
90
|
+
|
|
62
91
|
# Looks up the recorded method kind for a
|
|
63
92
|
# `(class_name, method_name)` pair. Returns `:instance`
|
|
64
93
|
# / `:singleton` when the walker observed a definition
|
|
@@ -41,7 +41,7 @@ module Rigor
|
|
|
41
41
|
# this per-gem so the Runner can surface a single
|
|
42
42
|
# `dynamic.dependency-source.budget-exceeded` warning
|
|
43
43
|
# naming the affected gem(s).
|
|
44
|
-
Outcome
|
|
44
|
+
class Outcome < Data.define(:catalog, :truncated)
|
|
45
45
|
def truncated? = truncated
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "dependency_source_inference/boundary_cross_reporter"
|
|
3
4
|
require_relative "dependency_source_inference/gem_resolver"
|
|
4
5
|
require_relative "dependency_source_inference/index"
|
|
5
6
|
require_relative "dependency_source_inference/walker"
|
|
@@ -24,10 +24,8 @@ module Rigor
|
|
|
24
24
|
# ADR-2 § "Plugin Diagnostic Provenance") let consumers
|
|
25
25
|
# distinguish where a diagnostic originated without committing
|
|
26
26
|
# to the plugin API itself.
|
|
27
|
-
# rubocop:disable Metrics/ParameterLists
|
|
28
27
|
def initialize(path:, line:, column:, message:, severity: :error, rule: nil,
|
|
29
28
|
source_family: DEFAULT_SOURCE_FAMILY)
|
|
30
|
-
# rubocop:enable Metrics/ParameterLists
|
|
31
29
|
@path = path
|
|
32
30
|
@line = line
|
|
33
31
|
@column = column
|