paperlint 8.0.0 → 9.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/CLAUDE.md +9 -25
- package/CONTRIBUTING.md +5 -4
- package/README.md +4 -3
- package/dist/adapters/bibtex/index.d.ts +3 -0
- package/dist/adapters/bibtex/index.d.ts.map +1 -0
- package/dist/adapters/bibtex/index.js +103 -0
- package/dist/adapters/bibtex/index.js.map +1 -0
- package/dist/adapters/latex/filecontents.d.ts +33 -0
- package/dist/adapters/latex/filecontents.d.ts.map +1 -0
- package/dist/adapters/latex/filecontents.js +65 -0
- package/dist/adapters/latex/filecontents.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +3 -3
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +18 -10
- package/dist/adapters/latex/includes.js.map +1 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +2 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +12 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +39 -0
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts +7 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +55 -6
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +9 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +27 -24
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/sources-record/index.d.ts +9 -0
- package/dist/adapters/sources-record/index.d.ts.map +1 -0
- package/dist/adapters/sources-record/index.js +68 -0
- package/dist/adapters/sources-record/index.js.map +1 -0
- package/dist/adapters/tex-output/blg.d.ts +9 -0
- package/dist/adapters/tex-output/blg.d.ts.map +1 -0
- package/dist/adapters/tex-output/blg.js +20 -0
- package/dist/adapters/tex-output/blg.js.map +1 -0
- package/dist/adapters/tex-output/fls.d.ts +8 -0
- package/dist/adapters/tex-output/fls.d.ts.map +1 -0
- package/dist/adapters/tex-output/fls.js +16 -0
- package/dist/adapters/tex-output/fls.js.map +1 -0
- package/dist/adapters/tex-output/index.d.ts +7 -0
- package/dist/adapters/tex-output/index.d.ts.map +1 -0
- package/dist/adapters/tex-output/index.js +7 -0
- package/dist/adapters/tex-output/index.js.map +1 -0
- package/dist/bib-rules.d.ts +47 -0
- package/dist/bib-rules.d.ts.map +1 -0
- package/dist/bib-rules.js +127 -0
- package/dist/bib-rules.js.map +1 -0
- package/dist/build.d.ts +29 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +40 -24
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +6 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -26
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +1 -10
- package/dist/domain/paper-source.d.ts.map +1 -1
- package/dist/domain/paper-source.js +34 -24
- package/dist/domain/paper-source.js.map +1 -1
- package/dist/domain/paper-sources.d.ts +72 -0
- package/dist/domain/paper-sources.d.ts.map +1 -0
- package/dist/domain/paper-sources.js +18 -0
- package/dist/domain/paper-sources.js.map +1 -0
- package/dist/domain/sources-record.d.ts +102 -0
- package/dist/domain/sources-record.d.ts.map +1 -0
- package/dist/domain/sources-record.js +38 -0
- package/dist/domain/sources-record.js.map +1 -0
- package/dist/domain/tex-run.d.ts +25 -0
- package/dist/domain/tex-run.d.ts.map +1 -0
- package/dist/domain/tex-run.js +79 -0
- package/dist/domain/tex-run.js.map +1 -0
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +4 -3
- package/dist/eslint-rules/paper-context.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +0 -9
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +12 -5
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/latex-log.d.ts +10 -0
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +10 -0
- package/dist/latex-log.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -0
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js.map +1 -1
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +3 -2
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-includes.d.ts +4 -7
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +29 -21
- package/dist/paper-includes.js.map +1 -1
- package/dist/paper-prose.d.ts +2 -2
- package/dist/paper-record.d.ts +25 -0
- package/dist/paper-record.d.ts.map +1 -0
- package/dist/paper-record.js +39 -0
- package/dist/paper-record.js.map +1 -0
- package/dist/paper-sources.d.ts +66 -0
- package/dist/paper-sources.d.ts.map +1 -0
- package/dist/paper-sources.js +92 -0
- package/dist/paper-sources.js.map +1 -0
- package/dist/ports/bib-reader.d.ts +18 -0
- package/dist/ports/bib-reader.d.ts.map +1 -0
- package/dist/ports/bib-reader.js +2 -0
- package/dist/ports/bib-reader.js.map +1 -0
- package/dist/ports/check-references.d.ts +9 -4
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/latex.d.ts +15 -0
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/ports/sources-codec.d.ts +11 -0
- package/dist/ports/sources-codec.d.ts.map +1 -0
- package/dist/ports/sources-codec.js +2 -0
- package/dist/ports/sources-codec.js.map +1 -0
- package/dist/ports/tex-output.d.ts +17 -0
- package/dist/ports/tex-output.d.ts.map +1 -0
- package/dist/ports/tex-output.js +2 -0
- package/dist/ports/tex-output.js.map +1 -0
- package/dist/recorded-bibliography.d.ts +89 -0
- package/dist/recorded-bibliography.d.ts.map +1 -0
- package/dist/recorded-bibliography.js +136 -0
- package/dist/recorded-bibliography.js.map +1 -0
- package/dist/reference-rules.d.ts +5 -5
- package/dist/reference-rules.d.ts.map +1 -1
- package/dist/reference-rules.js +150 -77
- package/dist/reference-rules.js.map +1 -1
- package/dist/references.d.ts +71 -16
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +110 -26
- package/dist/references.js.map +1 -1
- package/dist/register-bands.d.ts +3 -3
- package/dist/register-bands.d.ts.map +1 -1
- package/dist/register-bands.js.map +1 -1
- package/dist/register.d.ts +2 -2
- package/dist/sources-record.d.ts +38 -0
- package/dist/sources-record.d.ts.map +1 -0
- package/dist/sources-record.js +123 -0
- package/dist/sources-record.js.map +1 -0
- package/dist/sources-rules.d.ts +44 -0
- package/dist/sources-rules.d.ts.map +1 -0
- package/dist/sources-rules.js +76 -0
- package/dist/sources-rules.js.map +1 -0
- package/dist/tex-paper.d.ts +10 -15
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +24 -40
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +3 -3
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js.map +1 -1
- package/docs/configuration.md +12 -4
- package/docs/design/paper-sources.md +197 -0
- package/docs/package-shape-options.md +1 -1
- package/docs/references.md +34 -8
- package/docs/rules/bib/reachable-entry.md +64 -0
- package/docs/rules/paper/sources-fresh.md +81 -0
- package/docs/rules.md +62 -57
- package/eslint-rules/paper-context.ts +4 -3
- package/eslint-rules/paper-typography.test.mjs +12 -72
- package/eslint-rules/paper-typography.ts +13 -6
- package/eslint.config.mjs +22 -0
- package/fixtures/build-e2e/aidc/paper.tex +1 -1
- package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
- package/fixtures/build-e2e/anon-author/paper.tex +1 -1
- package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
- package/fixtures/build-e2e/anon-href/paper.tex +1 -1
- package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
- package/fixtures/paper-sources/p1/paper.tex +30 -0
- package/fixtures/paper-sources/p1/refs.bib +6 -0
- package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
- package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
- package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
- package/fixtures/paper-sources/p1/tex-truth.json +37 -0
- package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
- package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
- package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
- package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
- package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
- package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
- package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
- package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
- package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
- package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
- package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
- package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
- package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
- package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
- package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
- package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
- package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
- package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
- package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
- package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
- package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
- package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
- package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
- package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
- package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
- package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
- package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
- package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
- package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
- package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
- package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
- package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
- package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
- package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
- package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
- package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
- package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
- package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
- package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
- package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
- package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
- package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
- package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
- package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
- package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
- package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
- package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
- package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
- package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
- package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
- package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
- package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
- package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
- package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
- package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
- package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
- package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
- package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
- package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
- package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
- package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
- package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
- package/package.json +2 -10
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +19 -10
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/grade-paper-writing/SKILL.md +4 -3
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
- package/skills/grade-paper-writing/prose-lint.mjs +11 -2
- package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
- package/skills/verify-citations/SKILL.md +7 -4
- package/skills/verify-citations/SKILL.md.spec.ts +6 -3
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -19
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
- package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
- package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
- package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
- package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
- package/src/adapters/bibtex/index.test.ts +163 -0
- package/src/adapters/bibtex/index.ts +155 -0
- package/src/adapters/latex/filecontents.ts +114 -0
- package/src/adapters/latex/includes.ts +18 -10
- package/src/adapters/latex/index.test.ts +73 -0
- package/src/adapters/latex/index.ts +2 -0
- package/src/adapters/latex/nodes.ts +64 -0
- package/src/adapters/latex/parse.test.ts +38 -0
- package/src/adapters/latex/parse.ts +67 -6
- package/src/adapters/memory/index.ts +17 -0
- package/src/adapters/references/index.test.ts +75 -21
- package/src/adapters/references/index.ts +37 -32
- package/src/adapters/sources-record/index.test.ts +87 -0
- package/src/adapters/sources-record/index.ts +82 -0
- package/src/adapters/tex-output/blg.ts +27 -0
- package/src/adapters/tex-output/fls.ts +24 -0
- package/src/adapters/tex-output/index.test.ts +171 -0
- package/src/adapters/tex-output/index.ts +12 -0
- package/src/bib-rules.test.ts +356 -0
- package/src/bib-rules.ts +228 -0
- package/src/bibliography-at.test.ts +286 -0
- package/src/build.test.ts +210 -0
- package/src/build.ts +100 -35
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +20 -10
- package/src/cli.test.ts +14 -10
- package/src/cli.ts +37 -28
- package/src/domain/paper-source.test.ts +58 -20
- package/src/domain/paper-source.ts +38 -42
- package/src/domain/paper-sources.ts +94 -0
- package/src/domain/sources-record.test.ts +71 -0
- package/src/domain/sources-record.ts +136 -0
- package/src/domain/tex-run.test.ts +228 -0
- package/src/domain/tex-run.ts +139 -0
- package/src/heading-case.test.ts +24 -10
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +3 -2
- package/src/new-paper.test.ts +9 -5
- package/src/package-dirs.ts +3 -2
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +34 -5
- package/src/paper-prose.ts +2 -2
- package/src/paper-record.test.ts +173 -0
- package/src/paper-record.ts +66 -0
- package/src/paper-settings-commands.test.ts +5 -2
- package/src/paper-sources.ts +151 -0
- package/src/ports/bib-reader.ts +27 -0
- package/src/ports/check-references.ts +7 -3
- package/src/ports/latex.ts +19 -0
- package/src/ports/sources-codec.ts +11 -0
- package/src/ports/tex-output.ts +17 -0
- package/src/recorded-bibliography.ts +297 -0
- package/src/reference-rules.ts +217 -104
- package/src/references.test.ts +520 -177
- package/src/references.ts +185 -37
- package/src/register-bands.test.ts +5 -1
- package/src/register-bands.ts +3 -5
- package/src/register.test.ts +6 -1
- package/src/register.ts +2 -2
- package/src/rule-cases.test.ts +68 -12
- package/src/rule-docs.test.ts +0 -1
- package/src/sources-record.test.ts +308 -0
- package/src/sources-record.ts +231 -0
- package/src/sources-rules.test.ts +152 -0
- package/src/sources-rules.ts +117 -0
- package/src/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
- package/dist/eslint-rules/bib-reachable-entry.js +0 -75
- package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
- package/eslint-rules/bib-reachable-entry.ts +0 -76
package/src/build.test.ts
CHANGED
|
@@ -22,7 +22,14 @@ import {
|
|
|
22
22
|
type PaperFacts,
|
|
23
23
|
} from "./build.ts";
|
|
24
24
|
import type { Geometry } from "./domain/geometry.ts";
|
|
25
|
+
import { bibReader } from "./adapters/bibtex/index.ts";
|
|
26
|
+
import { nodeFiles } from "./adapters/node/index.ts";
|
|
27
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
28
|
+
import { sha256Hex } from "./domain/sha256.ts";
|
|
29
|
+
import { serializeSourcesRecord } from "./domain/sources-record.ts";
|
|
30
|
+
import { bibliographyReader } from "./references.ts";
|
|
25
31
|
import type { PdfReader } from "./pdf-facts.ts";
|
|
32
|
+
import type { Recorded, TexRun } from "./sources-record.ts";
|
|
26
33
|
|
|
27
34
|
const root = useTempDir("paperlint-build-test-");
|
|
28
35
|
const TEX = "\\documentclass{article}\\begin{document}x\\end{document}";
|
|
@@ -44,6 +51,8 @@ interface Fake {
|
|
|
44
51
|
noLog?: boolean;
|
|
45
52
|
/** Each pass writes a different .aux, so the loop never settles. */
|
|
46
53
|
churn?: boolean;
|
|
54
|
+
/** What pdflatex's `-recorder` file holds after pass `n`; without it, pdflatex records nothing. */
|
|
55
|
+
fls?: (pass: number) => string;
|
|
47
56
|
}
|
|
48
57
|
/** A pdflatex pass: writes the .aux, the log unless it dies first, and a PDF when it exits 0. */
|
|
49
58
|
function latexPass(f: Fake, cwd: string, pass: number) {
|
|
@@ -51,6 +60,7 @@ function latexPass(f: Fake, cwd: string, pass: number) {
|
|
|
51
60
|
const aux = f.churn ? `\\relax % ${String(pass)}\n` : (f.aux ?? "\\relax\n");
|
|
52
61
|
writeFileSync(join(cwd, "paper.aux"), aux);
|
|
53
62
|
if (!f.noLog) writeFileSync(join(cwd, "paper.log"), f.log ?? "");
|
|
63
|
+
if (f.fls) writeFileSync(join(cwd, "paper.fls"), f.fls(pass));
|
|
54
64
|
if (status === 0) writeFileSync(join(cwd, "paper.pdf"), "%PDF-fake");
|
|
55
65
|
return f.noLog ? { status } : { status, stdout: "", stderr: "" };
|
|
56
66
|
}
|
|
@@ -263,6 +273,55 @@ test("compile: warnings the final log still reports are named in the note", asyn
|
|
|
263
273
|
assert.match(r.notes?.[0] ?? "", /— ⚠️ the final log still reports /);
|
|
264
274
|
});
|
|
265
275
|
|
|
276
|
+
test("references: without a reader the step says it was not wired; with one and no checker, the record says not checked", async () => {
|
|
277
|
+
const text = {
|
|
278
|
+
"paper.tex":
|
|
279
|
+
"\\documentclass{article}\\begin{document}x\\bibliography{refs}\\end{document}",
|
|
280
|
+
"refs.bib": "@misc{a, url = {https://example.org}}\n",
|
|
281
|
+
};
|
|
282
|
+
// What a build of this paper records: TeX read paper.tex, bibtex opened refs.bib and typeset `a`.
|
|
283
|
+
const digest = (s: string) => sha256Hex(new TextEncoder().encode(s));
|
|
284
|
+
const bib = {
|
|
285
|
+
...text,
|
|
286
|
+
"_build/sources.json": serializeSourcesRecord({
|
|
287
|
+
schema: 1,
|
|
288
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
289
|
+
written: [],
|
|
290
|
+
bibdata: ["refs"],
|
|
291
|
+
bibtex: {
|
|
292
|
+
ran: true,
|
|
293
|
+
databases: ["refs.bib"],
|
|
294
|
+
keys: ["a"],
|
|
295
|
+
exit: 0,
|
|
296
|
+
errors: [],
|
|
297
|
+
},
|
|
298
|
+
sha256: {
|
|
299
|
+
"paper.tex": digest(text["paper.tex"]),
|
|
300
|
+
"refs.bib": digest(text["refs.bib"]),
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
303
|
+
};
|
|
304
|
+
const unwired = await build(paper(bib));
|
|
305
|
+
assert.ok(
|
|
306
|
+
unwired.r.notes?.includes(
|
|
307
|
+
"references NOT checked — no bibliography reader was wired into this build; lint will say so",
|
|
308
|
+
),
|
|
309
|
+
JSON.stringify(unwired.r.notes),
|
|
310
|
+
);
|
|
311
|
+
const readBibliography = bibliographyReader({
|
|
312
|
+
files: nodeFiles,
|
|
313
|
+
codec: sourcesCodec,
|
|
314
|
+
bib: bibReader,
|
|
315
|
+
});
|
|
316
|
+
const wired = await build(paper(bib), {}, { readBibliography });
|
|
317
|
+
assert.ok(
|
|
318
|
+
wired.r.notes?.includes(
|
|
319
|
+
"references NOT checked — no reference checker was wired into this build; lint will say so",
|
|
320
|
+
),
|
|
321
|
+
JSON.stringify(wired.r.notes),
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
266
325
|
test("measure: a review build and a stub last page are named; a measured geometry adds nothing", async () => {
|
|
267
326
|
const review = await build(paper(), {}, { readPdf: reader(page(20, 60)) });
|
|
268
327
|
assert.match(
|
|
@@ -382,3 +441,154 @@ test("a failed result without its lines still says what failed", () => {
|
|
|
382
441
|
].join("\n"),
|
|
383
442
|
);
|
|
384
443
|
});
|
|
444
|
+
|
|
445
|
+
// ── the record of the build's sources ────────────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
/** What a recorder was handed, for each paper it was asked about. */
|
|
448
|
+
function spyRecorder(answer: Recorded) {
|
|
449
|
+
const asked: { dir: string; run: TexRun }[] = [];
|
|
450
|
+
return {
|
|
451
|
+
asked,
|
|
452
|
+
recordSources: (dir: string, run: TexRun): Recorded => {
|
|
453
|
+
asked.push({ dir, run });
|
|
454
|
+
return answer;
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const RECORDED: Recorded = {
|
|
459
|
+
kind: "recorded",
|
|
460
|
+
path: "/p/_build/sources.json",
|
|
461
|
+
record: {
|
|
462
|
+
schema: 1,
|
|
463
|
+
inputs: [{ path: "paper.tex", role: "body" }],
|
|
464
|
+
written: [],
|
|
465
|
+
bibdata: [],
|
|
466
|
+
bibtex: { ran: false },
|
|
467
|
+
sha256: {},
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
test("sources: every pdflatex pass runs with -recorder, so TeX itself says which files it read", async () => {
|
|
472
|
+
const args: (readonly string[])[] = [];
|
|
473
|
+
const run = fakeRun({});
|
|
474
|
+
await build(
|
|
475
|
+
paper(),
|
|
476
|
+
{},
|
|
477
|
+
{
|
|
478
|
+
run: (bin, a, opts) => {
|
|
479
|
+
if (bin === "pdflatex") args.push(a);
|
|
480
|
+
return run(bin, a, opts);
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
assert.ok(args.length >= 2);
|
|
485
|
+
assert.ok(
|
|
486
|
+
args.every((a) => a.includes("-recorder")),
|
|
487
|
+
JSON.stringify(args),
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("sources: the recorder is handed the .fls of every pass, in order, and bibtex's exit when it ran", async () => {
|
|
492
|
+
const spy = spyRecorder(RECORDED);
|
|
493
|
+
const { r } = await build(
|
|
494
|
+
paper({ "paper.tex": TEX, "refs.bib": "@misc{k}\n" }),
|
|
495
|
+
{
|
|
496
|
+
aux: "\\citation{k}\n\\bibstyle{plain}\n\\bibdata{refs}\n",
|
|
497
|
+
fls: (pass) => `pass ${String(pass)}`,
|
|
498
|
+
},
|
|
499
|
+
{ recordSources: spy.recordSources },
|
|
500
|
+
);
|
|
501
|
+
assert.equal(r.status, "built");
|
|
502
|
+
const [only] = spy.asked;
|
|
503
|
+
assert.equal(spy.asked.length, 1);
|
|
504
|
+
assert.match(only?.dir ?? "", /\/p\d+$/);
|
|
505
|
+
const passes = only?.run.fls ?? [];
|
|
506
|
+
assert.ok(passes.length >= 2);
|
|
507
|
+
assert.deepEqual(
|
|
508
|
+
passes,
|
|
509
|
+
passes.map((_, i) => `pass ${String(i + 1)}`),
|
|
510
|
+
);
|
|
511
|
+
assert.equal(only?.run.bibtexExit, 0);
|
|
512
|
+
assert.match(
|
|
513
|
+
r.notes?.[0] ?? "",
|
|
514
|
+
/bibtex runs?; sources: 1 file read → _build\/sources\.json$/,
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("sources: a build that ran no bibtex says so to the recorder", async () => {
|
|
519
|
+
const spy = spyRecorder(RECORDED);
|
|
520
|
+
await build(
|
|
521
|
+
paper(),
|
|
522
|
+
{ fls: () => "x" },
|
|
523
|
+
{ recordSources: spy.recordSources },
|
|
524
|
+
);
|
|
525
|
+
assert.equal(spy.asked[0]?.run.bibtexExit, null);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("sources: a paper.fls an earlier build left is not this build's: pdflatex that wrote none hands the recorder none", async () => {
|
|
529
|
+
const spy = spyRecorder(RECORDED);
|
|
530
|
+
const dir = paper({ "paper.tex": TEX, "paper.fls": "INPUT stale.tex\n" });
|
|
531
|
+
await build(dir, {}, { recordSources: spy.recordSources });
|
|
532
|
+
assert.deepEqual(spy.asked[0]?.run.fls, []);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("sources: without a recorder, or when it records nothing, the build still builds and the note says it did not record", async () => {
|
|
536
|
+
const unwired = await build(paper());
|
|
537
|
+
assert.equal(unwired.r.status, "built");
|
|
538
|
+
assert.ok(
|
|
539
|
+
unwired.r.notes?.[0]?.endsWith(
|
|
540
|
+
"; sources NOT recorded — no recorder was wired into this build; lint will say so",
|
|
541
|
+
),
|
|
542
|
+
JSON.stringify(unwired.r.notes),
|
|
543
|
+
);
|
|
544
|
+
const empty = await build(
|
|
545
|
+
paper(),
|
|
546
|
+
{},
|
|
547
|
+
{
|
|
548
|
+
recordSources: spyRecorder({
|
|
549
|
+
kind: "not-recorded",
|
|
550
|
+
why: "pdflatex wrote no paper.fls — nothing to record",
|
|
551
|
+
}).recordSources,
|
|
552
|
+
},
|
|
553
|
+
);
|
|
554
|
+
assert.equal(empty.r.status, "built");
|
|
555
|
+
assert.ok(
|
|
556
|
+
empty.r.notes?.[0]?.endsWith(
|
|
557
|
+
"; sources NOT recorded — pdflatex wrote no paper.fls — nothing to record; lint will say so",
|
|
558
|
+
),
|
|
559
|
+
JSON.stringify(empty.r.notes),
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("sources: the note counts the files the record lists", async () => {
|
|
564
|
+
const two: Recorded = {
|
|
565
|
+
...RECORDED,
|
|
566
|
+
record: {
|
|
567
|
+
...RECORDED.record,
|
|
568
|
+
inputs: [
|
|
569
|
+
{ path: "paper.tex", role: "body" },
|
|
570
|
+
{ path: "sections/intro.tex", role: "body" },
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
const { r } = await build(
|
|
575
|
+
paper(),
|
|
576
|
+
{},
|
|
577
|
+
{ recordSources: spyRecorder(two).recordSources },
|
|
578
|
+
);
|
|
579
|
+
assert.ok(
|
|
580
|
+
r.notes?.[0]?.endsWith("; sources: 2 files read → _build/sources.json"),
|
|
581
|
+
JSON.stringify(r.notes),
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
test("sources: a build that fails records nothing", async () => {
|
|
586
|
+
const spy = spyRecorder(RECORDED);
|
|
587
|
+
const { r } = await build(
|
|
588
|
+
paper(),
|
|
589
|
+
{ latex: 1 },
|
|
590
|
+
{ recordSources: spy.recordSources },
|
|
591
|
+
);
|
|
592
|
+
assert.equal(r.status, "failed");
|
|
593
|
+
assert.deepEqual(spy.asked, []);
|
|
594
|
+
});
|
package/src/build.ts
CHANGED
|
@@ -66,7 +66,19 @@ import type { Runner } from "./engine.ts";
|
|
|
66
66
|
import type { Files } from "./ports/files.ts";
|
|
67
67
|
import type { MeasureGeometry } from "./ports/measure-geometry.ts";
|
|
68
68
|
import type { CheckReferences } from "./ports/check-references.ts";
|
|
69
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
notWiredRecord,
|
|
71
|
+
recordingNote,
|
|
72
|
+
type RecordSources,
|
|
73
|
+
type TexRun,
|
|
74
|
+
} from "./sources-record.ts";
|
|
75
|
+
import { JOBNAME } from "./domain/sources-record.ts";
|
|
76
|
+
import {
|
|
77
|
+
notWiredBibliography,
|
|
78
|
+
recordReferences,
|
|
79
|
+
REFERENCES_FILE,
|
|
80
|
+
type ReadBibliography,
|
|
81
|
+
} from "./references.ts";
|
|
70
82
|
import {
|
|
71
83
|
auxBib,
|
|
72
84
|
bibtexExcerpt,
|
|
@@ -93,7 +105,7 @@ export const PAPER_MARKERS = ["PIPELINE-STATUS.md", "paper.tex", CONFIG_FILE];
|
|
|
93
105
|
|
|
94
106
|
/** The source paperlint compiles, and the job name every output file carries. */
|
|
95
107
|
export const MAIN = "paper.tex";
|
|
96
|
-
export const JOB =
|
|
108
|
+
export const JOB = JOBNAME;
|
|
97
109
|
|
|
98
110
|
/**
|
|
99
111
|
* Scripts that `paperlint build` USED to run. Their presence is reported and nothing more: running a
|
|
@@ -101,11 +113,16 @@ export const JOB = "paper";
|
|
|
101
113
|
*/
|
|
102
114
|
export const IGNORED_SCRIPTS = ["build.sh", "repro/build-submission.sh"];
|
|
103
115
|
|
|
104
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* The pdflatex flags: never stop for input, stop at the first error, say file:line, and write
|
|
118
|
+
* `paper.fls` — every file the pass opened, which is how the build learns what the paper is made of
|
|
119
|
+
* (`sources-record.ts`) without reading its TeX.
|
|
120
|
+
*/
|
|
105
121
|
export const PDFLATEX_FLAGS = [
|
|
106
122
|
"-interaction=nonstopmode",
|
|
107
123
|
"-halt-on-error",
|
|
108
124
|
"-file-line-error",
|
|
125
|
+
"-recorder",
|
|
109
126
|
];
|
|
110
127
|
|
|
111
128
|
/** Everything the steps may know about a paper — parsed from it, not configured. */
|
|
@@ -138,6 +155,16 @@ export interface BuildContext {
|
|
|
138
155
|
readonly files: Files;
|
|
139
156
|
/** The online reference checks; the CLI wires the real ones, a test passes a function. */
|
|
140
157
|
readonly checkReferences: CheckReferences;
|
|
158
|
+
/**
|
|
159
|
+
* The databases the build's bibtex opened (`readBibliography`, from `_build/sources.json`); the CLI
|
|
160
|
+
* wires the real reader.
|
|
161
|
+
*/
|
|
162
|
+
readonly readBibliography: ReadBibliography;
|
|
163
|
+
/**
|
|
164
|
+
* Writes `_build/sources.json` from TeX's own files after a compile; the CLI wires the real one.
|
|
165
|
+
* `buildPapers` hands the step `notWiredRecord` when it is given none: it records nothing and says so.
|
|
166
|
+
*/
|
|
167
|
+
readonly recordSources: RecordSources;
|
|
141
168
|
}
|
|
142
169
|
|
|
143
170
|
export type StepOutcome =
|
|
@@ -265,7 +292,6 @@ export const inputsStep: BuildStep = {
|
|
|
265
292
|
yes: true,
|
|
266
293
|
why: `TEXINPUTS += the paper's directory, then ${texInputsDir()}`,
|
|
267
294
|
}),
|
|
268
|
-
// The same search path the lint rules read includes through: one answer to which file is meant.
|
|
269
295
|
// A pass runs IN the paper's directory, so that directory is `.` here. TeX names a file it finds
|
|
270
296
|
// through an absolute entry by that absolute path: with the directory itself on TEXINPUTS, the
|
|
271
297
|
// `broken` build fixture's log said `/tmp/…/paper.tex:6:` instead of `./paper.tex:6:` (measured).
|
|
@@ -374,6 +400,7 @@ function latexPass(
|
|
|
374
400
|
after: hashes(ctx.paperDir),
|
|
375
401
|
markers: logMarkers(log),
|
|
376
402
|
bib: bibInput(ctx.paperDir),
|
|
403
|
+
fls: readOr(join(ctx.paperDir, `${JOB}.fls`), "utf8"),
|
|
377
404
|
errorLines:
|
|
378
405
|
exitCode === 0
|
|
379
406
|
? []
|
|
@@ -420,28 +447,47 @@ function bibtexPass(ctx: BuildContext, opts: SpawnOptions): Observation | null {
|
|
|
420
447
|
};
|
|
421
448
|
}
|
|
422
449
|
|
|
450
|
+
/** What the loop's passes left for the record of the build's sources (`sources-record.ts`). */
|
|
451
|
+
function runOf(history: readonly Observation[]): TexRun {
|
|
452
|
+
const bibtexRuns = history.flatMap((o) =>
|
|
453
|
+
o.step === "bibtex" ? [o.exitCode] : [],
|
|
454
|
+
);
|
|
455
|
+
return {
|
|
456
|
+
fls: history.flatMap((o) =>
|
|
457
|
+
o.step === "latex" && o.fls !== null ? [o.fls] : [],
|
|
458
|
+
),
|
|
459
|
+
bibtexExit: bibtexRuns.at(-1) ?? null,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
423
463
|
/** Run the loop until `nextStep` says done or fail. */
|
|
424
464
|
export function compile(ctx: BuildContext): {
|
|
425
465
|
end: Terminal;
|
|
426
466
|
latex: number;
|
|
427
467
|
bibtex: number;
|
|
468
|
+
run: TexRun;
|
|
428
469
|
} {
|
|
429
470
|
const history: Observation[] = [];
|
|
430
471
|
const opts = spawnOptions(ctx);
|
|
431
|
-
// Every
|
|
432
|
-
// paper.log would otherwise have an earlier build's error quoted as this one's
|
|
433
|
-
|
|
472
|
+
// Every file this loop reads is one its own pdflatex wrote. A pdflatex that dies before opening
|
|
473
|
+
// paper.log would otherwise have an earlier build's error quoted as this one's, and one that wrote
|
|
474
|
+
// no paper.fls would hand the record the files an earlier build read.
|
|
475
|
+
const remove = (ext: string): void => {
|
|
476
|
+
rmSync(join(ctx.paperDir, `${JOB}.${ext}`), { force: true });
|
|
477
|
+
};
|
|
478
|
+
remove("log");
|
|
479
|
+
remove("fls");
|
|
434
480
|
const runs = { latex: 0, bibtex: 0 };
|
|
481
|
+
const finish = (end: Terminal) => ({ end, ...runs, run: runOf(history) });
|
|
435
482
|
for (;;) {
|
|
436
483
|
const step = nextStep(summarize(history));
|
|
437
|
-
if (step.kind === "done" || step.kind === "fail")
|
|
438
|
-
return { end: step, ...runs };
|
|
484
|
+
if (step.kind === "done" || step.kind === "fail") return finish(step);
|
|
439
485
|
runs[step.kind]++;
|
|
440
486
|
const seen =
|
|
441
487
|
step.kind === "latex"
|
|
442
488
|
? latexPass(ctx, step.final, opts)
|
|
443
489
|
: bibtexPass(ctx, opts);
|
|
444
|
-
if (seen === null) return
|
|
490
|
+
if (seen === null) return finish(cannotStart(step.kind));
|
|
445
491
|
history.push(seen);
|
|
446
492
|
}
|
|
447
493
|
}
|
|
@@ -468,7 +514,7 @@ export const compileStep: BuildStep = {
|
|
|
468
514
|
return { yes: true, why: `${facts.main} (${cls}${venue})` };
|
|
469
515
|
},
|
|
470
516
|
run: (ctx) => {
|
|
471
|
-
const { end, latex, bibtex } = compile(ctx);
|
|
517
|
+
const { end, latex, bibtex, run } = compile(ctx);
|
|
472
518
|
if (end.kind === "fail") {
|
|
473
519
|
const bin = end.step === "latex" ? "pdflatex" : "bibtex";
|
|
474
520
|
const where =
|
|
@@ -504,7 +550,7 @@ export const compileStep: BuildStep = {
|
|
|
504
550
|
: "";
|
|
505
551
|
return {
|
|
506
552
|
ok: true,
|
|
507
|
-
note: `${plural(latex, "pdflatex pass", "pdflatex passes")}, ${plural(bibtex, "bibtex run", "bibtex runs")}${warn}`,
|
|
553
|
+
note: `${plural(latex, "pdflatex pass", "pdflatex passes")}, ${plural(bibtex, "bibtex run", "bibtex runs")}${warn}; ${recordingNote(ctx.recordSources(ctx.paperDir, run))}`,
|
|
508
554
|
};
|
|
509
555
|
},
|
|
510
556
|
};
|
|
@@ -585,7 +631,12 @@ export const referencesStep: BuildStep = {
|
|
|
585
631
|
: { yes: false, why: "nothing is compiled" },
|
|
586
632
|
run: async (ctx) => ({
|
|
587
633
|
ok: true,
|
|
588
|
-
note: await recordReferences(
|
|
634
|
+
note: await recordReferences(
|
|
635
|
+
ctx.files,
|
|
636
|
+
ctx.paperDir,
|
|
637
|
+
ctx.readBibliography(ctx.paperDir),
|
|
638
|
+
ctx.checkReferences,
|
|
639
|
+
),
|
|
589
640
|
}),
|
|
590
641
|
};
|
|
591
642
|
|
|
@@ -648,6 +699,17 @@ export interface BuildOptions {
|
|
|
648
699
|
* warns. The CLI passes the real ones (`adapters/references`).
|
|
649
700
|
*/
|
|
650
701
|
checkReferences?: CheckReferences;
|
|
702
|
+
/**
|
|
703
|
+
* The databases the build's bibtex opened, for the references step. Default: none — the step says
|
|
704
|
+
* it was not wired and records nothing, and lint warns. The CLI passes `bibliographyReader` over the
|
|
705
|
+
* real ports.
|
|
706
|
+
*/
|
|
707
|
+
readBibliography?: ReadBibliography;
|
|
708
|
+
/**
|
|
709
|
+
* Writes `_build/sources.json` after a compile. Default: none — the build says it recorded nothing,
|
|
710
|
+
* and lint reports the paper as not built. The CLI passes `recordSources` over the real ports.
|
|
711
|
+
*/
|
|
712
|
+
recordSources?: RecordSources;
|
|
651
713
|
}
|
|
652
714
|
|
|
653
715
|
/**
|
|
@@ -669,7 +731,14 @@ function baseDefaults({
|
|
|
669
731
|
readPdf = pdfjsReader,
|
|
670
732
|
projectRoot = env["CLAUDE_PROJECT_DIR"] || cwd,
|
|
671
733
|
}: BuildOptions): Required<
|
|
672
|
-
Omit<
|
|
734
|
+
Omit<
|
|
735
|
+
BuildOptions,
|
|
736
|
+
| "measure"
|
|
737
|
+
| "files"
|
|
738
|
+
| "checkReferences"
|
|
739
|
+
| "readBibliography"
|
|
740
|
+
| "recordSources"
|
|
741
|
+
>
|
|
673
742
|
> {
|
|
674
743
|
return { run, cwd, env, steps, log, dryRun, readPdf, projectRoot };
|
|
675
744
|
}
|
|
@@ -686,7 +755,16 @@ const notWired: CheckReferences = (_bib, cache) =>
|
|
|
686
755
|
|
|
687
756
|
/** banal as the measurer, wired from the build's environment: the one piece of root work left here (#76). */
|
|
688
757
|
function defaultMeasurer(
|
|
689
|
-
b: Required<
|
|
758
|
+
b: Required<
|
|
759
|
+
Omit<
|
|
760
|
+
BuildOptions,
|
|
761
|
+
| "measure"
|
|
762
|
+
| "files"
|
|
763
|
+
| "checkReferences"
|
|
764
|
+
| "readBibliography"
|
|
765
|
+
| "recordSources"
|
|
766
|
+
>
|
|
767
|
+
>,
|
|
690
768
|
): MeasureGeometry {
|
|
691
769
|
const dirs = hostDirs({ cwd: b.cwd });
|
|
692
770
|
return banalMeasurer(
|
|
@@ -705,6 +783,8 @@ function withDefaults(o: BuildOptions): Required<BuildOptions> {
|
|
|
705
783
|
measure,
|
|
706
784
|
files: o.files ?? nodeFiles,
|
|
707
785
|
checkReferences: o.checkReferences ?? notWired,
|
|
786
|
+
readBibliography: o.readBibliography ?? notWiredBibliography,
|
|
787
|
+
recordSources: o.recordSources ?? notWiredRecord,
|
|
708
788
|
};
|
|
709
789
|
}
|
|
710
790
|
|
|
@@ -713,29 +793,14 @@ async function runSteps(
|
|
|
713
793
|
paperDir: string,
|
|
714
794
|
dir: string,
|
|
715
795
|
plan: PlanLine[],
|
|
716
|
-
|
|
717
|
-
run,
|
|
718
|
-
env,
|
|
719
|
-
steps,
|
|
720
|
-
readPdf,
|
|
721
|
-
measure,
|
|
722
|
-
files,
|
|
723
|
-
checkReferences,
|
|
724
|
-
}: Required<BuildOptions>,
|
|
796
|
+
o: Required<BuildOptions>,
|
|
725
797
|
): Promise<BuildResult> {
|
|
726
|
-
let stepEnv = env;
|
|
798
|
+
let stepEnv = o.env;
|
|
727
799
|
const notes: string[] = [];
|
|
728
|
-
for (const [i, step] of steps.entries()) {
|
|
800
|
+
for (const [i, step] of o.steps.entries()) {
|
|
729
801
|
if (!plan[i]?.applies) continue;
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
env: stepEnv,
|
|
733
|
-
run,
|
|
734
|
-
readPdf,
|
|
735
|
-
measure,
|
|
736
|
-
files,
|
|
737
|
-
checkReferences,
|
|
738
|
-
});
|
|
802
|
+
// Every port the options hold, the paper, and the environment so far.
|
|
803
|
+
const out = await step.run({ ...o, paperDir, env: stepEnv });
|
|
739
804
|
if (!out.ok) {
|
|
740
805
|
// The PDF THIS run wrote and the step then rejected (a partial pass).
|
|
741
806
|
// A PDF from an earlier run is already gone — `buildPapers` removed it before anything ran.
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
import { describe, expect, it } from "vitest";
|
|
8
8
|
import { latexReader } from "./adapters/latex/index.ts";
|
|
9
9
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
10
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
10
11
|
import {
|
|
11
12
|
claimProvenanceRule,
|
|
12
13
|
judgeClaimProvenance,
|
|
13
14
|
} from "./claim-provenance.ts";
|
|
15
|
+
import { builtPaper } from "../test/recorded-fixture.ts";
|
|
14
16
|
|
|
15
17
|
const doc = (body: string): string =>
|
|
16
18
|
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
@@ -317,7 +319,11 @@ function run(
|
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
const onDisk = (files: Record<string, string> = {}) =>
|
|
320
|
-
claimProvenanceRule({
|
|
322
|
+
claimProvenanceRule({
|
|
323
|
+
files: memoryFiles(files),
|
|
324
|
+
latex: latexReader,
|
|
325
|
+
codec: sourcesCodec,
|
|
326
|
+
});
|
|
321
327
|
|
|
322
328
|
describe("the rule, as ESLint runs it", () => {
|
|
323
329
|
it("reports each finding at its lines, with the message naming the owners that would do", () => {
|
|
@@ -337,16 +343,36 @@ describe("the rule, as ESLint runs it", () => {
|
|
|
337
343
|
]);
|
|
338
344
|
});
|
|
339
345
|
|
|
340
|
-
it("🔴 reads the files the
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
it("🔴 reads the files the build recorded TeX reading: a finding there is reported at the \\input, naming its file and line", () => {
|
|
347
|
+
const main = doc("Intro.\n\\input{sections/results}");
|
|
348
|
+
const rule = onDisk(
|
|
349
|
+
builtPaper(
|
|
350
|
+
"/p",
|
|
351
|
+
{
|
|
352
|
+
"paper.tex": main,
|
|
353
|
+
"sections/results.tex":
|
|
354
|
+
"\\section{Results}\nIt holds.\nAgents ignore 42\\% of rules.\n",
|
|
355
|
+
},
|
|
356
|
+
[
|
|
357
|
+
{ path: "paper.tex", role: "body" },
|
|
358
|
+
{ path: "sections/results.tex", role: "body" },
|
|
359
|
+
],
|
|
360
|
+
),
|
|
361
|
+
);
|
|
362
|
+
expect(run(rule, main)).toEqual([
|
|
346
363
|
{
|
|
347
364
|
line: 4,
|
|
348
365
|
message: `sections/results.tex:3:1: ${String(rule.meta.messages["noOwner"]).replace("{{number}}", "42%")}`,
|
|
349
366
|
},
|
|
350
367
|
]);
|
|
351
368
|
});
|
|
369
|
+
|
|
370
|
+
it("🔴 without a record the paper is paper.tex: the file it \\inputs is not read", () => {
|
|
371
|
+
const main = doc("Intro.\n\\input{sections/results}");
|
|
372
|
+
const rule = onDisk({
|
|
373
|
+
"/p/paper.tex": main,
|
|
374
|
+
"/p/sections/results.tex": "Agents ignore 42\\% of rules.\n",
|
|
375
|
+
});
|
|
376
|
+
expect(run(rule, main)).toEqual([]);
|
|
377
|
+
});
|
|
352
378
|
});
|
package/src/claim-provenance.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
segmentOf,
|
|
38
38
|
sentences,
|
|
39
39
|
} from "./domain/sentences.ts";
|
|
40
|
-
import { readPaper, reportInPaper, type
|
|
40
|
+
import { readPaper, reportInPaper, type ProseDeps } from "./tex-paper.ts";
|
|
41
41
|
import {
|
|
42
42
|
ruleDocsUrl,
|
|
43
43
|
type Located,
|
|
@@ -198,7 +198,7 @@ const META: TexRuleModule["meta"] = {
|
|
|
198
198
|
};
|
|
199
199
|
|
|
200
200
|
/** The rule, over the body's prose of the whole paper, its includes spliced (`readPaper`). */
|
|
201
|
-
export const claimProvenanceRule = (deps:
|
|
201
|
+
export const claimProvenanceRule = (deps: ProseDeps): TexRuleModule => ({
|
|
202
202
|
meta: META,
|
|
203
203
|
create(context) {
|
|
204
204
|
return {
|
package/src/cli.harness.mjs
CHANGED
|
@@ -46,6 +46,15 @@ const check = createChecker();
|
|
|
46
46
|
const tex = (body) =>
|
|
47
47
|
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* A run that linted a paper nobody built clean: exit 0, and the one finding is the warning that no
|
|
51
|
+
* build recorded what TeX read (`paper/sources-fresh`) — what "no findings" meant before that rule.
|
|
52
|
+
*/
|
|
53
|
+
const cleanButUnbuilt = (r) =>
|
|
54
|
+
r.code === 0 &&
|
|
55
|
+
/paper\/sources-fresh/.test(r.out) &&
|
|
56
|
+
/1 problem \(0 errors, 1 warning\)/.test(r.out);
|
|
57
|
+
|
|
49
58
|
/** Runs the utility with output captured — quieter and faster than spawning a process. */
|
|
50
59
|
async function cli(args, cwd) {
|
|
51
60
|
// 🔴 THE STREAMS ARE SPLIT, AND THAT IS LOAD-BEARING. As long as the harness dumped log and
|
|
@@ -196,7 +205,7 @@ check(
|
|
|
196
205
|
const bare = await cli(["lint"], root);
|
|
197
206
|
check(
|
|
198
207
|
"🔴 NO paperlint.json IS A VALID PROJECT — the default papers directory is linted",
|
|
199
|
-
|
|
208
|
+
cleanButUnbuilt(bare),
|
|
200
209
|
);
|
|
201
210
|
check(
|
|
202
211
|
"and no config line is printed when there is no file to name",
|
|
@@ -208,7 +217,7 @@ check(
|
|
|
208
217
|
const empty = await cli(["lint"], root);
|
|
209
218
|
check(
|
|
210
219
|
`an empty paperlint.json — no \`${PAPERS_DIR_FIELD}\`, so the default`,
|
|
211
|
-
|
|
220
|
+
cleanButUnbuilt(empty),
|
|
212
221
|
);
|
|
213
222
|
|
|
214
223
|
// 🔴 THE DECLARED DIRECTORY IS NOT THE DEFAULT: with `papers` declared, a CLI that ignored
|
|
@@ -221,7 +230,7 @@ check(
|
|
|
221
230
|
const r = await cli(["lint"], root);
|
|
222
231
|
check(
|
|
223
232
|
"🔴 THE UTILITY READS THE DECLARATION FROM paperlint.json — the file `paperlint init` writes and the hooks read",
|
|
224
|
-
|
|
233
|
+
cleanButUnbuilt(r),
|
|
225
234
|
);
|
|
226
235
|
check(
|
|
227
236
|
"and NAMES the carrier out loud — swapping settings is never silent",
|
|
@@ -713,7 +722,7 @@ check(
|
|
|
713
722
|
const clean = await cli(["lint", "papers"], root);
|
|
714
723
|
check(
|
|
715
724
|
"on a clean corpus — zero and a legible report",
|
|
716
|
-
|
|
725
|
+
cleanButUnbuilt(clean),
|
|
717
726
|
);
|
|
718
727
|
|
|
719
728
|
writeFileSync(join(paper, "PIPELINE-STATUS.md"), status(999));
|
|
@@ -822,7 +831,7 @@ check(
|
|
|
822
831
|
const found = await cli(["lint"], root);
|
|
823
832
|
check(
|
|
824
833
|
"the config is FOUND on its own: `lint` with no argument at all runs",
|
|
825
|
-
|
|
834
|
+
cleanButUnbuilt(found),
|
|
826
835
|
);
|
|
827
836
|
check(
|
|
828
837
|
"and the found file is NAMED out loud — swapping settings silently is not acceptable",
|
|
@@ -836,7 +845,7 @@ check(
|
|
|
836
845
|
const fromSub = await cli(["lint"], paper);
|
|
837
846
|
check(
|
|
838
847
|
"the path from the config resolves relative to the CONFIG'S DIRECTORY, not the current one",
|
|
839
|
-
|
|
848
|
+
cleanButUnbuilt(fromSub),
|
|
840
849
|
);
|
|
841
850
|
|
|
842
851
|
// Consumer data from the found config actually reaches the rules, not just gets read.
|
|
@@ -865,7 +874,7 @@ check(
|
|
|
865
874
|
const noPapers = await cli(["lint"], root);
|
|
866
875
|
check(
|
|
867
876
|
"a config WITHOUT the papers-directory field lints the default `papers/`",
|
|
868
|
-
|
|
877
|
+
cleanButUnbuilt(noPapers),
|
|
869
878
|
);
|
|
870
879
|
check(
|
|
871
880
|
'an empty string as the papers directory is refused, not read as the directory ""',
|
|
@@ -925,7 +934,7 @@ check(
|
|
|
925
934
|
);
|
|
926
935
|
check(
|
|
927
936
|
'and the failure names the NUMBER and the THRESHOLD, not just "too many"',
|
|
928
|
-
/
|
|
937
|
+
/4 warning\(s\) exceed the --max-warnings limit of 0/.test(strict.out),
|
|
929
938
|
);
|
|
930
939
|
const generous = await cli(["lint", "--max-warnings", "5"], root);
|
|
931
940
|
check(
|
|
@@ -1597,13 +1606,14 @@ console.log(
|
|
|
1597
1606
|
);
|
|
1598
1607
|
const r = await cli(["new", "demo"], root);
|
|
1599
1608
|
check(
|
|
1600
|
-
"🔴 `paperlint new demo` creates writing/demo/ and its lint is the verdict — exit 0, and only
|
|
1609
|
+
"🔴 `paperlint new demo` creates writing/demo/ and its lint is the verdict — exit 0, and only two warnings: no venue is chosen yet, the paper is not built",
|
|
1601
1610
|
r.code === 0 &&
|
|
1602
1611
|
existsSync(join(root, "writing", "demo", "PIPELINE-STATUS.md")) &&
|
|
1603
1612
|
existsSync(join(root, "writing", "demo", "paper.tex")) &&
|
|
1604
1613
|
existsSync(join(root, "writing", "demo", "paperlint.json")) &&
|
|
1605
1614
|
/names no venue preset yet/.test(r.out) &&
|
|
1606
|
-
|
|
1615
|
+
/paper\/sources-fresh/.test(r.out) &&
|
|
1616
|
+
/\(0 errors, 2 warnings\)/.test(r.out),
|
|
1607
1617
|
);
|
|
1608
1618
|
const again = await cli(["new", "demo"], root);
|
|
1609
1619
|
check(
|