probatio 0.3.0 → 0.5.1

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.
Files changed (74) hide show
  1. package/README.md +11 -197
  2. package/bin/probatio.js +52 -0
  3. package/package.json +22 -29
  4. package/LICENSE +0 -21
  5. package/dist/cli.js +0 -721
  6. package/dist/contract.js +0 -32
  7. package/dist/flags.js +0 -87
  8. package/dist/golden/check.js +0 -188
  9. package/dist/golden/record.js +0 -395
  10. package/dist/golden/recorder.mjs +0 -78
  11. package/dist/ledger/build.js +0 -348
  12. package/dist/ledger/check.js +0 -133
  13. package/dist/matrix/report.js +0 -256
  14. package/dist/mcp.js +0 -188
  15. package/dist/memory/state.js +0 -222
  16. package/dist/mutate/affected.js +0 -173
  17. package/dist/mutate/child-lines.mjs +0 -39
  18. package/dist/mutate/coverage-map.js +0 -229
  19. package/dist/mutate/csharp-coverage.js +0 -161
  20. package/dist/mutate/fail-reporter.mjs +0 -65
  21. package/dist/mutate/find.js +0 -13
  22. package/dist/mutate/generate.js +0 -156
  23. package/dist/mutate/go-coverage.js +0 -100
  24. package/dist/mutate/ids.js +0 -22
  25. package/dist/mutate/java-coverage.js +0 -217
  26. package/dist/mutate/kill-label.js +0 -34
  27. package/dist/mutate/load-failure.mjs +0 -40
  28. package/dist/mutate/mocha-coverage.cjs +0 -77
  29. package/dist/mutate/node-batch-loader.mjs +0 -99
  30. package/dist/mutate/node-batch.mjs +0 -167
  31. package/dist/mutate/node-coverage.mjs +0 -77
  32. package/dist/mutate/operators.js +0 -181
  33. package/dist/mutate/patch.js +0 -136
  34. package/dist/mutate/precise-lines.mjs +0 -304
  35. package/dist/mutate/probatio-jacoco-run.java +0 -516
  36. package/dist/mutate/project-config.js +0 -58
  37. package/dist/mutate/py-pytest-reporter.py +0 -145
  38. package/dist/mutate/py-reporter.py +0 -79
  39. package/dist/mutate/run.js +0 -1335
  40. package/dist/mutate/rust-coverage.js +0 -144
  41. package/dist/mutate/sealed.js +0 -118
  42. package/dist/mutate/suite-decision.js +0 -130
  43. package/dist/mutate/suites.js +0 -1524
  44. package/dist/mutate/tally.js +0 -240
  45. package/dist/mutate/text-operators.js +0 -637
  46. package/dist/mutate/titles.js +0 -23
  47. package/dist/node-version.js +0 -13
  48. package/dist/swarm/check-kill.js +0 -114
  49. package/dist/swarm/queue.js +0 -68
  50. package/dist/verify/change.js +0 -340
  51. package/schemas/check-kill.schema.json +0 -93
  52. package/schemas/findings.add.schema.json +0 -53
  53. package/schemas/gap.fix.schema.json +0 -53
  54. package/schemas/gap.revert.schema.json +0 -53
  55. package/schemas/golden.check.schema.json +0 -89
  56. package/schemas/golden.compare.schema.json +0 -151
  57. package/schemas/golden.record.schema.json +0 -126
  58. package/schemas/guard.check.schema.json +0 -53
  59. package/schemas/ledger.build.schema.json +0 -132
  60. package/schemas/ledger.check.schema.json +0 -143
  61. package/schemas/matrix.report.schema.json +0 -129
  62. package/schemas/mcp.schema.json +0 -53
  63. package/schemas/mutate.generate.schema.json +0 -127
  64. package/schemas/mutate.help.schema.json +0 -53
  65. package/schemas/mutate.run.schema.json +0 -272
  66. package/schemas/mutate.sealed.schema.json +0 -53
  67. package/schemas/mutate.tally.schema.json +0 -137
  68. package/schemas/queue.claim.schema.json +0 -53
  69. package/schemas/queue.reap.schema.json +0 -71
  70. package/schemas/queue.seed.schema.json +0 -71
  71. package/schemas/schema.schema.json +0 -74
  72. package/schemas/seal.schema.json +0 -69
  73. package/schemas/status.schema.json +0 -103
  74. package/schemas/verify-change.schema.json +0 -222
