paperlint 3.0.0 β 3.1.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 +179 -180
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +6 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +22 -3
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +66 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +230 -30
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/new-paper.d.ts +8 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +50 -15
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +30 -8
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +56 -29
- package/dist/paper-files.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/toolchain.d.ts +5 -1
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +22 -5
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.js +1 -1
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +48 -4
- package/docs/rules.md +29 -25
- package/docs/skills.md +80 -0
- package/eslint-rules/paper-typography.mjs +47 -7
- package/eslint-rules/paper-typography.test.mjs +40 -0
- package/eslint-rules/review-frontmatter.schema.json +1 -1
- package/fixtures/toolchain-mirror/catalog.txt +1 -0
- package/fixtures/toolchain-mirror/install-tl +4 -1
- package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
- package/package.json +1 -1
- package/scripts/mutation-batteries.frozen.json +1 -1
- package/src/build-engine.ts +6 -1
- package/src/build.harness.mjs +46 -2
- package/src/build.ts +27 -5
- package/src/cli.harness.mjs +2 -2
- package/src/cli.ts +301 -32
- package/src/engine.harness.mjs +50 -0
- package/src/engine.ts +35 -0
- package/src/init.ts +1 -1
- package/src/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +2 -0
- package/src/new-paper.mutations.mjs +0 -14
- package/src/new-paper.test.ts +172 -1
- package/src/new-paper.ts +84 -17
- package/src/paper-files.test.ts +110 -5
- package/src/paper-files.ts +93 -37
- package/src/paper-settings-commands.test.ts +41 -5
- package/src/parse-args.test.ts +48 -0
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +2 -2
- package/src/structure.ts +1 -1
- package/src/toolchain.harness.mjs +41 -6
- package/src/toolchain.ts +29 -5
- package/src/types.ts +6 -0
- package/src/venue-rules.test.ts +3 -0
- package/src/venue-rules.ts +1 -1
- package/templates/paper/paperlint.json +1 -1
|
@@ -50,6 +50,14 @@
|
|
|
50
50
|
* inserts the zero there. The arXiv lookbehind stays: in `2310.05736` the dot follows a digit.
|
|
51
51
|
* A text macro outside the known set (`\hl{.05}`) is read as a parameter and NOT reported β a
|
|
52
52
|
* miss costs less than a finding on markup.
|
|
53
|
+
*
|
|
54
|
+
* π΄ A NUMBER AFTER `ΒΆ` OR `Β§` IS A DESIGNATOR, NOT A DECIMAL (#102). An auditing standard is cited
|
|
55
|
+
* by paragraph as `ΒΆΒΆ.42`, and `--fix` rewrote it to `ΒΆΒΆ0.42` β a fix that changed a reference.
|
|
56
|
+
* So a lexeme right after the sign (`ΒΆ`, `Β§`, `\P`, `\S`, spaces or `~` between) is silent. The
|
|
57
|
+
* same sentence then goes on `and .44`: a paragraph number continuing the list, or a decimal β
|
|
58
|
+
* the text does not say which. In a paragraph that already cites `ΒΆ.nn` this way, a bare decimal
|
|
59
|
+
* is REPORTED WITHOUT A FIX and the zero is offered as a suggestion: an autofix must never be the
|
|
60
|
+
* one to decide what a number means. A real `.05` in any other paragraph is fixed as before.
|
|
53
61
|
*/
|
|
54
62
|
import { getParser } from "@unified-latex/unified-latex-util-parse";
|
|
55
63
|
|
|
@@ -312,16 +320,43 @@ function sectionWord(context) {
|
|
|
312
320
|
// (2310.05736) out: there the dot follows a digit.
|
|
313
321
|
const BARE_DECIMAL = /(?<![\d.\w])\.\d{2,}\b/g;
|
|
314
322
|
|
|
323
|
+
// A paragraph or section sign β the glyph, or `\P`/`\S` not followed by a letter β and what may
|
|
324
|
+
// sit between it and the number. Both read the SOURCE, because in LaTeX `\P\P.42` reaches the
|
|
325
|
+
// visible runs as `.42` alone: the macros end the run.
|
|
326
|
+
const DESIGNATOR_BEFORE = /(?:[ΒΆΒ§]|\\[PS](?![A-Za-z]))[ \t~]*$/;
|
|
327
|
+
const DESIGNATED = /(?:[ΒΆΒ§]|\\[PS](?![A-Za-z]))[ \t~]*\.\d/;
|
|
328
|
+
|
|
329
|
+
/** The source of the paragraph holding `i`, up to `i`: from the last blank line before it. */
|
|
330
|
+
const paragraphBefore = (src, i) => {
|
|
331
|
+
const head = src.slice(0, i);
|
|
332
|
+
const blank = [...head.matchAll(/\n[ \t]*\n/g)].at(-1);
|
|
333
|
+
return head.slice(blank ? blank.index + blank[0].length : 0);
|
|
334
|
+
};
|
|
335
|
+
|
|
315
336
|
function leadingZero(context) {
|
|
316
|
-
|
|
337
|
+
const sc = context.sourceCode;
|
|
338
|
+
const src = isTex(sc) ? sc.raw : sc.text;
|
|
339
|
+
for (const r of visibleRuns(sc))
|
|
317
340
|
for (const m of r.text.matchAll(BARE_DECIMAL)) {
|
|
318
341
|
const dot = r.offs[m.index];
|
|
319
342
|
if (dot === null || dot === undefined) continue;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
343
|
+
const before = paragraphBefore(src, dot);
|
|
344
|
+
if (DESIGNATOR_BEFORE.test(before)) continue;
|
|
345
|
+
const zero = (f) => f.insertTextBeforeRange([dot, dot], "0");
|
|
346
|
+
at(
|
|
347
|
+
context,
|
|
348
|
+
dot,
|
|
349
|
+
dot + 1,
|
|
350
|
+
DESIGNATED.test(before)
|
|
351
|
+
? {
|
|
352
|
+
messageId: "ambiguous",
|
|
353
|
+
data: { n: m[0] },
|
|
354
|
+
suggest: [
|
|
355
|
+
{ messageId: "insertZero", data: { n: m[0] }, fix: zero },
|
|
356
|
+
],
|
|
357
|
+
}
|
|
358
|
+
: { messageId: "bare", data: { n: m[0] }, fix: zero },
|
|
359
|
+
);
|
|
325
360
|
}
|
|
326
361
|
}
|
|
327
362
|
|
|
@@ -349,13 +384,14 @@ function figureRefStyle(context) {
|
|
|
349
384
|
}
|
|
350
385
|
}
|
|
351
386
|
|
|
352
|
-
const rule = (description, messages, check) => ({
|
|
387
|
+
const rule = (description, messages, check, meta = {}) => ({
|
|
353
388
|
meta: {
|
|
354
389
|
type: "suggestion",
|
|
355
390
|
fixable: "code",
|
|
356
391
|
docs: { description },
|
|
357
392
|
schema: [],
|
|
358
393
|
messages,
|
|
394
|
+
...meta,
|
|
359
395
|
},
|
|
360
396
|
create: (context) => ({ "root:exit": () => check(context) }),
|
|
361
397
|
});
|
|
@@ -373,8 +409,12 @@ export default {
|
|
|
373
409
|
"a decimal below 1 written without its leading zero (IEEE / ISO 80000-1); fixable",
|
|
374
410
|
{
|
|
375
411
|
bare: "`{{n}}` has no leading zero β write `0{{n}}` (IEEE / ISO 80000-1); `paperlint lint --fix` inserts it",
|
|
412
|
+
ambiguous:
|
|
413
|
+
"`{{n}}` in a paragraph that cites `ΒΆ`/`Β§` numbers: a decimal without its leading zero, or another paragraph number? Not fixed automatically β write `0{{n}}` for a decimal, or put the sign before a paragraph number",
|
|
414
|
+
insertZero: "It is a decimal: write `0{{n}}`",
|
|
376
415
|
},
|
|
377
416
|
leadingZero,
|
|
417
|
+
{ hasSuggestions: true },
|
|
378
418
|
),
|
|
379
419
|
"figure-ref-style": rule(
|
|
380
420
|
"`Fig.~\\ref` and `Figure~\\ref` mixed in one document; fixable to the majority form",
|
|
@@ -203,6 +203,46 @@ describe("paper/leading-zero", () => {
|
|
|
203
203
|
});
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
describe("paper/leading-zero β a number after ΒΆ or Β§ is not a decimal (#102)", () => {
|
|
207
|
+
// From a consumer's bibliography: `--fix` rewrote the paragraph number `ΒΆΒΆ.42` to `ΒΆΒΆ0.42`.
|
|
208
|
+
const CITE =
|
|
209
|
+
"PCAOB. \\emph{Audit of Financial Statements}, ΒΆΒΆ.42 (design\neffectiveness) and .44 (operating effectiveness).";
|
|
210
|
+
|
|
211
|
+
it.each([
|
|
212
|
+
["ΒΆΒΆ.42", "Standard 5, ΒΆΒΆ.42 applies."],
|
|
213
|
+
["ΒΆ .42", "Standard 5, ΒΆ .42 applies."],
|
|
214
|
+
["\\P\\P.42", "Standard 5, \\P\\P.42 applies."],
|
|
215
|
+
["Β§.12", "Rule 10b-5, Β§.12 applies."],
|
|
216
|
+
])("SILENT: %s is a designator", async (_, body) => {
|
|
217
|
+
// Guards: the defect β the lexeme after a paragraph/section sign was read as a decimal.
|
|
218
|
+
expect(ids(await lint(doc(body)), "paper/leading-zero")).toEqual([]);
|
|
219
|
+
expect(await fixed(doc(body))).toBe(doc(body));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("π΄ the list continuation `and .44` is reported WITHOUT a fix, with the zero as a suggestion", async () => {
|
|
223
|
+
const res = await lint(doc(CITE));
|
|
224
|
+
const found = ids(res, "paper/leading-zero");
|
|
225
|
+
expect(found.map((m) => [m.line, m.message.slice(0, 5)])).toEqual([
|
|
226
|
+
[4, "`.44`"],
|
|
227
|
+
]);
|
|
228
|
+
expect(found[0].fix).toBeUndefined();
|
|
229
|
+
expect(found[0].suggestions).toHaveLength(1);
|
|
230
|
+
// Guards: --fix changes nothing in an ambiguous paragraph β neither number.
|
|
231
|
+
expect(await fixed(doc(CITE))).toBe(doc(CITE));
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("β¦while a real decimal still fixes, in markdown too, even beside a cited paragraph", async () => {
|
|
235
|
+
const body = `${CITE}\n\nWe use a threshold of .05 throughout.`;
|
|
236
|
+
expect(await fixed(doc(body))).toBe(
|
|
237
|
+
doc(`${CITE}\n\nWe use a threshold of 0.05 throughout.`),
|
|
238
|
+
);
|
|
239
|
+
const md = "See ΒΆΒΆ.42 and .44.\n\nSignificant at p<.05.\n";
|
|
240
|
+
expect(await fixed(md, "paper.md")).toBe(
|
|
241
|
+
"See ΒΆΒΆ.42 and .44.\n\nSignificant at p<0.05.\n",
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
206
246
|
describe("paper/figure-ref-style", () => {
|
|
207
247
|
it("the minority form is reported and rewritten to the majority", async () => {
|
|
208
248
|
const src = doc("Figure~\\ref{a}, Figure~\\ref{b} and Fig.~\\ref{c}.");
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "urn:paperlint:review-frontmatter",
|
|
4
4
|
"title": "paperlint review frontmatter",
|
|
5
|
-
"description": "The YAML frontmatter of a review report under reviews/. A review with no `findings` key is not a findings record and is not checked for findings.
|
|
5
|
+
"description": "The YAML frontmatter of a review report under reviews/. A review with no `findings` key is not a findings record and is not checked for findings.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"findings": {
|
|
@@ -17,11 +17,14 @@ if [ -n "${FAKE_INSTALL_TL_FAIL:-}" ]; then
|
|
|
17
17
|
exit 1
|
|
18
18
|
fi
|
|
19
19
|
mkdir -p "$dir/bin/x86_64-linux"
|
|
20
|
-
for t in pdflatex kpsewhich tlmgr; do
|
|
20
|
+
for t in pdflatex kpsewhich tlmgr fmtutil-sys; do
|
|
21
21
|
cp "$here/stub-$t" "$dir/bin/x86_64-linux/$t"
|
|
22
22
|
chmod +x "$dir/bin/x86_64-linux/$t"
|
|
23
23
|
done
|
|
24
24
|
: > "$dir/files.txt"
|
|
25
25
|
# The real install-tl leaves release-texlive.txt at TEXDIR; stub-tlmgr reads the tree's year there.
|
|
26
26
|
cp "$here/release-texlive.txt" "$dir/release-texlive.txt"
|
|
27
|
+
# The real install-tl, when a mirror fails to serve a package, prints "continuing anyway" and
|
|
28
|
+
# exits as if nothing happened. FAKE_INSTALL_TL_DROP names the package it lost.
|
|
29
|
+
if [ -n "${FAKE_INSTALL_TL_DROP:-}" ]; then echo "$FAKE_INSTALL_TL_DROP" > "$dir/missing-deps.txt"; fi
|
|
27
30
|
echo "fake install-tl: scheme-basic into $dir"
|
|
@@ -9,6 +9,15 @@ while [ $# -gt 0 ]; do
|
|
|
9
9
|
case "$1" in
|
|
10
10
|
--repository) repo=$2; shift 2 ;;
|
|
11
11
|
install) shift; break ;;
|
|
12
|
+
# `check depends` in tlmgr's own layout: every name in missing-deps.txt (what the stub
|
|
13
|
+
# install-tl was told to drop) is a dependency without a package. Not logged: it installs nothing.
|
|
14
|
+
check)
|
|
15
|
+
if [ -s "$tree/missing-deps.txt" ]; then
|
|
16
|
+
echo " DEPENDS WITHOUT PACKAGES:"
|
|
17
|
+
sed 's/$/ in: collection-basic latex-bin/' "$tree/missing-deps.txt"
|
|
18
|
+
exit 2
|
|
19
|
+
fi
|
|
20
|
+
exit 0 ;;
|
|
12
21
|
*) shift ;;
|
|
13
22
|
esac
|
|
14
23
|
done
|
|
@@ -34,6 +43,12 @@ for p in "$@"; do
|
|
|
34
43
|
status=1
|
|
35
44
|
continue
|
|
36
45
|
fi
|
|
46
|
+
# Installing a dropped dependency repairs the tree.
|
|
47
|
+
# Portable on purpose: macOS sed -i takes a backup suffix, GNU sed does not.
|
|
48
|
+
if [ -f "$tree/missing-deps.txt" ]; then
|
|
49
|
+
grep -vx "$p" "$tree/missing-deps.txt" > "$tree/missing-deps.tmp" || true
|
|
50
|
+
mv "$tree/missing-deps.tmp" "$tree/missing-deps.txt"
|
|
51
|
+
fi
|
|
37
52
|
for item in ${line#* }; do
|
|
38
53
|
case "$item" in
|
|
39
54
|
bin:*) : > "$bin/${item#bin:}"; chmod +x "$bin/${item#bin:}" ;;
|
package/package.json
CHANGED
package/src/build-engine.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { homedir } from "node:os";
|
|
|
13
13
|
import { delimiter, join } from "node:path";
|
|
14
14
|
import {
|
|
15
15
|
resolveEngine,
|
|
16
|
+
missingDependencies,
|
|
16
17
|
probeTree,
|
|
17
18
|
supportedPlatform,
|
|
18
19
|
whichOnPath,
|
|
@@ -103,7 +104,11 @@ export function gatherFacts(o: Resolved): EngineFacts {
|
|
|
103
104
|
(tree): TreeProbe => ({
|
|
104
105
|
label: `TeX Live ${tree.year} β paperlint cache (${tree.dir})`,
|
|
105
106
|
bin: tree.bin,
|
|
106
|
-
|
|
107
|
+
// Only a cache tree is asked for its dependencies: its tlmgr writes where we own.
|
|
108
|
+
missing: [
|
|
109
|
+
...probeTree(tree.bin, o.tex.packages, o.run),
|
|
110
|
+
...missingDependencies(tree.bin, o.run),
|
|
111
|
+
],
|
|
107
112
|
}),
|
|
108
113
|
);
|
|
109
114
|
const cache = usableTree(probes, (p) => p.missing.length === 0);
|
package/src/build.harness.mjs
CHANGED
|
@@ -74,6 +74,10 @@ function fakeTex({
|
|
|
74
74
|
log = "",
|
|
75
75
|
missing = false,
|
|
76
76
|
noPdf = false,
|
|
77
|
+
// `noLog`: pdflatex that dies before it opens paper.log (a format it cannot load); what it
|
|
78
|
+
// printed is `stdout`.
|
|
79
|
+
noLog = false,
|
|
80
|
+
stdout = "",
|
|
77
81
|
} = {}) {
|
|
78
82
|
const calls = [];
|
|
79
83
|
const run = (bin, args, opts) => {
|
|
@@ -85,11 +89,11 @@ function fakeTex({
|
|
|
85
89
|
};
|
|
86
90
|
if (bin === "pdflatex") {
|
|
87
91
|
writeFileSync(join(opts.cwd, "paper.aux"), aux);
|
|
88
|
-
writeFileSync(join(opts.cwd, "paper.log"), log);
|
|
92
|
+
if (!noLog) writeFileSync(join(opts.cwd, "paper.log"), log);
|
|
89
93
|
// `noPdf`: what real pdflatex does on a document with no pages β "No pages of output.", exit 0.
|
|
90
94
|
if (exitCode === 0 && !noPdf)
|
|
91
95
|
writeFileSync(join(opts.cwd, "paper.pdf"), "%PDF-fake");
|
|
92
|
-
return { status: exitCode, stdout
|
|
96
|
+
return { status: exitCode, stdout, stderr: "" };
|
|
93
97
|
}
|
|
94
98
|
if (bin === "bibtex") {
|
|
95
99
|
writeFileSync(join(opts.cwd, "paper.bbl"), "\\begin{thebibliography}{1}");
|
|
@@ -395,6 +399,46 @@ try {
|
|
|
395
399
|
check("β¦and says the PDF was removed", /paper\.pdf removed/.test(shown));
|
|
396
400
|
check("a failed pass is not retried", ftex.calls.length === 1);
|
|
397
401
|
|
|
402
|
+
// ββ pdflatex dies before writing a log ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
403
|
+
// Seen 2026-09-26: a TeX Live whose pdflatex format was never built. pdflatex exits 1 having
|
|
404
|
+
// printed the reason, and paper.log is either absent or left over from an earlier build.
|
|
405
|
+
const nolog = paper("nolog", {
|
|
406
|
+
"paper.tex": CLEAN_TEX,
|
|
407
|
+
"paper.log": "./paper.tex:9: OLD ERROR FROM YESTERDAY\nl.9 \\old\n",
|
|
408
|
+
});
|
|
409
|
+
const dieTex = fakeTex({
|
|
410
|
+
exitCode: 1,
|
|
411
|
+
noLog: true,
|
|
412
|
+
stdout:
|
|
413
|
+
"This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2026) (INITEX)\n" +
|
|
414
|
+
"I can't find the format file `pdflatex.fmt'!\n",
|
|
415
|
+
});
|
|
416
|
+
const rn = await buildPaper(nolog, {
|
|
417
|
+
cwd: root,
|
|
418
|
+
readPdf: fakeRead,
|
|
419
|
+
run: dieTex.run,
|
|
420
|
+
env: ENV,
|
|
421
|
+
log: quiet,
|
|
422
|
+
});
|
|
423
|
+
const shownN = formatResult(rn);
|
|
424
|
+
// Guards: the reason pdflatex printed reaches the human when there is no log to quote.
|
|
425
|
+
check(
|
|
426
|
+
"π΄ no log: the failure shows what pdflatex printed",
|
|
427
|
+
rn.status === "failed" && shownN.includes("I can't find the format file"),
|
|
428
|
+
shownN,
|
|
429
|
+
);
|
|
430
|
+
// Guards: a log from an earlier build is never quoted as this build's error.
|
|
431
|
+
check(
|
|
432
|
+
"π΄ no log: a stale paper.log from an earlier build is not quoted",
|
|
433
|
+
!shownN.includes("OLD ERROR FROM YESTERDAY"),
|
|
434
|
+
shownN,
|
|
435
|
+
);
|
|
436
|
+
check(
|
|
437
|
+
"no log: it does not point at a log file that this run did not write",
|
|
438
|
+
!shownN.includes("full log:") && shownN.includes("wrote no paper.log"),
|
|
439
|
+
shownN,
|
|
440
|
+
);
|
|
441
|
+
|
|
398
442
|
// ββ pdflatex not installed ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
399
443
|
const rm = await buildPaper(clean, {
|
|
400
444
|
cwd: root,
|
package/src/build.ts
CHANGED
|
@@ -354,9 +354,8 @@ function latexPass(
|
|
|
354
354
|
const r = ctx.run("pdflatex", pdflatexArgs(final), opts);
|
|
355
355
|
if (r.error) return null;
|
|
356
356
|
const exitCode = r.status ?? 1;
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
);
|
|
357
|
+
const written = readOr(join(ctx.paperDir, `${JOB}.log`), "latin1");
|
|
358
|
+
const log = unwrapLog(written ?? "");
|
|
360
359
|
return {
|
|
361
360
|
step: "latex",
|
|
362
361
|
final,
|
|
@@ -365,10 +364,30 @@ function latexPass(
|
|
|
365
364
|
after: hashes(ctx.paperDir),
|
|
366
365
|
markers: logMarkers(log),
|
|
367
366
|
bib: bibInput(ctx.paperDir),
|
|
368
|
-
errorLines:
|
|
367
|
+
errorLines:
|
|
368
|
+
exitCode === 0
|
|
369
|
+
? []
|
|
370
|
+
: written === null
|
|
371
|
+
? printed(r)
|
|
372
|
+
: errorExcerpt(log).map(fromLatin1),
|
|
369
373
|
};
|
|
370
374
|
}
|
|
371
375
|
|
|
376
|
+
/**
|
|
377
|
+
* The last lines pdflatex printed, for the run that wrote no log β it died before opening one (a
|
|
378
|
+
* format it cannot load, a missing TeX Live file). Marked by NO_LOG, which the report keys on.
|
|
379
|
+
*/
|
|
380
|
+
function printed(r: { stdout?: unknown; stderr?: unknown }): string[] {
|
|
381
|
+
const lines = fromLatin1(
|
|
382
|
+
`${String(r.stdout ?? "")}\n${String(r.stderr ?? "")}`,
|
|
383
|
+
)
|
|
384
|
+
.split("\n")
|
|
385
|
+
.filter((l) => l.trim());
|
|
386
|
+
return [NO_LOG, ...lines.slice(-8).map((l) => ` ${l}`)];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const NO_LOG = `pdflatex wrote no ${JOB}.log β the last lines it printed:`;
|
|
390
|
+
|
|
372
391
|
/** One bibtex run, on the input the `.aux` names BEFORE it runs, or null when it could not start. */
|
|
373
392
|
function bibtexPass(ctx: BuildContext, opts: SpawnOptions): Observation | null {
|
|
374
393
|
const before = hashes(ctx.paperDir);
|
|
@@ -395,6 +414,9 @@ export function compile(ctx: BuildContext): {
|
|
|
395
414
|
} {
|
|
396
415
|
const history: Observation[] = [];
|
|
397
416
|
const opts = spawnOptions(ctx);
|
|
417
|
+
// Every log this loop reads is one its own pdflatex wrote. A pdflatex that dies before opening
|
|
418
|
+
// paper.log would otherwise have an earlier build's error quoted as this one's.
|
|
419
|
+
rmSync(join(ctx.paperDir, `${JOB}.log`), { force: true });
|
|
398
420
|
const runs = { latex: 0, bibtex: 0 };
|
|
399
421
|
for (;;) {
|
|
400
422
|
const step = nextStep(summarize(history));
|
|
@@ -450,7 +472,7 @@ export const compileStep: BuildStep = {
|
|
|
450
472
|
...(end.lines.length
|
|
451
473
|
? end.lines
|
|
452
474
|
: ["(no error line found in the log)"]),
|
|
453
|
-
`full log: ${log}
|
|
475
|
+
...(end.lines[0] === NO_LOG ? [] : [`full log: ${log}`]),
|
|
454
476
|
],
|
|
455
477
|
};
|
|
456
478
|
}
|
package/src/cli.harness.mjs
CHANGED
|
@@ -996,7 +996,7 @@ check(
|
|
|
996
996
|
);
|
|
997
997
|
check(
|
|
998
998
|
"and the finding is printed before the ESLint report, with its consequence",
|
|
999
|
-
/missing `PIPELINE-STATUS\.md`/.test(r.out) && /
|
|
999
|
+
/missing `PIPELINE-STATUS\.md`/.test(r.out) && /are skipped/.test(r.out),
|
|
1000
1000
|
);
|
|
1001
1001
|
check(
|
|
1002
1002
|
'and it does NOT print "no findings" over something that was found',
|
|
@@ -1783,7 +1783,7 @@ console.log(
|
|
|
1783
1783
|
"π΄ an optional rule turned on for a glob that reaches no paper.tex FAILS the run β not a green zero",
|
|
1784
1784
|
silent.code === 1 &&
|
|
1785
1785
|
silent.out.includes(
|
|
1786
|
-
'pdf/last-page-balance is turned on in "rules", but no
|
|
1786
|
+
'pdf/last-page-balance is turned on in "rules", but no paper.tex of the project gets it',
|
|
1787
1787
|
),
|
|
1788
1788
|
);
|
|
1789
1789
|
settings({
|