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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The readers of what TeX writes beside a paper — `.fls` (pdflatex `-recorder`) and the errors in
|
|
3
|
+
* bibtex's `.blg` — on files captured whole from real runs of the planted papers in
|
|
4
|
+
* `fixtures/paper-sources/` (test/fixtures/tex-output/README.md), not on text written for the test.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { texOutput } from "./index.ts";
|
|
11
|
+
|
|
12
|
+
const CAPTURES = join(
|
|
13
|
+
dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))),
|
|
14
|
+
"test",
|
|
15
|
+
"fixtures",
|
|
16
|
+
"tex-output",
|
|
17
|
+
);
|
|
18
|
+
const capture = (paper: string, file: string): string =>
|
|
19
|
+
readFileSync(join(CAPTURES, paper, file), "utf8");
|
|
20
|
+
|
|
21
|
+
/** The lines that name a file of the paper: not the TeX tree's (absolute) files. */
|
|
22
|
+
const ofPaper = (text: string) =>
|
|
23
|
+
texOutput.fls(text).lines.filter((l) => !l.path.startsWith("/"));
|
|
24
|
+
|
|
25
|
+
describe("the .fls reader", () => {
|
|
26
|
+
it("reads every INPUT and OUTPUT line in order, and the directory pdflatex ran in", () => {
|
|
27
|
+
const fls = texOutput.fls(capture("v8-jobname", "paper.fls"));
|
|
28
|
+
expect(fls.pwd).toBe("/work/paper");
|
|
29
|
+
expect(fls.lines.filter((l) => !l.path.startsWith("/"))).toEqual([
|
|
30
|
+
{ op: "INPUT", path: "paper.tex" },
|
|
31
|
+
{ op: "OUTPUT", path: "paper.log" },
|
|
32
|
+
{ op: "INPUT", path: "./paper.bib" },
|
|
33
|
+
{ op: "INPUT", path: "paper.bib" },
|
|
34
|
+
{ op: "OUTPUT", path: "paper.bib" },
|
|
35
|
+
{ op: "INPUT", path: "./paper.aux" },
|
|
36
|
+
{ op: "INPUT", path: "./paper.aux" },
|
|
37
|
+
{ op: "INPUT", path: "paper.aux" },
|
|
38
|
+
{ op: "OUTPUT", path: "paper.aux" },
|
|
39
|
+
{ op: "INPUT", path: "./paper.bbl" },
|
|
40
|
+
{ op: "INPUT", path: "./paper.bbl" },
|
|
41
|
+
{ op: "INPUT", path: "paper.bbl" },
|
|
42
|
+
{ op: "OUTPUT", path: "paper.pdf" },
|
|
43
|
+
{ op: "INPUT", path: "paper.aux" },
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("keeps the TeX tree's files too, as the absolute paths TeX opened", () => {
|
|
48
|
+
const fls = texOutput.fls(capture("v3-declared", "paper.fls"));
|
|
49
|
+
expect(fls.lines.slice(0, 4)).toEqual([
|
|
50
|
+
{ op: "INPUT", path: "/usr/share/texlive/texmf.cnf" },
|
|
51
|
+
{ op: "INPUT", path: "/usr/share/texlive/texmf-dist/web2c/texmf.cnf" },
|
|
52
|
+
{
|
|
53
|
+
op: "INPUT",
|
|
54
|
+
path: "/usr/share/texlive/texmf-var/web2c/pdftex/pdflatex.fmt",
|
|
55
|
+
},
|
|
56
|
+
{ op: "INPUT", path: "paper.tex" },
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("the .fls reader, on the shapes of a real run", () => {
|
|
62
|
+
it("a file in a folder keeps its folder; a first pass has no .aux to read yet; a block over a .bib that is already there writes nothing", () => {
|
|
63
|
+
expect(ofPaper(capture("p1", "first-pass.fls"))).toEqual([
|
|
64
|
+
{ op: "INPUT", path: "paper.tex" },
|
|
65
|
+
{ op: "OUTPUT", path: "paper.log" },
|
|
66
|
+
{ op: "INPUT", path: "./refs.bib" },
|
|
67
|
+
{ op: "INPUT", path: "refs.bib" },
|
|
68
|
+
{ op: "OUTPUT", path: "paper.aux" },
|
|
69
|
+
{ op: "INPUT", path: "./sections/intro.tex" },
|
|
70
|
+
{ op: "INPUT", path: "./sections/intro.tex" },
|
|
71
|
+
{ op: "INPUT", path: "./sections/intro.tex" },
|
|
72
|
+
{ op: "INPUT", path: "./sections/intro.tex" },
|
|
73
|
+
{ op: "INPUT", path: "sections/intro.tex" },
|
|
74
|
+
{ op: "OUTPUT", path: "paper.pdf" },
|
|
75
|
+
{ op: "INPUT", path: "paper.aux" },
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("a path with a space is the whole rest of the line; text that is not a line of the format is skipped", () => {
|
|
80
|
+
expect(
|
|
81
|
+
texOutput.fls(
|
|
82
|
+
"PWD /a b/p\nINPUT my file.tex\nOUTPUT out dir/x.aux\nsomething else\nINPUT\n",
|
|
83
|
+
),
|
|
84
|
+
).toEqual({
|
|
85
|
+
pwd: "/a b/p",
|
|
86
|
+
lines: [
|
|
87
|
+
{ op: "INPUT", path: "my file.tex" },
|
|
88
|
+
{ op: "OUTPUT", path: "out dir/x.aux" },
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("an .fls with no PWD line has no directory", () => {
|
|
94
|
+
expect(texOutput.fls("INPUT paper.tex\n")).toEqual({
|
|
95
|
+
pwd: null,
|
|
96
|
+
lines: [{ op: "INPUT", path: "paper.tex" }],
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("the .blg error reader", () => {
|
|
102
|
+
it("a run that ends well reports none, warnings included", () => {
|
|
103
|
+
expect(texOutput.blgErrors(capture("v3-declared", "paper.blg"))).toEqual(
|
|
104
|
+
[],
|
|
105
|
+
);
|
|
106
|
+
expect(
|
|
107
|
+
texOutput.blgErrors(
|
|
108
|
+
"Database file #1: x.bib\nWarning--I'm ignoring a's extra \"year\" field\n--line 1 of file x.bib\n",
|
|
109
|
+
),
|
|
110
|
+
).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("the .blg error reader, on what real runs reported", () => {
|
|
115
|
+
it("an entry never closed: the message before the dashes, the file and the line after", () => {
|
|
116
|
+
expect(texOutput.blgErrors(capture("v6-unclosed", "paper.blg"))).toEqual([
|
|
117
|
+
{
|
|
118
|
+
message: "I was expecting a `,' or a `}'",
|
|
119
|
+
file: "refs.bib",
|
|
120
|
+
line: 4,
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("a second \\bibdata command is an error in the .aux", () => {
|
|
126
|
+
expect(
|
|
127
|
+
texOutput.blgErrors(capture("v9-two-bibliographies", "paper.blg")),
|
|
128
|
+
).toEqual([
|
|
129
|
+
{
|
|
130
|
+
message: "Illegal, another \\bibdata command",
|
|
131
|
+
file: "paper.aux",
|
|
132
|
+
line: 5,
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("dashes on the first line of the log have no message before them", () => {
|
|
138
|
+
expect(texOutput.blgErrors("---line 1 of file paper.aux\n")).toEqual([
|
|
139
|
+
{ message: "", file: "paper.aux", line: 1 },
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("a message on the line before its dashes, and an error with no line", () => {
|
|
144
|
+
expect(
|
|
145
|
+
texOutput.blgErrors(
|
|
146
|
+
[
|
|
147
|
+
"The style file: plain.bst",
|
|
148
|
+
"I couldn't open database file paper.bib",
|
|
149
|
+
"---line 4 of file paper.aux",
|
|
150
|
+
" : \\bibdata{paper",
|
|
151
|
+
" : }",
|
|
152
|
+
"I'm skipping whatever remains of this command",
|
|
153
|
+
"I found no database files---while reading file paper.aux",
|
|
154
|
+
'Warning--I didn\'t find a database entry for "declared2023"',
|
|
155
|
+
"(There were 2 error messages)",
|
|
156
|
+
].join("\n"),
|
|
157
|
+
),
|
|
158
|
+
).toEqual([
|
|
159
|
+
{
|
|
160
|
+
message: "I couldn't open database file paper.bib",
|
|
161
|
+
file: "paper.aux",
|
|
162
|
+
line: 4,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
message: "I found no database files",
|
|
166
|
+
file: "paper.aux",
|
|
167
|
+
line: null,
|
|
168
|
+
},
|
|
169
|
+
]);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The readers of what TeX writes beside a paper: pdflatex's `-recorder` file and bibtex's errors.
|
|
3
|
+
* One implementation of `TexOutput` (`src/ports/tex-output.ts`).
|
|
4
|
+
*/
|
|
5
|
+
import type { TexOutput } from "../../ports/tex-output.ts";
|
|
6
|
+
import { parseBlgErrors } from "./blg.ts";
|
|
7
|
+
import { parseFls } from "./fls.ts";
|
|
8
|
+
|
|
9
|
+
export const texOutput: TexOutput = {
|
|
10
|
+
fls: parseFls,
|
|
11
|
+
blgErrors: parseBlgErrors,
|
|
12
|
+
};
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `bib` rules through real ESLint on the `tex/latex` language: `bib/reachable-entry` judges the
|
|
3
|
+
* entries of the databases the build's bibtex OPENED (`_build/sources.json`), fires, stays silent, and
|
|
4
|
+
* reports where the entry is — on its line in `paper.tex` for a `.bib` TeX wrote from a block there,
|
|
5
|
+
* at the top of `paper.tex` with `refs.bib:3:1:` first for any other. A paper with no record, or one
|
|
6
|
+
* that changed since, gets nothing from this rule (`paper/sources-fresh` speaks once instead).
|
|
7
|
+
* On the planted papers of fixtures/paper-sources too, with the record TeX wrote for each.
|
|
8
|
+
*/
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { ESLint, type Linter } from "eslint";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import { texLanguage } from "../eslint-rules/latex-language.ts";
|
|
13
|
+
import { bibReader } from "./adapters/bibtex/index.ts";
|
|
14
|
+
import { latexReader } from "./adapters/latex/index.ts";
|
|
15
|
+
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
16
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
17
|
+
import { BIB_RULE_LEVELS, bibRules } from "./bib-rules.ts";
|
|
18
|
+
import { eslintConfig } from "./cli.ts";
|
|
19
|
+
import {
|
|
20
|
+
leftByTeX,
|
|
21
|
+
planted,
|
|
22
|
+
recordText,
|
|
23
|
+
type Shape,
|
|
24
|
+
} from "../test/recorded-fixture.ts";
|
|
25
|
+
import { present } from "../test/support.ts";
|
|
26
|
+
|
|
27
|
+
const PAPER = "/work/papers/p";
|
|
28
|
+
|
|
29
|
+
/** A paper in memory: its files by relative path, and the record its build wrote (none: not built). */
|
|
30
|
+
interface Paper {
|
|
31
|
+
readonly tex: string;
|
|
32
|
+
readonly files?: Readonly<Record<string, string>>;
|
|
33
|
+
readonly record?: string | Shape | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Lint `paper.tex` (or `file`) of a paper laid out as TeX leaves it. */
|
|
37
|
+
async function lint(
|
|
38
|
+
p: Paper,
|
|
39
|
+
{ file = "paper.tex", buffer = p.tex } = {},
|
|
40
|
+
): Promise<{ messages: Linter.LintMessage[] }> {
|
|
41
|
+
const all = { "paper.tex": p.tex, ...p.files };
|
|
42
|
+
const record =
|
|
43
|
+
p.record === undefined || p.record === null
|
|
44
|
+
? null
|
|
45
|
+
: typeof p.record === "string"
|
|
46
|
+
? p.record
|
|
47
|
+
: recordText(all, p.record);
|
|
48
|
+
const files = memoryFiles({
|
|
49
|
+
...Object.fromEntries(
|
|
50
|
+
Object.entries(all).map(([rel, text]) => [`${PAPER}/${rel}`, text]),
|
|
51
|
+
),
|
|
52
|
+
...(record === null ? {} : { [`${PAPER}/_build/sources.json`]: record }),
|
|
53
|
+
});
|
|
54
|
+
const eslint = new ESLint({
|
|
55
|
+
cwd: "/work",
|
|
56
|
+
overrideConfigFile: true,
|
|
57
|
+
overrideConfig: eslintConfig([
|
|
58
|
+
{
|
|
59
|
+
files: ["**/*.tex"],
|
|
60
|
+
plugins: {
|
|
61
|
+
tex: { languages: { latex: texLanguage } },
|
|
62
|
+
bib: {
|
|
63
|
+
rules: bibRules({
|
|
64
|
+
files,
|
|
65
|
+
codec: sourcesCodec,
|
|
66
|
+
latex: latexReader,
|
|
67
|
+
bib: bibReader,
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
language: "tex/latex",
|
|
72
|
+
linterOptions: { reportUnusedDisableDirectives: "error" },
|
|
73
|
+
rules: BIB_RULE_LEVELS,
|
|
74
|
+
},
|
|
75
|
+
]),
|
|
76
|
+
});
|
|
77
|
+
const [res] = await eslint.lintText(buffer, {
|
|
78
|
+
filePath: join(PAPER, file),
|
|
79
|
+
});
|
|
80
|
+
const r = present(res, "one lint result");
|
|
81
|
+
expect(r.messages.filter((m) => m.fatal)).toEqual([]);
|
|
82
|
+
return { messages: r.messages };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const found = (
|
|
86
|
+
messages: readonly Linter.LintMessage[],
|
|
87
|
+
rule: string,
|
|
88
|
+
): readonly (readonly [number, number, string])[] =>
|
|
89
|
+
messages
|
|
90
|
+
.filter((m) => m.ruleId === `bib/${rule}`)
|
|
91
|
+
.map((m) => [m.line, m.column, m.message] as const);
|
|
92
|
+
|
|
93
|
+
/** A paper whose bibliography is a block, and the `.bib` TeX wrote from it, as a build leaves them. */
|
|
94
|
+
const inline = (entries: string): Paper => ({
|
|
95
|
+
tex: `\\documentclass{acmart}\n\\begin{filecontents*}[overwrite]{refs.bib}\n${entries}\n\\end{filecontents*}\n\\begin{document}\nx\n\\bibliography{refs}\n\\end{document}\n`,
|
|
96
|
+
files: { "refs.bib": `${entries}\n` },
|
|
97
|
+
record: {
|
|
98
|
+
inputs: [["paper.tex", "body"]],
|
|
99
|
+
written: ["refs.bib"],
|
|
100
|
+
databases: ["refs.bib"],
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const DECLARING =
|
|
105
|
+
"\\documentclass{acmart}\n\\begin{document}\nx\n\\bibliography{refs}\n\\end{document}\n";
|
|
106
|
+
const KEPT: Shape = {
|
|
107
|
+
inputs: [
|
|
108
|
+
["paper.tex", "body"],
|
|
109
|
+
["refs.bib", "preamble"],
|
|
110
|
+
],
|
|
111
|
+
databases: ["refs.bib"],
|
|
112
|
+
};
|
|
113
|
+
/** A paper whose bibliography is the `refs.bib` the author keeps. */
|
|
114
|
+
const declaring = (bib: string): Paper => ({
|
|
115
|
+
tex: DECLARING,
|
|
116
|
+
files: { "refs.bib": bib },
|
|
117
|
+
record: KEPT,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("bib/reachable-entry", () => {
|
|
121
|
+
it("a doi, a url or an arXiv id each make an entry reachable; @string is not an entry", async () => {
|
|
122
|
+
const { messages } = await lint(
|
|
123
|
+
inline(
|
|
124
|
+
"@article{a, doi = {10.1/x}}\n@misc{b, url = {https://x.org}}\n@misc{c, note = {arXiv:2310.05736}}\n@string{v = {x}}",
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
expect(found(messages, "reachable-entry")).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Measured on the accepted corpus: 89 entries carry their only link as `howpublished = {\url{…}}`,
|
|
131
|
+
// which the bibliography style typesets — a link the reader has.
|
|
132
|
+
it("a \\url or \\href in any field is a link the reader has; a bare address in a field the style drops is not", async () => {
|
|
133
|
+
const { messages } = await lint(
|
|
134
|
+
inline(
|
|
135
|
+
"@misc{web, howpublished = {\\url{https://x.org}}}\n@misc{ref, note = {see \\href{https://x.org}{here}}}\n@techreport{src, source = {http://x.org}}",
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
expect(
|
|
139
|
+
found(messages, "reachable-entry").map((m) => m[2].slice(0, 5)),
|
|
140
|
+
).toEqual(["`src`"]);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("bib/reachable-entry — keys and where a finding goes", () => {
|
|
145
|
+
it("an entry written with no key is named `?`", async () => {
|
|
146
|
+
const { messages } = await lint(inline("@article{, title = {x}}"));
|
|
147
|
+
expect(
|
|
148
|
+
found(messages, "reachable-entry").map((m) => m[2].slice(0, 3)),
|
|
149
|
+
).toEqual(["`?`"]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("an entry of a `.bib` TeX wrote from a block of paper.tex is reported on its own line there", async () => {
|
|
153
|
+
const { messages } = await lint(
|
|
154
|
+
inline("@misc{ok, url = {https://x.org}}\n@article{lost, title = {x}}"),
|
|
155
|
+
);
|
|
156
|
+
expect(found(messages, "reachable-entry")).toEqual([
|
|
157
|
+
[4, 1, expect.stringMatching(/^`lost` has no doi/)],
|
|
158
|
+
]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("🔴 an entry in refs.bib — a file the author keeps — is reported at the top of paper.tex, with its file and line", async () => {
|
|
162
|
+
const { messages } = await lint(
|
|
163
|
+
declaring(
|
|
164
|
+
"@misc{ok, url = {https://x.org}}\n\n@article{lost, title = {x}}\n",
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
expect(found(messages, "reachable-entry")).toEqual([
|
|
168
|
+
[1, 1, expect.stringMatching(/^refs\.bib:3:1: `lost` has no doi/)],
|
|
169
|
+
]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("in a .bib, the directive on the line above the entry keeps the exception where the entry is", async () => {
|
|
173
|
+
const { messages } = await lint(
|
|
174
|
+
declaring(
|
|
175
|
+
"% eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists\n@misc{talk, title = {x}}\n" +
|
|
176
|
+
"% eslint-disable-next-line bib/other-rule\n@misc{lost, title = {y}}\n",
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
expect(found(messages, "reachable-entry").map((m) => m[2])).toEqual([
|
|
180
|
+
expect.stringMatching(/^refs\.bib:4:1: `lost`/),
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("🔴 inside the block, a disable directive silences exactly the next entry", async () => {
|
|
185
|
+
const { messages } = await lint(
|
|
186
|
+
inline(
|
|
187
|
+
"% eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists\n@misc{talk, title = {x}}\n@misc{lost, title = {y}}",
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
expect(found(messages, "reachable-entry").map((m) => m[2])).toEqual([
|
|
191
|
+
expect.stringMatching(/^`lost`/),
|
|
192
|
+
]);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("bib/reachable-entry — a block is only a lookup of where the text stands", () => {
|
|
197
|
+
it("🔴 the block is a lookup of where the text stands: when paper.tex no longer holds what TeX read, the finding is at the top, naming the .bib", async () => {
|
|
198
|
+
const p = inline("@article{lost, title = {x}}");
|
|
199
|
+
const { messages } = await lint(p, {
|
|
200
|
+
buffer: p.tex.replace("{x}", "{edited since the build}"),
|
|
201
|
+
});
|
|
202
|
+
expect(found(messages, "reachable-entry")).toEqual([
|
|
203
|
+
[1, 1, expect.stringMatching(/^refs\.bib:1:1: `lost`/)],
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("a block in an included file: the finding is at the top of paper.tex, naming the entry in the file that holds the block", async () => {
|
|
208
|
+
const entries =
|
|
209
|
+
"@misc{ok, url = {https://x.org}}\n@article{lost, title = {x}}";
|
|
210
|
+
const { messages } = await lint({
|
|
211
|
+
tex: "\\documentclass{acmart}\n\\input{bibblock}\n\\begin{document}\nx\n\\bibliography{refs}\n\\end{document}\n",
|
|
212
|
+
files: {
|
|
213
|
+
"bibblock.tex": `\\begin{filecontents*}{refs.bib}\n${entries}\n\\end{filecontents*}\n`,
|
|
214
|
+
"refs.bib": `${entries}\n`,
|
|
215
|
+
},
|
|
216
|
+
record: {
|
|
217
|
+
inputs: [
|
|
218
|
+
["paper.tex", "body"],
|
|
219
|
+
["bibblock.tex", "preamble"],
|
|
220
|
+
],
|
|
221
|
+
written: ["refs.bib"],
|
|
222
|
+
databases: ["refs.bib"],
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
expect(found(messages, "reachable-entry")).toEqual([
|
|
226
|
+
[1, 1, expect.stringMatching(/^bibblock\.tex:3:1: `lost`/)],
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("🔴 two blocks with the same entries: the finding is in the block that wrote the database bibtex opened", async () => {
|
|
231
|
+
const entry = "@article{lost, title = {x}}";
|
|
232
|
+
const block = (name: string) =>
|
|
233
|
+
`\\begin{filecontents*}{${name}}\n${entry}\n\\end{filecontents*}\n`;
|
|
234
|
+
const { messages } = await lint({
|
|
235
|
+
tex: `\\documentclass{acmart}\n${block("a.bib")}${block("b.bib")}\\begin{document}\nx\n\\bibliography{b}\n\\end{document}\n`,
|
|
236
|
+
files: { "a.bib": `${entry}\n`, "b.bib": `${entry}\n` },
|
|
237
|
+
record: {
|
|
238
|
+
inputs: [["paper.tex", "body"]],
|
|
239
|
+
written: ["a.bib", "b.bib"],
|
|
240
|
+
databases: ["b.bib"],
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
expect(found(messages, "reachable-entry")).toEqual([
|
|
244
|
+
[6, 1, expect.stringMatching(/`lost`/)],
|
|
245
|
+
]);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("bib/reachable-entry — which bibliography: the one bibtex opened", () => {
|
|
250
|
+
it("only the databases the build's bibtex opened are judged, not every .bib that lies in the folder", async () => {
|
|
251
|
+
const { messages } = await lint({
|
|
252
|
+
...declaring("@misc{r1, title = {x}}\n"),
|
|
253
|
+
files: {
|
|
254
|
+
"refs.bib": "@misc{r1, title = {x}}\n",
|
|
255
|
+
"anon.bib": "@misc{a1, title = {x}}\n",
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
expect(
|
|
259
|
+
found(messages, "reachable-entry").map((m) => m[2].slice(0, 16)),
|
|
260
|
+
).toEqual(["refs.bib:1:1: `r"]);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("a paper with no record is not judged: paper/sources-fresh says it was not built", async () => {
|
|
264
|
+
const p = declaring("@misc{lost, title = {x}}\n");
|
|
265
|
+
expect(
|
|
266
|
+
found((await lint({ ...p, record: null })).messages, "reachable-entry"),
|
|
267
|
+
).toEqual([]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("🔴 a record the paper has changed since is not judged either — the entries it names may be gone", async () => {
|
|
271
|
+
const p = declaring("@misc{lost, title = {x}}\n");
|
|
272
|
+
const record = recordText({ "paper.tex": p.tex, ...p.files }, KEPT);
|
|
273
|
+
expect(
|
|
274
|
+
found(
|
|
275
|
+
(
|
|
276
|
+
await lint({
|
|
277
|
+
...p,
|
|
278
|
+
files: {
|
|
279
|
+
"refs.bib": "@misc{lost, title = {x}, note = {edited}}\n",
|
|
280
|
+
},
|
|
281
|
+
record,
|
|
282
|
+
})
|
|
283
|
+
).messages,
|
|
284
|
+
"reachable-entry",
|
|
285
|
+
),
|
|
286
|
+
).toEqual([]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("a build whose bibtex ran none, and a database that is not on disk now, hold no entries to judge", async () => {
|
|
290
|
+
const none = declaring("@misc{lost, title = {x}}\n");
|
|
291
|
+
expect(
|
|
292
|
+
found(
|
|
293
|
+
(await lint({ ...none, record: { inputs: [["paper.tex", "body"]] } }))
|
|
294
|
+
.messages,
|
|
295
|
+
"reachable-entry",
|
|
296
|
+
),
|
|
297
|
+
).toEqual([]);
|
|
298
|
+
const gone = await lint({
|
|
299
|
+
tex: DECLARING,
|
|
300
|
+
files: {},
|
|
301
|
+
record: {
|
|
302
|
+
inputs: [["paper.tex", "body"]],
|
|
303
|
+
databases: ["refs.bib"],
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
expect(found(gone.messages, "reachable-entry")).toEqual([]);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe("bib/reachable-entry — which file", () => {
|
|
311
|
+
it("only paper.tex is judged", async () => {
|
|
312
|
+
const { messages } = await lint(inline("@article{lost, title = {x}}"), {
|
|
313
|
+
file: "sections/a.tex",
|
|
314
|
+
});
|
|
315
|
+
expect(messages).toEqual([]);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ── the planted papers ─────────────────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
describe("on the planted papers (fixtures/paper-sources, the record TeX wrote in tex-truth.json)", () => {
|
|
322
|
+
// The entries of the databases bibtex opened that carry no doi, url or arXiv id. A `.bib` TeX
|
|
323
|
+
// wrote is on disk after a build (`written`), holding the lines of its block. An entry only
|
|
324
|
+
// bibtex reads (v5: behind `%`) is the post-build check's, not this rule's.
|
|
325
|
+
it.each([
|
|
326
|
+
["v1-stale", []],
|
|
327
|
+
["v5-percent-entry", []],
|
|
328
|
+
["v6-unclosed", ["a3"]],
|
|
329
|
+
["v8-jobname", ["jkey"]],
|
|
330
|
+
["v16-included-block", ["stale"]],
|
|
331
|
+
["v24-quoted-fields", ["q2", "q3", "q4", "q5"]],
|
|
332
|
+
["v25-string-differs", ["k1"]],
|
|
333
|
+
])("%s: %j", async (name, keys) => {
|
|
334
|
+
const p = planted(name);
|
|
335
|
+
const { messages } = await lint({
|
|
336
|
+
tex: p.files["paper.tex"] ?? "",
|
|
337
|
+
files: leftByTeX(p),
|
|
338
|
+
record: p.record,
|
|
339
|
+
});
|
|
340
|
+
expect(
|
|
341
|
+
messages.map((m) => [m.ruleId, /`([^`]+)`/.exec(m.message)?.[1]]),
|
|
342
|
+
).toEqual(keys.map((k) => ["bib/reachable-entry", k]));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("v8-jobname: the entry of the paper.bib TeX wrote is at its line in the block that wrote it", async () => {
|
|
346
|
+
const p = planted("v8-jobname");
|
|
347
|
+
const { messages } = await lint({
|
|
348
|
+
tex: p.files["paper.tex"] ?? "",
|
|
349
|
+
files: leftByTeX(p),
|
|
350
|
+
record: p.record,
|
|
351
|
+
});
|
|
352
|
+
expect(found(messages, "reachable-entry")).toEqual([
|
|
353
|
+
[3, 1, expect.stringMatching(/^`jkey` has no doi/)],
|
|
354
|
+
]);
|
|
355
|
+
});
|
|
356
|
+
});
|