Class: Rigor::Analysis::Diagnostic

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/analysis/diagnostic.rb,
sig/rigor.rbs

Constant Summary collapse

DEFAULT_SOURCE_FAMILY =

The default source family. Matches the existing analyzer-internal rule families; serialised as "builtin" and is the baseline against which non-default families are recognised.

:builtin

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:, line:, column:, message:, severity: :error, rule: nil, source_family: DEFAULT_SOURCE_FAMILY, receiver_type: nil, method_name: nil, project_definition_site: nil) ⇒ Diagnostic

rule: is the stable identifier (a kebab-case string) of the diagnostic's source rule. It is used by the configuration and the in-source # rigor:disable <rule> suppression comment system to identify diagnostics by category. Diagnostics not produced by CheckRules (parse errors, path errors, internal analyzer errors) may leave rule as nil and stay unsuppressible.

source_family: names the producer of the rule. The default :builtin covers analyzer-internal rules; future families like :rbs_extended, :generated, or "plugin.<id>" (per ADR-2 § "Plugin Diagnostic Provenance") let consumers distinguish where a diagnostic originated without committing to the plugin API itself.

receiver_type: / method_name: are optional structured fields populated by the call-related rules (call.undefined-method) — the rendered receiver type and the called method name as plain strings. ADR-23 WD3 / slice 4: rigor triage's heuristic recognisers read these directly instead of parsing the diagnostic message, so the catalogue no longer couples to message wording. Both stay nil for rules that have no such pair; a consumer that finds them nil falls back to message parsing.

project_definition_site: is an optional "path:line" string set by call.undefined-method when the project itself defines the called method on the receiver class somewhere in the analyzed file set (a reopened core/stdlib/gem class the dispatcher does not apply cross-file — see ADR-17). Its presence is the high-confidence "this is a project monkey-patch, not a bug" signal rigor triage keys on to recommend pre_eval:. Nil for every other diagnostic.

Parameters:

  • path: (String)
  • line: (Integer)
  • column: (Integer)
  • message: (String)
  • severity: (Symbol) (defaults to: :error)

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rigor/analysis/diagnostic.rb', line 34

def initialize(path:, line:, column:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
               source_family: DEFAULT_SOURCE_FAMILY,
               receiver_type: nil, method_name: nil, project_definition_site: nil)
  raise ArgumentError, "line must be >= 1, got #{line}" if line < 1
  raise ArgumentError, "column must be >= 1, got #{column}" if column < 1

  @path = path
  @line = line
  @column = column
  @message = message
  @severity = severity
  @rule = rule
  @source_family = source_family
  @receiver_type = receiver_type
  @method_name = method_name
  @project_definition_site = project_definition_site
end

Instance Attribute Details

#columnInteger (readonly)

Returns the value of attribute column.

Returns:

  • (Integer)


10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def column
  @column
end

#lineInteger (readonly)

Returns the value of attribute line.

Returns:

  • (Integer)


10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def line
  @line
end

#messageString (readonly)

Returns the value of attribute message.

Returns:

  • (String)


10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def message
  @message
end

#method_nameObject (readonly)

Returns the value of attribute method_name.



10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def method_name
  @method_name
end

#pathString (readonly)

Returns the value of attribute path.

Returns:

  • (String)


10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def path
  @path
end

#project_definition_siteObject (readonly)

Returns the value of attribute project_definition_site.



10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def project_definition_site
  @project_definition_site
end

#receiver_typeObject (readonly)

Returns the value of attribute receiver_type.



10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def receiver_type
  @receiver_type
end

#ruleObject (readonly)

Returns the value of attribute rule.



10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def rule
  @rule
end

#severitySymbol (readonly)

Returns the value of attribute severity.

Returns:

  • (Symbol)


10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def severity
  @severity
end

#source_familyObject (readonly)

Returns the value of attribute source_family.



10
11
12
# File 'lib/rigor/analysis/diagnostic.rb', line 10

def source_family
  @source_family
end

Class Method Details

.from_location(location, path:, message:, severity: :error, rule: nil, source_family: DEFAULT_SOURCE_FAMILY, receiver_type: nil, method_name: nil, project_definition_site: nil) ⇒ Rigor::Analysis::Diagnostic

Builds a Diagnostic from an explicit Prism location, applying the same 1-based line / start_column + 1 convention as from_node. Use this when the diagnostic should point at a sub-location rather than the whole node — most often a call's message_loc (the matcher / method name) instead of the receiver-spanning node.location. from_node is sugar for from_location(node.location, …).

Parameters:

  • location (Object)
  • path: (String)
  • message: (String)
  • severity: (Symbol) (defaults to: :error)
  • rule: (String, nil) (defaults to: nil)
  • source_family: (String) (defaults to: DEFAULT_SOURCE_FAMILY)
  • receiver_type: (Object) (defaults to: nil)
  • method_name: (Object) (defaults to: nil)
  • project_definition_site: (Object) (defaults to: nil)

