@jterrazz/test 12.2.0 → 13.0.0
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.
- package/README.md +12 -14
- package/dist/checker.js +554 -48
- package/dist/index.d.ts +102 -88
- package/dist/index.js +16 -14
- package/dist/literate.js +156 -164
- package/dist/match.js +60 -180
- package/dist/oxlint.cjs +83 -11
- package/dist/oxlint.js +83 -11
- package/dist/queue.js +1 -1
- package/dist/spec-document.js +536 -0
- package/dist/vitest.d.ts +3 -3
- package/dist/vitest.js +16 -15
- package/package.json +6 -4
- package/schema/spec.schema.json +179 -0
package/dist/match.js
CHANGED
|
@@ -1,194 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const HEADER_KEYS = [
|
|
4
|
-
"test",
|
|
5
|
-
"given",
|
|
6
|
-
"then",
|
|
7
|
-
"fixture",
|
|
8
|
-
"env",
|
|
9
|
-
"serve"
|
|
10
|
-
];
|
|
11
|
-
/** The separator opening a block's stderr section. */
|
|
12
|
-
const STDERR_MARKER = "--- stderr";
|
|
13
|
-
const HEADER_LINE = /^(?<key>[a-z]+):(?<value>.*)$/;
|
|
14
|
-
const EXIT_LINE = /^exit:\s*(?<code>-?\d+)\s*$/;
|
|
15
|
-
const KEY_SET = new Set(HEADER_KEYS);
|
|
16
|
-
/** A refusal from the grammar, carrying where it happened and which rule it is. */
|
|
17
|
-
var LiterateSyntaxError = class extends Error {
|
|
18
|
-
defect;
|
|
19
|
-
line;
|
|
20
|
-
constructor(fileName, line, message, defect) {
|
|
21
|
-
super(`${fileName}:${line}: ${message}`);
|
|
22
|
-
this.defect = defect;
|
|
23
|
-
this.line = line;
|
|
24
|
-
this.name = "LiterateSyntaxError";
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
function fail(fileName, line, message, defect = "shape") {
|
|
28
|
-
throw new LiterateSyntaxError(fileName, line, message, defect);
|
|
29
|
-
}
|
|
30
|
-
/** Is this line the start of a block? `$ cmd`, or a bare `$` (no arguments). */
|
|
31
|
-
function isBlockStart(line) {
|
|
32
|
-
return line === "$" || line.startsWith("$ ");
|
|
33
|
-
}
|
|
34
|
-
function parseEnvTokens(value, fileName, line) {
|
|
35
|
-
const tokens = [];
|
|
36
|
-
for (const token of value.split(/\s+/).filter((part) => part.length > 0)) {
|
|
37
|
-
const separator = token.indexOf("=");
|
|
38
|
-
if (separator === -1) {
|
|
39
|
-
tokens.push({
|
|
40
|
-
kind: "set",
|
|
41
|
-
name: token
|
|
42
|
-
});
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const key = token.slice(0, separator);
|
|
46
|
-
if (key.length === 0) fail(fileName, line, `env: "${token}" has no variable name before the "="`);
|
|
47
|
-
tokens.push({
|
|
48
|
-
key,
|
|
49
|
-
kind: "pair",
|
|
50
|
-
value: token.slice(separator + 1)
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (tokens.length === 0) fail(fileName, line, "env: needs at least one KEY=value pair or a registered env-set name");
|
|
54
|
-
return tokens;
|
|
55
|
-
}
|
|
56
|
-
function parseServeEntry(value, fileName, line) {
|
|
57
|
-
const parts = value.split(/\s+/).filter((part) => part.length > 0);
|
|
58
|
-
const name = parts.shift();
|
|
59
|
-
if (name === void 0) fail(fileName, line, "serve: needs the name of a server registered in specification.cli()");
|
|
60
|
-
if (name.includes("=")) fail(fileName, line, `serve: "${name}" is a KEY=value pair — the server NAME comes first`);
|
|
61
|
-
const env = {};
|
|
62
|
-
for (const part of parts) {
|
|
63
|
-
const separator = part.indexOf("=");
|
|
64
|
-
if (separator <= 0) fail(fileName, line, `serve: "${part}" is not a KEY=value pair`);
|
|
65
|
-
env[part.slice(0, separator)] = part.slice(separator + 1);
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
env,
|
|
69
|
-
line,
|
|
70
|
-
name
|
|
71
|
-
};
|
|
72
|
-
}
|
|
1
|
+
import { basename, dirname, resolve } from "node:path";
|
|
2
|
+
//#region src/core/specification/shared/fixtures.ts
|
|
73
3
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
4
|
+
* Fixture path resolution + copy semantics for the `cli` facet's `.fixture()`.
|
|
5
|
+
*
|
|
6
|
+
* A fixture path is one of two shapes:
|
|
7
|
+
*
|
|
8
|
+
* - `$FIXTURES/<rest>` — the shared pool at `<specs-root>/fixtures/<rest>`,
|
|
9
|
+
* where `<specs-root>` is the nearest ancestor directory named `specs`.
|
|
10
|
+
* - `<path>` (no marker) — feature-local, at `<test-dir>/fixtures/<path>`.
|
|
11
|
+
*
|
|
12
|
+
* Any other `$`-prefixed marker is a usage error (listed against the known
|
|
13
|
+
* markers). Copy semantics mirror rsync's trailing-slash rule (see
|
|
14
|
+
* {@link copyPlan}).
|
|
77
15
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
if (value.length === 0) fail(fileName, number, `${key}: is empty`);
|
|
97
|
-
if (key === "fixture") fixtures.push(value);
|
|
98
|
-
else if (key === "env") env.push(...parseEnvTokens(value, fileName, number));
|
|
99
|
-
else serve.push(parseServeEntry(value, fileName, number));
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (singles.has(key)) fail(fileName, number, `${key}: is declared twice — it takes exactly one line`);
|
|
103
|
-
if (value.length === 0) fail(fileName, number, `${key}: is empty`);
|
|
104
|
-
singles.set(key, value);
|
|
16
|
+
/**
|
|
17
|
+
* Markers understood in a `.fixture()` path. Extend here + in
|
|
18
|
+
* {@link resolveFixtureSource}. Exported so the lint layer (rule
|
|
19
|
+
* `jterrazz/b2-known-fixture-marker`) validates literals against the same list.
|
|
20
|
+
*/
|
|
21
|
+
const KNOWN_FIXTURE_MARKERS = ["$FIXTURES"];
|
|
22
|
+
/**
|
|
23
|
+
* Walk up from `startDir` to the nearest ancestor directory named `specs`.
|
|
24
|
+
* Throws with guidance if none is found up to the filesystem root — the
|
|
25
|
+
* `$FIXTURES` marker is meaningless without a specs root.
|
|
26
|
+
*/
|
|
27
|
+
function discoverSpecsRoot(startDir) {
|
|
28
|
+
let dir = startDir;
|
|
29
|
+
for (;;) {
|
|
30
|
+
if (basename(dir) === "specs") return dir;
|
|
31
|
+
const parent = dirname(dir);
|
|
32
|
+
if (parent === dir) break;
|
|
33
|
+
dir = parent;
|
|
105
34
|
}
|
|
106
|
-
|
|
107
|
-
"test",
|
|
108
|
-
"given",
|
|
109
|
-
"then"
|
|
110
|
-
]) if (!singles.has(key)) fail(fileName, 1, `missing "${key}:" in the header — test, given and then are mandatory`, "narrative");
|
|
111
|
-
const header = {
|
|
112
|
-
env,
|
|
113
|
-
fixtures,
|
|
114
|
-
given: singles.get("given"),
|
|
115
|
-
serve,
|
|
116
|
-
test: singles.get("test"),
|
|
117
|
-
then: singles.get("then")
|
|
118
|
-
};
|
|
119
|
-
return {
|
|
120
|
-
end: index,
|
|
121
|
-
header
|
|
122
|
-
};
|
|
35
|
+
throw new Error(`.fixture(): the $FIXTURES marker resolves to <specs-root>/fixtures, but no directory named "specs" was found walking up from ${startDir}. Move the specification under a specs/ directory, or use a feature-local fixtures/ path (no $FIXTURES marker).`);
|
|
123
36
|
}
|
|
124
37
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
38
|
+
* Resolve the absolute source path of a fixture, applying marker resolution.
|
|
39
|
+
* The trailing slash (spread semantics) is preserved by callers via
|
|
40
|
+
* {@link copyPlan} — it is irrelevant to source resolution.
|
|
128
41
|
*/
|
|
129
|
-
function
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
/** Parse the blocks: everything after the header's blank line. */
|
|
143
|
-
function parseBlocks(lines, start, fileName) {
|
|
144
|
-
const starts = [];
|
|
145
|
-
for (let index = start; index < lines.length; index++) if (isBlockStart(lines[index])) starts.push(index);
|
|
146
|
-
if (starts.length === 0) fail(fileName, start + 1, "no \"$ <command>\" block — a literate spec runs at least one command");
|
|
147
|
-
for (let index = start; index < starts[0]; index++) if (lines[index].trim() !== "") fail(fileName, index + 1, `"${lines[index]}" sits outside a block — the first line after the header must be "$ <command>"`);
|
|
148
|
-
return starts.map((blockStart, position) => {
|
|
149
|
-
const next = starts[position + 1] ?? lines.length;
|
|
150
|
-
const argv = lines[blockStart].slice(1).trim();
|
|
151
|
-
const exitLine = lines[blockStart + 1];
|
|
152
|
-
const exit = exitLine === void 0 ? null : EXIT_LINE.exec(exitLine);
|
|
153
|
-
if (!exit?.groups) fail(fileName, blockStart + 2, `the line after "$ ${argv}" must be "exit: <integer>", got ${exitLine === void 0 ? "(end of file)" : `"${exitLine}"`}`);
|
|
154
|
-
const streams = parseStreams(lines.slice(blockStart + 2, next), position + 1 < starts.length);
|
|
155
|
-
return {
|
|
156
|
-
argv,
|
|
157
|
-
exitCode: Number(exit.groups.code),
|
|
158
|
-
line: blockStart + 1,
|
|
159
|
-
stderr: streams.stderr,
|
|
160
|
-
stdout: streams.stdout
|
|
161
|
-
};
|
|
162
|
-
});
|
|
42
|
+
function resolveFixtureSource(path, testDir) {
|
|
43
|
+
const clean = path.replace(/\/+$/, "");
|
|
44
|
+
if (clean === "$FIXTURES" || path.startsWith("$FIXTURES/")) {
|
|
45
|
+
const rest = clean.slice(9).replace(/^\/+/, "");
|
|
46
|
+
return resolve(discoverSpecsRoot(testDir), "fixtures", rest);
|
|
47
|
+
}
|
|
48
|
+
if (path.startsWith("$")) {
|
|
49
|
+
const marker = clean.split("/")[0];
|
|
50
|
+
throw new Error(`.fixture("${path}"): unknown marker "${marker}". Known markers: ${KNOWN_FIXTURE_MARKERS.join(", ")}. A path without a marker is feature-local (resolved under <test-dir>/fixtures/).`);
|
|
51
|
+
}
|
|
52
|
+
return resolve(testDir, "fixtures", clean);
|
|
163
53
|
}
|
|
164
54
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
55
|
+
* Compute the source + destination for copying a fixture into the working
|
|
56
|
+
* directory. Copy semantics follow rsync's trailing-slash rule:
|
|
57
|
+
*
|
|
58
|
+
* - `dir/` (trailing slash) — the CONTENTS are spread into the cwd.
|
|
59
|
+
* - `dir` (no slash, a directory) — the directory is copied as `<cwd>/<basename>`.
|
|
60
|
+
* - `file` — copied as `<cwd>/<basename>`.
|
|
61
|
+
*
|
|
62
|
+
* Chained `.fixture()` calls layer in order: a later fixture overwrites files
|
|
63
|
+
* written by an earlier one.
|
|
167
64
|
*/
|
|
168
|
-
function
|
|
169
|
-
const
|
|
170
|
-
const { end, header } = parseHeader(lines, fileName);
|
|
65
|
+
function copyPlan(path, testDir, workDir) {
|
|
66
|
+
const src = resolveFixtureSource(path, testDir);
|
|
171
67
|
return {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
headerText: `${lines.slice(0, end).join("\n")}\n`
|
|
68
|
+
dest: /\/+$/.test(path) ? workDir : resolve(workDir, basename(src)),
|
|
69
|
+
src
|
|
175
70
|
};
|
|
176
71
|
}
|
|
177
|
-
/** Render one block back to its source form (the update-mode inverse of the parser). */
|
|
178
|
-
function serializeLiterateBlock(block) {
|
|
179
|
-
const lines = [`$ ${block.argv}`.trimEnd(), `exit: ${block.exitCode}`];
|
|
180
|
-
if (block.stdout.length > 0) lines.push(block.stdout);
|
|
181
|
-
if (block.stderr !== null && block.stderr.length > 0) lines.push(STDERR_MARKER, block.stderr);
|
|
182
|
-
return lines.join("\n");
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Rebuild a `.cli` file from its VERBATIM header text and a fresh set of
|
|
186
|
-
* blocks. Update mode rewrites only what follows each `$` — the header comes
|
|
187
|
-
* back byte-identical, comments and all.
|
|
188
|
-
*/
|
|
189
|
-
function serializeLiterateFile(headerText, blocks) {
|
|
190
|
-
return `${headerText}\n${blocks.map((block) => serializeLiterateBlock(block)).join("\n\n")}\n`;
|
|
191
|
-
}
|
|
192
72
|
//#endregion
|
|
193
73
|
//#region src/core/matching/match.ts
|
|
194
74
|
/** Every kind usable as a `{{token}}` in fixture files (all but ref/regex/includes). */
|
|
@@ -353,4 +233,4 @@ var CaptureScope = class {
|
|
|
353
233
|
}
|
|
354
234
|
};
|
|
355
235
|
//#endregion
|
|
356
|
-
export {
|
|
236
|
+
export { copyPlan as a, match as i, Matcher as n, resolveFixtureSource as o, TOKEN_KINDS as r, CaptureScope as t };
|
package/dist/oxlint.cjs
CHANGED
|
@@ -306,7 +306,7 @@ const RULE_DOCS = {
|
|
|
306
306
|
},
|
|
307
307
|
"b4-given-then": {
|
|
308
308
|
channel: "statique",
|
|
309
|
-
convention: "Chaque test contient `// Given -` puis `// Then -` (les deux, dans cet ordre) ; Given déclaré après Then est une erreur.
|
|
309
|
+
convention: "Chaque test contient `// Given -` puis `// Then -` (les deux, dans cet ordre) ; Given déclaré après Then est une erreur. Un document `<cas>.spec.yaml` n’a pas de commentaires : sa narration est sa `description:`.",
|
|
310
310
|
family: "B",
|
|
311
311
|
id: "B4",
|
|
312
312
|
rationale: "La narration Given/Then rend l’intention du test lisible sans lire les assertions."
|
|
@@ -567,7 +567,7 @@ const RULE_DOCS = {
|
|
|
567
567
|
const CHECKER_PASSES = [
|
|
568
568
|
{
|
|
569
569
|
channel: "checker",
|
|
570
|
-
convention: "Tout `{{token}}` dans une fixture `expected/` — ou dans
|
|
570
|
+
convention: "Tout `{{token}}` dans une fixture `expected/` — ou dans un flux attendu d’un document `<cas>.spec.yaml` — appartient au vocabulaire figé ; un token inconnu est une erreur.",
|
|
571
571
|
family: "D",
|
|
572
572
|
id: "D4",
|
|
573
573
|
name: "d4-unknown-token",
|
|
@@ -591,19 +591,91 @@ const CHECKER_PASSES = [
|
|
|
591
591
|
},
|
|
592
592
|
{
|
|
593
593
|
channel: "checker",
|
|
594
|
-
convention: "Un
|
|
594
|
+
convention: "Un document `<cas>.spec.yaml` respecte sa grammaire : clés fermées au niveau du document (`kind`, `description`, `fixture`, `env`, `serve`, `runs`) comme dans un run (`command`, `stdin`, `timeout`, `waitFor`, `exit`, `stdout`, `stderr`, `files`), `description:` et `runs:` obligatoires, `command:` et `exit:` obligatoires dans chaque run, `exit:` entier littéral, `waitFor:` seulement sur le dernier run et jamais avec `stdin:`, chemin `files:` relatif et sous le workdir.",
|
|
595
595
|
family: "D",
|
|
596
596
|
id: "D4b",
|
|
597
|
-
name: "d4b-
|
|
598
|
-
rationale: "La grammaire est lue par le parseur du runner lui-même : le
|
|
597
|
+
name: "d4b-spec-shape",
|
|
598
|
+
rationale: "La grammaire est lue par le parseur du runner lui-même : le document que le lint accepte est exactement celui que le runner exécute."
|
|
599
599
|
},
|
|
600
600
|
{
|
|
601
601
|
channel: "checker",
|
|
602
|
-
convention: "
|
|
603
|
-
family: "
|
|
604
|
-
id: "
|
|
605
|
-
name: "
|
|
606
|
-
rationale: "Le
|
|
602
|
+
convention: "Les clés d’un `<cas>.spec.yaml` suivent l’ordre canonique — `kind, description, fixture, env, serve, runs` au niveau du document, `command, stdin, timeout, waitFor, exit, stdout, stderr, files` dans un run. Corrigeable (`--fix`).",
|
|
603
|
+
family: "D",
|
|
604
|
+
id: "D4b",
|
|
605
|
+
name: "d4b-spec-key-order",
|
|
606
|
+
rationale: "Le décor avant la session, l’entrée avant la sortie : un ordre unique rend les documents comparables et supprime les diffs qui ne changent rien."
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
channel: "checker",
|
|
610
|
+
convention: "`stdout`, `stderr`, `stdin` et `files.*.equals` d’un `<cas>.spec.yaml` s’écrivent en scalaire bloc (`|` garde le saut de ligne final, `|-` le supprime), jamais en chaîne entre guillemets avec des `\\n`. Corrigeable (`--fix`).",
|
|
611
|
+
family: "D",
|
|
612
|
+
id: "D4b",
|
|
613
|
+
name: "d4b-spec-block-scalar",
|
|
614
|
+
rationale: "Une sortie attendue doit ressembler à une sortie — une golden sur une ligne est illisible et aucun diff ne peut la montrer."
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
channel: "checker",
|
|
618
|
+
convention: "Un document se nomme `<cas>.spec.yaml` : `<cas>` en kebab-case, sans les mots `test`/`spec`/`cli` que le suffixe porte déjà, et jamais le nom nu de son dossier.",
|
|
619
|
+
family: "C",
|
|
620
|
+
id: "C12",
|
|
621
|
+
name: "c12-spec-file-name",
|
|
622
|
+
rationale: "Le nom du fichier est la première phrase que le lecteur lit ; `rm/rm.spec.yaml` ne dit rien deux fois."
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
channel: "checker",
|
|
626
|
+
convention: "La `description:` d’un document est un titre : une seule ligne, initiale minuscule (identifiant tout en majuscules exempté), sans point final, moins de 100 caractères.",
|
|
627
|
+
family: "J",
|
|
628
|
+
id: "J5",
|
|
629
|
+
name: "j5-spec-description",
|
|
630
|
+
rationale: "La description EST le titre vitest — les règles du titre s’y appliquent, pas celles d’un paragraphe."
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
channel: "checker",
|
|
634
|
+
convention: "Deux `<cas>.spec.yaml` d’un même dossier ne partagent pas leur `description:`.",
|
|
635
|
+
family: "J",
|
|
636
|
+
id: "J4",
|
|
637
|
+
name: "j4-spec-description-unique",
|
|
638
|
+
rationale: "Le rapporteur nomme le titre : deux titres identiques laissent deux fichiers à ouvrir."
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
channel: "checker",
|
|
642
|
+
convention: "Un flux attendu ne contient pas de valeur volatile littérale : origine loopback avec port (`127.0.0.1:8080`, `localhost:3000`), chemin temporaire absolu (`/tmp/`, `/private/tmp/`, `/var/folders/`) ou chemin home (`/Users/…`, `/home/…`). Le message nomme le token à écrire.",
|
|
643
|
+
family: "D",
|
|
644
|
+
id: "D5",
|
|
645
|
+
name: "d5-spec-volatile-literal",
|
|
646
|
+
rationale: "C’est exactement la forme qu’un `TEST_UPDATE` aurait tokenisée : en trouver une signifie qu’un token a été écrasé à la main."
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
channel: "checker",
|
|
650
|
+
convention: "Un horodatage ISO-8601 ou un uuid littéral dans un flux attendu → warning, sauf si la même valeur apparaît dans un `fixture:` ou un `stdin:` du même document (elle est alors fixée, pas volatile).",
|
|
651
|
+
family: "D",
|
|
652
|
+
id: "D5",
|
|
653
|
+
name: "d5w-spec-pinned-value",
|
|
654
|
+
rationale: "Le défaut n’est pas le littéral : c’est le littéral que rien n’a fixé."
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
channel: "checker",
|
|
658
|
+
convention: "Un flux attendu dont tout le contenu est `{{any}}` → warning : le run s’exécute et ne prouve rien.",
|
|
659
|
+
family: "J",
|
|
660
|
+
id: "J3",
|
|
661
|
+
name: "j3w-spec-empty-assertion",
|
|
662
|
+
rationale: "Le miroir de J3 pour les documents — une assertion qui accepte tout est une assertion absente."
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
channel: "checker",
|
|
666
|
+
convention: "Un run dont `exit:` est non nul sans `stdout:` ni `stderr:` → warning : le document ne dit pas pourquoi la commande a refusé.",
|
|
667
|
+
family: "D",
|
|
668
|
+
id: "D11",
|
|
669
|
+
name: "d11w-spec-silent-refusal",
|
|
670
|
+
rationale: "Un refus muet est soit un défaut du produit, soit une golden qui a oublié que les mots partaient sur stderr."
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
channel: "checker",
|
|
674
|
+
convention: "Les mots nus d’`env:` et les noms de `serve:` d’un document sont des clés des objets `env`/`serve` d’un `specification.cli(…)` du plus proche dossier portant des `*.specification.ts` (lecture statique des littéraux ; ignoré sinon).",
|
|
675
|
+
family: "C",
|
|
676
|
+
id: "C8",
|
|
677
|
+
name: "c8-spec-registered-name",
|
|
678
|
+
rationale: "Une faute de frappe (`serve: dashbord`) échoue aujourd’hui à l’exécution, un fichier de test plus tard ; ici c’est une ligne de lint."
|
|
607
679
|
},
|
|
608
680
|
{
|
|
609
681
|
channel: "checker",
|
|
@@ -615,7 +687,7 @@ const CHECKER_PASSES = [
|
|
|
615
687
|
},
|
|
616
688
|
{
|
|
617
689
|
channel: "checker",
|
|
618
|
-
convention: "Aucune fixture morte : tout fichier sous `seeds/`/`requests/`/`fixtures/` et toute entrée de premier niveau de `expected/` doit être référencée ; un dossier de feature sans `*.test.ts` ni `*.
|
|
690
|
+
convention: "Aucune fixture morte : tout fichier sous `seeds/`/`requests/`/`fixtures/` et toute entrée de premier niveau de `expected/` doit être référencée ; un dossier de feature sans `*.test.ts` ni `*.spec.yaml` est orphelin (warning si argument non littéral). Un `<cas>.spec.yaml` référence les fixtures nommées par ses entrées `fixture:`.",
|
|
619
691
|
family: "C",
|
|
620
692
|
id: "C9",
|
|
621
693
|
name: "c9-dead-fixtures",
|
package/dist/oxlint.js
CHANGED
|
@@ -302,7 +302,7 @@ const RULE_DOCS = {
|
|
|
302
302
|
},
|
|
303
303
|
"b4-given-then": {
|
|
304
304
|
channel: "statique",
|
|
305
|
-
convention: "Chaque test contient `// Given -` puis `// Then -` (les deux, dans cet ordre) ; Given déclaré après Then est une erreur.
|
|
305
|
+
convention: "Chaque test contient `// Given -` puis `// Then -` (les deux, dans cet ordre) ; Given déclaré après Then est une erreur. Un document `<cas>.spec.yaml` n’a pas de commentaires : sa narration est sa `description:`.",
|
|
306
306
|
family: "B",
|
|
307
307
|
id: "B4",
|
|
308
308
|
rationale: "La narration Given/Then rend l’intention du test lisible sans lire les assertions."
|
|
@@ -563,7 +563,7 @@ const RULE_DOCS = {
|
|
|
563
563
|
const CHECKER_PASSES = [
|
|
564
564
|
{
|
|
565
565
|
channel: "checker",
|
|
566
|
-
convention: "Tout `{{token}}` dans une fixture `expected/` — ou dans
|
|
566
|
+
convention: "Tout `{{token}}` dans une fixture `expected/` — ou dans un flux attendu d’un document `<cas>.spec.yaml` — appartient au vocabulaire figé ; un token inconnu est une erreur.",
|
|
567
567
|
family: "D",
|
|
568
568
|
id: "D4",
|
|
569
569
|
name: "d4-unknown-token",
|
|
@@ -587,19 +587,91 @@ const CHECKER_PASSES = [
|
|
|
587
587
|
},
|
|
588
588
|
{
|
|
589
589
|
channel: "checker",
|
|
590
|
-
convention: "Un
|
|
590
|
+
convention: "Un document `<cas>.spec.yaml` respecte sa grammaire : clés fermées au niveau du document (`kind`, `description`, `fixture`, `env`, `serve`, `runs`) comme dans un run (`command`, `stdin`, `timeout`, `waitFor`, `exit`, `stdout`, `stderr`, `files`), `description:` et `runs:` obligatoires, `command:` et `exit:` obligatoires dans chaque run, `exit:` entier littéral, `waitFor:` seulement sur le dernier run et jamais avec `stdin:`, chemin `files:` relatif et sous le workdir.",
|
|
591
591
|
family: "D",
|
|
592
592
|
id: "D4b",
|
|
593
|
-
name: "d4b-
|
|
594
|
-
rationale: "La grammaire est lue par le parseur du runner lui-même : le
|
|
593
|
+
name: "d4b-spec-shape",
|
|
594
|
+
rationale: "La grammaire est lue par le parseur du runner lui-même : le document que le lint accepte est exactement celui que le runner exécute."
|
|
595
595
|
},
|
|
596
596
|
{
|
|
597
597
|
channel: "checker",
|
|
598
|
-
convention: "
|
|
599
|
-
family: "
|
|
600
|
-
id: "
|
|
601
|
-
name: "
|
|
602
|
-
rationale: "Le
|
|
598
|
+
convention: "Les clés d’un `<cas>.spec.yaml` suivent l’ordre canonique — `kind, description, fixture, env, serve, runs` au niveau du document, `command, stdin, timeout, waitFor, exit, stdout, stderr, files` dans un run. Corrigeable (`--fix`).",
|
|
599
|
+
family: "D",
|
|
600
|
+
id: "D4b",
|
|
601
|
+
name: "d4b-spec-key-order",
|
|
602
|
+
rationale: "Le décor avant la session, l’entrée avant la sortie : un ordre unique rend les documents comparables et supprime les diffs qui ne changent rien."
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
channel: "checker",
|
|
606
|
+
convention: "`stdout`, `stderr`, `stdin` et `files.*.equals` d’un `<cas>.spec.yaml` s’écrivent en scalaire bloc (`|` garde le saut de ligne final, `|-` le supprime), jamais en chaîne entre guillemets avec des `\\n`. Corrigeable (`--fix`).",
|
|
607
|
+
family: "D",
|
|
608
|
+
id: "D4b",
|
|
609
|
+
name: "d4b-spec-block-scalar",
|
|
610
|
+
rationale: "Une sortie attendue doit ressembler à une sortie — une golden sur une ligne est illisible et aucun diff ne peut la montrer."
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
channel: "checker",
|
|
614
|
+
convention: "Un document se nomme `<cas>.spec.yaml` : `<cas>` en kebab-case, sans les mots `test`/`spec`/`cli` que le suffixe porte déjà, et jamais le nom nu de son dossier.",
|
|
615
|
+
family: "C",
|
|
616
|
+
id: "C12",
|
|
617
|
+
name: "c12-spec-file-name",
|
|
618
|
+
rationale: "Le nom du fichier est la première phrase que le lecteur lit ; `rm/rm.spec.yaml` ne dit rien deux fois."
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
channel: "checker",
|
|
622
|
+
convention: "La `description:` d’un document est un titre : une seule ligne, initiale minuscule (identifiant tout en majuscules exempté), sans point final, moins de 100 caractères.",
|
|
623
|
+
family: "J",
|
|
624
|
+
id: "J5",
|
|
625
|
+
name: "j5-spec-description",
|
|
626
|
+
rationale: "La description EST le titre vitest — les règles du titre s’y appliquent, pas celles d’un paragraphe."
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
channel: "checker",
|
|
630
|
+
convention: "Deux `<cas>.spec.yaml` d’un même dossier ne partagent pas leur `description:`.",
|
|
631
|
+
family: "J",
|
|
632
|
+
id: "J4",
|
|
633
|
+
name: "j4-spec-description-unique",
|
|
634
|
+
rationale: "Le rapporteur nomme le titre : deux titres identiques laissent deux fichiers à ouvrir."
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
channel: "checker",
|
|
638
|
+
convention: "Un flux attendu ne contient pas de valeur volatile littérale : origine loopback avec port (`127.0.0.1:8080`, `localhost:3000`), chemin temporaire absolu (`/tmp/`, `/private/tmp/`, `/var/folders/`) ou chemin home (`/Users/…`, `/home/…`). Le message nomme le token à écrire.",
|
|
639
|
+
family: "D",
|
|
640
|
+
id: "D5",
|
|
641
|
+
name: "d5-spec-volatile-literal",
|
|
642
|
+
rationale: "C’est exactement la forme qu’un `TEST_UPDATE` aurait tokenisée : en trouver une signifie qu’un token a été écrasé à la main."
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
channel: "checker",
|
|
646
|
+
convention: "Un horodatage ISO-8601 ou un uuid littéral dans un flux attendu → warning, sauf si la même valeur apparaît dans un `fixture:` ou un `stdin:` du même document (elle est alors fixée, pas volatile).",
|
|
647
|
+
family: "D",
|
|
648
|
+
id: "D5",
|
|
649
|
+
name: "d5w-spec-pinned-value",
|
|
650
|
+
rationale: "Le défaut n’est pas le littéral : c’est le littéral que rien n’a fixé."
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
channel: "checker",
|
|
654
|
+
convention: "Un flux attendu dont tout le contenu est `{{any}}` → warning : le run s’exécute et ne prouve rien.",
|
|
655
|
+
family: "J",
|
|
656
|
+
id: "J3",
|
|
657
|
+
name: "j3w-spec-empty-assertion",
|
|
658
|
+
rationale: "Le miroir de J3 pour les documents — une assertion qui accepte tout est une assertion absente."
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
channel: "checker",
|
|
662
|
+
convention: "Un run dont `exit:` est non nul sans `stdout:` ni `stderr:` → warning : le document ne dit pas pourquoi la commande a refusé.",
|
|
663
|
+
family: "D",
|
|
664
|
+
id: "D11",
|
|
665
|
+
name: "d11w-spec-silent-refusal",
|
|
666
|
+
rationale: "Un refus muet est soit un défaut du produit, soit une golden qui a oublié que les mots partaient sur stderr."
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
channel: "checker",
|
|
670
|
+
convention: "Les mots nus d’`env:` et les noms de `serve:` d’un document sont des clés des objets `env`/`serve` d’un `specification.cli(…)` du plus proche dossier portant des `*.specification.ts` (lecture statique des littéraux ; ignoré sinon).",
|
|
671
|
+
family: "C",
|
|
672
|
+
id: "C8",
|
|
673
|
+
name: "c8-spec-registered-name",
|
|
674
|
+
rationale: "Une faute de frappe (`serve: dashbord`) échoue aujourd’hui à l’exécution, un fichier de test plus tard ; ici c’est une ligne de lint."
|
|
603
675
|
},
|
|
604
676
|
{
|
|
605
677
|
channel: "checker",
|
|
@@ -611,7 +683,7 @@ const CHECKER_PASSES = [
|
|
|
611
683
|
},
|
|
612
684
|
{
|
|
613
685
|
channel: "checker",
|
|
614
|
-
convention: "Aucune fixture morte : tout fichier sous `seeds/`/`requests/`/`fixtures/` et toute entrée de premier niveau de `expected/` doit être référencée ; un dossier de feature sans `*.test.ts` ni `*.
|
|
686
|
+
convention: "Aucune fixture morte : tout fichier sous `seeds/`/`requests/`/`fixtures/` et toute entrée de premier niveau de `expected/` doit être référencée ; un dossier de feature sans `*.test.ts` ni `*.spec.yaml` est orphelin (warning si argument non littéral). Un `<cas>.spec.yaml` référence les fixtures nommées par ses entrées `fixture:`.",
|
|
615
687
|
family: "C",
|
|
616
688
|
id: "C9",
|
|
617
689
|
name: "c9-dead-fixtures",
|
package/dist/queue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as CaptureScope } from "./match.js";
|
|
2
|
-
import {
|
|
2
|
+
import { A as placeholderPatternSource, D as hasPlaceholders, P as textEquals } from "./literate.js";
|
|
3
3
|
//#region src/core/contracts/contract.ts
|
|
4
4
|
/**
|
|
5
5
|
* Declare one contract. Identity function — its value is the enforced shape
|