Class: SpecGuard::RSpec::Linter

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

Overview

Applies the schema to the Findings discovery produced, and says which ones the run must fail on.

This is the join between the two halves of the linter: discovery decides what an annotation is, the schema decides whether it is valid, and Result is the single shape the reporter and the exit code are both derived from — so "what was printed" and "what we exited with" cannot drift apart.

What counts as a failure

Three things reach the same verdict from different directions:

* the payload could not be captured off the line at all
(`Finding::KIND_EXTRACTION`) — a typo'd annotation;
* it was captured but is not JSON even after normalization
(`Finding::KIND_PARSE`);
* it parsed but violates the schema.

All three are "an annotation is malformed", which the contract fixes at exit 1. A missing annotation is not among them — "lint, don't require" (SPGD-12 §1): a file with no @intent: at all is clean.

The one case that is arguably not an annotation problem

Finding::KIND_READ — a spec file that could not be opened or is not valid UTF-8 — is also reported as a failure, and therefore also exits 1. That is a deliberate parity choice, not an oversight: the reference tool classifies it separately (its own KIND_READ, bin/validate-intent:91) and still reports it as FAIL with exit 1.

The CLASSIFICATION and the EXIT CODE are what match, and both are now asserted rather than described: open-test-intent's tests/parity/run_ruby_parity.sh runs this linter and the Go port over the same inputs and compares them. Its sections named "ratified difference (a)", "ratified difference (b)" and "an unreadable file" cover the three read-failure shapes. (Cited by name, not by number — that file renumbers, and a number nothing checks across two repositories is a citation waiting to point somewhere else.)

The MESSAGE TEXT does not match on two of those three, and an earlier version of this comment claimed it did — it quoted a FAIL bad_spec.rb — could not read file: ... line as if the two tools emitted the same bytes. They do not, and the harness now says so out loud:

* a file that is not valid UTF-8 — the reference reproduces CPython's
`'utf-8' codec can't decode byte 0xe9 in position 117: invalid
continuation byte`; `Scanner.scan_text` emits a fixed string.
* a file that does not exist — the reference reports it on *stderr* as
`error: no file(s) match '<path>'` (its arguments are glob patterns);
`Scanner.scan_file` reports it on stdout as a read failure of that
path (its arguments are paths).

Both differences are RATIFIED, with the reasoning written down in that script's header and asserted there — including the assertion that they still differ, so closing either one fails the harness and forces the ratification to be retired rather than left to rot the way this comment did.

The line the contract actually draws is the linter is broken (2) versus the input it was pointed at is bad (1). An unopenable file named on the command line is the second. The kind is carried through rather than flattened into prose so this stays a visible decision that a later ticket can reverse in one place.

Defined Under Namespace

Classes: Result

Instance Method Summary collapse

Constructor Details

#initialize(schema) ⇒ Linter

Returns a new instance of Linter.



110
111
112
# File 'lib/specguard/rspec/linter.rb', line 110

def initialize(schema)
  @schema = schema
end

Instance Method Details

#check(findings) ⇒ Array<Result>

Returns in the order the findings were discovered.

Parameters:

Returns:

  • (Array<Result>)

    in the order the findings were discovered



116
117
118
# File 'lib/specguard/rspec/linter.rb', line 116

def check(findings)
  findings.map { |finding| check_one(finding) }
end

#check_one(finding) ⇒ Result

Parameters:

Returns:



122
123
124
125
126
127
128
129
130
131
# File 'lib/specguard/rspec/linter.rb', line 122

def check_one(finding)
  unless finding.extracted?
    return Result.new(file: finding.file, line: finding.line,
                      kind: finding.kind, problem: finding.problem)
  end

  reasons = @schema.violations(finding.intent)
  Result.new(file: finding.file, line: finding.line, reasons: reasons,
             kind: reasons.empty? ? nil : Finding::KIND_SCHEMA)
end