specguard-ruby 0.3.28 → 0.3.29
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 +5 -4
- data/lib/specguard/rspec/cli.rb +12 -9
- data/lib/specguard/rspec/finding.rb +5 -2
- data/lib/specguard/rspec/linter.rb +5 -3
- data/lib/specguard/rspec/scanner.rb +67 -10
- data/lib/specguard/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 887fc6d71732b1947cc48c34f27c68ac5b9b71c729f604cf95c41e508cc8d6af
|
|
4
|
+
data.tar.gz: e2f20dbc68a7324cc34381e204bc8ac0f5db262002d86ffc3bd24c8c2cfb3663
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3d18c2b2d19c8d25b0c5a9277847767996ff27be06f7f5802b052c7a4dab7f20e7244214a339fc0a4bc0343103572ad842bd381f95ccd6efa670422da445edc
|
|
7
|
+
data.tar.gz: 9a2b14e04e71e6e957134e29ad38f11fdaad0aede17893907a048d9e6414d93affb7844f4a99e630194f5095c0bc945e271800871f39f71f325ed8b4805492ae
|
data/README.md
CHANGED
|
@@ -49,8 +49,9 @@ bundle exec ruby -rminitest/specguard_plugin -e 'Minitest.extensions << "specgua
|
|
|
49
49
|
Validates `# @intent:` annotations in changed (or all) `*_spec.rb` files against the OpenTestIntent
|
|
50
50
|
JSON Schema. Exits `1` on a malformed annotation — or a well-formed but unreachable one (stacked
|
|
51
51
|
above another comment-form `@intent:` line, so the one-line lookback never claims it; separated
|
|
52
|
-
from its `it` by one blank or ordinary comment line, which puts it out of the lookback's reach;
|
|
53
|
-
|
|
52
|
+
from its `it` by one blank or ordinary comment line, which puts it out of the lookback's reach;
|
|
53
|
+
directly above an `it` that carries its own trailing `@intent:`, which extraction prefers, so the
|
|
54
|
+
comment is shadowed; or trailing on a `describe`/`context` group line, which no example's extraction can ever reach) — and
|
|
54
55
|
**never** fails on a *missing* one (adoption is opt-in and gradual).
|
|
55
56
|
|
|
56
57
|
```bash
|
|
@@ -336,8 +337,8 @@ Every way the backend can fail — the binary is missing, will not execute, exit
|
|
|
336
337
|
is not a verdict, or emits output that is not a report — is **exit 2**, the linter's "could not do
|
|
337
338
|
my job" code. It never becomes exit 1, which means "an annotation is malformed" — or well-formed
|
|
338
339
|
but unreachable: stacked above another comment-form `@intent:` line, so the one-line lookback
|
|
339
|
-
never claims it, separated from its `it` by one blank or ordinary comment line,
|
|
340
|
-
`describe`/`context` group line, which no example's extraction can ever reach — and nothing else.
|
|
340
|
+
never claims it, separated from its `it` by one blank or ordinary comment line, shadowed by its
|
|
341
|
+
`it`'s own trailing `@intent:`, or trailing on a `describe`/`context` group line, which no example's extraction can ever reach — and nothing else.
|
|
341
342
|
|
|
342
343
|
## The formatter — `SpecGuard::RSpecFormatter`
|
|
343
344
|
|
data/lib/specguard/rspec/cli.rb
CHANGED
|
@@ -126,9 +126,10 @@ module SpecGuard
|
|
|
126
126
|
# "Lint, don't require": a missing annotation is never an error.
|
|
127
127
|
EXIT_OK = 0
|
|
128
128
|
# One or more annotations are malformed — or well-formed but
|
|
129
|
-
# unreachable (SPGD-900/SPGD-1510/SPGD-1554: stacked above
|
|
130
|
-
# comment-form `@intent:` line so the one-line lookback never
|
|
131
|
-
# separated from its example by one blank/comment line,
|
|
129
|
+
# unreachable (SPGD-900/SPGD-1510/SPGD-1554/SPGD-1560: stacked above
|
|
130
|
+
# another comment-form `@intent:` line so the one-line lookback never
|
|
131
|
+
# claims it, separated from its example by one blank/comment line,
|
|
132
|
+
# shadowed by its example's own trailing `@intent:`, or trailing on
|
|
132
133
|
# an example-group line no example's extraction can reach — a dead
|
|
133
134
|
# contract in every case, and still a failure). The only code produced by
|
|
134
135
|
# inspecting content, and the only path that reaches it is a failed
|
|
@@ -168,13 +169,15 @@ module SpecGuard
|
|
|
168
169
|
|
|
169
170
|
results = backend.check(selection.files)
|
|
170
171
|
|
|
171
|
-
# The structural pass (SPGD-900, SPGD-1510, SPGD-1554): annotations
|
|
172
|
+
# The structural pass (SPGD-900, SPGD-1510, SPGD-1554, SPGD-1560): annotations
|
|
172
173
|
# that are valid in isolation but can never be extracted — stacked
|
|
173
174
|
# consecutive comment-form `@intent:` lines, where the one-line
|
|
174
175
|
# lookback (SPGD-12 §2) claims only the line just above the example,
|
|
175
176
|
# comment-form runs separated from their example by one blank/comment
|
|
176
|
-
# line,
|
|
177
|
-
#
|
|
177
|
+
# line, comment-form lines shadowed by their example's own trailing
|
|
178
|
+
# `@intent:` (extraction is own-line-first), and `@intent:` tokens
|
|
179
|
+
# trailing on an example-group line, which no extraction rule can ever
|
|
180
|
+
# reach. These are Linter::Results like any
|
|
178
181
|
# other, so both renderers and the exit code pick them up with no
|
|
179
182
|
# second path to keep in step.
|
|
180
183
|
results += unreachable_results(selection.files)
|
|
@@ -482,15 +485,15 @@ module SpecGuard
|
|
|
482
485
|
#
|
|
483
486
|
# The boundary is the set-difference the partition above already
|
|
484
487
|
# supports: a file counts as covered when it yielded any line-scoped
|
|
485
|
-
# result (an annotation, valid or not — the SPGD-900/SPGD-1510/SPGD-1554
|
|
488
|
+
# result (an annotation, valid or not — the SPGD-900/SPGD-1510/SPGD-1554/SPGD-1560
|
|
486
489
|
# unreachable findings are line-scoped too and require an annotation to
|
|
487
490
|
# exist, so they cover their file) or when it could not be read at all
|
|
488
491
|
# (KIND_READ → `unread`). An unread file is NOT a zero-annotation file:
|
|
489
492
|
# it already gets its own summary clause and FAIL line, and calling it
|
|
490
493
|
# annotation-free would be the same overstatement `summary_line` refuses
|
|
491
494
|
# to make. Unreachable findings need an annotation to exist — a stacked
|
|
492
|
-
# comment-form pair, a separated comment-form run,
|
|
493
|
-
# token on a group line — impossible in a file with none, so `annotations ∪ unread` already covers every
|
|
495
|
+
# comment-form pair, a separated comment-form run, a shadowed comment-form
|
|
496
|
+
# run, or an `@intent:` token on a group line — impossible in a file with none, so `annotations ∪ unread` already covers every
|
|
494
497
|
# non-bare file and no third subtraction is needed.
|
|
495
498
|
#
|
|
496
499
|
# It is a NOTE on stderr, never a warning and never an exit code: "lint,
|
|
@@ -49,7 +49,7 @@ module SpecGuard
|
|
|
49
49
|
KIND_SCHEMA = :schema
|
|
50
50
|
|
|
51
51
|
# The annotation is well-formed in isolation but can never be EXTRACTED.
|
|
52
|
-
#
|
|
52
|
+
# Four shapes, all dead the moment they are written:
|
|
53
53
|
#
|
|
54
54
|
# * a comment-form `@intent:` stacked above another comment-form
|
|
55
55
|
# `@intent:` line, so the one-line lookback (SPGD-12 §2 —
|
|
@@ -61,7 +61,10 @@ module SpecGuard
|
|
|
61
61
|
# it, and the example beneath it silently ingests unannotated;
|
|
62
62
|
# * a comment-form `@intent:` run separated from its example by ONE
|
|
63
63
|
# blank or ordinary comment line (SPGD-1554): the one-line lookback
|
|
64
|
-
# reads only that interleave, so the whole run is out of reach
|
|
64
|
+
# reads only that interleave, so the whole run is out of reach;
|
|
65
|
+
# * a comment-form `@intent:` directly above an example whose OWN line
|
|
66
|
+
# also carries a trailing `@intent:` (SPGD-1560): extraction is
|
|
67
|
+
# own-line-first, so the example's own annotation shadows the comment.
|
|
65
68
|
#
|
|
66
69
|
# Whichever shape, the contract is dead metadata: counted by the linter,
|
|
67
70
|
# discarded by extraction. Produced by {Scanner}'s structural pass, not
|
|
@@ -40,15 +40,17 @@ module SpecGuard
|
|
|
40
40
|
# `Finding::KIND_UNREACHABLE` — an annotation that is well-formed in
|
|
41
41
|
# isolation but can never be extracted, so the one-line lookback
|
|
42
42
|
# (SPGD-12 §2) never claims it — is also reported as a failure, and
|
|
43
|
-
# therefore also exits 1.
|
|
43
|
+
# therefore also exits 1. Four shapes reach this kind: a comment-form
|
|
44
44
|
# `@intent:` stacked above another comment-form `@intent:` line (SPGD-900),
|
|
45
45
|
# an `@intent:` trailing on an example-group line, which is no
|
|
46
46
|
# example's own and not comment-only (SPGD-1510), and a comment-form
|
|
47
47
|
# `@intent:` run separated from its example by one blank or ordinary
|
|
48
|
-
# comment line (SPGD-1554)
|
|
48
|
+
# comment line (SPGD-1554), and a comment-form `@intent:` directly above an
|
|
49
|
+
# example whose own line carries a trailing `@intent:`, which own-line-first
|
|
50
|
+
# extraction prefers (SPGD-1560). Nothing about any of them is malformed; the
|
|
49
51
|
# contract is dead metadata, counted by the linter and discarded by
|
|
50
52
|
# extraction, and the structural pass that flags them (SPGD-900,
|
|
51
|
-
# SPGD-1510, SPGD-1554) reports it loudly rather than letting it pass as
|
|
53
|
+
# SPGD-1510, SPGD-1554, SPGD-1560) reports it loudly rather than letting it pass as
|
|
52
54
|
# clean.
|
|
53
55
|
module Linter
|
|
54
56
|
# One annotation's verdict. `problem` is set when discovery could not
|
|
@@ -261,7 +261,7 @@ module SpecGuard
|
|
|
261
261
|
# ({AnnotationLookup}, SPGD-12 §2) is positional: an annotation reaches
|
|
262
262
|
# an example only through its own line (the trailing form, and only when
|
|
263
263
|
# that line IS the example's) or through the comment-form line
|
|
264
|
-
# IMMEDIATELY ABOVE it.
|
|
264
|
+
# IMMEDIATELY ABOVE it. Four shapes fall outside both and are dead the
|
|
265
265
|
# moment they are written — silently discarded by extraction while this
|
|
266
266
|
# pipeline counts them as valid annotations and the run exits 0:
|
|
267
267
|
#
|
|
@@ -283,7 +283,15 @@ module SpecGuard
|
|
|
283
283
|
# the example, which is the interleave — so the annotation is out of
|
|
284
284
|
# reach and every line of the run is dead.
|
|
285
285
|
#
|
|
286
|
-
#
|
|
286
|
+
# * the SHADOWED annotation (SPGD-1560, ported from specguard-ts
|
|
287
|
+
# SPGD-1556): a comment-form `@intent:` run directly above an example
|
|
288
|
+
# whose OWN line also carries a trailing `@intent: {`. Extraction is
|
|
289
|
+
# own-line-first ({AnnotationLookup::Index#intent_for} returns the
|
|
290
|
+
# example's own annotation before it ever consults the line above),
|
|
291
|
+
# so the comment is shadowed and its LAST line is dead. (Any earlier
|
|
292
|
+
# lines of the run are the stacked arm's.)
|
|
293
|
+
#
|
|
294
|
+
# All four are reported loudly, not skipped — the same stance
|
|
287
295
|
# {AnnotationScanner} takes for NO_PAYLOAD.
|
|
288
296
|
#
|
|
289
297
|
# A comment-form annotation line is a comment-only line
|
|
@@ -336,6 +344,12 @@ module SpecGuard
|
|
|
336
344
|
"discarded at extraction. Move it directly above the example (or merge it into " \
|
|
337
345
|
"the line that is)"
|
|
338
346
|
|
|
347
|
+
UNREACHABLE_SHADOWED_ANNOTATION =
|
|
348
|
+
"unreachable annotation: the example directly below carries its own trailing @intent:, " \
|
|
349
|
+
"and extraction is own-line-first (SPGD-12 §2), so this comment-form annotation is " \
|
|
350
|
+
"shadowed and silently discarded. Delete this comment, or merge it into the trailing " \
|
|
351
|
+
"payload"
|
|
352
|
+
|
|
339
353
|
# A comment-only line carrying an `@intent:` token — the only form an
|
|
340
354
|
# example on the NEXT line may claim ({AnnotationLookup::COMMENT_LINE}).
|
|
341
355
|
COMMENT_INTENT_LINE = /\A\s*#.*@intent:/
|
|
@@ -370,14 +384,18 @@ module SpecGuard
|
|
|
370
384
|
# @return [Array<Finding>] the structural findings for one file, in line
|
|
371
385
|
# order: one per comment-form `@intent:` line in a stacked run (see
|
|
372
386
|
# {stacked_findings_in_text}), one per line of a separated run (see
|
|
373
|
-
# {separated_findings_in_text})
|
|
374
|
-
# trailing
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
#
|
|
387
|
+
# {separated_findings_in_text}), one per run whose example carries its own
|
|
388
|
+
# trailing annotation (see {own_line_shadow_findings_in_text}) plus one
|
|
389
|
+
# per group line carrying the trailing form (see
|
|
390
|
+
# {group_line_findings_in_text}). The four passes are disjoint by
|
|
391
|
+
# construction: the stacked pass flags all but the LAST line of a run
|
|
392
|
+
# when an example directly follows, the shadow pass flags ONLY that last
|
|
393
|
+
# line (and only when the example also carries `@intent: {`), the
|
|
394
|
+
# separated pass needs a blank or comment-only line after the run (one
|
|
395
|
+
# slot cannot be both that and an example), and the group pass reads
|
|
396
|
+
# only lines that are not comment-only — so the merge cannot double-flag
|
|
397
|
+
# a line, and the sort restores the file-then-line order
|
|
398
|
+
# {unreachable_findings} promises.
|
|
381
399
|
def unreachable_findings_in_text(text, file:)
|
|
382
400
|
# A file that is not valid UTF-8 is reported once, loudly, by the
|
|
383
401
|
# backend as a read failure; nothing positional can be said about it,
|
|
@@ -386,6 +404,7 @@ module SpecGuard
|
|
|
386
404
|
|
|
387
405
|
(stacked_findings_in_text(text, file: file) +
|
|
388
406
|
separated_findings_in_text(text, file: file) +
|
|
407
|
+
own_line_shadow_findings_in_text(text, file: file) +
|
|
389
408
|
group_line_findings_in_text(text, file: file)).sort_by(&:line)
|
|
390
409
|
end
|
|
391
410
|
|
|
@@ -473,6 +492,44 @@ module SpecGuard
|
|
|
473
492
|
findings
|
|
474
493
|
end
|
|
475
494
|
|
|
495
|
+
# @param text [String] source of one file
|
|
496
|
+
# @param file [String] path to record on each Finding
|
|
497
|
+
# @return [Array<Finding>] one per maximal run of comment-form
|
|
498
|
+
# `@intent:` lines whose following line is an example
|
|
499
|
+
# ({EXAMPLE_LINE}) that ALSO carries a trailing `@intent: {`
|
|
500
|
+
# ({INTENT_WITH_PAYLOAD}) — reported on the run's LAST line, the one
|
|
501
|
+
# the lookback would otherwise claim. Extraction is own-line-first
|
|
502
|
+
# (SPGD-12 §2), so the example's own annotation always wins and that
|
|
503
|
+
# comment is dead. Earlier lines of a longer run are the stacked arm's
|
|
504
|
+
# ({stacked_findings_in_text}); each dead line is flagged exactly once.
|
|
505
|
+
def own_line_shadow_findings_in_text(text, file:)
|
|
506
|
+
# Same UTF-8 contract as the stacked pass above.
|
|
507
|
+
return [] unless text.valid_encoding?
|
|
508
|
+
|
|
509
|
+
lines = text.lines
|
|
510
|
+
i = 0
|
|
511
|
+
findings = []
|
|
512
|
+
|
|
513
|
+
while i < lines.length
|
|
514
|
+
unless COMMENT_INTENT_LINE.match?(lines[i])
|
|
515
|
+
i += 1
|
|
516
|
+
next
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
i += 1 while i < lines.length && COMMENT_INTENT_LINE.match?(lines[i])
|
|
520
|
+
run_end = i # exclusive; lines[run_end] is the line after the run
|
|
521
|
+
|
|
522
|
+
example = lines[run_end]
|
|
523
|
+
next if example.nil?
|
|
524
|
+
next unless EXAMPLE_LINE.match?(example) && INTENT_WITH_PAYLOAD.match?(example)
|
|
525
|
+
|
|
526
|
+
findings << Finding.new(file: file, line: run_end, problem: UNREACHABLE_SHADOWED_ANNOTATION,
|
|
527
|
+
kind: Finding::KIND_UNREACHABLE)
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
findings
|
|
531
|
+
end
|
|
532
|
+
|
|
476
533
|
# RSpec's example-group keywords, at the start of a line and optionally
|
|
477
534
|
# `RSpec.`-prefixed. A line matching this hosts a GROUP, not an example
|
|
478
535
|
# — and {AnnotationLookup}'s extraction can claim an annotation only
|
data/lib/specguard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: specguard-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.29
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- specguard Agent
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|