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,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `paper/sources-fresh` — the one finding for a paper whose build record cannot be used
|
|
3
|
+
* (docs/design/paper-sources.md §1, docs/rules/paper/sources-fresh.md). Every rule that needs to know
|
|
4
|
+
* which files make up the paper, or which bibliography TeX reads, reads `_build/sources.json`
|
|
5
|
+
* (`paperRecord`, `src/paper-record.ts`); when there is none, or the paper has changed since, they
|
|
6
|
+
* are silent and this rule speaks once instead:
|
|
7
|
+
*
|
|
8
|
+
* no record "not built" — run `npx paperlint build`
|
|
9
|
+
* a record this paperlint cannot read the reason, and the same command
|
|
10
|
+
* a file changed since the build which files, and the same command
|
|
11
|
+
*
|
|
12
|
+
* A warning, not an error, for the reason `pdf/measured` is one: lint often runs where nothing is
|
|
13
|
+
* built (the CI action only lints). Each message says what stays unchecked until a build: the files
|
|
14
|
+
* `paper.tex` includes are not linted, and the rules over the bibliography say nothing.
|
|
15
|
+
*/
|
|
16
|
+
import { basename, dirname } from "node:path";
|
|
17
|
+
import { callerPath } from "./caller-path.ts";
|
|
18
|
+
import { describeChanges } from "./domain/sources-record.ts";
|
|
19
|
+
import { MAIN_FILE } from "./domain/paper-sources.ts";
|
|
20
|
+
import { paperRecord, type RecordReadDeps } from "./paper-record.ts";
|
|
21
|
+
|
|
22
|
+
/** One finding: its message, and the values the message names. */
|
|
23
|
+
interface Finding {
|
|
24
|
+
readonly messageId: string;
|
|
25
|
+
readonly data?: Readonly<Record<string, string>>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Loc {
|
|
29
|
+
readonly line: number;
|
|
30
|
+
readonly column: number;
|
|
31
|
+
}
|
|
32
|
+
interface RuleContext {
|
|
33
|
+
readonly filename: string;
|
|
34
|
+
readonly sourceCode: { getLocFromIndex(i: number): Loc };
|
|
35
|
+
report(
|
|
36
|
+
d: Finding & { readonly loc: Readonly<{ start: Loc; end: Loc }> },
|
|
37
|
+
): void;
|
|
38
|
+
}
|
|
39
|
+
export interface SourcesRuleModule {
|
|
40
|
+
readonly meta: {
|
|
41
|
+
readonly type: "problem" | "suggestion";
|
|
42
|
+
readonly docs: {
|
|
43
|
+
readonly description: string;
|
|
44
|
+
readonly url: string;
|
|
45
|
+
};
|
|
46
|
+
readonly schema: readonly object[];
|
|
47
|
+
readonly messages: Readonly<Record<string, string>>;
|
|
48
|
+
};
|
|
49
|
+
create(context: RuleContext): { "root:exit"?: () => void };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const BUILD = "`npx paperlint build`";
|
|
53
|
+
const UNTIL =
|
|
54
|
+
"until then the files `paper.tex` includes are not linted, and the rules that read the bibliography say nothing";
|
|
55
|
+
|
|
56
|
+
const META: SourcesRuleModule["meta"] = {
|
|
57
|
+
type: "problem",
|
|
58
|
+
docs: {
|
|
59
|
+
description:
|
|
60
|
+
"the build recorded which files TeX read, and the paper has not changed since (_build/sources.json)",
|
|
61
|
+
url: "https://github.com/zernie/paperlint/blob/main/docs/rules/paper/sources-fresh.md",
|
|
62
|
+
},
|
|
63
|
+
schema: [],
|
|
64
|
+
messages: {
|
|
65
|
+
notBuilt: `the paper has not been built — run ${BUILD}, which records the files TeX reads. Until it has, the files \`paper.tex\` includes are not linted, and the rules that read the bibliography say nothing`,
|
|
66
|
+
unreadable: `the last build's record cannot be used ({{why}}) — run ${BUILD}; ${UNTIL}`,
|
|
67
|
+
stale: `the paper changed since the last build ({{changed}}) — run ${BUILD}; ${UNTIL}`,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** The finding for a paper's record, or none when it is current. */
|
|
72
|
+
function findingFor(dir: string, deps: RecordReadDeps): Finding | null {
|
|
73
|
+
const r = paperRecord(dir, deps);
|
|
74
|
+
switch (r.kind) {
|
|
75
|
+
case "none":
|
|
76
|
+
return r.unreadable === null
|
|
77
|
+
? { messageId: "notBuilt" }
|
|
78
|
+
: { messageId: "unreadable", data: { why: r.unreadable } };
|
|
79
|
+
case "stale":
|
|
80
|
+
return {
|
|
81
|
+
messageId: "stale",
|
|
82
|
+
data: { changed: describeChanges(r.changed) },
|
|
83
|
+
};
|
|
84
|
+
case "fresh":
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** `paper/sources-fresh`, reading the paper through `deps`. It acts on `paper.tex` only. */
|
|
90
|
+
export function sourcesRules(
|
|
91
|
+
deps: RecordReadDeps,
|
|
92
|
+
): Readonly<Record<"sources-fresh", SourcesRuleModule>> {
|
|
93
|
+
return {
|
|
94
|
+
"sources-fresh": {
|
|
95
|
+
meta: META,
|
|
96
|
+
create: (context) =>
|
|
97
|
+
basename(context.filename) !== MAIN_FILE
|
|
98
|
+
? {}
|
|
99
|
+
: {
|
|
100
|
+
"root:exit": () => {
|
|
101
|
+
const f = findingFor(
|
|
102
|
+
dirname(callerPath(context.filename)),
|
|
103
|
+
deps,
|
|
104
|
+
);
|
|
105
|
+
if (f === null) return;
|
|
106
|
+
const at = context.sourceCode.getLocFromIndex(0);
|
|
107
|
+
context.report({ loc: { start: at, end: at }, ...f });
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The level it is on at for every `paper.tex` in paperlint's own config. */
|
|
115
|
+
export const SOURCES_RULE_LEVELS = {
|
|
116
|
+
"paper/sources-fresh": "warn",
|
|
117
|
+
} as const;
|
package/src/tex-paper.test.ts
CHANGED
|
@@ -1,106 +1,162 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The reading every parse-tree rule shares (`readPaper`)
|
|
3
|
-
*
|
|
4
|
-
* (`reportInPaper`).
|
|
2
|
+
* The reading every parse-tree rule shares (`readPaper`): the paper's text with the files TeX read
|
|
3
|
+
* spliced in — the files the build's record lists, and no others — and where a finding of that text
|
|
4
|
+
* is reported (`reportInPaper`). The record fed to it is TeX's own answer for a planted paper
|
|
5
|
+
* (`fixtures/paper-sources/<name>/tex-truth.json`).
|
|
5
6
|
*/
|
|
6
7
|
import { describe, expect, it } from "vitest";
|
|
7
8
|
import { latexReader } from "./adapters/latex/index.ts";
|
|
8
9
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
10
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
9
11
|
import { join } from "node:path";
|
|
10
12
|
import { texInputsDir } from "./package-dirs.ts";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
import { readPaper, reportInPaper } from "./tex-paper.ts";
|
|
14
|
+
import {
|
|
15
|
+
builtFixture,
|
|
16
|
+
builtPaper,
|
|
17
|
+
fixtureFiles,
|
|
18
|
+
} from "../test/recorded-fixture.ts";
|
|
19
|
+
|
|
20
|
+
/** What `readPaper` reads with: the files held in memory, the LaTeX reader, the record's schema. */
|
|
21
|
+
const proseDeps = (files: Record<string, string>) => ({
|
|
22
|
+
files: memoryFiles(files),
|
|
23
|
+
latex: latexReader,
|
|
24
|
+
codec: sourcesCodec,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const MAIN = "/p/paper.tex";
|
|
28
|
+
const textOf = (files: Record<string, string>): string => files[MAIN] ?? "";
|
|
29
|
+
|
|
30
|
+
describe("readPaper — the paper as the rules read it, from the files TeX read", () => {
|
|
31
|
+
it("🔴 splices a file the record lists where its include stands, and TeX's end of line after it", () => {
|
|
32
|
+
// p1: TeX read sections/intro.tex, and the file's last line ends with a newline.
|
|
33
|
+
const files = builtFixture("p1", "/p");
|
|
34
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
35
|
+
const main = textOf(files);
|
|
36
|
+
const intro = files["/p/sections/intro.tex"] ?? "";
|
|
37
|
+
expect(p.text).toBe(main.replace("\\input{sections/intro}", intro));
|
|
20
38
|
expect(p.main).toBe("paper.tex");
|
|
21
39
|
});
|
|
22
40
|
|
|
23
|
-
it("an include
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
41
|
+
it("an include behind a comment or `\\iffalse` is not spliced: the record does not list it", () => {
|
|
42
|
+
const files = builtFixture("p1", "/p");
|
|
43
|
+
const { text } = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
44
|
+
expect(text).not.toContain("Dead section");
|
|
45
|
+
expect(text).not.toContain("Parked section");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("🔴 a file the text names after \\end{document} is not spliced: TeX never read it", () => {
|
|
49
|
+
// v23-parked-include: `\input{parked}` stands past the end of the document; parked.tex is on
|
|
50
|
+
// disk, and the record lists paper.tex alone.
|
|
51
|
+
const files = builtFixture("v23-parked-include", "/p");
|
|
52
|
+
expect(files["/p/parked.tex"]).toBeDefined();
|
|
53
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
54
|
+
expect(p.text).toBe(textOf(files));
|
|
55
|
+
expect(p.text).not.toContain("\\bibliography{old}");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("🔴 a .tex a filecontents block wrote is spliced where the body \\input's it: TeX typeset its text", () => {
|
|
59
|
+
// v29-generated-input: the record lists body.tex, which the block in paper.tex wrote.
|
|
60
|
+
const files = builtFixture("v29-generated-input", "/p");
|
|
61
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
62
|
+
expect(p.text).toBe(
|
|
63
|
+
textOf(files).replace("\\input{body}", files["/p/body.tex"] ?? ""),
|
|
64
|
+
);
|
|
65
|
+
expect([...new Set(p.segments.map((s) => s.file))]).toEqual([
|
|
66
|
+
"paper.tex",
|
|
67
|
+
"body.tex",
|
|
68
|
+
]);
|
|
32
69
|
});
|
|
33
70
|
|
|
34
|
-
it("the paper
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
71
|
+
it("🔴 with no record, the paper is paper.tex alone: its includes are not read", () => {
|
|
72
|
+
const files = fixtureFiles("p1", "/p");
|
|
73
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
74
|
+
expect(p.text).toBe(textOf(files));
|
|
75
|
+
expect(p.segments).toHaveLength(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("🔴 with a stale record, the paper is paper.tex alone", () => {
|
|
79
|
+
const files = {
|
|
80
|
+
...builtFixture("p1", "/p"),
|
|
81
|
+
"/p/sections/intro.tex": "\\section{Changed}\n",
|
|
82
|
+
};
|
|
83
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
84
|
+
expect(p.text).toBe(textOf(files));
|
|
43
85
|
});
|
|
44
86
|
});
|
|
45
87
|
|
|
46
|
-
describe("
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"/p/sections/b.tex": "B",
|
|
54
|
-
[join(texInputsDir(), "guards.tex")]: "G",
|
|
55
|
-
...extra,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("every file an include in the document body brings in, nested ones too, by absolute path", () => {
|
|
59
|
-
expect(
|
|
60
|
-
bodyFiles("/p/paper.tex", doc("", "\\input{sections/a}"), {
|
|
61
|
-
files: files(),
|
|
62
|
-
latex: latexReader,
|
|
63
|
-
}),
|
|
64
|
-
).toEqual({
|
|
65
|
-
files: ["/p/sections/a.tex", "/p/sections/b.tex"],
|
|
66
|
-
missing: [],
|
|
67
|
-
});
|
|
88
|
+
describe("readPaper — which files, in which order", () => {
|
|
89
|
+
it("a file the record lists that is not on disk (it was absent when recorded and still is) contributes nothing", () => {
|
|
90
|
+
const files = builtPaper("/p", { "paper.tex": "x\\input{gone}y" }, [
|
|
91
|
+
{ path: "paper.tex", role: "body" },
|
|
92
|
+
{ path: "gone.tex", role: "body" },
|
|
93
|
+
]);
|
|
94
|
+
expect(readPaper(MAIN, textOf(files), proseDeps(files)).text).toBe("xy");
|
|
68
95
|
});
|
|
69
96
|
|
|
70
|
-
it("
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
97
|
+
it("files are found in the paper's directory only: paperlint's inputs are not the paper's", () => {
|
|
98
|
+
const files = {
|
|
99
|
+
...builtPaper("/p", { "paper.tex": "x\\input{guards}y" }, [
|
|
100
|
+
{ path: "paper.tex", role: "body" },
|
|
101
|
+
]),
|
|
102
|
+
[join(texInputsDir(), "guards.tex")]: "G",
|
|
103
|
+
};
|
|
104
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
105
|
+
expect(p.text).toBe("xy");
|
|
78
106
|
});
|
|
79
107
|
|
|
80
|
-
it("
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
it("nested includes are spliced in the order TeX read them, each once", () => {
|
|
109
|
+
const files = builtPaper(
|
|
110
|
+
"/p",
|
|
111
|
+
{
|
|
112
|
+
"paper.tex": "A\n\\input{sections/a}\nZ\n",
|
|
113
|
+
"sections/a.tex": "a1\n\\input{sections/b}\na2\n",
|
|
114
|
+
"sections/b.tex": "b1",
|
|
115
|
+
},
|
|
116
|
+
[
|
|
117
|
+
{ path: "paper.tex", role: "body" },
|
|
118
|
+
{ path: "sections/a.tex", role: "body" },
|
|
119
|
+
{ path: "sections/b.tex", role: "body" },
|
|
120
|
+
],
|
|
121
|
+
);
|
|
122
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
123
|
+
expect(p.text).toBe("A\na1\nb1\n\na2\n\nZ\n");
|
|
124
|
+
expect([...new Set(p.segments.map((s) => s.file))]).toEqual([
|
|
125
|
+
"paper.tex",
|
|
126
|
+
"sections/a.tex",
|
|
127
|
+
"sections/b.tex",
|
|
128
|
+
]);
|
|
87
129
|
});
|
|
88
130
|
|
|
89
|
-
it("a
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
it("a preamble file the record lists is spliced too: it is a file of the paper", () => {
|
|
132
|
+
const files = builtPaper(
|
|
133
|
+
"/p",
|
|
134
|
+
{
|
|
135
|
+
"paper.tex": "\\input{macros}\n\\begin{document}\nText.\n",
|
|
136
|
+
"macros.tex": "\\newcommand{\\x}{y}\n",
|
|
137
|
+
},
|
|
138
|
+
[
|
|
139
|
+
{ path: "paper.tex", role: "body" },
|
|
140
|
+
{ path: "macros.tex", role: "preamble" },
|
|
141
|
+
],
|
|
142
|
+
);
|
|
143
|
+
const p = readPaper(MAIN, textOf(files), proseDeps(files));
|
|
144
|
+
expect(p.text).toBe("\\newcommand{\\x}{y}\n\n\\begin{document}\nText.\n");
|
|
96
145
|
});
|
|
97
146
|
});
|
|
98
147
|
|
|
99
148
|
describe("reportInPaper — where a finding of the assembled text is reported", () => {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
149
|
+
const main = "x\n\\input{a}\ny";
|
|
150
|
+
const paper = readPaper(
|
|
151
|
+
"/p/paper.tex",
|
|
152
|
+
main,
|
|
153
|
+
proseDeps(
|
|
154
|
+
builtPaper("/p", { "paper.tex": main, "a.tex": "one\ntwo" }, [
|
|
155
|
+
{ path: "paper.tex", role: "body" },
|
|
156
|
+
{ path: "a.tex", role: "body" },
|
|
157
|
+
]),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
104
160
|
const report = (
|
|
105
161
|
at: { start: number; end: number } | null,
|
|
106
162
|
messageId = "m",
|
|
@@ -146,3 +202,17 @@ describe("reportInPaper — where a finding of the assembled text is reported",
|
|
|
146
202
|
]);
|
|
147
203
|
});
|
|
148
204
|
});
|
|
205
|
+
|
|
206
|
+
describe("readPaper — how an include names its file", () => {
|
|
207
|
+
it("🔴 an include spelled otherwise than the disk is the file the record names: TeX opened it by either spelling", () => {
|
|
208
|
+
const files = builtPaper(
|
|
209
|
+
"/p",
|
|
210
|
+
{ "paper.tex": "x\\input{Sections/Intro}y", "sections/intro.tex": "I" },
|
|
211
|
+
[
|
|
212
|
+
{ path: "paper.tex", role: "body" },
|
|
213
|
+
{ path: "sections/intro.tex", role: "body" },
|
|
214
|
+
],
|
|
215
|
+
);
|
|
216
|
+
expect(readPaper(MAIN, textOf(files), proseDeps(files)).text).toBe("xI\ny");
|
|
217
|
+
});
|
|
218
|
+
});
|
package/src/tex-paper.ts
CHANGED
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* file at the include in `paper.tex` that brought it in — the one place ESLint can point to in this
|
|
8
8
|
* file — with the included file, line and column at the front of the message.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* 🔴 WHICH FILES ARE THE PAPER'S IS TEX'S ANSWER, NOT THIS MODULE'S (docs/design/paper-sources.md §1).
|
|
11
|
+
* `readPaper` splices only the files the build's record lists (`_build/sources.json`, `paperRecord`):
|
|
12
|
+
* the files of the paper directory TeX read. An include of a file the record does not list — behind
|
|
13
|
+
* `\iffalse`, never reached — contributes nothing, as it did to TeX. With no
|
|
14
|
+
* record, or a stale one, the paper is `paper.tex` alone, and `paper/sources-fresh` says the files it
|
|
15
|
+
* includes went unlinted. Where an include stands in the text is the only thing read from the text:
|
|
16
|
+
* whether the file it names is part of the paper is the record's to say, never the disk's.
|
|
13
17
|
*/
|
|
14
18
|
import { basename, dirname, join } from "node:path";
|
|
15
19
|
import {
|
|
@@ -21,8 +25,8 @@ import type { Span } from "./domain/tex-document.ts";
|
|
|
21
25
|
import type { LatexReader } from "./ports/latex.ts";
|
|
22
26
|
import type { Files } from "./ports/files.ts";
|
|
23
27
|
import { callerPath } from "./caller-path.ts";
|
|
24
|
-
import type
|
|
25
|
-
import {
|
|
28
|
+
import { paperRecord, type RecordReadDeps } from "./paper-record.ts";
|
|
29
|
+
import { spelledIn } from "./domain/sources-record.ts";
|
|
26
30
|
import type { Located, TexRuleContext } from "./tex-venue-rules.ts";
|
|
27
31
|
|
|
28
32
|
/** What reading a paper needs: the disk, and the LaTeX reader. */
|
|
@@ -31,75 +35,41 @@ export interface PaperDeps {
|
|
|
31
35
|
readonly latex: LatexReader;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
/**
|
|
38
|
+
/** What `readPaper` needs: the disk, the LaTeX reader (where an include stands), and the record's schema. */
|
|
39
|
+
export interface ProseDeps extends PaperDeps, RecordReadDeps {}
|
|
40
|
+
|
|
41
|
+
/** The text of a file read from the disk, or null when there was none. */
|
|
42
|
+
export const decoded = (b: Uint8Array | null): string | null =>
|
|
43
|
+
b === null ? null : new TextDecoder().decode(b);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The paper whose main file is `filename` with the text `src`: the files the last build's record lists
|
|
47
|
+
* spliced where their includes stand. No record, or one the paper has outgrown, is `src` alone.
|
|
48
|
+
*/
|
|
35
49
|
export function readPaper(
|
|
36
50
|
filename: string,
|
|
37
51
|
src: string,
|
|
38
|
-
deps:
|
|
52
|
+
deps: ProseDeps,
|
|
39
53
|
): PaperSource {
|
|
40
54
|
const dir = dirname(filename);
|
|
55
|
+
const record = paperRecord(dir, deps);
|
|
56
|
+
const listed =
|
|
57
|
+
record.kind === "fresh" ? record.record.inputs.map((i) => i.path) : [];
|
|
41
58
|
return assemblePaper(basename(filename), src, {
|
|
42
|
-
|
|
59
|
+
// No record: no include is looked at, so there is nothing to splice.
|
|
60
|
+
includes: record.kind === "fresh" ? deps.latex.includes : () => [],
|
|
43
61
|
documentBody: deps.latex.documentBody,
|
|
44
62
|
read: (rel) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
const recorded = spelledIn(listed, rel);
|
|
64
|
+
return recorded === null
|
|
65
|
+
? null
|
|
66
|
+
: decoded(deps.files.readBytes(callerPath(join(dir, recorded))));
|
|
48
67
|
},
|
|
49
68
|
});
|
|
50
69
|
}
|
|
51
70
|
|
|
52
|
-
/** `rel` in the first directory of the paper's TeX search path that holds it, or null. */
|
|
53
|
-
const located = (dir: string, rel: string, files: Files): AbsolutePath | null =>
|
|
54
|
-
texSearchPath(dir)
|
|
55
|
-
.map((d) => callerPath(join(d, rel)))
|
|
56
|
-
.find((p) => files.isFile(p)) ?? null;
|
|
57
|
-
|
|
58
|
-
/** An include that resolved nowhere: the file that wrote it, and the path as written. */
|
|
59
|
-
export interface Unread {
|
|
60
|
-
readonly file: string;
|
|
61
|
-
readonly target: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The files of the body of the paper whose main file is `filename`: every file an include inside its
|
|
66
|
-
* `document` environment brings in, nested ones too, found in the paper's own directory. Not a
|
|
67
|
-
* preamble include (macros are not the body), and not a file found only in paperlint's inputs (not
|
|
68
|
-
* the author's text). `missing` is every include, anywhere, that resolved nowhere.
|
|
69
|
-
*/
|
|
70
|
-
export function bodyFiles(
|
|
71
|
-
filename: string,
|
|
72
|
-
src: string,
|
|
73
|
-
deps: PaperDeps,
|
|
74
|
-
): {
|
|
75
|
-
readonly files: readonly AbsolutePath[];
|
|
76
|
-
readonly missing: readonly Unread[];
|
|
77
|
-
} {
|
|
78
|
-
const dir = dirname(filename);
|
|
79
|
-
const paper = readPaper(filename, src, deps);
|
|
80
|
-
const body = deps.latex.documentBody(src);
|
|
81
|
-
const inBody = (via: Span | null): boolean =>
|
|
82
|
-
via !== null &&
|
|
83
|
-
(body === null || (via.start >= body.start && via.end <= body.end));
|
|
84
|
-
const own = (rel: string): AbsolutePath | null => {
|
|
85
|
-
const at = located(dir, rel, deps.files);
|
|
86
|
-
return at === callerPath(join(dir, rel)) ? at : null;
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
files: [
|
|
90
|
-
...new Set(
|
|
91
|
-
paper.segments
|
|
92
|
-
.filter((s) => s.file !== paper.main && inBody(s.via))
|
|
93
|
-
.map((s) => own(s.file))
|
|
94
|
-
.filter((f): f is AbsolutePath => f !== null),
|
|
95
|
-
),
|
|
96
|
-
],
|
|
97
|
-
missing: paper.missing.map(({ file, target }) => ({ file, target })),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
71
|
/** Line and column (1-based) of an offset in a text. */
|
|
102
|
-
function lineColumn(text: string, at: number): string {
|
|
72
|
+
export function lineColumn(text: string, at: number): string {
|
|
103
73
|
const line = text.slice(0, at).split("\n").length;
|
|
104
74
|
const column = at - text.lastIndexOf("\n", at - 1);
|
|
105
75
|
return `${String(line)}:${String(column)}`;
|
|
@@ -7,6 +7,7 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
9
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
10
|
+
import { sourcesCodec } from "./adapters/sources-record/index.ts";
|
|
10
11
|
import { presetsDir } from "./package-dirs.ts";
|
|
11
12
|
import { latexReader } from "./adapters/latex/index.ts";
|
|
12
13
|
import {
|
|
@@ -21,10 +22,13 @@ import {
|
|
|
21
22
|
} from "./tex-venue-rules.ts";
|
|
22
23
|
import { resolvePreset } from "./presets.ts";
|
|
23
24
|
import { buildConfig, SHIPPED_RULES } from "./cli.ts";
|
|
25
|
+
import { builtPaper } from "../test/recorded-fixture.ts";
|
|
24
26
|
import { venuePreset } from "../test/support.ts";
|
|
25
27
|
|
|
26
28
|
const VENUES = presetsDir();
|
|
27
29
|
const PAPER = "/work/papers/p";
|
|
30
|
+
/** What the rules read with besides the files: the presets, the LaTeX reader, the record's schema. */
|
|
31
|
+
const READERS = { venuesDir: VENUES, latex: latexReader, codec: sourcesCodec };
|
|
28
32
|
const shipped = Object.fromEntries(
|
|
29
33
|
readdirSync(VENUES)
|
|
30
34
|
.filter((f) => f.endsWith(".jsonc") || f.endsWith(".json"))
|
|
@@ -70,7 +74,7 @@ function lint(
|
|
|
70
74
|
...extra,
|
|
71
75
|
});
|
|
72
76
|
const out: Finding[] = [];
|
|
73
|
-
const deps = { files,
|
|
77
|
+
const deps = { files, ...READERS };
|
|
74
78
|
for (const [id, rule] of [
|
|
75
79
|
...Object.entries(texVenueRules(deps)).map(
|
|
76
80
|
([n, r]) => [`tex/${n}`, r] as const,
|
|
@@ -207,13 +211,29 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
|
|
|
207
211
|
"\\documentclass[conference,compsoc]{IEEEtran}",
|
|
208
212
|
"Text.\n\\input{sections/closing}",
|
|
209
213
|
);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
// The build recorded TeX reading both files.
|
|
215
|
+
const extra = builtPaper(
|
|
216
|
+
PAPER,
|
|
217
|
+
{
|
|
218
|
+
"paper.tex": tex,
|
|
219
|
+
"sections/closing.tex": "\\section*{LLM Usage Statement}\nNone.\n",
|
|
220
|
+
},
|
|
221
|
+
[
|
|
222
|
+
{ path: "paper.tex", role: "body" },
|
|
223
|
+
{ path: "sections/closing.tex", role: "body" },
|
|
224
|
+
],
|
|
225
|
+
);
|
|
214
226
|
expect(lint(tex, AIDC, { extra })).toEqual([]);
|
|
215
227
|
// Without the file, the section is missing.
|
|
216
228
|
expect(ids(lint(tex, AIDC))).toEqual(["tex/required-section:missing"]);
|
|
229
|
+
// 🔴 The file on disk and no record of the build: the paper is paper.tex, and the section is missing.
|
|
230
|
+
const unbuilt = {
|
|
231
|
+
[`${PAPER}/sections/closing.tex`]:
|
|
232
|
+
"\\section*{LLM Usage Statement}\nNone.\n",
|
|
233
|
+
};
|
|
234
|
+
expect(ids(lint(tex, AIDC, { extra: unbuilt }))).toEqual([
|
|
235
|
+
"tex/required-section:missing",
|
|
236
|
+
]);
|
|
217
237
|
});
|
|
218
238
|
|
|
219
239
|
it("a leftover venue name in an included file is reported at the \\input, naming the file and line", () => {
|
|
@@ -222,9 +242,17 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
|
|
|
222
242
|
"Text.\n\\input{intro}\n\\section*{LLM Usage Statement}\nNone.",
|
|
223
243
|
);
|
|
224
244
|
const fs = lint(tex, AIDC, {
|
|
225
|
-
extra:
|
|
226
|
-
|
|
227
|
-
|
|
245
|
+
extra: builtPaper(
|
|
246
|
+
PAPER,
|
|
247
|
+
{
|
|
248
|
+
"paper.tex": tex,
|
|
249
|
+
"intro.tex": "One line.\nFirst written for AgenticDev.\n",
|
|
250
|
+
},
|
|
251
|
+
[
|
|
252
|
+
{ path: "paper.tex", role: "body" },
|
|
253
|
+
{ path: "intro.tex", role: "body" },
|
|
254
|
+
],
|
|
255
|
+
),
|
|
228
256
|
});
|
|
229
257
|
expect(fs.map((f) => [f.line, f.message.split(": ")[0]])).toEqual([
|
|
230
258
|
[5, "intro.tex:2:19"],
|
package/src/tex-venue-rules.ts
CHANGED
|
@@ -49,7 +49,7 @@ import type { LatexReader } from "./ports/latex.ts";
|
|
|
49
49
|
import { paperPreset, shippedVenueNames, type Preset } from "./presets.ts";
|
|
50
50
|
import type { NamedVenue } from "./domain/venue-name.ts";
|
|
51
51
|
import type { PaperSource } from "./domain/paper-source.ts";
|
|
52
|
-
import { readPaper, reportInPaper } from "./tex-paper.ts";
|
|
52
|
+
import { readPaper, reportInPaper, type ProseDeps } from "./tex-paper.ts";
|
|
53
53
|
import type { RequiredSection } from "./tex-requirements.ts";
|
|
54
54
|
import type { Finding, VenueRuleDeps } from "./venue-rules.ts";
|
|
55
55
|
import { isPorting } from "./domain/cycle.ts";
|
|
@@ -476,10 +476,8 @@ export function otherVenues(
|
|
|
476
476
|
.map(({ label, aliases, mentions }) => ({ label, aliases, mentions }));
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
-
/** What the venue-conformance rules are built with: the preset store, and the
|
|
480
|
-
export interface TexVenueRuleDeps extends VenueRuleDeps {
|
|
481
|
-
readonly latex: LatexReader;
|
|
482
|
-
}
|
|
479
|
+
/** What the venue-conformance rules are built with: the preset store, the LaTeX reader, and the paper's files as the build recorded them. */
|
|
480
|
+
export interface TexVenueRuleDeps extends VenueRuleDeps, ProseDeps {}
|
|
483
481
|
|
|
484
482
|
/**
|
|
485
483
|
* What a judge reads for the paper whose `paper.tex` is `filename` — the paper with its includes
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { RuleContext } from "./rule-context.ts";
|
|
2
|
-
declare const _default: {
|
|
3
|
-
rules: {
|
|
4
|
-
"reachable-entry": {
|
|
5
|
-
meta: {
|
|
6
|
-
type: "suggestion";
|
|
7
|
-
docs: {
|
|
8
|
-
description: string;
|
|
9
|
-
};
|
|
10
|
-
schema: never[];
|
|
11
|
-
messages: {
|
|
12
|
-
unreachable: string;
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
create(context: RuleContext): {
|
|
16
|
-
"root:exit"(): void;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export default _default;
|
|
22
|
-
//# sourceMappingURL=bib-reachable-entry.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bib-reachable-entry.d.ts","sourceRoot":"","sources":["../../eslint-rules/bib-reachable-entry.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;;;;;;;;;;;;;;4BAsB/B,WAAW;;;;;;AAfjC,wBA4CE"}
|