Returns:



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rigor/analysis/diagnostic.rb', line 74

def self.from_location(location, path:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
                       source_family: DEFAULT_SOURCE_FAMILY,
                       receiver_type: nil, method_name: nil, project_definition_site: nil)
  new(
    path: path,
    line: location.start_line,
    column: location.start_column + 1,
    message: message, severity: severity, rule: rule, source_family: source_family,
    receiver_type: receiver_type, method_name: method_name,
    project_definition_site: project_definition_site
  )
end

.from_message_loc(node) ⇒ Object

Builds a Diagnostic at a call node's message_loc (the method-name / matcher span), falling back to the receiver-spanning node.location when no message location is available. Absorbs the node.message_loc || node.location idiom the call-related rules otherwise repeat; all other fields forward to from_location.



91
92
93
# File 'lib/rigor/analysis/diagnostic.rb', line 91

def self.from_message_loc(node, **)
  from_location(node.message_loc || node.location, **)
end

.from_name_loc(node) ⇒ Object

Builds a Diagnostic at a definition / assignment node's name_loc (the declared name span), falling back to node.location. Absorbs the node.name_loc || node.location idiom the def / write rules otherwise repeat.



98
99
100
# File 'lib/rigor/analysis/diagnostic.rb', line 98

def self.from_name_loc(node, **)
  from_location(node.name_loc || node.location, **)
end

.from_node(node, path:, message:, severity: :error, rule: nil, source_family: DEFAULT_SOURCE_FAMILY, receiver_type: nil, method_name: nil, project_definition_site: nil) ⇒ Rigor::Analysis::Diagnostic

Singleton factories plugins build diagnostics through (the node / location are Prism values, received as untyped).

Parameters:

  • node (Object)
  • path: (String)
  • message: (String)
  • severity: (Symbol) (defaults to: :error)
  • rule: (String, nil) (defaults to: nil)
  • source_family: (String) (defaults to: DEFAULT_SOURCE_FAMILY)
  • receiver_type: (Object) (defaults to: nil)
  • method_name: (Object) (defaults to: nil)
  • project_definition_site: (Object) (defaults to: nil)

Returns:



59
60
61
62
63
64
65
66
67
# File 'lib/rigor/analysis/diagnostic.rb', line 59

def self.from_node(node, path:, message:, severity: :error, rule: nil, # rubocop:disable Metrics/ParameterLists
                   source_family: DEFAULT_SOURCE_FAMILY,
                   receiver_type: nil, method_name: nil, project_definition_site: nil)
  from_location(
    node.location, path: path, message: message, severity: severity, rule: rule,
                   source_family: source_family, receiver_type: receiver_type,
                   method_name: method_name, project_definition_site: project_definition_site
  )
end

Instance Method Details

#error?Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/rigor/analysis/diagnostic.rb', line 102

def error?
  severity == :error
end

#qualified_ruleObject

The fully-qualified rule identifier — <source_family>.<rule> when the source is non-default, or just <rule> for the :builtin family. Returns nil when rule itself is nil (e.g. parse errors and internal-analyzer errors).



109
110
111
112
113
114
# File 'lib/rigor/analysis/diagnostic.rb', line 109

def qualified_rule
  return nil if rule.nil?
  return rule if source_family == DEFAULT_SOURCE_FAMILY

  "#{source_family}.#{rule}"
end

#to_hHash[String, untyped]

--format json serialisation. The structured receiver_type / method_name / project_definition_site fields are emitted only when populated, so a consumer (jq, rigor triage, an AI agent) can group a rigor check --format json stream by the called class / method without parsing the human-readable message — the message wording is presentation, not contract.

Returns:

  • (Hash[String, untyped])


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/rigor/analysis/diagnostic.rb', line 120

def to_h
  base = {
    "path" => path,
    "line" => line,
    "column" => column,
    "severity" => severity.to_s,
    "rule" => rule,
    "source_family" => source_family.to_s,
    "message" => message
  }
  base["receiver_type"] = receiver_type if receiver_type
  base["method_name"] = method_name if method_name
  base["project_definition_site"] = project_definition_site if project_definition_site
  base
end

#to_sString

Text rendering for rigor check. The qualified rule identifier (per ADR-2 § "Plugin Diagnostic Provenance" — plugin.<id>.<rule>, rbs_extended.<rule>, generated.<provider>.<rule>) is appended in brackets whenever the diagnostic carries a non-default source_family, so plugin / RBS::Extended / generated provenance is visible in the standard text output without changing the layout for built-in rules. Slice 5 (v0.1.0) wires this surface.

Returns:

  • (String)


141
142
143
144
145
146
147
148
149
# File 'lib/rigor/analysis/diagnostic.rb', line 141

def to_s
  base = "#{path}:#{line}:#{column}: #{severity}: #{message}"
  return base if source_family == DEFAULT_SOURCE_FAMILY

  qualified = qualified_rule
  return base if qualified.nil?

  "#{base} [#{qualified}]"
end