Class: SpecGuard::RSpec::Linter::Result

Inherits:
Data
  • Object
show all
Defined in:
lib/specguard/rspec/linter.rb

Overview

One annotation's verdict. problem is set when discovery could not produce an intent at all; reasons when the schema rejected one. They are mutually exclusive by construction.

intent is WHAT THE PAYLOAD PARSED TO, carried alongside the verdict rather than left for a second parser to re-derive. It is nil when there was no payload (any problem), and — like the port's intent key — it is populated even when reasons is not empty: a schema-rejected annotation did parse, and ok? already reports the verdict.

Constant Summary collapse

MAX_INTENT_DEPTH =

The nesting budget a payload gets, well under JSON.generate's default ceiling of 100.

The margin is deliberate rather than tight. Both renderers WRAP the payload before generating it — JSONReporter in findings[] -> finding -> intent and Formatter in specs[] -> spec -> intent, three levels each — so a payload probed at the bare ceiling would be accepted here and still raise there. Budgeting 32 leaves either envelope room to grow without silently re-opening this hole.

It costs nothing real: the schema admits four string values and nothing nested, so every payload a consumer could act on is depth 1. Anything deep enough to be refused here was already schema-invalid; this only decides whether its finding also carries the value.

32

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file:, line:, kind: nil, problem: nil, reasons: [], intent: nil) ⇒ Result

Returns a new instance of Result.



49
50
51
# File 'lib/specguard/rspec/linter.rb', line 49

def initialize(file:, line:, kind: nil, problem: nil, reasons: [], intent: nil)
  super
end

Instance Attribute Details

#fileObject (readonly)

Returns the value of attribute file

Returns:

  • (Object)

    the current value of file



48
49
50
# File 'lib/specguard/rspec/linter.rb', line 48

def file
  @file
end

#intentObject (readonly)

Returns the value of attribute intent

Returns:

  • (Object)

    the current value of intent



48
49
50
# File 'lib/specguard/rspec/linter.rb', line 48

def intent
  @intent
end

#kindObject (readonly)

Returns the value of attribute kind

Returns:

  • (Object)

    the current value of kind



48
49
50
# File 'lib/specguard/rspec/linter.rb', line 48

def kind
  @kind
end

#lineObject (readonly)

Returns the value of attribute line

Returns:

  • (Object)

    the current value of line



48
49
50
# File 'lib/specguard/rspec/linter.rb', line 48

def line
  @line
end

#problemObject (readonly)

Returns the value of attribute problem

Returns:

  • (Object)

    the current value of problem



48
49
50
# File 'lib/specguard/rspec/linter.rb', line 48

def problem
  @problem
end

#reasonsObject (readonly)

Returns the value of attribute reasons

Returns:

  • (Object)

    the current value of reasons



48
49
50
# File 'lib/specguard/rspec/linter.rb', line 48

def reasons
  @reasons
end

Instance Method Details

#failed?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/specguard/rspec/linter.rb', line 57

def failed?
  !ok?
end

#line_scoped?Boolean

A read failure is not line-scoped: nothing in the file was ever seen, and slice 1's line is a 0 sentinel rather than a location. The binary drops the line for exactly these findings (JSONFinding in open-test-intent's cmd/validate-intent/report.go — "line is null where a finding is not line-scoped, kind is null on a passing finding"), and :0 is not somewhere a reader can go: anything parsing file:line — CI annotations, editor quickfix, review comments — would point at a line that does not exist.

This is what makes kind load-bearing rather than decorative.

The rule is a predicate rather than an inline comparison because it has two renderers: CLI#report_failure prints file instead of file:0, and JSONReporter emits "line": null for the same findings. Spelling kind == Finding::KIND_READ in both would let one of them keep emitting the sentinel after the rule changed here.

Returns:

  • (Boolean)


77
78
79
# File 'lib/specguard/rspec/linter.rb', line 77

def line_scoped?
  kind != Finding::KIND_READ
end

#locationObject



81
82
83
# File 'lib/specguard/rspec/linter.rb', line 81

def location
  line_scoped? ? "#{file}:#{line}" : file
end

#ok?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/specguard/rspec/linter.rb', line 53

def ok?
  problem.nil? && reasons.empty?
end

#representable_intentObject

The intent, but only when it is a value this gem can actually hand on — nil otherwise.

Parsing a payload is not the same as being able to REPRODUCE it, and Ruby draws that line in a place the validator does not. Three classes clear the binary's verdict AND then detonate at the point of USE:

  • a lone LOW surrogate ("\udc00Or") — the report parser may accept it and hand back a String whose valid_encoding? is false, and JSON.generate refuses that same value with source sequence is illegal/malformed utf-8;
  • a non-finite Float — ValidatorBackend's parse options set allow_nan: true on the way IN, while generate's default is allow_nan: false on the way OUT;
  • a container nested past the generator's max_nesting — the same asymmetry, since those parse options also set max_nesting: false.

The last two are the parse options' own doing. Relaxing the parse side without relaxing the generate side does not remove the failure, it MOVES it — out of the parser, where it costs one annotation its payload, and into the generator, where it costs the whole batch its document and its exit code. Admitting a value we cannot then emit is strictly worse than never admitting it.

So the question is ASKED rather than enumerated: hand the value to the generator and see whether it comes back. A predicate that lists the known-bad shapes is a list somebody has to keep complete, and the two cases above are exactly what that list missed while covering the first. JSON.generate is not an approximation of the oracle here, it IS the call both renderers go on to make.

Answered here, once, because BOTH renderers need it and neither is the natural owner. Nothing is substituted — a repaired payload is one the author did not write, and shipping it is indistinguishable downstream from shipping the right one (KB SPGD-78). Unshippable means unannotated.



137
138
139
# File 'lib/specguard/rspec/linter.rb', line 137

def representable_intent
  intent unless unrepresentable?(intent)
end