rigortype 0.0.9 → 0.1.1
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 +45 -2
- data/data/builtins/ruby_core/array.yml +6 -6
- data/data/builtins/ruby_core/hash.yml +1 -1
- data/data/builtins/ruby_core/io.yml +3 -3
- data/data/builtins/ruby_core/numeric.yml +1 -1
- data/data/builtins/ruby_core/pathname.yml +100 -100
- data/data/builtins/ruby_core/proc.yml +1 -1
- data/data/builtins/ruby_core/time.yml +3 -3
- data/lib/rigor/analysis/check_rules.rb +228 -40
- data/lib/rigor/analysis/diagnostic.rb +15 -1
- data/lib/rigor/analysis/runner.rb +269 -7
- data/lib/rigor/builtins/regex_refinement.rb +104 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +1 -1
- data/lib/rigor/cache/rbs_constant_table.rb +2 -2
- data/lib/rigor/cache/rbs_descriptor.rb +2 -0
- data/lib/rigor/cache/rbs_instance_definitions.rb +79 -0
- data/lib/rigor/cache/store.rb +2 -0
- data/lib/rigor/cli/type_of_command.rb +3 -3
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli.rb +20 -7
- data/lib/rigor/configuration/severity_profile.rb +109 -0
- data/lib/rigor/configuration.rb +286 -15
- data/lib/rigor/environment/rbs_loader.rb +89 -13
- data/lib/rigor/environment.rb +12 -4
- data/lib/rigor/flow_contribution/conflict.rb +81 -0
- data/lib/rigor/flow_contribution/element.rb +53 -0
- data/lib/rigor/flow_contribution/fact.rb +88 -0
- data/lib/rigor/flow_contribution/merge_result.rb +67 -0
- data/lib/rigor/flow_contribution/merger.rb +275 -0
- data/lib/rigor/flow_contribution.rb +51 -0
- data/lib/rigor/inference/block_parameter_binder.rb +15 -0
- data/lib/rigor/inference/expression_typer.rb +87 -6
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +31 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +136 -9
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +21 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +68 -2
- data/lib/rigor/inference/method_dispatcher.rb +50 -1
- data/lib/rigor/inference/multi_target_binder.rb +2 -0
- data/lib/rigor/inference/narrowing.rb +246 -127
- data/lib/rigor/inference/scope_indexer.rb +124 -16
- data/lib/rigor/inference/statement_evaluator.rb +406 -37
- data/lib/rigor/plugin/access_denied_error.rb +24 -0
- data/lib/rigor/plugin/base.rb +284 -0
- data/lib/rigor/plugin/fact_store.rb +92 -0
- data/lib/rigor/plugin/io_boundary.rb +102 -0
- data/lib/rigor/plugin/load_error.rb +35 -0
- data/lib/rigor/plugin/loader.rb +307 -0
- data/lib/rigor/plugin/manifest.rb +203 -0
- data/lib/rigor/plugin/registry.rb +50 -0
- data/lib/rigor/plugin/services.rb +77 -0
- data/lib/rigor/plugin/trust_policy.rb +99 -0
- data/lib/rigor/plugin.rb +62 -0
- data/lib/rigor/rbs_extended.rb +57 -9
- data/lib/rigor/reflection.rb +2 -2
- data/lib/rigor/trinary.rb +1 -1
- data/lib/rigor/type/integer_range.rb +6 -2
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +7 -0
- data/sig/rigor/environment.rbs +10 -3
- data/sig/rigor/inference.rbs +1 -0
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +7 -0
- data/sig/rigor.rbs +8 -2
- metadata +20 -1
|
@@ -684,7 +684,7 @@ classes:
|
|
|
684
684
|
body_kind: composed
|
|
685
685
|
cexpr_target:
|
|
686
686
|
prelude_at: references/ruby/timev.rb:440
|
|
687
|
-
purity:
|
|
687
|
+
purity: dispatch
|
|
688
688
|
arity: -1
|
|
689
689
|
cfunc:
|
|
690
690
|
defined_at: references/ruby/timev.rb:440
|
|
@@ -726,7 +726,7 @@ classes:
|
|
|
726
726
|
body_kind: composed
|
|
727
727
|
cexpr_target:
|
|
728
728
|
prelude_at: references/ruby/timev.rb:270
|
|
729
|
-
purity:
|
|
729
|
+
purity: dispatch
|
|
730
730
|
arity: -1
|
|
731
731
|
cfunc:
|
|
732
732
|
defined_at: references/ruby/timev.rb:270
|
|
@@ -739,7 +739,7 @@ classes:
|
|
|
739
739
|
body_kind: composed
|
|
740
740
|
cexpr_target:
|
|
741
741
|
prelude_at: references/ruby/timev.rb:329
|
|
742
|
-
purity:
|
|
742
|
+
purity: dispatch
|
|
743
743
|
arity: -2
|
|
744
744
|
cfunc:
|
|
745
745
|
defined_at: references/ruby/timev.rb:329
|
|
@@ -42,19 +42,25 @@ module Rigor
|
|
|
42
42
|
# the first preview; later slices broaden it.
|
|
43
43
|
# rubocop:disable Metrics/ModuleLength
|
|
44
44
|
module CheckRules
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
# Canonical identifiers for each rule. Per ADR-8 §
|
|
46
|
+
# "Diagnostic ID family hierarchy", rule names are
|
|
47
|
+
# `family.rule-name` two-segment strings; the families
|
|
48
|
+
# group diagnostics by where they originate
|
|
49
|
+
# (`call.*` for call-site rules, `flow.*` for flow-analysis
|
|
50
|
+
# proofs, `assert.*` for runtime-assertion rules,
|
|
51
|
+
# `dump.*` for debug helpers, `def.*` for method-definition
|
|
52
|
+
# rules). Used by the configuration `disable:` list and the
|
|
53
|
+
# in-source `# rigor:disable <rule>` suppression comment
|
|
54
|
+
# system; new rules MUST register here so user configuration
|
|
55
|
+
# can refer to them.
|
|
56
|
+
RULE_UNDEFINED_METHOD = "call.undefined-method"
|
|
57
|
+
RULE_WRONG_ARITY = "call.wrong-arity"
|
|
58
|
+
RULE_ARGUMENT_TYPE = "call.argument-type-mismatch"
|
|
59
|
+
RULE_NIL_RECEIVER = "call.possible-nil-receiver"
|
|
60
|
+
RULE_DUMP_TYPE = "dump.type"
|
|
61
|
+
RULE_ASSERT_TYPE = "assert.type-mismatch"
|
|
62
|
+
RULE_ALWAYS_RAISES = "flow.always-raises"
|
|
63
|
+
RULE_RETURN_TYPE = "def.return-type-mismatch"
|
|
58
64
|
|
|
59
65
|
ALL_RULES = [
|
|
60
66
|
RULE_UNDEFINED_METHOD,
|
|
@@ -63,9 +69,46 @@ module Rigor
|
|
|
63
69
|
RULE_NIL_RECEIVER,
|
|
64
70
|
RULE_DUMP_TYPE,
|
|
65
71
|
RULE_ASSERT_TYPE,
|
|
66
|
-
RULE_ALWAYS_RAISES
|
|
72
|
+
RULE_ALWAYS_RAISES,
|
|
73
|
+
RULE_RETURN_TYPE
|
|
67
74
|
].freeze
|
|
68
75
|
|
|
76
|
+
# Backward-compat alias table (ADR-8 § "Backward
|
|
77
|
+
# compatibility"). Existing user code with
|
|
78
|
+
# `# rigor:disable undefined-method` /
|
|
79
|
+
# `disable: [undefined-method]` keeps working — the
|
|
80
|
+
# legacy unprefixed identifiers map to their canonical
|
|
81
|
+
# `family.rule-name` form here. Removing the aliases is
|
|
82
|
+
# a future ADR once user code has migrated; until then,
|
|
83
|
+
# both spellings resolve identically.
|
|
84
|
+
LEGACY_RULE_ALIASES = {
|
|
85
|
+
"undefined-method" => RULE_UNDEFINED_METHOD,
|
|
86
|
+
"wrong-arity" => RULE_WRONG_ARITY,
|
|
87
|
+
"argument-type-mismatch" => RULE_ARGUMENT_TYPE,
|
|
88
|
+
"possible-nil-receiver" => RULE_NIL_RECEIVER,
|
|
89
|
+
"dump-type" => RULE_DUMP_TYPE,
|
|
90
|
+
"assert-type" => RULE_ASSERT_TYPE,
|
|
91
|
+
"always-raises" => RULE_ALWAYS_RAISES
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
# Family wildcard — a `<family>` token in a suppression
|
|
95
|
+
# comment or `disable:` list disables every rule whose
|
|
96
|
+
# canonical id starts with `<family>.`. Per ADR-8 § "1".
|
|
97
|
+
RULE_FAMILIES = %w[call flow assert dump def].freeze
|
|
98
|
+
|
|
99
|
+
# Resolves a user-supplied rule token (`undefined-method`,
|
|
100
|
+
# `call.undefined-method`, or the family wildcard `call`)
|
|
101
|
+
# to the set of canonical rule identifiers it disables.
|
|
102
|
+
# Returns `nil` for `"all"` (the existing wildcard meaning
|
|
103
|
+
# "every rule"), or for unknown tokens.
|
|
104
|
+
def self.resolve_rule_token(token)
|
|
105
|
+
return nil if token == "all"
|
|
106
|
+
return [LEGACY_RULE_ALIASES.fetch(token)] if LEGACY_RULE_ALIASES.key?(token)
|
|
107
|
+
return ALL_RULES.select { |r| r.start_with?("#{token}.") } if RULE_FAMILIES.include?(token)
|
|
108
|
+
|
|
109
|
+
ALL_RULES.include?(token) ? [token] : []
|
|
110
|
+
end
|
|
111
|
+
|
|
69
112
|
module_function
|
|
70
113
|
|
|
71
114
|
# Yields diagnostics for every unrecognised method call on
|
|
@@ -78,35 +121,31 @@ module Rigor
|
|
|
78
121
|
# @param root [Prism::Node]
|
|
79
122
|
# @param scope_index [Hash{Prism::Node => Rigor::Scope}]
|
|
80
123
|
# @return [Array<Rigor::Analysis::Diagnostic>]
|
|
81
|
-
def diagnose(path:, root:, scope_index:, comments: [], disabled_rules: [])
|
|
124
|
+
def diagnose(path:, root:, scope_index:, comments: [], disabled_rules: [])
|
|
82
125
|
diagnostics = []
|
|
83
126
|
Source::NodeWalker.each(root) do |node|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
diagnostics << arity_diagnostic if arity_diagnostic
|
|
91
|
-
|
|
92
|
-
arg_type_diagnostic = argument_type_diagnostic(path, node, scope_index)
|
|
93
|
-
diagnostics << arg_type_diagnostic if arg_type_diagnostic
|
|
94
|
-
|
|
95
|
-
nil_diagnostic = nil_receiver_diagnostic(path, node, scope_index)
|
|
96
|
-
diagnostics << nil_diagnostic if nil_diagnostic
|
|
97
|
-
|
|
98
|
-
dump_diagnostic = dump_type_diagnostic(path, node, scope_index)
|
|
99
|
-
diagnostics << dump_diagnostic if dump_diagnostic
|
|
100
|
-
|
|
101
|
-
assert_diagnostic = assert_type_diagnostic(path, node, scope_index)
|
|
102
|
-
diagnostics << assert_diagnostic if assert_diagnostic
|
|
103
|
-
|
|
104
|
-
raises_diagnostic = always_raises_diagnostic(path, node, scope_index)
|
|
105
|
-
diagnostics << raises_diagnostic if raises_diagnostic
|
|
127
|
+
if node.is_a?(Prism::CallNode)
|
|
128
|
+
diagnostics.concat(call_node_diagnostics(path, node, scope_index))
|
|
129
|
+
elsif node.is_a?(Prism::DefNode)
|
|
130
|
+
return_diagnostic = return_type_mismatch_diagnostic(path, node, scope_index)
|
|
131
|
+
diagnostics << return_diagnostic if return_diagnostic
|
|
132
|
+
end
|
|
106
133
|
end
|
|
107
134
|
filter_suppressed(diagnostics, comments: comments, disabled_rules: disabled_rules)
|
|
108
135
|
end
|
|
109
136
|
|
|
137
|
+
def call_node_diagnostics(path, node, scope_index)
|
|
138
|
+
[
|
|
139
|
+
undefined_method_diagnostic(path, node, scope_index),
|
|
140
|
+
wrong_arity_diagnostic(path, node, scope_index),
|
|
141
|
+
argument_type_diagnostic(path, node, scope_index),
|
|
142
|
+
nil_receiver_diagnostic(path, node, scope_index),
|
|
143
|
+
dump_type_diagnostic(path, node, scope_index),
|
|
144
|
+
assert_type_diagnostic(path, node, scope_index),
|
|
145
|
+
always_raises_diagnostic(path, node, scope_index)
|
|
146
|
+
].compact
|
|
147
|
+
end
|
|
148
|
+
|
|
110
149
|
# v0.0.2 #6 — diagnostic suppression. Two kinds of
|
|
111
150
|
# suppression compose:
|
|
112
151
|
#
|
|
@@ -125,7 +164,7 @@ module Rigor
|
|
|
125
164
|
# silence away.
|
|
126
165
|
def filter_suppressed(diagnostics, comments:, disabled_rules:)
|
|
127
166
|
suppressions = parse_suppression_comments(comments)
|
|
128
|
-
disabled = disabled_rules
|
|
167
|
+
disabled = expand_rule_tokens(disabled_rules)
|
|
129
168
|
|
|
130
169
|
diagnostics.reject do |diagnostic|
|
|
131
170
|
rule = diagnostic.rule
|
|
@@ -137,7 +176,7 @@ module Rigor
|
|
|
137
176
|
end
|
|
138
177
|
end
|
|
139
178
|
|
|
140
|
-
SUPPRESSION_PATTERN = /#\s*rigor:disable\s+(?<rules>[\w
|
|
179
|
+
SUPPRESSION_PATTERN = /#\s*rigor:disable\s+(?<rules>[\w.,\s-]+)/
|
|
141
180
|
private_constant :SUPPRESSION_PATTERN
|
|
142
181
|
|
|
143
182
|
def parse_suppression_comments(comments)
|
|
@@ -148,11 +187,29 @@ module Rigor
|
|
|
148
187
|
next if match.nil?
|
|
149
188
|
|
|
150
189
|
rules = match[:rules].to_s.split(/[\s,]+/).reject(&:empty?)
|
|
151
|
-
rules.each { |
|
|
190
|
+
rules.each { |token| result[comment.location.start_line].merge(expand_token(token)) }
|
|
152
191
|
end
|
|
153
192
|
result
|
|
154
193
|
end
|
|
155
194
|
|
|
195
|
+
# Expands a list of user-supplied rule tokens into the
|
|
196
|
+
# canonical-id set per ADR-8 § "Backward compatibility".
|
|
197
|
+
# `disabled_rules` accepts unprefixed legacy names
|
|
198
|
+
# (`undefined-method`), canonical names
|
|
199
|
+
# (`call.undefined-method`), and family wildcards (`call`).
|
|
200
|
+
def expand_rule_tokens(tokens)
|
|
201
|
+
Array(tokens).each_with_object(Set.new) do |token, set|
|
|
202
|
+
set.merge(expand_token(token.to_s))
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def expand_token(token)
|
|
207
|
+
return ["all"] if token == "all"
|
|
208
|
+
|
|
209
|
+
resolved = resolve_rule_token(token)
|
|
210
|
+
resolved.nil? || resolved.empty? ? [token] : resolved
|
|
211
|
+
end
|
|
212
|
+
|
|
156
213
|
# rubocop:disable Metrics/ClassLength
|
|
157
214
|
class << self
|
|
158
215
|
private
|
|
@@ -792,6 +849,137 @@ module Rigor
|
|
|
792
849
|
severity: :error
|
|
793
850
|
)
|
|
794
851
|
end
|
|
852
|
+
|
|
853
|
+
# ADR-8 § "`def.return-type-mismatch` rule" — flags a
|
|
854
|
+
# `def m(...) ... end` whose body's last expression's
|
|
855
|
+
# type cannot satisfy the RBS-declared return type.
|
|
856
|
+
# Conservative envelope (v0.1.x first cut):
|
|
857
|
+
#
|
|
858
|
+
# - Skips methods without an RBS declaration. The rule
|
|
859
|
+
# has no contract to compare against for source-only
|
|
860
|
+
# methods.
|
|
861
|
+
# - Skips methods whose enclosing class isn't a
|
|
862
|
+
# `Type::Singleton` self_type that we can name (top-
|
|
863
|
+
# level / module-level methods land outside the rule).
|
|
864
|
+
# - Skips methods whose body's last expression is
|
|
865
|
+
# absent or types as `Dynamic[top]` (the analyzer's
|
|
866
|
+
# fail-soft fallback) — emitting on `Dynamic[top]`
|
|
867
|
+
# would be noise.
|
|
868
|
+
# - Compares the inferred body type against the
|
|
869
|
+
# declared return via `accepts?`:
|
|
870
|
+
# :yes → silent
|
|
871
|
+
# :no → emit at :error (severity_profile may
|
|
872
|
+
# re-stamp; default `balanced` keeps the
|
|
873
|
+
# authored severity).
|
|
874
|
+
# :maybe → emit at :warning. Promoted to :error
|
|
875
|
+
# under `severity_profile: strict` per
|
|
876
|
+
# ADR-8 § "Severity profile".
|
|
877
|
+
def return_type_mismatch_diagnostic(path, def_node, scope_index) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
878
|
+
return nil if def_node.body.nil?
|
|
879
|
+
|
|
880
|
+
last_expr = body_last_expression(def_node.body)
|
|
881
|
+
return nil if last_expr.nil?
|
|
882
|
+
|
|
883
|
+
inner_scope = scope_index[last_expr] || scope_index[def_node.body] || scope_index[def_node]
|
|
884
|
+
return nil if inner_scope.nil?
|
|
885
|
+
|
|
886
|
+
declared = declared_return_type(def_node, scope_index)
|
|
887
|
+
return nil if declared.nil?
|
|
888
|
+
|
|
889
|
+
inferred = inner_scope.type_of(last_expr)
|
|
890
|
+
return nil if dynamic_top?(inferred)
|
|
891
|
+
|
|
892
|
+
severity = compare_return(declared, inferred)
|
|
893
|
+
return nil if severity.nil?
|
|
894
|
+
|
|
895
|
+
build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# The body of a `def` is the last `Prism::StatementsNode`
|
|
899
|
+
# child (or a single expression for one-liner defs).
|
|
900
|
+
# Take the last statement; that's the implicit return.
|
|
901
|
+
def body_last_expression(body)
|
|
902
|
+
case body
|
|
903
|
+
when Prism::StatementsNode then body.body.last
|
|
904
|
+
when Prism::BeginNode then body_last_expression(body.statements)
|
|
905
|
+
else body
|
|
906
|
+
end
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# Pulls the declared RBS return type for the def. The
|
|
910
|
+
# enclosing class name comes from the def's scope's
|
|
911
|
+
# `self_type`; the method name is on the def itself.
|
|
912
|
+
# `def self.foo` is a singleton method — dispatched
|
|
913
|
+
# through `Reflection.singleton_method_definition`;
|
|
914
|
+
# plain `def foo` uses `instance_method_definition`.
|
|
915
|
+
# Method overloads contribute their union of declared
|
|
916
|
+
# return types (any one of them satisfying the body
|
|
917
|
+
# silences the rule).
|
|
918
|
+
def declared_return_type(def_node, scope_index)
|
|
919
|
+
scope = scope_index[def_node]
|
|
920
|
+
return nil if scope.nil?
|
|
921
|
+
|
|
922
|
+
self_type = scope.self_type
|
|
923
|
+
return nil unless self_type.respond_to?(:class_name)
|
|
924
|
+
|
|
925
|
+
method_def =
|
|
926
|
+
if def_node.receiver.nil?
|
|
927
|
+
Reflection.instance_method_definition(self_type.class_name, def_node.name, scope: scope)
|
|
928
|
+
else
|
|
929
|
+
Reflection.singleton_method_definition(self_type.class_name, def_node.name, scope: scope)
|
|
930
|
+
end
|
|
931
|
+
return nil if method_def.nil?
|
|
932
|
+
|
|
933
|
+
declared_return_union(method_def, scope.environment)
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
def declared_return_union(method_def, _environment)
|
|
937
|
+
translated = method_def.method_types.filter_map do |mt|
|
|
938
|
+
Inference::RbsTypeTranslator.translate(
|
|
939
|
+
mt.type.return_type,
|
|
940
|
+
self_type: nil, instance_type: nil, type_vars: {}
|
|
941
|
+
)
|
|
942
|
+
rescue StandardError
|
|
943
|
+
nil
|
|
944
|
+
end
|
|
945
|
+
return nil if translated.empty?
|
|
946
|
+
|
|
947
|
+
translated.size == 1 ? translated.first : Type::Combinator.union(*translated)
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
def dynamic_top?(type)
|
|
951
|
+
type.is_a?(Type::Dynamic) || (type.respond_to?(:top?) && type.top?.yes?)
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
# Returns the severity to emit at, or nil to stay
|
|
955
|
+
# silent. The first-cut implementation only fires on
|
|
956
|
+
# proven (`:no`) mismatches; `:maybe` is treated as
|
|
957
|
+
# silent until the analyzer's narrowing becomes precise
|
|
958
|
+
# enough to avoid noise on common patterns (`{}` →
|
|
959
|
+
# declared `Hash[K, V]`, `Set.new` → declared
|
|
960
|
+
# `Set[Symbol]`, …). ADR-8's promise to emit on
|
|
961
|
+
# `:maybe` under `severity_profile: strict` is
|
|
962
|
+
# deferred to a follow-up that lands together with the
|
|
963
|
+
# narrowing precision improvements.
|
|
964
|
+
def compare_return(declared, inferred)
|
|
965
|
+
result = declared.accepts(inferred)
|
|
966
|
+
return :error if result.no?
|
|
967
|
+
|
|
968
|
+
nil
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
972
|
+
location = def_node.name_loc || def_node.location
|
|
973
|
+
Diagnostic.new(
|
|
974
|
+
rule: RULE_RETURN_TYPE,
|
|
975
|
+
path: path,
|
|
976
|
+
line: location.start_line,
|
|
977
|
+
column: location.start_column + 1,
|
|
978
|
+
message: "return-type mismatch on `#{def_node.name}': " \
|
|
979
|
+
"declared #{declared.describe(:short)}, inferred #{inferred.describe(:short)}",
|
|
980
|
+
severity: severity
|
|
981
|
+
)
|
|
982
|
+
end
|
|
795
983
|
end
|
|
796
984
|
# rubocop:enable Metrics/ClassLength
|
|
797
985
|
end
|
|
@@ -64,8 +64,22 @@ module Rigor
|
|
|
64
64
|
}
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# Text rendering for `rigor check`. The qualified rule
|
|
68
|
+
# identifier (per ADR-2 § "Plugin Diagnostic Provenance" —
|
|
69
|
+
# `plugin.<id>.<rule>`, `rbs_extended.<rule>`,
|
|
70
|
+
# `generated.<provider>.<rule>`) is appended in brackets
|
|
71
|
+
# whenever the diagnostic carries a non-default `source_family`,
|
|
72
|
+
# so plugin / RBS::Extended / generated provenance is visible
|
|
73
|
+
# in the standard text output without changing the layout for
|
|
74
|
+
# built-in rules. Slice 5 (v0.1.0) wires this surface.
|
|
67
75
|
def to_s
|
|
68
|
-
"#{path}:#{line}:#{column}: #{severity}: #{message}"
|
|
76
|
+
base = "#{path}:#{line}:#{column}: #{severity}: #{message}"
|
|
77
|
+
return base if source_family == DEFAULT_SOURCE_FAMILY
|
|
78
|
+
|
|
79
|
+
qualified = qualified_rule
|
|
80
|
+
return base if qualified.nil?
|
|
81
|
+
|
|
82
|
+
"#{base} [#{qualified}]"
|
|
69
83
|
end
|
|
70
84
|
end
|
|
71
85
|
end
|