paperlint 7.2.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 +41 -30
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +9 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -68
- 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/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +10 -13
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +6 -5
- 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 +34 -96
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/papers.d.ts +1 -3
- package/dist/eslint-rules/papers.d.ts.map +1 -1
- package/dist/eslint-rules/papers.js +2 -4
- package/dist/eslint-rules/papers.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +8 -25
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +1 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -9
- package/dist/init.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/lib/markdown.d.ts +5 -81
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +5 -116
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +0 -2
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/lib/skill-eval-fixture.d.ts +6 -4
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
- package/dist/lib/skill-eval-fixture.js +25 -18
- package/dist/lib/skill-eval-fixture.js.map +1 -1
- package/dist/new-paper.d.ts +5 -11
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +12 -23
- package/dist/new-paper.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-files.d.ts +4 -4
- 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 +17 -0
- package/dist/paper-prose.d.ts.map +1 -0
- package/dist/paper-prose.js +46 -0
- package/dist/paper-prose.js.map +1 -0
- 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/structure.d.ts.map +1 -1
- package/dist/structure.js +4 -3
- package/dist/structure.js.map +1 -1
- 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/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/configuration.md +18 -12
- package/docs/design/paper-sources.md +197 -0
- package/docs/e2e.md +4 -6
- package/docs/package-shape-options.md +1 -1
- package/docs/prior-art/paper-folder-scaffolding.md +24 -29
- 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/tex/claim-provenance.md +0 -1
- package/docs/rules.md +62 -61
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.ts +10 -13
- package/eslint-rules/paper-context.ts +6 -5
- package/eslint-rules/paper-typography.test.mjs +29 -157
- package/eslint-rules/paper-typography.ts +35 -104
- package/eslint-rules/papers.harness.mjs +1 -1
- package/eslint-rules/papers.ts +2 -4
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/tex-build.ts +8 -25
- package/eslint.config.mjs +22 -1
- 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/hooks/hooks.harness.ts +15 -15
- package/hooks/paper-edit-guard.hook.mjs +5 -8
- package/hooks/paper-edit-guard.test.ts +12 -3
- package/hooks/paper-skills-nudge.hook.mjs +2 -3
- package/hooks/paper-skills-nudge.test.ts +13 -3
- package/hooks/paper-status-gates.hook.mjs +2 -2
- package/hooks/paper-status-gates.test.ts +14 -2
- package/lib/markdown.test.mjs +5 -60
- package/lib/markdown.ts +9 -136
- package/lib/paper-config.ts +0 -2
- package/lib/skill-checks.test.mjs +4 -1
- package/lib/skill-eval-fixture.ts +25 -18
- package/package.json +2 -12
- package/presets/realm.jsonc +1 -1
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +19 -10
- package/scripts/harness-api.frozen.json +0 -5
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/cold-read-diff/SKILL.md +3 -3
- package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
- package/skills/grade-paper-writing/SKILL.md +15 -22
- package/skills/grade-paper-writing/SKILL.md.spec.ts +14 -21
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
- package/skills/grade-paper-writing/prose-lint.mjs +117 -278
- package/skills/grade-paper-writing/prose-lint.test.ts +230 -79
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +9 -12
- package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
- package/skills/paper-pipeline/description-language.eval.mjs +25 -15
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
- package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
- package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
- package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
- package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
- package/skills/paper-pipeline/scripts/README.md +0 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +91 -105
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +85 -241
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +166 -153
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -2
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/extract-pdf-facts.mjs +2 -2
- package/skills/tighten-paper/SKILL.md +29 -42
- package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
- 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 -32
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +138 -43
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +54 -193
- package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
- 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.harness.mjs +2 -2
- package/src/build.test.ts +211 -1
- package/src/build.ts +101 -41
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +46 -56
- package/src/cli.test.ts +44 -48
- package/src/cli.ts +51 -82
- 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/folder-venue-rule.test.ts +1 -1
- package/src/heading-case.test.ts +24 -10
- package/src/init.test.ts +6 -7
- package/src/init.ts +3 -22
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/lint-fix.test.ts +10 -4
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +12 -30
- package/src/new-paper.test.ts +15 -11
- package/src/new-paper.ts +11 -30
- package/src/package-dirs.ts +3 -2
- package/src/paper-files.test.ts +5 -4
- package/src/paper-files.ts +4 -4
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +78 -0
- package/src/paper-prose.ts +67 -0
- 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/parse-args.test.ts +0 -1
- 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/structure.harness.ts +5 -11
- package/src/structure.ts +4 -3
- 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/src/types.ts +0 -2
- 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/fixtures/real-markdown-paper/baseline.json +0 -22
- package/fixtures/real-markdown-paper/baseline.ts +0 -70
- package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
- package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
- package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
- package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
- package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
- package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
- package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
- package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
- package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
- package/skills/tighten-paper/structure.mjs +0 -364
- package/skills/tighten-paper/structure.test.ts +0 -117
- package/templates/paper/paper.md +0 -4
|
@@ -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/structure.harness.ts
CHANGED
|
@@ -47,7 +47,6 @@ const paper = (name: string, files: readonly string[]) => {
|
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
49
|
paper("complete", ["PIPELINE-STATUS.md", "paper.tex", "refs.bib"]);
|
|
50
|
-
paper("complete-md", ["PIPELINE-STATUS.md", "paper.md"]);
|
|
51
50
|
// the marker is present (paper.tex), the scorecard is not — the directory is linted by ZERO
|
|
52
51
|
// rules and reports clean
|
|
53
52
|
paper("no-scorecard", ["paper.tex", "refs.bib"]);
|
|
@@ -65,10 +64,6 @@ try {
|
|
|
65
64
|
"a complete paper directory — NOT A SINGLE finding",
|
|
66
65
|
at("complete").length === 0,
|
|
67
66
|
);
|
|
68
|
-
check(
|
|
69
|
-
"and `paper.md` counts on equal footing with `paper.tex` — the corpus holds both forms",
|
|
70
|
-
at("complete-md").length === 0,
|
|
71
|
-
);
|
|
72
67
|
check(
|
|
73
68
|
"a missing scorecard — a finding",
|
|
74
69
|
at("no-scorecard").length === 1 &&
|
|
@@ -86,10 +81,11 @@ try {
|
|
|
86
81
|
/no-scorecard/.test(at("no-scorecard")[0]?.message ?? ""),
|
|
87
82
|
);
|
|
88
83
|
check(
|
|
89
|
-
"a directory with no source — a finding
|
|
84
|
+
"a directory with no source — a finding naming `paper.tex`",
|
|
90
85
|
at("no-source").length === 1 &&
|
|
91
|
-
/`paper\.tex
|
|
92
|
-
|
|
86
|
+
/`paper\.tex` — a paper directory with no source/.test(
|
|
87
|
+
at("no-source")[0]?.message ?? "",
|
|
88
|
+
),
|
|
93
89
|
);
|
|
94
90
|
|
|
95
91
|
// 🔴 THE PAIRED HALF: detection is GENEROUS. Without this the check would scream about every
|
|
@@ -124,9 +120,7 @@ try {
|
|
|
124
120
|
{
|
|
125
121
|
cwd: root,
|
|
126
122
|
},
|
|
127
|
-
).some(
|
|
128
|
-
(x) => x.file.endsWith("complete-md") && /refs\.bib/.test(x.message),
|
|
129
|
-
),
|
|
123
|
+
).some((x) => x.file.endsWith("no-source") && /refs\.bib/.test(x.message)),
|
|
130
124
|
);
|
|
131
125
|
check(
|
|
132
126
|
"a nonexistent root does not crash it — that's the empty-set guard talking",
|
package/src/structure.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { readdirSync, existsSync } from "node:fs";
|
|
|
26
26
|
import { join, relative, basename, isAbsolute, sep } from "node:path";
|
|
27
27
|
import type { StructureConfig, StructureFinding } from "./types.ts";
|
|
28
28
|
import { CONFIG_FILE } from "#lib/paper-config";
|
|
29
|
+
import { SOURCE_FILE } from "./new-paper.ts";
|
|
29
30
|
|
|
30
31
|
/** The requirements after the consumer's config is laid over the defaults. */
|
|
31
32
|
type Rules = Required<StructureConfig>;
|
|
@@ -38,9 +39,9 @@ type Rules = Required<StructureConfig>;
|
|
|
38
39
|
* byte-compared by the `paper/stages` rule anyway.
|
|
39
40
|
*/
|
|
40
41
|
export const STRUCTURE_DEFAULTS = {
|
|
41
|
-
markers: ["PIPELINE-STATUS.md",
|
|
42
|
+
markers: ["PIPELINE-STATUS.md", SOURCE_FILE, CONFIG_FILE],
|
|
42
43
|
require: ["PIPELINE-STATUS.md"],
|
|
43
|
-
requireOneOf: [[
|
|
44
|
+
requireOneOf: [[SOURCE_FILE]],
|
|
44
45
|
ignore: [],
|
|
45
46
|
};
|
|
46
47
|
|
|
@@ -118,7 +119,7 @@ export function checkStructure(
|
|
|
118
119
|
* The message NAMES THE CONSEQUENCE rather than restating the condition. "missing
|
|
119
120
|
* PIPELINE-STATUS.md" without the second half reads as nitpicking about formatting; with it you
|
|
120
121
|
* can see which checks the directory silently loses — the rules that read the scorecard; the rules
|
|
121
|
-
* over `paper.
|
|
122
|
+
* over `paper.tex` still run.
|
|
122
123
|
*/
|
|
123
124
|
function whyMissingMatters(file: string, dirName: string): string {
|
|
124
125
|
if (file === "PIPELINE-STATUS.md")
|
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)}`;
|