specguard-ruby 0.3.14 → 0.3.15
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 +11 -1
- data/lib/specguard/rspec/cli.rb +27 -5
- data/lib/specguard/rspec/file_selector.rb +85 -6
- data/lib/specguard/rspec/formatter.rb +1 -1
- data/lib/specguard/rspec/ingest_reporter.rb +2 -1
- data/lib/specguard/rspec/json_reporter.rb +8 -5
- 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: 3475a2dc3666fdebc75d6c75f47589bd8d92fa22a1caf57f3a13e4d6082ce58a
|
|
4
|
+
data.tar.gz: f2c122923ced904d8ccc6c08c8ff93c013a40361a494523f3c4209e5605e0797
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80bb44b49402facf181703f2aee6faf94e3d42b55ab88182ad94056a43ed127ded3db724f8c68fc90a461dd79939cd29703277e3d4ea19783e94ee5bd4ab56f2
|
|
7
|
+
data.tar.gz: fb8deaf26aaea6e3d5e94fbfdf07ac66aa51a50f2292398e49accbefc34a4b1bf768733d026d1d2340a380a610c32af585aa95a8380677f12b0721b5fa2b3f3a
|
data/README.md
CHANGED
|
@@ -51,9 +51,19 @@ above another comment-form `@intent:` line, so the one-line lookback never claim
|
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
bundle exec specguard-lint --changed # CI mode: only files in the current diff
|
|
54
|
-
bundle exec specguard-lint # one-off audit: every *_spec.rb
|
|
54
|
+
bundle exec specguard-lint # one-off audit: every *_spec.rb outside dependency/build directories
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
The default walk is fenced out of dependency and build directories — `node_modules`,
|
|
58
|
+
`vendor`, `tmp`, `log`, `dist`, `.test-build`, `coverage`, `.git`, matched as whole
|
|
59
|
+
directory names, so `spec/vendor_helpers/` is still yours. A bundled Rails tree
|
|
60
|
+
(`bundle install --deployment` → `vendor/bundle/`) ships thousands of third-party gem
|
|
61
|
+
specs; the walk neither reports on them nor fails over their annotations, and the
|
|
62
|
+
zero-annotation coverage note counts only files you wrote — the same material
|
|
63
|
+
`--changed` keeps out via `.gitignore`. When the fence removed files, the
|
|
64
|
+
`checked N spec files` line says how many (`skipping M in dependency or build
|
|
65
|
+
directories`); a file named explicitly on the command line is always checked.
|
|
66
|
+
|
|
57
67
|
`--changed` is the CI selection mode: it diffs against the **merge base with
|
|
58
68
|
the default branch** (`origin/HEAD`, then `origin/main`/`origin/master`, then
|
|
59
69
|
their local names) — never a bare working-tree-vs-index `git diff`, which is
|
data/lib/specguard/rspec/cli.rb
CHANGED
|
@@ -262,21 +262,43 @@ module SpecGuard
|
|
|
262
262
|
# of record; the json renderer printing the SAME bytes is what makes
|
|
263
263
|
# "the machine reads the provenance the human does" a property of the
|
|
264
264
|
# code rather than a promise two copies could drift apart on.
|
|
265
|
+
#
|
|
266
|
+
# The `:all` count names its fence: `FileSelector.select_all` refuses
|
|
267
|
+
# dependency/build directories (`file_selector.rb`
|
|
268
|
+
# `SKIPPED_DIRECTORIES`), and a fence that actually removed files says
|
|
269
|
+
# how many — the narrowing must never be silent. The clause is
|
|
270
|
+
# count-gated exactly like the untracked one, so a run whose fence
|
|
271
|
+
# removed nothing prints byte-identically to before the fence existed.
|
|
265
272
|
def selection_line(selection)
|
|
266
273
|
untracked = selection.mode == :changed ? selection.stats&.untracked.to_i : 0
|
|
274
|
+
skipped = selection.mode == :all ? selection.skipped : 0
|
|
267
275
|
"specguard-lint: checked #{selection.count} spec file#{'s' unless selection.count == 1}" \
|
|
268
276
|
"#{" changed since #{selection.base}" if selection.mode == :changed}" \
|
|
269
277
|
"#{" under #{Dir.pwd}" if selection.mode == :all}" \
|
|
270
|
-
"#{" including #{untracked} untracked" if untracked.positive?}"
|
|
278
|
+
"#{" including #{untracked} untracked" if untracked.positive?}" \
|
|
279
|
+
"#{" skipping #{skipped} in dependency or build directories" if skipped.positive?}"
|
|
271
280
|
end
|
|
272
281
|
|
|
273
282
|
# `:explicit` is absent by construction — an explicit Selection is only
|
|
274
283
|
# built from a non-empty file list, so it can never be empty.
|
|
275
284
|
def empty_reason(selection)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
285
|
+
return changed_empty_reason(selection) if selection.mode == :changed
|
|
286
|
+
# An `:all` selection the fence emptied must not blame the tree for
|
|
287
|
+
# holding no spec files — the files existed and the fence removed
|
|
288
|
+
# them, and "no file found" is the confidently wrong reason this
|
|
289
|
+
# ladder exists to prevent.
|
|
290
|
+
return all_fenced_reason(selection) if selection.mode == :all && selection.skipped.positive?
|
|
291
|
+
|
|
292
|
+
"no *_spec.rb or *_test.rb file found under #{Dir.pwd}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# The fence arm of the `:all` empty reason: every matching file the walk
|
|
296
|
+
# found was inside a {SpecGuard::RSpec::FileSelector::SKIPPED_DIRECTORIES}
|
|
297
|
+
# directory, so the silence is the fence's, not the tree's.
|
|
298
|
+
def all_fenced_reason(selection)
|
|
299
|
+
n = selection.skipped
|
|
300
|
+
"#{n} *_spec.rb or *_test.rb file#{'s' unless n == 1} found, " \
|
|
301
|
+
"all in dependency or build directories"
|
|
280
302
|
end
|
|
281
303
|
|
|
282
304
|
# Names the filter that actually emptied the selection. Saying "nothing in
|
|
@@ -16,6 +16,14 @@ module SpecGuard
|
|
|
16
16
|
# diff names paths, not directories, and a changed test file is a test
|
|
17
17
|
# file wherever the author put it.
|
|
18
18
|
#
|
|
19
|
+
# The default walk also carries a fixed directory fence
|
|
20
|
+
# ({SKIPPED_DIRECTORIES}): dependency, build-output, scratch and VCS
|
|
21
|
+
# directories are never selected, so a bundled tree's `vendor/bundle/`
|
|
22
|
+
# gem specs are neither reported on nor failed over — third-party code
|
|
23
|
+
# the user did not write. `--changed` draws the same boundary from
|
|
24
|
+
# `.gitignore` (`--exclude-standard`) for free; the walk, which must keep
|
|
25
|
+
# working outside a repository, carries it as a name list instead.
|
|
26
|
+
#
|
|
19
27
|
# == Why `--changed` is not `git diff --name-only`
|
|
20
28
|
#
|
|
21
29
|
# The Client Gem spec words `--changed` as "files in the current diff (via
|
|
@@ -122,6 +130,44 @@ module SpecGuard
|
|
|
122
130
|
# no competitor convention to fence against.
|
|
123
131
|
DEFAULT_GLOB = ["**/*_spec.rb", "test/**/*_test.rb"].freeze
|
|
124
132
|
|
|
133
|
+
# A named-file default-walk fence, ported from this product's own
|
|
134
|
+
# TypeScript answer (`specguard-ts` `src/lint/discover.ts`
|
|
135
|
+
# `SKIPPED_DIRECTORIES`, widened by the Ruby ecosystem's members). Every
|
|
136
|
+
# member is dependency, build-output, scratch or VCS material — code the
|
|
137
|
+
# user did not write and cannot edit, whose specs must never be selected:
|
|
138
|
+
#
|
|
139
|
+
# * `node_modules` — npm dependencies (the port's founding member).
|
|
140
|
+
# * `.git` — VCS internals; `Dir.glob` already skips hidden
|
|
141
|
+
# directories, so this is belt-and-braces, named so the fence reads
|
|
142
|
+
# complete beside its TypeScript twin.
|
|
143
|
+
# * `dist` — build output.
|
|
144
|
+
# * `.test-build` — this project family's own build/test scratch.
|
|
145
|
+
# * `coverage` — SimpleCov's default report directory.
|
|
146
|
+
# * `vendor` — a bundled Rails tree (`bundle install --deployment`)
|
|
147
|
+
# puts every gem under `vendor/bundle/`, each shipping its own
|
|
148
|
+
# specs; the measured failure this fence exists for.
|
|
149
|
+
# * `tmp` — Rails' scratch directory (caches, pids, sockets).
|
|
150
|
+
# * `log` — Rails' log directory.
|
|
151
|
+
#
|
|
152
|
+
# `--changed` gets this boundary for free — `--exclude-standard` keeps
|
|
153
|
+
# `.gitignore`d paths (scratch directories, vendored code, build output)
|
|
154
|
+
# out of its untracked leg. The walk has no git to ask, because it must
|
|
155
|
+
# keep working outside a repository exactly where `--changed` correctly
|
|
156
|
+
# refuses, so it carries the boundary as a name list instead. The list
|
|
157
|
+
# is matched against whole directory SEGMENTS of the root-relative path
|
|
158
|
+
# (see {skipped_directory?}), never as a substring — `spec/vendor_helpers/`
|
|
159
|
+
# is project code — and never against the absolute path.
|
|
160
|
+
SKIPPED_DIRECTORIES = %w[
|
|
161
|
+
node_modules
|
|
162
|
+
.git
|
|
163
|
+
dist
|
|
164
|
+
.test-build
|
|
165
|
+
coverage
|
|
166
|
+
vendor
|
|
167
|
+
tmp
|
|
168
|
+
log
|
|
169
|
+
].freeze
|
|
170
|
+
|
|
125
171
|
# The `--changed` counterparts of {DEFAULT_GLOB}: git hands back paths,
|
|
126
172
|
# so the filter is a match over the whole path rather than a glob walk,
|
|
127
173
|
# and the suffix alone decides. Deliberately not directory-scoped — a
|
|
@@ -146,8 +192,19 @@ module SpecGuard
|
|
|
146
192
|
end
|
|
147
193
|
|
|
148
194
|
# What a selection produced, plus enough context to report it honestly.
|
|
149
|
-
|
|
150
|
-
|
|
195
|
+
#
|
|
196
|
+
# `skipped` is the fence count for the `:all` mode ({SKIPPED_DIRECTORIES}),
|
|
197
|
+
# defaulted to 0 so every other construction site is untouched. It
|
|
198
|
+
# deliberately rides `Selection` rather than a new `Stats` member:
|
|
199
|
+
# `Stats` is documented as the `--changed` empty-reason ladder ("why an
|
|
200
|
+
# empty `--changed` selection is empty") and the fence is neither
|
|
201
|
+
# `--changed`-bound nor an emptiness explanation — it is report context
|
|
202
|
+
# for a selection that may be perfectly full. `Selection` is what this
|
|
203
|
+
# file already uses to carry per-mode context (`base`, `note`, `stats`
|
|
204
|
+
# are all `--changed`-only and defaulted the same way), so the count
|
|
205
|
+
# follows that precedent instead of widening `Stats`'s contract.
|
|
206
|
+
Selection = Data.define(:files, :mode, :base, :note, :stats, :skipped) do
|
|
207
|
+
def initialize(files:, mode:, base: nil, note: nil, stats: nil, skipped: 0)
|
|
151
208
|
super
|
|
152
209
|
end
|
|
153
210
|
|
|
@@ -174,11 +231,33 @@ module SpecGuard
|
|
|
174
231
|
end
|
|
175
232
|
|
|
176
233
|
# Every test file under `root`, recursively, in either naming
|
|
177
|
-
# convention
|
|
178
|
-
#
|
|
234
|
+
# convention, minus anything whose path runs through a
|
|
235
|
+
# {SKIPPED_DIRECTORIES} directory. Hidden directories are not traversed
|
|
236
|
+
# (no `File::FNM_DOTMATCH`), so `.git` and friends are skipped.
|
|
237
|
+
#
|
|
238
|
+
# The fence is decided on the path `Dir.glob(base: root)` yields —
|
|
239
|
+
# already relative to `root` — never on an absolute path: fixture roots
|
|
240
|
+
# (and the whole spec suite's) live under `Dir.mktmpdir`, i.e. inside a
|
|
241
|
+
# directory *named* `tmp`, and a fence reading absolute paths would
|
|
242
|
+
# drop every file in the tree. How many files the fence removed is
|
|
243
|
+
# carried on the `Selection` (`skipped`) so the CLI can disclose the
|
|
244
|
+
# narrowing instead of doing it silently.
|
|
179
245
|
def select_all(root: Dir.pwd)
|
|
180
|
-
|
|
181
|
-
|
|
246
|
+
candidates = Dir.glob(DEFAULT_GLOB, base: root).select { |f| File.file?(File.join(root, f)) }.sort
|
|
247
|
+
kept, fenced = candidates.partition { |f| !skipped_directory?(f) }
|
|
248
|
+
Selection.new(files: kept, mode: :all, skipped: fenced.length)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Whether a root-relative path runs through a {SKIPPED_DIRECTORIES}
|
|
252
|
+
# directory. Whole segments only — `File::SEPARATOR`-delimited — so a
|
|
253
|
+
# directory merely *named after* a fenced word (`spec/vendor_helpers/`)
|
|
254
|
+
# is not fenced, and the basename is excluded from the segment set: a
|
|
255
|
+
# path's last component is the file itself, so `spec/tmpfile_spec.rb`
|
|
256
|
+
# is selected whatever its name contains.
|
|
257
|
+
def skipped_directory?(path)
|
|
258
|
+
segments = path.split(File::SEPARATOR)
|
|
259
|
+
segments.pop
|
|
260
|
+
segments.any? { |segment| SKIPPED_DIRECTORIES.include?(segment) }
|
|
182
261
|
end
|
|
183
262
|
|
|
184
263
|
def select_changed(base: nil, root: Dir.pwd)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# == Why this file is not on `require "specguard/rspec"`'s chain
|
|
8
8
|
#
|
|
9
9
|
# `rspec` is a **development** dependency of this gem, not a runtime one
|
|
10
|
-
# (specguard-
|
|
10
|
+
# (specguard-ruby.gemspec declares exactly one runtime dependency: `json`,
|
|
11
11
|
# pinned; since SPGD-867 the validator arrives as a resolved binary rather
|
|
12
12
|
# than as a gem). `bin/specguard-lint` loads `specguard/rspec`, and it has to
|
|
13
13
|
# keep working for someone who installed the gem to lint annotations on a
|
|
@@ -250,7 +250,8 @@ module SpecGuard
|
|
|
250
250
|
# ALWAYS a list of strings — `[]` where the line landed and where a
|
|
251
251
|
# refusal's body said nothing this gem could read, never `null` and never
|
|
252
252
|
# a bare string. {JSONReporter}'s `errors` makes the identical guarantee
|
|
253
|
-
# for the identical reason: `
|
|
253
|
+
# for the identical reason: `JSONFinding` (open-test-intent,
|
|
254
|
+
# `cmd/validate-intent/report.go`) is explicit that a consumer
|
|
254
255
|
# must never have to branch on the type of the field that says why
|
|
255
256
|
# something failed, and one list is what lets a refusal's per-spec errors,
|
|
256
257
|
# a socket error and "this line is not a run" be read by one code path.
|
|
@@ -46,9 +46,10 @@ module SpecGuard
|
|
|
46
46
|
# * `errors` is ALWAYS a list of strings — `reasons` when the schema
|
|
47
47
|
# rejected the annotation, `[problem]` when discovery could not produce
|
|
48
48
|
# one at all, `[]` when it passed. Never null, never a bare string:
|
|
49
|
-
# `report.go
|
|
50
|
-
#
|
|
51
|
-
#
|
|
49
|
+
# `JSONFinding` (open-test-intent, `cmd/validate-intent/report.go`) is
|
|
50
|
+
# explicit that a consumer must never branch on its type, and this is
|
|
51
|
+
# the one place the gem's mutually-exclusive `problem`/`reasons` pair
|
|
52
|
+
# is normalised into the port's single list.
|
|
52
53
|
# * `line` is null exactly where the finding is not line-scoped, which is
|
|
53
54
|
# {Linter::Result#line_scoped?} — the same rule `#location` uses to
|
|
54
55
|
# print `file` rather than `file:0`. `:0` is not somewhere a reader can
|
|
@@ -71,7 +72,8 @@ module SpecGuard
|
|
|
71
72
|
#
|
|
72
73
|
# `ok` is likewise handed in, derived from the exit code the text path would
|
|
73
74
|
# also have produced rather than recomputed from the findings, following
|
|
74
|
-
# `report.go
|
|
75
|
+
# `Emit` (open-test-intent, `cmd/validate-intent/report.go`) for the same
|
|
76
|
+
# reason.
|
|
75
77
|
#
|
|
76
78
|
# `summary.failed`, by contrast, is counted here — from the findings this
|
|
77
79
|
# document actually emitted, so `failed` always equals the number of entries
|
|
@@ -93,7 +95,8 @@ module SpecGuard
|
|
|
93
95
|
# and read the line; it is still exactly one line, on every run, on both
|
|
94
96
|
# arms.
|
|
95
97
|
module JSONReporter
|
|
96
|
-
# The port's `jsonSchemaID` (
|
|
98
|
+
# The port's `jsonSchemaID` (open-test-intent,
|
|
99
|
+
# `cmd/validate-intent/report.go`), and the basename of the
|
|
97
100
|
# gem's own vendored schema. Pinned to each other by a spec: the document
|
|
98
101
|
# names the protocol it validated against, so it must not be able to name
|
|
99
102
|
# one the gem does not carry.
|
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.15
|
|
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-09-
|
|
11
|
+
date: 2026-09-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|