@@ -1,637 +0,0 @@
1
- import path from "node:path";
2
- const C_OPS = [
3
- { original: "&&", op: "and-to-or", replacement: "||" },
4
- { original: "||", op: "or-to-and", replacement: "&&" },
5
- { original: "<=", op: "le-to-lt", replacement: "<" },
6
- { original: ">=", op: "ge-to-gt", replacement: ">" },
7
- { original: "==", op: "eq-to-neq", replacement: "!=" },
8
- { original: "!=", op: "neq-to-eq", replacement: "==" },
9
- { original: "<", op: "lt-to-le", replacement: "<=" },
10
- { original: ">", op: "gt-to-ge", replacement: ">=" },
11
- ];
12
- const PY_WORDS = [
13
- { original: "and", op: "and-to-or", replacement: "or" },
14
- { original: "or", op: "or-to-and", replacement: "and" },
15
- ];
16
- const EQ_OPS = [
17
- { original: "===", op: "eq-to-neq", replacement: "!==" },
18
- { original: "!==", op: "neq-to-eq", replacement: "===" },
19
- ];
20
- const X86_OPS = [
21
- { original: "je", op: "eq-to-neq", replacement: "jne" },
22
- { original: "jne", op: "neq-to-eq", replacement: "je" },
23
- { original: "jz", op: "eq-to-neq", replacement: "jnz" },
24
- { original: "jnz", op: "neq-to-eq", replacement: "jz" },
25
- { original: "jg", op: "gt-to-ge", replacement: "jge" },
26
- { original: "jge", op: "ge-to-gt", replacement: "jg" },
27
- { original: "jl", op: "lt-to-le", replacement: "jle" },
28
- { original: "jle", op: "le-to-lt", replacement: "jl" },
29
- { original: "ja", op: "gt-to-ge", replacement: "jae" },
30
- { original: "jae", op: "ge-to-gt", replacement: "ja" },
31
- { original: "jb", op: "lt-to-le", replacement: "jbe" },
32
- { original: "jbe", op: "le-to-lt", replacement: "jb" },
33
- { original: "and", op: "and-to-or", replacement: "or" },
34
- { original: "or", op: "or-to-and", replacement: "and" },
35
- ];
36
- const ARM_OPS = [
37
- { original: "b.eq", op: "eq-to-neq", replacement: "b.ne" },
38
- { original: "b.ne", op: "neq-to-eq", replacement: "b.eq" },
39
- { original: "b.gt", op: "gt-to-ge", replacement: "b.ge" },
40
- { original: "b.ge", op: "ge-to-gt", replacement: "b.gt" },
41
- { original: "b.lt", op: "lt-to-le", replacement: "b.le" },
42
- { original: "b.le", op: "le-to-lt", replacement: "b.lt" },
43
- { original: "b.hi", op: "gt-to-ge", replacement: "b.hs" },
44
- { original: "b.hs", op: "ge-to-gt", replacement: "b.hi" },
45
- { original: "b.lo", op: "lt-to-le", replacement: "b.ls" },
46
- { original: "b.ls", op: "le-to-lt", replacement: "b.lo" },
47
- { original: "cbz", op: "eq-to-neq", replacement: "cbnz" },
48
- { original: "cbnz", op: "neq-to-eq", replacement: "cbz" },
49
- ];
50
- const RISCV_OPS = [
51
- { original: "beq", op: "eq-to-neq", replacement: "bne" },
52
- { original: "bne", op: "neq-to-eq", replacement: "beq" },
53
- { original: "blt", op: "lt-to-le", replacement: "bge" },
54
- { original: "bge", op: "le-to-lt", replacement: "blt" },
55
- { original: "bltu", op: "lt-to-le", replacement: "bgeu" },
56
- { original: "bgeu", op: "le-to-lt", replacement: "bltu" },
57
- ];
58
- /** Which text finder owns this path. TypeScript stays null so the TS parser is not used. */
59
- export function textKind(file) {
60
- switch (path.extname(file).toLowerCase()) {
61
- case ".py":
62
- return "python";
63
- case ".cob":
64
- case ".cbl":
65
- case ".cobol":
66
- return "cobol";
67
- case ".rs":
68
- return "rust";
69
- case ".go":
70
- return "go";
71
- case ".java":
72
- case ".c":
73
- case ".h":
74
- case ".cpp":
75
- case ".cc":
76
- case ".cxx":
77
- case ".hpp":
78
- case ".hh":
79
- case ".hxx":
80
- return "c";
81
- case ".js":
82
- case ".mjs":
83
- case ".cjs":
84
- return "js";
85
- case ".cs":
86
- return "csharp";
87
- case ".swift":
88
- return "swift";
89
- case ".asm":
90
- case ".nasm":
91
- case ".yasm":
92
- return "asm-x86_64";
93
- case ".s":
94
- case ".S":
95
- return "asm-aarch64";
96
- default:
97
- return null;
98
- }
99
- }
100
- /**
101
- * Operator mutants for one non-TypeScript source. Strings and comments are skipped
102
- * before a point is proposed, so a `/*`, `*>`, or `#` inside a string cannot hide later code.
103
- */
104
- export function findTextMutants(file, text, operators = "core") {
105
- const kind = resolveKind(file, text);
106
- if (!kind)
107
- return { points: [], violations: [] };
108
- const code = codeMask(text, kind);
109
- const points = [];
110
- let line = 1;
111
- let index = 0;
112
- while (index < text.length) {
113
- if (text[index] === "\n") {
114
- line += 1;
115
- index += 1;
116
- continue;
117
- }
118
- const match = code[index] ? matchAt(text, index, code, kind) ?? (operators === "wide" ? wideAt(text, index, code, kind) : null) : null;
119
- if (!match) {
120
- index += 1;
121
- continue;
122
- }
123
- points.push({
124
- file,
125
- start: index,
126
- end: index + match.original.length,
127
- line,
128
- op: match.op,
129
- original: match.original,
130
- replacement: match.replacement,
131
- });
132
- index += match.original.length;
133
- }
134
- if (operators === "wide")
135
- points.push(...droppedStatements(file, text, code, kind));
136
- return { points, violations: [] };
137
- }
138
- const DROP_KINDS = new Set(["c", "csharp", "js", "rust"]);
139
- const NOT_DROPPED = /^(return|throw|break|continue|goto|case|default|yield|else|if|for|while|do|switch|try|catch|finally|new|package|import|using|let|const|var|val|super|assert)\b/;
140
- const CALL = /^(this\.)?[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*\s*\(.*\)\s*;$/;
141
- const ASSIGN = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\]]*\])*\s*([-+*/%|&^]|<<|>>)?=(?!=).+;$/;
142
- const STEP = /^((\+\+|--)[A-Za-z_$][\w$.]*|[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*(\+\+|--));$/;
143
- /**
144
- * Wide set, semicolon languages only: drop a call, an assignment, or an increment that is a whole
145
- * statement on its own line. `;` takes its place, which is an empty statement in every one of them.
146
- * A declaration has a type before the name, so it never matches. A line inside a string, a comment,
147
- * or the middle of a multi-line statement does not start with code that matches either.
148
- */
149
- function droppedStatements(file, text, code, kind) {
150
- if (!DROP_KINDS.has(kind))
151
- return [];
152
- const points = [];
153
- let offset = 0;
154
- const lines = text.split("\n");
155
- for (const [index, raw] of lines.entries()) {
156
- const lead = raw.length - raw.trimStart().length;
157
- const statement = raw.trim();
158
- const start = offset + lead;
159
- const end = start + statement.length;
160
- offset += raw.length + 1;
161
- if (!statement.endsWith(";") || !code[start] || !code[end - 1])
162
- continue;
163
- if (NOT_DROPPED.test(statement))
164
- continue;
165
- if (!CALL.test(statement) && !ASSIGN.test(statement) && !STEP.test(statement))
166
- continue;
167
- points.push({ file, start, end, line: index + 1, op: "drop-statement", original: statement, replacement: ";" });
168
- }
169
- return points;
170
- }
171
- function matchAt(text, index, code, kind) {
172
- const word = matchWord(text, index, code, kind);
173
- if (word)
174
- return word;
175
- for (const op of symbolOps(kind)) {
176
- if (!text.startsWith(op.original, index))
177
- continue;
178
- if (!spanIsCode(code, index, op.original.length))
179
- continue;
180
- if (blocked(text, index, op.original))
181
- continue;
182
- return op;
183
- }
184
- return null;
185
- }
186
- const ARITHMETIC = [
187
- { original: "+", op: "add-to-sub", replacement: "-" },
188
- { original: "-", op: "sub-to-add", replacement: "+" },
189
- { original: "*", op: "mul-to-div", replacement: "/" },
190
- { original: "/", op: "div-to-mul", replacement: "*" },
191
- { original: "%", op: "mod-to-mul", replacement: "*" },
192
- ];
193
- /**
194
- * Wide set for text languages: spaced arithmetic and integer constants. No statement deletion here:
195
- * without a parser, a removed line can be half of a statement. COBOL and assembly are left out,
196
- * because a COBOL level number or an assembler immediate is not a value the program computes.
197
- */
198
- function wideAt(text, index, code, kind) {
199
- if (kind === "cobol" || kind.startsWith("asm-"))
200
- return null;
201
- const prev = text[index - 1] ?? "";
202
- const next = text[index + 1] ?? "";
203
- for (const op of ARITHMETIC) {
204
- if (text[index] !== op.original)
205
- continue;
206
- // Spaced on both sides, the way a binary operator is written. That leaves `++`, `+=`, `->`,
207
- // `//`, `**`, unary minus, and most pointer stars alone.
208
- if (!(/[ \t]/.test(prev) && /[ \t]/.test(next)))
209
- return null;
210
- const before = text.slice(0, index).trimEnd().slice(-1);
211
- if (before === "" || /[=(,[{:?!<>&|+\-*/%^~]/.test(before))
212
- return null;
213
- // `"x" + y` builds a string. In Java or C# a `-` there does not compile, so it is not a useful mutant.
214
- if (op.original === "+" && (stringBeside(text, code, index, -1) || stringBeside(text, code, index, 1)))
215
- return null;
216
- return spanIsCode(code, index, 1) ? op : null;
217
- }
218
- if (!/[0-9]/.test(text[index]) || /[A-Za-z0-9_.$#]/.test(prev))
219
- return null;
220
- const digits = /^[0-9]+/.exec(text.slice(index, index + 20))?.[0] ?? "";
221
- // A hex, binary, float, exponent, or suffixed literal (`0x10`, `1.5`, `1e5`, `1u32`) is left alone.
222
- if (!digits || /[A-Za-z0-9_.]/.test(text[index + digits.length] ?? ""))
223
- return null;
224
- if (!spanIsCode(code, index, digits.length))
225
- return null;
226
- if (digits === "0")
227
- return { original: "0", op: "const-zero-to-one", replacement: "1" };
228
- if (digits === "1")
229
- return { original: "1", op: "const-one-to-zero", replacement: "0" };
230
- if (digits.length > 15 || digits.startsWith("0"))
231
- return null;
232
- return { original: digits, op: "const-inc", replacement: String(Number(digits) + 1) };
233
- }
234
- /** True when the nearest non-space character on that side is a quote that the mask hides: a string literal's edge. */
235
- function stringBeside(text, code, index, step) {
236
- let at = index + step;
237
- while (at >= 0 && at < text.length && (text[at] === " " || text[at] === "\t"))
238
- at += step;
239
- return at >= 0 && at < text.length && !code[at] && /["'`]/.test(text[at]);
240
- }
241
- function resolveKind(file, text) {
242
- const kind = textKind(file);
243
- if (!kind)
244
- return null;
245
- if (kind.startsWith("asm-"))
246
- return sniffAsm(file, text);
247
- return kind;
248
- }
249
- function sniffAsm(file, text) {
250
- const ext = path.extname(file);
251
- const lower = ext.toLowerCase();
252
- if (lower === ".asm" || lower === ".nasm" || lower === ".yasm")
253
- return "asm-x86_64";
254
- const riscv = /\b(beq|bne|blt|bge|bltu|bgeu|ecall)\b/.test(text);
255
- const arm = /\bb\.(eq|ne|lt|le|gt|ge|hi|hs|lo|ls)\b/.test(text) || /\b(adrp|svc)\b/.test(text);
256
- if (riscv && !arm)
257
- return "asm-riscv";
258
- if (arm && !riscv)
259
- return "asm-aarch64";
260
- // `.S` lowercases to `.s`. A capital S stays RISC-V when the body has no marker.
261
- return lower === ".s" && ext !== ".S" ? "asm-aarch64" : "asm-riscv";
262
- }
263
- function symbolOps(kind) {
264
- if (kind.startsWith("asm-"))
265
- return [];
266
- if (kind === "cobol")
267
- return C_OPS.filter((op) => op.original === "<" || op.original === ">" || op.original === "<=" || op.original === ">=");
268
- if (kind === "python")
269
- return C_OPS.filter((op) => op.original !== "&&" && op.original !== "||");
270
- if (kind === "js" || kind === "swift")
271
- return [...EQ_OPS, ...C_OPS];
272
- return C_OPS;
273
- }
274
- function matchWord(text, index, code, kind) {
275
- if (kind === "python")
276
- return matchExactWord(text, index, code, PY_WORDS, "");
277
- if (kind === "cobol")
278
- return matchCobolWord(text, index, code);
279
- if (kind === "asm-x86_64")
280
- return matchMnemonic(text, index, code, X86_OPS, true);
281
- if (kind === "asm-aarch64")
282
- return matchMnemonic(text, index, code, ARM_OPS, false);
283
- if (kind === "asm-riscv")
284
- return matchMnemonic(text, index, code, RISCV_OPS, false);
285
- return matchExactWord(text, index, code, [
286
- { original: "true", op: "true-to-false", replacement: "false" },
287
- { original: "false", op: "false-to-true", replacement: "true" },
288
- ], "");
289
- }
290
- function matchMnemonic(text, index, code, words, insensitive) {
291
- const ordered = [...words].sort((left, right) => right.original.length - left.original.length);
292
- for (const word of ordered) {
293
- const slice = text.slice(index, index + word.original.length);
294
- const same = insensitive ? slice.toLowerCase() === word.original : slice === word.original;
295
- if (!same)
296
- continue;
297
- if (!wordEdge(text, index, index + word.original.length, "."))
298
- continue;
299
- if (text[index + word.original.length] === ":")
300
- continue;
301
- if (!spanIsCode(code, index, word.original.length))
302
- continue;
303
- return { original: slice, op: word.op, replacement: mirrorCase(slice, word.replacement) };
304
- }
305
- return null;
306
- }
307
- function matchExactWord(text, index, code, words, extra) {
308
- for (const word of words) {
309
- if (!text.startsWith(word.original, index))
310
- continue;
311
- if (!wordEdge(text, index, index + word.original.length, extra))
312
- continue;
313
- if (!spanIsCode(code, index, word.original.length))
314
- continue;
315
- return word;
316
- }
317
- return null;
318
- }
319
- function matchCobolWord(text, index, code) {
320
- const words = ["AND", "OR", "TRUE", "FALSE"];
321
- for (const word of words) {
322
- const slice = text.slice(index, index + word.length);
323
- if (slice.toUpperCase() !== word)
324
- continue;
325
- if (!wordEdge(text, index, index + word.length, "-"))
326
- continue;
327
- if (!spanIsCode(code, index, word.length))
328
- continue;
329
- if (word === "AND")
330
- return { original: slice, op: "and-to-or", replacement: mirrorCase(slice, "OR") };
331
- if (word === "OR")
332
- return { original: slice, op: "or-to-and", replacement: mirrorCase(slice, "AND") };
333
- if (word === "TRUE")
334
- return { original: slice, op: "true-to-false", replacement: mirrorCase(slice, "FALSE") };
335
- return { original: slice, op: "false-to-true", replacement: mirrorCase(slice, "TRUE") };
336
- }
337
- return null;
338
- }
339
- function mirrorCase(source, replacement) {
340
- if (source === source.toUpperCase())
341
- return replacement.toUpperCase();
342
- if (source === source.toLowerCase())
343
- return replacement.toLowerCase();
344
- return replacement[0].toUpperCase() + replacement.slice(1).toLowerCase();
345
- }
346
- function wordEdge(text, start, end, extra) {
347
- const ident = (ch) => ch !== undefined && (/[A-Za-z0-9_]/.test(ch) || extra.includes(ch));
348
- return !ident(text[start - 1]) && !ident(text[end]);
349
- }
350
- function spanIsCode(code, start, length) {
351
- for (let offset = 0; offset < length; offset += 1)
352
- if (!code[start + offset])
353
- return false;
354
- return true;
355
- }
356
- function blocked(text, index, original) {
357
- const prev = text[index - 1] ?? "";
358
- const next = text[index + original.length] ?? "";
359
- // `<-` is a Go channel operator. `=>` is a Rust fat arrow. Neither is a comparison.
360
- if (original === "<" && (next === "<" || next === "=" || next === "-" || prev === "<"))
361
- return true;
362
- if (original === ">" && (next === ">" || next === "=" || prev === ">" || prev === "-" || prev === "="))
363
- return true;
364
- // Both sides must be whitespace. One side still matches `#include <stdio.h>` and `vector<int>`.
365
- if ((original === "<" || original === ">") && !(/\s/.test(prev) && /\s/.test(next)))
366
- return true;
367
- // `<<=`, `>>=`, and `<=>` contain `<=` or `>=` but are not relational operators.
368
- if (original === "<=" && (prev === "<" || next === ">" || next === "="))
369
- return true;
370
- if (original === ">=" && (prev === ">" || next === "="))
371
- return true;
372
- if ((original === "==" || original === "!=" || original === "===" || original === "!==") && next === "=")
373
- return true;
374
- if (original === "&&" && (next === "&" || prev === "&"))
375
- return true;
376
- if (original === "||" && (next === "|" || prev === "|"))
377
- return true;
378
- return false;
379
- }
380
- function codeMask(text, kind) {
381
- const code = new Uint8Array(text.length);
382
- let index = 0;
383
- while (index < text.length) {
384
- const hidden = skipHidden(text, index, kind);
385
- if (hidden > index) {
386
- index = hidden;
387
- continue;
388
- }
389
- code[index] = 1;
390
- index += 1;
391
- }
392
- return code;
393
- }
394
- function skipHidden(text, index, kind) {
395
- if (kind === "python")
396
- return skipPython(text, index);
397
- if (kind === "cobol")
398
- return skipCobol(text, index);
399
- if (kind === "js")
400
- return skipJs(text, index);
401
- if (kind === "swift")
402
- return skipSwift(text, index);
403
- if (kind === "csharp")
404
- return skipCSharp(text, index);
405
- if (kind === "asm-x86_64")
406
- return skipAsm(text, index, { hash: false, semi: true, slash: true });
407
- if (kind === "asm-aarch64")
408
- return skipAsm(text, index, { hash: false, semi: true, slash: true });
409
- if (kind === "asm-riscv")
410
- return skipAsm(text, index, { hash: true, semi: false, slash: true });
411
- if (kind === "go")
412
- return skipC(text, index, { nested: false, rust: false, raw: true });
413
- if (kind === "rust")
414
- return skipC(text, index, { nested: true, rust: true, raw: false });
415
- return skipC(text, index, { nested: false, rust: false, raw: false });
416
- }
417
- function skipJs(text, index) {
418
- const hidden = skipC(text, index, { nested: false, rust: false, raw: false });
419
- if (hidden > index)
420
- return hidden;
421
- if (text[index] === "`")
422
- return skipTemplate(text, index);
423
- return index;
424
- }
425
- function skipSwift(text, index) {
426
- if (text.startsWith('"""', index))
427
- return skipQuotes(text, index, '"""', false);
428
- return skipC(text, index, { nested: false, rust: false, raw: false });
429
- }
430
- function skipCSharp(text, index) {
431
- if (text.startsWith('@"', index) || text.startsWith('$@"', index)) {
432
- const start = text[index] === "@" ? index + 1 : index + 2;
433
- let cursor = start + 1;
434
- while (cursor < text.length) {
435
- if (text.startsWith('""', cursor)) {
436
- cursor += 2;
437
- continue;
438
- }
439
- if (text[cursor] === '"')
440
- return cursor + 1;
441
- cursor += 1;
442
- }
443
- return text.length;
444
- }
445
- return skipC(text, index, { nested: false, rust: false, raw: false });
446
- }
447
- function skipTemplate(text, index) {
448
- let cursor = index + 1;
449
- while (cursor < text.length) {
450
- if (text[cursor] === "\\") {
451
- cursor += 2;
452
- continue;
453
- }
454
- if (text[cursor] === "`")
455
- return cursor + 1;
456
- cursor += 1;
457
- }
458
- return text.length;
459
- }
460
- function skipAsm(text, index, mode) {
461
- if (mode.slash && text.startsWith("//", index))
462
- return eol(text, index);
463
- if (mode.semi && text[index] === ";")
464
- return eol(text, index);
465
- if (mode.hash && text[index] === "#")
466
- return eol(text, index);
467
- if (text.startsWith("/*", index))
468
- return skipBlock(text, index, false);
469
- if (text[index] === '"')
470
- return skipEscaped(text, index, '"');
471
- if (text[index] === "'")
472
- return skipEscaped(text, index, "'");
473
- return index;
474
- }
475
- function skipPython(text, index) {
476
- if (text[index] === "#")
477
- return eol(text, index);
478
- const quoted = pythonStringAt(text, index);
479
- if (!quoted)
480
- return index;
481
- return skipQuotes(text, quoted.quoteAt, quoted.quote, quoted.raw);
482
- }
483
- function pythonStringAt(text, index) {
484
- if (text[index] === "'" || text[index] === '"') {
485
- const quote = text.startsWith(text[index].repeat(3), index) ? text[index].repeat(3) : text[index];
486
- return { quoteAt: index, quote, raw: false };
487
- }
488
- if (index > 0 && /[A-Za-z0-9_]/.test(text[index - 1]))
489
- return null;
490
- if (!/[rRuUbBfF]/.test(text[index]))
491
- return null;
492
- let cursor = index;
493
- let letters = "";
494
- while (cursor < text.length && letters.length < 2 && /[rRuUbBfF]/.test(text[cursor])) {
495
- letters += text[cursor];
496
- cursor += 1;
497
- }
498
- if (text[cursor] !== "'" && text[cursor] !== '"')
499
- return null;
500
- if (!/^(?:[rRfFbBuU]|[rR][fFbB]|[fFbB][rR])$/.test(letters))
501
- return null;
502
- const quote = text.startsWith(text[cursor].repeat(3), cursor) ? text[cursor].repeat(3) : text[cursor];
503
- return { quoteAt: cursor, quote, raw: /[rR]/.test(letters) };
504
- }
505
- function skipCobol(text, index) {
506
- // Column 7 is the fixed-format indicator. `*` and `/` comment out the whole line.
507
- if ((index === 0 || text[index - 1] === "\n") && (text[index + 6] === "*" || text[index + 6] === "/"))
508
- return eol(text, index);
509
- if (text.startsWith("*>", index))
510
- return eol(text, index);
511
- if (text[index] !== '"' && text[index] !== "'")
512
- return index;
513
- const quote = text[index];
514
- let cursor = index + 1;
515
- while (cursor < text.length) {
516
- if (text[cursor] === "\n")
517
- return cursor;
518
- if (text[cursor] === quote) {
519
- if (text[cursor + 1] === quote) {
520
- cursor += 2;
521
- continue;
522
- }
523
- return cursor + 1;
524
- }
525
- cursor += 1;
526
- }
527
- return text.length;
528
- }
529
- function skipC(text, index, mode) {
530
- if (mode.rust) {
531
- const raw = rustRawEnd(text, index);
532
- if (raw !== null)
533
- return raw;
534
- }
535
- if (text.startsWith("//", index))
536
- return eol(text, index);
537
- if (text.startsWith("/*", index))
538
- return skipBlock(text, index, mode.nested);
539
- if (text[index] === '"')
540
- return skipEscaped(text, index, '"');
541
- if (mode.raw && text[index] === "`") {
542
- const end = text.indexOf("`", index + 1);
543
- return end === -1 ? text.length : end + 1;
544
- }
545
- if (text[index] !== "'")
546
- return index;
547
- if (mode.rust)
548
- return rustCharEnd(text, index) ?? index;
549
- return skipEscaped(text, index, "'");
550
- }
551
- function skipBlock(text, index, nested) {
552
- if (!nested) {
553
- const end = text.indexOf("*/", index + 2);
554
- return end === -1 ? text.length : end + 2;
555
- }
556
- let depth = 1;
557
- let cursor = index + 2;
558
- while (cursor < text.length && depth > 0) {
559
- if (text.startsWith("/*", cursor)) {
560
- depth += 1;
561
- cursor += 2;
562
- continue;
563
- }
564
- if (text.startsWith("*/", cursor)) {
565
- depth -= 1;
566
- cursor += 2;
567
- continue;
568
- }
569
- cursor += 1;
570
- }
571
- return cursor;
572
- }
573
- function rustRawEnd(text, index) {
574
- if (text[index] !== "r" && !(text[index] === "b" && text[index + 1] === "r"))
575
- return null;
576
- if (index > 0 && /[A-Za-z0-9_]/.test(text[index - 1]))
577
- return null;
578
- let cursor = text[index] === "b" ? index + 2 : index + 1;
579
- let hashes = 0;
580
- while (text[cursor] === "#") {
581
- hashes += 1;
582
- cursor += 1;
583
- }
584
- if (text[cursor] !== '"')
585
- return null;
586
- cursor += 1;
587
- const closer = `"${"#".repeat(hashes)}`;
588
- const end = text.indexOf(closer, cursor);
589
- return end === -1 ? text.length : end + closer.length;
590
- }
591
- function rustCharEnd(text, index) {
592
- if (text[index + 1] === "\\" && text[index + 2] === "'" && text[index + 3] === "'")
593
- return index + 4;
594
- if (text[index + 1] === "\\") {
595
- let cursor = index + 2;
596
- while (cursor < text.length && cursor < index + 12 && text[cursor] !== "'")
597
- cursor += 1;
598
- return text[cursor] === "'" ? cursor + 1 : null;
599
- }
600
- if (text[index + 2] === "'")
601
- return index + 3;
602
- return null;
603
- }
604
- function skipEscaped(text, index, quote) {
605
- let cursor = index + 1;
606
- while (cursor < text.length) {
607
- if (text[cursor] === "\\") {
608
- cursor += 2;
609
- continue;
610
- }
611
- if (text[cursor] === "\n")
612
- return cursor;
613
- if (text[cursor] === quote)
614
- return cursor + 1;
615
- cursor += 1;
616
- }
617
- return text.length;
618
- }
619
- function skipQuotes(text, quoteAt, quote, raw) {
620
- let cursor = quoteAt + quote.length;
621
- while (cursor < text.length) {
622
- if (!raw && text[cursor] === "\\") {
623
- cursor += 2;
624
- continue;
625
- }
626
- if (text.startsWith(quote, cursor))
627
- return cursor + quote.length;
628
- cursor += 1;
629
- }
630
- return text.length;
631
- }
632
- function eol(text, index) {
633
- let cursor = index;
634
- while (cursor < text.length && text[cursor] !== "\n")
635
- cursor += 1;
636
- return cursor;
637
- }
@@ -1,23 +0,0 @@
1
- const TITLE = /^(?:test|it)\((["'`])(.*?)\1/;
2
- export function titlesInText(text) {
3
- const titles = [];
4
- for (const line of text.split("\n")) {
5
- const match = line.match(TITLE);
6
- if (match)
7
- titles.push(match[2]);
8
- }
9
- return titles;
10
- }
11
- export function duplicateTitles(files) {
12
- const seen = new Map();
13
- for (const file of files) {
14
- for (const title of titlesInText(file.text)) {
15
- const list = seen.get(title) ?? [];
16
- list.push(file.file);
17
- seen.set(title, list);
18
- }
19
- }
20
- return [...seen.entries()]
21
- .filter(([, files]) => files.length > 1)
22
- .map(([title, files]) => ({ title, files }));
23
- }
@@ -1,13 +0,0 @@
1
- /** `--experimental-strip-types` needs this. Older Node is refused before a command runs. */
2
- export const MIN_NODE = "22.6";
3
- export function nodeTooOld(version) {
4
- const match = /^(\d+)\.(\d+)/.exec(version);
5
- const required = `Node ${version} is too old. Probatio needs Node ${MIN_NODE} or newer.`;
6
- if (!match)
7
- return required;
8
- const major = Number(match[1]);
9
- const minor = Number(match[2]);
10
- if (major > 22 || (major === 22 && minor >= 6))
11
- return null;
12
- return required;
13
- }