paperlint 3.1.3 → 3.2.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/.github/workflows/ci.yml +12 -7
- package/CLAUDE.md +3 -3
- package/CONTRIBUTING.md +34 -5
- package/dist/adapters/banal/index.d.ts.map +1 -1
- package/dist/adapters/banal/index.js +9 -5
- package/dist/adapters/banal/index.js.map +1 -1
- package/dist/adapters/banal/invocation.d.ts +1 -1
- package/dist/adapters/node/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +2 -1
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/node/process.io.d.ts +0 -5
- package/dist/adapters/node/process.io.d.ts.map +1 -1
- package/dist/adapters/node/process.io.js +4 -3
- package/dist/adapters/node/process.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +13 -7
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +4 -5
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts.map +1 -1
- package/dist/adapters/references/pool.js +4 -5
- package/dist/adapters/references/pool.js.map +1 -1
- package/dist/adapters/references/reach.io.d.ts.map +1 -1
- package/dist/adapters/references/reach.io.js +2 -1
- package/dist/adapters/references/reach.io.js.map +1 -1
- package/dist/build-engine.js +2 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +14 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -34
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +3 -2
- package/dist/doctor.js.map +1 -1
- package/dist/domain/lookup-cache.d.ts.map +1 -1
- package/dist/domain/lookup-cache.js +10 -8
- package/dist/domain/lookup-cache.js.map +1 -1
- package/dist/domain/text.d.ts +12 -0
- package/dist/domain/text.d.ts.map +1 -1
- package/dist/domain/text.js +17 -0
- package/dist/domain/text.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -3
- package/dist/engine.js.map +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +22 -0
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +1 -0
- package/dist/eslint-rules/bib-reachable-entry.js +75 -0
- package/dist/eslint-rules/bib-reachable-entry.js.map +1 -0
- package/dist/eslint-rules/install-path-literals.d.ts +80 -0
- package/dist/eslint-rules/install-path-literals.d.ts.map +1 -0
- package/dist/eslint-rules/install-path-literals.js +105 -0
- package/dist/eslint-rules/install-path-literals.js.map +1 -0
- package/dist/eslint-rules/latex-language.d.ts +136 -0
- package/dist/eslint-rules/latex-language.d.ts.map +1 -0
- package/dist/eslint-rules/latex-language.js +639 -0
- package/dist/eslint-rules/latex-language.js.map +1 -0
- package/dist/eslint-rules/paper-research-question.d.ts +31 -0
- package/dist/eslint-rules/paper-research-question.d.ts.map +1 -0
- package/dist/eslint-rules/paper-research-question.js +188 -0
- package/dist/eslint-rules/paper-research-question.js.map +1 -0
- package/dist/eslint-rules/paper-stages.d.ts +83 -0
- package/dist/eslint-rules/paper-stages.d.ts.map +1 -0
- package/dist/eslint-rules/paper-stages.js +358 -0
- package/dist/eslint-rules/paper-stages.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +74 -0
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -0
- package/dist/eslint-rules/paper-typography.js +404 -0
- package/dist/eslint-rules/paper-typography.js.map +1 -0
- package/dist/eslint-rules/papers.d.ts +42 -0
- package/dist/eslint-rules/papers.d.ts.map +1 -0
- package/dist/eslint-rules/papers.js +145 -0
- package/dist/eslint-rules/papers.js.map +1 -0
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +72 -0
- package/dist/eslint-rules/pdf-last-page-balance.d.ts.map +1 -0
- package/dist/eslint-rules/pdf-last-page-balance.js +237 -0
- package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -0
- package/dist/eslint-rules/review-frontmatter.d.ts +57 -0
- package/dist/eslint-rules/review-frontmatter.d.ts.map +1 -0
- package/dist/eslint-rules/review-frontmatter.js +126 -0
- package/dist/eslint-rules/review-frontmatter.js.map +1 -0
- package/dist/eslint-rules/review-frontmatter.schema.json +36 -0
- package/dist/eslint-rules/rule-context.d.ts +55 -0
- package/dist/eslint-rules/rule-context.d.ts.map +1 -0
- package/dist/eslint-rules/rule-context.js +9 -0
- package/dist/eslint-rules/rule-context.js.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.d.ts +25 -0
- package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.js +16 -0
- package/dist/eslint-rules/sibling-frontmatter.js.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.schema.json +14 -0
- package/dist/eslint-rules/temp-root-realpath.d.ts +71 -0
- package/dist/eslint-rules/temp-root-realpath.d.ts.map +1 -0
- package/dist/eslint-rules/temp-root-realpath.js +139 -0
- package/dist/eslint-rules/temp-root-realpath.js.map +1 -0
- package/dist/eslint-rules/tex-build.d.ts +35 -0
- package/dist/eslint-rules/tex-build.d.ts.map +1 -0
- package/dist/eslint-rules/tex-build.js +313 -0
- package/dist/eslint-rules/tex-build.js.map +1 -0
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +2 -1
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts +1 -1
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +14 -9
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +5 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +15 -7
- package/dist/init.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -2
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js +2 -2
- package/dist/latex-loop.js.map +1 -1
- package/dist/lib/agent-cli-version.d.ts +65 -0
- package/dist/lib/agent-cli-version.d.ts.map +1 -0
- package/dist/lib/agent-cli-version.js +77 -0
- package/dist/lib/agent-cli-version.js.map +1 -0
- package/dist/lib/check.d.ts +19 -0
- package/dist/lib/check.d.ts.map +1 -0
- package/dist/lib/check.js +52 -0
- package/dist/lib/check.js.map +1 -0
- package/dist/lib/markdown.d.ts +180 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/dist/lib/markdown.js +302 -0
- package/dist/lib/markdown.js.map +1 -0
- package/dist/lib/paper-config.d.ts +84 -0
- package/dist/lib/paper-config.d.ts.map +1 -0
- package/dist/lib/paper-config.js +142 -0
- package/dist/lib/paper-config.js.map +1 -0
- package/dist/lib/skill-eval-fixture.d.ts +29 -0
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -0
- package/dist/lib/skill-eval-fixture.js +62 -0
- package/dist/lib/skill-eval-fixture.js.map +1 -0
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +5 -4
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +2 -2
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +5 -4
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +2 -1
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -2
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +11 -6
- package/dist/references.js.map +1 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +6 -3
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +8 -3
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +21 -16
- package/dist/toolchain.js.map +1 -1
- package/dist/venue-rules.js +1 -1
- package/dist/venue-rules.js.map +1 -1
- package/docs/e2e.md +7 -7
- package/docs/install.md +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/package-location.md +5 -5
- package/docs/prior-art/repro/README.md +1 -1
- package/docs/prior-art/test-tooling.md +2 -2
- package/docs/testing.md +11 -8
- package/docs/toolchain.md +1 -1
- package/eslint-rules/{bib-reachable-entry.mjs → bib-reachable-entry.ts} +4 -3
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +16 -8
- package/eslint-rules/latex-language.harness.mjs +2 -2
- package/eslint-rules/latex-language.test.mjs +7 -1
- package/eslint-rules/{latex-language.mjs → latex-language.ts} +297 -109
- package/eslint-rules/paper-research-question.harness.mjs +3 -3
- package/eslint-rules/{paper-research-question.mjs → paper-research-question.ts} +34 -15
- package/eslint-rules/paper-stages.harness.mjs +2 -2
- package/eslint-rules/paper-stages.test.mjs +8 -2
- package/eslint-rules/{paper-stages.mjs → paper-stages.ts} +74 -29
- package/eslint-rules/paper-typography.test.mjs +3 -3
- package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +132 -54
- package/eslint-rules/papers.harness.mjs +2 -2
- package/eslint-rules/{papers.mjs → papers.ts} +7 -4
- package/eslint-rules/pdf-last-page-balance.harness.mjs +3 -3
- package/eslint-rules/{pdf-last-page-balance.mjs → pdf-last-page-balance.ts} +88 -31
- package/eslint-rules/review-frontmatter.test.mjs +2 -2
- package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +37 -15
- package/eslint-rules/rule-context.ts +62 -0
- package/eslint-rules/{sibling-frontmatter.mjs → sibling-frontmatter.ts} +1 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +1 -1
- package/eslint-rules/{temp-root-realpath.mjs → temp-root-realpath.ts} +19 -6
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/{tex-build.mjs → tex-build.ts} +4 -3
- package/eslint.config.mjs +139 -30
- package/fixtures/pdf-facts/README.md +2 -2
- package/fixtures/real-markdown-paper/{baseline.mjs → baseline.ts} +31 -10
- package/hooks/{hooks.harness.mjs → hooks.harness.ts} +51 -34
- package/hooks/paper-edit-guard.hook.mjs +1 -1
- package/hooks/{paper-edit-guard.test.mjs → paper-edit-guard.test.ts} +3 -7
- package/hooks/paper-skills-nudge.hook.mjs +1 -1
- package/hooks/{paper-skills-nudge.test.mjs → paper-skills-nudge.test.ts} +2 -6
- package/hooks/{paper-status-gates.test.mjs → paper-status-gates.test.ts} +2 -6
- package/lib/agent-cli-version.harness.mjs +1 -1
- package/lib/{agent-cli-version.mjs → agent-cli-version.ts} +32 -18
- package/lib/check.test.ts +14 -9
- package/lib/{check.mjs → check.ts} +22 -9
- package/lib/markdown.test.mjs +7 -7
- package/lib/{markdown.mjs → markdown.ts} +68 -29
- package/lib/paper-config.harness.mjs +8 -8
- package/lib/{paper-config.mjs → paper-config.ts} +19 -14
- package/lib/skill-checks.mjs +1 -1
- package/lib/skill-checks.test.mjs +1 -1
- package/lib/skill-corpus.mjs +1 -1
- package/lib/skill-eval-kit.mjs +1 -1
- package/package.json +42 -10
- package/scripts/{check.harness.mjs → check.harness.ts} +111 -10
- package/scripts/{check.test.mjs → check.test.ts} +13 -10
- package/scripts/{check.mjs → check.ts} +116 -42
- package/scripts/coverage-config.test.ts +80 -37
- package/scripts/eslint-report-guard.test.mjs +1 -1
- package/scripts/harness-api.frozen.json +14 -15
- package/scripts/harness-api.test.ts +8 -5
- package/scripts/layer-legacy-frozen.test.ts +17 -13
- package/scripts/{layer-legacy-frozen.mjs → layer-legacy-frozen.ts} +128 -39
- package/scripts/release-config.test.ts +12 -4
- package/scripts/rules-see-files.test.mjs +1 -1
- package/scripts/test.test.ts +78 -0
- package/scripts/test.ts +87 -0
- package/skills/grade-paper-writing/prose-lint.mjs +1 -1
- package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +2 -2
- package/skills/paper-pipeline/description-language.eval.mjs +1 -1
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- package/skills/paper-pipeline/scripts/README.md +1 -1
- package/skills/paper-pipeline/scripts/announce.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +1 -1
- package/skills/paper-pipeline/scripts/{artifact-coverage.test.mjs → artifact-coverage.test.ts} +2 -2
- package/skills/paper-pipeline/scripts/consumer.mjs +1 -5
- package/skills/paper-pipeline/scripts/consumer.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +1 -1
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +1 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/fixtures/{scorecard.mjs → scorecard.ts} +36 -7
- package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +1 -4
- package/skills/paper-pipeline/scripts/population-map.mjs +1 -4
- package/skills/paper-pipeline/scripts/population-map.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/{round-diff.harness.mjs → round-diff.harness.ts} +45 -18
- package/skills/paper-pipeline/scripts/round-diff.mjs +1 -1
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/status.test.mjs +1 -1
- package/skills/plan-paper-timeline/fixtures/{fake-google-calendar.mjs → fake-google-calendar.ts} +101 -70
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +3 -3
- package/skills/render-paper/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- package/skills/tighten-paper/structure.mjs +1 -1
- package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
- package/skills/verify-citations/scripts/{bib-authors.cli.test.mjs → bib-authors.cli.test.ts} +14 -4
- package/skills/verify-citations/scripts/bib-authors.d.mts +63 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +1 -4
- package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts} +32 -9
- package/skills/verify-citations/scripts/verify-cites.d.mts +58 -0
- package/src/CLAUDE.md +1 -1
- package/src/adapters/banal/index.test.ts +3 -6
- package/src/adapters/banal/index.ts +7 -4
- package/src/adapters/banal/invocation.test.ts +3 -1
- package/src/adapters/banal/invocation.ts +1 -1
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +3 -3
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/curl/download.test.ts +4 -2
- package/src/adapters/node/files.io.ts +2 -2
- package/src/adapters/node/files.test.ts +3 -1
- package/src/adapters/node/process.io.ts +4 -3
- package/src/adapters/node/process.test.ts +1 -1
- package/src/adapters/node/workspace.test.ts +3 -1
- package/src/adapters/pdfjs/fill.test.ts +2 -2
- package/src/adapters/pdfjs/fill.ts +14 -7
- package/src/adapters/references/index.test.ts +31 -38
- package/src/adapters/references/index.ts +26 -58
- package/src/adapters/references/pool.ts +3 -5
- package/src/adapters/references/reach.io.ts +2 -1
- package/src/build-engine.harness.mjs +1 -1
- package/src/build-engine.test.ts +2 -2
- package/src/build-engine.ts +2 -2
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +15 -14
- package/src/build.ts +28 -19
- package/src/cli.harness.mjs +6 -6
- package/src/cli.test.ts +13 -6
- package/src/cli.ts +39 -37
- package/src/doctor.harness.mjs +3 -3
- package/src/doctor.ts +3 -2
- package/src/domain/lookup-cache.ts +21 -11
- package/src/domain/result.test.ts +2 -2
- package/src/domain/text.test.ts +32 -0
- package/src/domain/text.ts +23 -0
- package/src/engine.harness.mjs +2 -2
- package/src/engine.ts +4 -3
- package/src/facts-file.test.ts +9 -7
- package/src/facts-file.ts +2 -1
- package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +39 -20
- package/src/hooks-settings.ts +20 -9
- package/src/init.test.ts +12 -9
- package/src/init.ts +22 -12
- package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
- package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
- package/src/latex-loop.ts +3 -3
- package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
- package/src/link-skills.test.ts +3 -3
- package/src/link-skills.ts +5 -4
- package/src/lint-fix.test.ts +4 -6
- package/src/linted-set.test.ts +2 -2
- package/src/new-paper.harness.mjs +2 -2
- package/src/new-paper.test.ts +8 -10
- package/src/new-paper.ts +2 -2
- package/src/paper-files.test.ts +3 -5
- package/src/paper-settings-commands.test.ts +5 -4
- package/src/paper-settings.test.ts +5 -5
- package/src/paper-settings.ts +8 -9
- package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
- package/src/pdf-facts.test.ts +10 -11
- package/src/pdf-facts.ts +2 -1
- package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
- package/src/pdf-geometry.ts +3 -1
- package/src/presets.ts +3 -2
- package/src/references.test.ts +51 -43
- package/src/references.ts +14 -7
- package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
- package/src/rules-config.ts +7 -4
- package/src/rules-off.test.ts +6 -4
- package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
- package/src/structure.ts +1 -1
- package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
- package/src/tex-requirements.ts +8 -5
- package/src/toolchain.harness.mjs +2 -2
- package/src/toolchain.test.ts +1 -1
- package/src/toolchain.ts +31 -17
- package/src/venue-rules.test.ts +62 -64
- package/src/venue-rules.ts +1 -1
- package/tsconfig.json +6 -3
- package/tsconfig.pkg.json +10 -0
- package/lib/check.d.mts +0 -17
- package/lib/paper-config.d.mts +0 -31
- package/scripts/layer-legacy-frozen.d.mts +0 -36
- package/scripts/rules-are-content-only.harness.mjs +0 -105
- package/scripts/rules-are-content-only.mjs +0 -148
- package/scripts/rules-are-content-only.test.mjs +0 -61
- package/scripts/test.mjs +0 -44
- package/scripts/test.test.mjs +0 -52
- /package/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
- /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
- /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
- /package/skills/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +0 -0
- /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
package/src/paper-settings.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
ROOT_ONLY_KEYS,
|
|
22
22
|
SETTINGS_KEYS,
|
|
23
23
|
findProjectRoot,
|
|
24
|
-
} from "
|
|
24
|
+
} from "#lib/paper-config";
|
|
25
25
|
import type { AbsolutePath } from "./domain/paths.ts";
|
|
26
26
|
import { err, ok, type Result } from "./domain/result.ts";
|
|
27
27
|
import type { Files } from "./ports/files.ts";
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type Parsed,
|
|
31
31
|
type RuleEntry,
|
|
32
32
|
} from "./rules-config.ts";
|
|
33
|
+
import { messageOf } from "./domain/text.ts";
|
|
33
34
|
|
|
34
35
|
/** What one paper declares. Every absent field is null. */
|
|
35
36
|
export interface PaperSettings {
|
|
@@ -92,11 +93,13 @@ export function parsePaperSettings(
|
|
|
92
93
|
if (!isObject(json)) return err("must be a JSON object");
|
|
93
94
|
const unknown = Object.keys(json).filter((k) => !KNOWN.includes(k));
|
|
94
95
|
if (unknown.length > 0)
|
|
95
|
-
return err(
|
|
96
|
+
return err(
|
|
97
|
+
`unknown key "${String(unknown[0])}" — known keys: ${KNOWN.join(", ")}`,
|
|
98
|
+
);
|
|
96
99
|
const project = Object.keys(json).filter((k) => ROOT_ONLY_KEYS.includes(k));
|
|
97
100
|
if (project.length > 0)
|
|
98
101
|
return err(
|
|
99
|
-
`"${project[0]}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
|
|
102
|
+
`"${String(project[0])}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
|
|
100
103
|
);
|
|
101
104
|
const fields = stringFields(json);
|
|
102
105
|
if (!fields.ok) return fields;
|
|
@@ -123,7 +126,7 @@ function readJson(
|
|
|
123
126
|
try {
|
|
124
127
|
return ok(JSON.parse(new TextDecoder().decode(bytes)));
|
|
125
128
|
} catch (e) {
|
|
126
|
-
return err({ kind: "broken", why: `not JSON (${(e
|
|
129
|
+
return err({ kind: "broken", why: `not JSON (${messageOf(e)})` });
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
|
|
@@ -200,9 +203,5 @@ export function paperRules(
|
|
|
200
203
|
if (settings.rules === null || Array.isArray(settings.rules))
|
|
201
204
|
return { ok: true, value: null };
|
|
202
205
|
const where = `${join(paperDir, CONFIG_FILE)} → "rules"`;
|
|
203
|
-
return parseRuleEntries(
|
|
204
|
-
settings.rules as Readonly<Record<string, unknown>>,
|
|
205
|
-
where,
|
|
206
|
-
shipped,
|
|
207
|
-
);
|
|
206
|
+
return parseRuleEntries(settings.rules, where, shipped);
|
|
208
207
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `pdf-facts.ts` — pdf.js reading REAL PDFs, committed under `fixtures/pdf-facts/`.
|
|
3
3
|
*
|
|
4
|
-
* The pure decisions are tested in `pdf-geometry.harness.
|
|
4
|
+
* The pure decisions are tested in `pdf-geometry.harness.ts` with hand-written words. This file is
|
|
5
5
|
* the other half: that the shell asks pdf.js the right questions, with the right options, and maps
|
|
6
6
|
* what comes back. The expected numbers are the ones poppler reported for the same files in the
|
|
7
7
|
* 2026-09-24 measurement (issue #61), so a pass here is agreement with the tool this replaced.
|
|
@@ -11,23 +11,24 @@ import { writeFileSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { dirname, join, resolve } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { createChecker } from "../lib/check.
|
|
14
|
+
import { createChecker } from "../lib/check.ts";
|
|
15
|
+
import type { PdfFacts } from "./pdf-facts.ts";
|
|
15
16
|
|
|
16
17
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const FIX = resolve(HERE, "..", "fixtures", "pdf-facts");
|
|
18
|
-
const { readPdf } = await import(
|
|
19
|
-
const { classifyLastPage } = await import(
|
|
19
|
+
const { readPdf } = await import("./pdf-facts.ts");
|
|
20
|
+
const { classifyLastPage } = await import("./pdf-geometry.ts");
|
|
20
21
|
|
|
21
22
|
const check = createChecker();
|
|
22
23
|
|
|
23
|
-
const read = async (name) => {
|
|
24
|
+
const read = async (name: string): Promise<PdfFacts> => {
|
|
24
25
|
const r = await readPdf(join(FIX, name));
|
|
25
26
|
assert.ok(r.ok, `${name} must read: ${JSON.stringify(r)}`);
|
|
26
27
|
return r.facts;
|
|
27
28
|
};
|
|
28
|
-
const kinds = (facts) =>
|
|
29
|
+
const kinds = (facts: PdfFacts): string[] =>
|
|
29
30
|
facts.fonts.kind === "drawn" ? facts.fonts.list.map((f) => f.kind) : [];
|
|
30
|
-
const named = (facts, name) =>
|
|
31
|
+
const named = (facts: PdfFacts, name: string) =>
|
|
31
32
|
facts.fonts.kind === "drawn"
|
|
32
33
|
? facts.fonts.list.find((f) => f.name === name)
|
|
33
34
|
: undefined;
|
|
@@ -139,7 +140,7 @@ try {
|
|
|
139
140
|
const boxes = f.layout.flatMap((p, i) =>
|
|
140
141
|
p.boxes.map((b) => ({ ...b, page: i + 1 })),
|
|
141
142
|
);
|
|
142
|
-
const chars = (pred) =>
|
|
143
|
+
const chars = (pred: (b: (typeof boxes)[number]) => boolean) =>
|
|
143
144
|
boxes.filter(pred).reduce((sum, b) => sum + b.text.length, 0);
|
|
144
145
|
check(
|
|
145
146
|
"hidden-text: one layout per page, each US letter",
|
package/src/pdf-facts.test.ts
CHANGED
|
@@ -8,6 +8,7 @@ import assert from "node:assert/strict";
|
|
|
8
8
|
import { getResolvedPDFJS } from "unpdf";
|
|
9
9
|
import { test } from "vitest";
|
|
10
10
|
import { factsOf, failureOf, rawFontOf } from "./pdf-facts.ts";
|
|
11
|
+
import { present } from "../test/support.ts";
|
|
11
12
|
|
|
12
13
|
const lib = await getResolvedPDFJS();
|
|
13
14
|
const H = 792;
|
|
@@ -23,8 +24,8 @@ interface FakeItem {
|
|
|
23
24
|
function page(items: FakeItem[], fonts: Record<string, unknown>) {
|
|
24
25
|
const objs = new Map(Object.entries(fonts));
|
|
25
26
|
return {
|
|
26
|
-
getOperatorList:
|
|
27
|
-
getTextContent:
|
|
27
|
+
getOperatorList: () => Promise.resolve({ fnArray: [], argsArray: [] }),
|
|
28
|
+
getTextContent: () => Promise.resolve({ items }),
|
|
28
29
|
// Iterable like pdf.js's `commonObjs`, with its `has`/`get`.
|
|
29
30
|
commonObjs: {
|
|
30
31
|
has: (id: string) => objs.has(id),
|
|
@@ -42,7 +43,7 @@ function page(items: FakeItem[], fonts: Record<string, unknown>) {
|
|
|
42
43
|
const doc = (pages: ReturnType<typeof page>[]) =>
|
|
43
44
|
({
|
|
44
45
|
numPages: pages.length,
|
|
45
|
-
getPage:
|
|
46
|
+
getPage: (i: number) => Promise.resolve(pages[i - 1]),
|
|
46
47
|
}) as never;
|
|
47
48
|
const item = (
|
|
48
49
|
fontName: string,
|
|
@@ -89,7 +90,7 @@ test("a document with no pages is `empty`; text drawn with no font is refused",
|
|
|
89
90
|
lib,
|
|
90
91
|
);
|
|
91
92
|
assert.equal(r.ok, false);
|
|
92
|
-
assert.equal(
|
|
93
|
+
assert.equal(r.reason, "zero-fonts-on-text");
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
@@ -117,7 +118,7 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
|
117
118
|
lib,
|
|
118
119
|
);
|
|
119
120
|
assert.equal(r.ok, true);
|
|
120
|
-
const boxes = r.
|
|
121
|
+
const boxes = present(r.facts.layout[0], "a first page").boxes;
|
|
121
122
|
assert.deepEqual(
|
|
122
123
|
boxes.map((b) => [b.font, b.size, b.fill]),
|
|
123
124
|
[
|
|
@@ -127,12 +128,10 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
|
127
128
|
["gone", 10, { kind: "unknown" }],
|
|
128
129
|
],
|
|
129
130
|
);
|
|
130
|
-
assert.deepEqual(
|
|
131
|
-
|
|
132
|
-
"word",
|
|
133
|
-
|
|
134
|
-
"word",
|
|
135
|
-
]);
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
r.facts.last.words.map((w) => w.text),
|
|
133
|
+
["word", "word", "word", "word"],
|
|
134
|
+
);
|
|
136
135
|
});
|
|
137
136
|
|
|
138
137
|
test("a failure that is not an Error is named `Error` with its text", () => {
|
package/src/pdf-facts.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import type { PageLayout, TextBox } from "./domain/page-layout.ts";
|
|
35
35
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
36
36
|
import { fillsFor, isUpright } from "./adapters/pdfjs/fill.ts";
|
|
37
|
+
import { messageOf } from "./domain/text.ts";
|
|
37
38
|
|
|
38
39
|
type PdfJs = Awaited<ReturnType<typeof import("unpdf").getResolvedPDFJS>>;
|
|
39
40
|
type Doc = Awaited<ReturnType<PdfJs["getDocument"]>["promise"]>;
|
|
@@ -303,7 +304,7 @@ export const readPdf: PdfReader = async (path) => {
|
|
|
303
304
|
try {
|
|
304
305
|
data = new Uint8Array(readFileSync(path));
|
|
305
306
|
} catch (e) {
|
|
306
|
-
return fail("unreadable", (e
|
|
307
|
+
return fail("unreadable", messageOf(e));
|
|
307
308
|
}
|
|
308
309
|
const lib = await loadPdfJs();
|
|
309
310
|
const task = lib.getDocument({ data, ...PDFJS_OPTIONS });
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* on 2026-08-29 (the midpoint cut, the review build, years mistaken for line numbers), ported from
|
|
7
7
|
* the harness of the function this replaces.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { createChecker } from "../lib/check.mjs";
|
|
9
|
+
import { createChecker } from "../lib/check.ts";
|
|
10
|
+
import type { RawFont } from "./pdf-geometry.ts";
|
|
12
11
|
|
|
13
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
12
|
const {
|
|
15
13
|
classifyLastPage,
|
|
16
14
|
wordsOf,
|
|
@@ -19,14 +17,23 @@ const {
|
|
|
19
17
|
programOf,
|
|
20
18
|
popplerType,
|
|
21
19
|
STUB_WORDS,
|
|
22
|
-
} = await import(
|
|
20
|
+
} = await import("./pdf-geometry.ts");
|
|
23
21
|
|
|
24
22
|
const check = createChecker();
|
|
25
23
|
|
|
24
|
+
/** A row of stacked words: `n` of them from `top` downwards, `step` apart. */
|
|
25
|
+
interface Row {
|
|
26
|
+
x: number;
|
|
27
|
+
top: number;
|
|
28
|
+
n?: number;
|
|
29
|
+
step?: number;
|
|
30
|
+
text?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
const W = 612; // US letter, points
|
|
27
34
|
|
|
28
35
|
/** Words from rows `{x, top, n, step, text}`: `n` words stacked downwards, each 10 pt tall. */
|
|
29
|
-
const words = (rows) =>
|
|
36
|
+
const words = (rows: readonly Row[]) =>
|
|
30
37
|
rows.flatMap(({ x, top, n: count = 1, step = 10, text = "word" }) =>
|
|
31
38
|
Array.from({ length: count }, (_, i) => ({
|
|
32
39
|
x0: x,
|
|
@@ -36,7 +43,7 @@ const words = (rows) =>
|
|
|
36
43
|
text,
|
|
37
44
|
})),
|
|
38
45
|
);
|
|
39
|
-
const page = (rows, widthPt = W) => ({
|
|
46
|
+
const page = (rows: readonly Row[], widthPt = W) => ({
|
|
40
47
|
widthPt,
|
|
41
48
|
heightPt: 792,
|
|
42
49
|
words: words(rows),
|
|
@@ -164,19 +171,19 @@ check(
|
|
|
164
171
|
check(
|
|
165
172
|
"a run splits at whitespace into words with proportional x",
|
|
166
173
|
ws.length === 2 &&
|
|
167
|
-
ws[0]
|
|
174
|
+
ws[0]?.text === "ab" &&
|
|
168
175
|
ws[0].x0 === 100 &&
|
|
169
176
|
ws[0].x1 === 120 &&
|
|
170
|
-
ws[1]
|
|
177
|
+
ws[1]?.x0 === 140,
|
|
171
178
|
);
|
|
172
179
|
check(
|
|
173
180
|
"a word spans ascent above the baseline to descent below it",
|
|
174
|
-
ws[0]
|
|
181
|
+
ws[0]?.y0 === 192 && ws[0].y1 === 202,
|
|
175
182
|
);
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
// ── fonts ───────────────────────────────────────────────────────────────────────────────
|
|
179
|
-
const font = (over) => ({
|
|
186
|
+
const font = (over: Partial<RawFont>): RawFont => ({
|
|
180
187
|
id: "f1",
|
|
181
188
|
name: "ABCDEF+LinLibertineT",
|
|
182
189
|
type: "Type1",
|
|
@@ -227,7 +234,7 @@ check(
|
|
|
227
234
|
]);
|
|
228
235
|
check(
|
|
229
236
|
"fonts: each font once, in the order pages first drew them",
|
|
230
|
-
r.kind === "drawn" && r.list.length === 2 && r.list[1]
|
|
237
|
+
r.kind === "drawn" && r.list.length === 2 && r.list[1]?.name === "B",
|
|
231
238
|
);
|
|
232
239
|
}
|
|
233
240
|
{
|
package/src/pdf-geometry.ts
CHANGED
|
@@ -89,7 +89,9 @@ function isLineNumber(w: Word, widthPt: number): boolean {
|
|
|
89
89
|
*/
|
|
90
90
|
export function classifyLastPage(p: PageText): LastPage {
|
|
91
91
|
if (!(p.widthPt > 0))
|
|
92
|
-
throw new RangeError(
|
|
92
|
+
throw new RangeError(
|
|
93
|
+
`page width must be positive, got ${String(p.widthPt)}`,
|
|
94
|
+
);
|
|
93
95
|
if (p.words.length < STUB_WORDS)
|
|
94
96
|
return { kind: "stub", words: p.words.length };
|
|
95
97
|
const lineNumbers = p.words.filter((w) => isLineNumber(w, p.widthPt)).length;
|
package/src/presets.ts
CHANGED
|
@@ -47,7 +47,8 @@ import {
|
|
|
47
47
|
type PaperSettings,
|
|
48
48
|
type SettingsProblem,
|
|
49
49
|
} from "./paper-settings.ts";
|
|
50
|
-
import { CONFIG_FILE } from "
|
|
50
|
+
import { CONFIG_FILE } from "#lib/paper-config";
|
|
51
|
+
import { messageOf } from "./domain/text.ts";
|
|
51
52
|
|
|
52
53
|
/** The prefix of a shipped preset's spec. */
|
|
53
54
|
export const SHIPPED_PREFIX = "paperlint:";
|
|
@@ -132,7 +133,7 @@ function readPreset(
|
|
|
132
133
|
return err({
|
|
133
134
|
kind: "broken",
|
|
134
135
|
file: where.value,
|
|
135
|
-
why: (e
|
|
136
|
+
why: messageOf(e).split("\n").join(" "),
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
}
|
package/src/references.test.ts
CHANGED
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
type LookupCache,
|
|
33
33
|
} from "./domain/lookup-cache.ts";
|
|
34
34
|
import { referenceRules, REFERENCE_RULE_LEVELS } from "./reference-rules.ts";
|
|
35
|
-
|
|
36
|
-
import {
|
|
35
|
+
import { texLanguage } from "../eslint-rules/latex-language.ts";
|
|
36
|
+
import { present } from "../test/support.ts";
|
|
37
37
|
|
|
38
38
|
const PAPER = "/work/papers/p";
|
|
39
39
|
const TEX = (entries: string) =>
|
|
@@ -63,14 +63,16 @@ const verdict = (
|
|
|
63
63
|
|
|
64
64
|
const checker =
|
|
65
65
|
(entries: readonly EntryVerdict[]): CheckReferences =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
66
|
+
(_bib, cache) =>
|
|
67
|
+
Promise.resolve({ check: { kind: "checked", entries }, cache });
|
|
68
|
+
const offline: CheckReferences = (_bib, cache) =>
|
|
69
|
+
Promise.resolve({
|
|
70
|
+
check: {
|
|
71
|
+
kind: "not-checked",
|
|
72
|
+
why: "the citation services cannot be reached (fetch failed)",
|
|
73
|
+
},
|
|
74
|
+
cache,
|
|
75
|
+
});
|
|
74
76
|
|
|
75
77
|
/** The step's context over `files`, with every port it does not use stubbed. */
|
|
76
78
|
const ctx = (files: ReturnType<typeof memoryFiles>) => ({
|
|
@@ -122,7 +124,7 @@ async function lint(files: ReturnType<typeof memoryFiles>, tex: string) {
|
|
|
122
124
|
const [res] = await eslint.lintText(tex, {
|
|
123
125
|
filePath: join(PAPER, "paper.tex"),
|
|
124
126
|
});
|
|
125
|
-
return res
|
|
127
|
+
return present(res, "one lint result").messages;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
describe("the references build step", () => {
|
|
@@ -135,7 +137,9 @@ describe("the references build step", () => {
|
|
|
135
137
|
expect(out.ok).toBe(true);
|
|
136
138
|
const doc = readReferences(files, PAPER);
|
|
137
139
|
expect(doc?.status).toBe("checked");
|
|
138
|
-
expect(doc?.bib.sha256).toBe(
|
|
140
|
+
expect(doc?.bib.sha256).toBe(
|
|
141
|
+
bibHash(present(bibliographyOf(files, PAPER), "the bibliography")),
|
|
142
|
+
);
|
|
139
143
|
expect(await lint(files, tex)).toEqual([]);
|
|
140
144
|
});
|
|
141
145
|
|
|
@@ -148,13 +152,13 @@ describe("the references build step", () => {
|
|
|
148
152
|
expect(msgs.map((m) => [m.ruleId, m.severity])).toEqual([
|
|
149
153
|
["paper/refs-checked", 1],
|
|
150
154
|
]);
|
|
151
|
-
expect(msgs[0]
|
|
155
|
+
expect(msgs[0]?.message).toMatch(/cannot be reached/);
|
|
152
156
|
});
|
|
153
157
|
|
|
154
158
|
it("a checker that throws is recorded as not checked, not as a failed build", async () => {
|
|
155
|
-
const { files, out } = await build(TEX(ENTRIES),
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
const { files, out } = await build(TEX(ENTRIES), () =>
|
|
160
|
+
Promise.reject(new Error("DBLP exploded")),
|
|
161
|
+
);
|
|
158
162
|
expect(out.ok).toBe(true);
|
|
159
163
|
expect(readReferences(files, PAPER)?.why).toMatch(/DBLP exploded/);
|
|
160
164
|
});
|
|
@@ -167,9 +171,9 @@ const ANSWER: CachedResponse = {
|
|
|
167
171
|
/** A checker that fetches `key` unless the cache has it, recording the cache it was handed. */
|
|
168
172
|
function fetching(key: string) {
|
|
169
173
|
const seen: LookupCache[] = [];
|
|
170
|
-
const check: CheckReferences =
|
|
174
|
+
const check: CheckReferences = (_bib, cache) => {
|
|
171
175
|
seen.push(cache);
|
|
172
|
-
return {
|
|
176
|
+
return Promise.resolve({
|
|
173
177
|
check: { kind: "checked", entries: [verdict("schick2023")] },
|
|
174
178
|
cache: cache.citations.has(key)
|
|
175
179
|
? cache
|
|
@@ -177,7 +181,7 @@ function fetching(key: string) {
|
|
|
177
181
|
citations: new Map([...cache.citations, [key, ANSWER]]),
|
|
178
182
|
dblp: cache.dblp,
|
|
179
183
|
},
|
|
180
|
-
};
|
|
184
|
+
});
|
|
181
185
|
};
|
|
182
186
|
return { check, seen };
|
|
183
187
|
}
|
|
@@ -210,23 +214,26 @@ describe("the lookup cache — <paper>/repro/references-cache.json", () => {
|
|
|
210
214
|
note: "references: 1 checked, 0 failing → _build/references.json; nothing fetched",
|
|
211
215
|
});
|
|
212
216
|
// The second build was handed exactly what the first one wrote.
|
|
213
|
-
expect(parseLookupCache(written
|
|
217
|
+
expect(parseLookupCache(present(written, "the written cache"))).toEqual(
|
|
218
|
+
ok(f.seen[1]),
|
|
219
|
+
);
|
|
214
220
|
expect(cacheText(files)).toBe(written);
|
|
215
221
|
});
|
|
216
222
|
});
|
|
217
223
|
|
|
218
224
|
describe("the lookup cache — counting and refusing", () => {
|
|
219
225
|
it("two new answers are counted as two", async () => {
|
|
220
|
-
const two: CheckReferences =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
[
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
226
|
+
const two: CheckReferences = (_bib, cache) =>
|
|
227
|
+
Promise.resolve({
|
|
228
|
+
check: { kind: "checked", entries: [verdict("schick2023")] },
|
|
229
|
+
cache: {
|
|
230
|
+
citations: new Map([
|
|
231
|
+
["a", ANSWER],
|
|
232
|
+
["b", ANSWER],
|
|
233
|
+
]),
|
|
234
|
+
dblp: cache.dblp,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
230
237
|
const { out } = await build(TEX(ENTRIES), two);
|
|
231
238
|
expect(out).toEqual({
|
|
232
239
|
ok: true,
|
|
@@ -271,8 +278,8 @@ describe("the reference rules", () => {
|
|
|
271
278
|
);
|
|
272
279
|
const msgs = await lint(files, tex);
|
|
273
280
|
expect(msgs.map((m) => m.ruleId)).toEqual(["paper/author-list"]);
|
|
274
|
-
expect(msgs[0]
|
|
275
|
-
expect(msgs[0]
|
|
281
|
+
expect(msgs[0]?.line).toBe(3);
|
|
282
|
+
expect(msgs[0]?.message).toMatch(/`schick2023`.*missing hambro/);
|
|
276
283
|
});
|
|
277
284
|
|
|
278
285
|
it("an identifier that provably fails → paper/cite-exists on that entry", async () => {
|
|
@@ -311,7 +318,7 @@ describe("the reference rules", () => {
|
|
|
311
318
|
const files = memoryFiles({});
|
|
312
319
|
const msgs = await lint(files, TEX(ENTRIES));
|
|
313
320
|
expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
|
|
314
|
-
expect(msgs[0]
|
|
321
|
+
expect(msgs[0]?.message).toMatch(/npx paperlint build/);
|
|
315
322
|
});
|
|
316
323
|
|
|
317
324
|
it("a paper with no bibliography gets nothing", async () => {
|
|
@@ -354,7 +361,7 @@ const record = (
|
|
|
354
361
|
files: ReturnType<typeof memoryFiles>,
|
|
355
362
|
body: Record<string, unknown>,
|
|
356
363
|
) => {
|
|
357
|
-
const bib = bibliographyOf(files, PAPER)
|
|
364
|
+
const bib = present(bibliographyOf(files, PAPER), "the bibliography");
|
|
358
365
|
files.writeAtomic(
|
|
359
366
|
referencesPath(PAPER) as AbsolutePath,
|
|
360
367
|
new TextEncoder().encode(
|
|
@@ -374,7 +381,7 @@ describe("the reference rules over a record written by hand", () => {
|
|
|
374
381
|
record(files, { status: "not-checked", entries: [] });
|
|
375
382
|
const msgs = await lint(files, tex);
|
|
376
383
|
expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
|
|
377
|
-
expect(msgs[0]
|
|
384
|
+
expect(msgs[0]?.message).toMatch(/no reason recorded/);
|
|
378
385
|
});
|
|
379
386
|
|
|
380
387
|
it("a verdict without a reason, and one for a key the bibliography lacks, still report", async () => {
|
|
@@ -410,7 +417,7 @@ describe("the reference rules over refs.bib, and on other files", () => {
|
|
|
410
417
|
expect(msgs.map((m) => [m.ruleId, m.line])).toEqual([
|
|
411
418
|
["paper/author-list", 1],
|
|
412
419
|
]);
|
|
413
|
-
expect(msgs[0]
|
|
420
|
+
expect(msgs[0]?.message).toMatch(/schick2023/);
|
|
414
421
|
});
|
|
415
422
|
|
|
416
423
|
it("only paper.tex is judged", () => {
|
|
@@ -430,13 +437,14 @@ describe("the lookup cache — refreshed answers", () => {
|
|
|
430
437
|
const OLD: CachedResponse = { ...ANSWER, fetched: "2026-08-01" };
|
|
431
438
|
const put =
|
|
432
439
|
(answer: CachedResponse): CheckReferences =>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
+
(_bib, cache) =>
|
|
441
|
+
Promise.resolve({
|
|
442
|
+
check: { kind: "checked", entries: [verdict("schick2023")] },
|
|
443
|
+
cache: {
|
|
444
|
+
citations: new Map([...cache.citations, ["a", answer]]),
|
|
445
|
+
dblp: cache.dblp,
|
|
446
|
+
},
|
|
447
|
+
});
|
|
440
448
|
const { files } = await build(TEX(ENTRIES), put(OLD));
|
|
441
449
|
const again = await referencesStep.run({
|
|
442
450
|
...ctx(files),
|
package/src/references.ts
CHANGED
|
@@ -44,8 +44,8 @@ import type {
|
|
|
44
44
|
ReferencesCheck,
|
|
45
45
|
ReferencesRun,
|
|
46
46
|
} from "./ports/check-references.ts";
|
|
47
|
-
|
|
48
|
-
import {
|
|
47
|
+
import { bibRange } from "#eslint-rules/paper-typography";
|
|
48
|
+
import { messageOf } from "./domain/text.ts";
|
|
49
49
|
|
|
50
50
|
export const REFERENCES_SCHEMA = 1;
|
|
51
51
|
export const REFERENCES_FILE = "references.json";
|
|
@@ -109,6 +109,15 @@ export const documentOf = (
|
|
|
109
109
|
: { status: "not-checked" as const, why: check.why, entries: [] }),
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
/** A parsed `references.json` of this schema, its entries a list. */
|
|
113
|
+
const isReferencesDocument = (d: unknown): d is ReferencesDocument =>
|
|
114
|
+
typeof d === "object" &&
|
|
115
|
+
d !== null &&
|
|
116
|
+
"schema" in d &&
|
|
117
|
+
d.schema === REFERENCES_SCHEMA &&
|
|
118
|
+
"entries" in d &&
|
|
119
|
+
Array.isArray(d.entries);
|
|
120
|
+
|
|
112
121
|
/** The recorded verdicts, or null when there are none or they do not parse. */
|
|
113
122
|
export function readReferences(
|
|
114
123
|
files: Files,
|
|
@@ -117,10 +126,8 @@ export function readReferences(
|
|
|
117
126
|
const raw = text(files, referencesPath(paperDir));
|
|
118
127
|
if (raw === null) return null;
|
|
119
128
|
try {
|
|
120
|
-
const d = JSON.parse(raw)
|
|
121
|
-
return d
|
|
122
|
-
? d
|
|
123
|
-
: null;
|
|
129
|
+
const d: unknown = JSON.parse(raw);
|
|
130
|
+
return isReferencesDocument(d) ? d : null;
|
|
124
131
|
} catch {
|
|
125
132
|
return null;
|
|
126
133
|
}
|
|
@@ -159,7 +166,7 @@ async function run(
|
|
|
159
166
|
try {
|
|
160
167
|
return await check(bib, cache);
|
|
161
168
|
} catch (e) {
|
|
162
|
-
return { check: { kind: "not-checked", why: (e
|
|
169
|
+
return { check: { kind: "not-checked", why: messageOf(e) }, cache };
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
172
|
|
|
@@ -3,24 +3,18 @@
|
|
|
3
3
|
* paperlint ships. The end-to-end path (a project's paperlint.json → `paperlint lint`) is in `cli.harness.mjs`;
|
|
4
4
|
* this pins the parser's output shape and each refusal.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { createChecker } from "../lib/check.mjs";
|
|
6
|
+
import { createChecker } from "../lib/check.ts";
|
|
9
7
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
join(HERE, "rules-config.ts")
|
|
13
|
-
);
|
|
8
|
+
const { parseRuleBlocks, shippedRuleIds, unknownKeys } =
|
|
9
|
+
await import("./rules-config.ts");
|
|
14
10
|
|
|
15
11
|
const check = createChecker();
|
|
16
12
|
|
|
17
13
|
const SHIPPED = new Set(["pdf/last-page-balance", "paper/typography"]);
|
|
18
|
-
const parse = (raw) => parseRuleBlocks(raw, "S", SHIPPED, "/consumer");
|
|
14
|
+
const parse = (raw: unknown) => parseRuleBlocks(raw, "S", SHIPPED, "/consumer");
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
parse(undefined).ok && parse(undefined).value.length === 0,
|
|
23
|
-
);
|
|
16
|
+
const none = parse(undefined);
|
|
17
|
+
check("no `rules` key: no blocks", none.ok && none.value.length === 0);
|
|
24
18
|
{
|
|
25
19
|
const r = parse([
|
|
26
20
|
{
|
|
@@ -62,7 +56,7 @@ check(
|
|
|
62
56
|
parse([{ rules: { "paper/typography": 0, "pdf/last-page-balance": "warn" } }])
|
|
63
57
|
.ok,
|
|
64
58
|
);
|
|
65
|
-
const refusals = [
|
|
59
|
+
const refusals: [unknown, string][] = [
|
|
66
60
|
["pdf/last-page-balance", 'S → "rules" must be { "<rule>": "<severity>" }'],
|
|
67
61
|
[{ a: 1 }, '"a" is not a rule paperlint ships'],
|
|
68
62
|
[[42], "S → rules[0] must be an object"],
|
package/src/rules-config.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* would need that plugin, which JSON cannot carry. Which rules paperlint ships is read off its own
|
|
26
26
|
* config (`buildConfig`), not listed a second time.
|
|
27
27
|
*/
|
|
28
|
-
import { SETTINGS_KEYS } from "
|
|
28
|
+
import { SETTINGS_KEYS } from "#lib/paper-config";
|
|
29
29
|
|
|
30
30
|
/** ESLint's severities, in both of its spellings. */
|
|
31
31
|
export type Severity = "off" | "warn" | "error" | 0 | 1 | 2;
|
|
@@ -61,7 +61,7 @@ const isStringList = (v: unknown): v is string[] =>
|
|
|
61
61
|
|
|
62
62
|
/** A severity, or `[severity, ...options]`. */
|
|
63
63
|
function parseEntry(v: unknown): RuleEntry | null {
|
|
64
|
-
const sev = Array.isArray(v) ? v[0] : v;
|
|
64
|
+
const sev: unknown = Array.isArray(v) ? v[0] : v;
|
|
65
65
|
if (!SEVERITIES.includes(sev)) return null;
|
|
66
66
|
return v as RuleEntry;
|
|
67
67
|
}
|
|
@@ -126,7 +126,7 @@ function parseBlock(
|
|
|
126
126
|
const extra = Object.keys(o).filter((k) => !BLOCK_KEYS.has(k));
|
|
127
127
|
if (extra.length)
|
|
128
128
|
return bad(
|
|
129
|
-
`${at}: unknown key "${extra[0]}" — a block takes "files", "ignores" and "rules"`,
|
|
129
|
+
`${at}: unknown key "${String(extra[0])}" — a block takes "files", "ignores" and "rules"`,
|
|
130
130
|
);
|
|
131
131
|
const globs = badGlobs(o, at);
|
|
132
132
|
if (globs) return bad(globs);
|
|
@@ -167,7 +167,10 @@ export function parseRuleBlocks(
|
|
|
167
167
|
);
|
|
168
168
|
const out: RuleBlock[] = [];
|
|
169
169
|
for (const [i, v] of raw.entries()) {
|
|
170
|
-
const b = parseBlock(v, `${where} → rules[${i}]`, {
|
|
170
|
+
const b = parseBlock(v, `${where} → rules[${String(i)}]`, {
|
|
171
|
+
shipped,
|
|
172
|
+
baseDir,
|
|
173
|
+
});
|
|
171
174
|
if (!b.ok) return b;
|
|
172
175
|
out.push(b.value);
|
|
173
176
|
}
|
package/src/rules-off.test.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { ESLint, type Linter } from "eslint";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
|
-
|
|
13
|
-
import { texLanguage } from "../eslint-rules/latex-language.mjs";
|
|
12
|
+
import { texLanguage } from "../eslint-rules/latex-language.ts";
|
|
14
13
|
import { rulePlugins, rulesOff, SHIPPED_RULES } from "./cli.ts";
|
|
14
|
+
import { present } from "../test/support.ts";
|
|
15
15
|
|
|
16
16
|
const PAPER = [
|
|
17
17
|
"\\documentclass{article}",
|
|
@@ -45,7 +45,7 @@ async function lintAsConsumer(
|
|
|
45
45
|
const [res] = await eslint.lintText(PAPER, {
|
|
46
46
|
filePath: "papers/a/paper.tex",
|
|
47
47
|
});
|
|
48
|
-
return res
|
|
48
|
+
return present(res, "one lint result").messages;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
describe("rulesOff — paperlint's rules, registered and off, for a consumer's own ESLint run", () => {
|
|
@@ -100,7 +100,9 @@ describe("rulesOff — paperlint's rules, registered and off, for a consumer's o
|
|
|
100
100
|
"// eslint-disable-next-line paper/leading-zero -- nothing to silence\nconst x = 1;\n",
|
|
101
101
|
{ filePath: "src/x.js" },
|
|
102
102
|
);
|
|
103
|
-
expect(
|
|
103
|
+
expect(
|
|
104
|
+
present(res, "one lint result").messages.map((m) => m.message),
|
|
105
|
+
).toEqual([
|
|
104
106
|
"Unused eslint-disable directive (no problems were reported from 'paper/leading-zero').",
|
|
105
107
|
]);
|
|
106
108
|
});
|