paperlint 3.1.2 → 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/skills/verify-citations/scripts/verify-cites.mjs +11 -1
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +35 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +14 -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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Types for bib-authors.mjs, for the exports `src/` imports — the same arrangement as
|
|
2
|
+
// `verify-cites.d.mts` beside it. Only what `src/` imports is declared.
|
|
3
|
+
|
|
4
|
+
/** One entry as `parseBib` returns it; a field the entry lacks is "". */
|
|
5
|
+
export interface BibEntry {
|
|
6
|
+
readonly type: string;
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly author: string;
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly booktitle: string;
|
|
11
|
+
readonly journal: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export declare function parseBib(text: string): BibEntry[];
|
|
15
|
+
|
|
16
|
+
/** True when the author list ends in `and others` — there is nothing complete to compare. */
|
|
17
|
+
export declare const truncated: (authorField: string) => boolean;
|
|
18
|
+
|
|
19
|
+
/** True when the entry names a venue that is not a preprint server. */
|
|
20
|
+
export declare function claimsPublished(
|
|
21
|
+
entry: Pick<BibEntry, "booktitle" | "journal">,
|
|
22
|
+
): boolean;
|
|
23
|
+
|
|
24
|
+
/** One DBLP search hit, as `dblpHits` normalises it. */
|
|
25
|
+
export interface DblpHit {
|
|
26
|
+
readonly venue: string;
|
|
27
|
+
readonly year: string;
|
|
28
|
+
readonly type: string;
|
|
29
|
+
readonly title: string;
|
|
30
|
+
readonly authors: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** DBLP's hits for a title; throws on a failed request (with `retryable` for a 429). */
|
|
34
|
+
export declare function dblpHits(title: string): Promise<DblpHit[]>;
|
|
35
|
+
|
|
36
|
+
export interface AuthorFinding {
|
|
37
|
+
readonly key: string;
|
|
38
|
+
readonly venue: string;
|
|
39
|
+
readonly missing: readonly string[];
|
|
40
|
+
readonly extra: readonly string[];
|
|
41
|
+
readonly orderDiffers: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface KeyWhy {
|
|
45
|
+
readonly key: string;
|
|
46
|
+
readonly why: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The buckets of one comparison; an entry in none of them was a preprint entry. */
|
|
50
|
+
export interface AuthorBuckets {
|
|
51
|
+
readonly findings: readonly AuthorFinding[];
|
|
52
|
+
readonly skipped: readonly KeyWhy[];
|
|
53
|
+
readonly unchecked: readonly KeyWhy[];
|
|
54
|
+
readonly matched: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export declare function checkAuthors(
|
|
58
|
+
parsed: readonly BibEntry[],
|
|
59
|
+
options?: {
|
|
60
|
+
readonly lookup?: (title: string) => Promise<readonly DblpHit[]>;
|
|
61
|
+
readonly pause?: (ms: number) => Promise<void> | void;
|
|
62
|
+
},
|
|
63
|
+
): Promise<AuthorBuckets>;
|
|
@@ -45,10 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
47
47
|
import { join, extname } from "node:path";
|
|
48
|
-
import {
|
|
49
|
-
headings as mdHeadings,
|
|
50
|
-
requireMarkdown,
|
|
51
|
-
} from "../../../lib/markdown.mjs";
|
|
48
|
+
import { headings as mdHeadings, requireMarkdown } from "#lib/markdown";
|
|
52
49
|
import { isMain } from "../../paper-pipeline/scripts/consumer.mjs";
|
|
53
50
|
|
|
54
51
|
const DBLP = "https://dblp.org/search/publ/api";
|
package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts}
RENAMED
|
@@ -9,7 +9,23 @@ import { readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { test } from "vitest";
|
|
12
|
-
import {
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import {
|
|
14
|
+
runNode,
|
|
15
|
+
useTempDir,
|
|
16
|
+
writeTree,
|
|
17
|
+
type ScriptResult,
|
|
18
|
+
} from "../../../test/support.ts";
|
|
19
|
+
|
|
20
|
+
/** The verdicts `verify-cites --json`… prints: one per citation, with the fields these tests read. */
|
|
21
|
+
const Verdicts = z.array(
|
|
22
|
+
z.looseObject({
|
|
23
|
+
id: z.unknown().optional(),
|
|
24
|
+
verdict: z.unknown().optional(),
|
|
25
|
+
flags: z.array(z.unknown()).nullish(),
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
const Cache = z.record(z.string(), z.unknown());
|
|
13
29
|
|
|
14
30
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
31
|
const root = useTempDir("verify-cites-cli-");
|
|
@@ -48,12 +64,17 @@ writeTree(root, {
|
|
|
48
64
|
" return new Response(null, { status: 404 });\n};\n",
|
|
49
65
|
"consumer/paperlint.json": JSON.stringify({ contactEmail: "me@example.org" }),
|
|
50
66
|
});
|
|
51
|
-
const offline = (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
const offline = (
|
|
68
|
+
args: readonly string[],
|
|
69
|
+
opts: { input?: string } = {},
|
|
70
|
+
): ScriptResult => runNode(SCRIPT, [...args, "--offline"], { env, ...opts });
|
|
71
|
+
const parsed = (r: ScriptResult) => ({
|
|
72
|
+
...r,
|
|
73
|
+
stdout: Verdicts.parse(JSON.parse(r.stdout)),
|
|
74
|
+
});
|
|
75
|
+
const SUMMARY_PASS = (n: number, t: number, u: number, flagged: number) =>
|
|
76
|
+
`\nverify-cites: ${String(n)} citation(s) — ${String(t)} true, 0 false (fabrication), ${String(u)} unresolvable` +
|
|
77
|
+
(flagged ? `, ${String(flagged)} with hygiene flag(s)` : "") +
|
|
57
78
|
"\nPASS — no fabrication (unresolvable is advisory)\n";
|
|
58
79
|
|
|
59
80
|
test("-h is --help", () => {
|
|
@@ -118,9 +139,11 @@ test("online, a DOI the authority does not know is FALSE: FAIL, exit 1, and the
|
|
|
118
139
|
assert.deepEqual(
|
|
119
140
|
{
|
|
120
141
|
status: r.status,
|
|
121
|
-
verdicts: JSON.parse(r.stdout).map((x) => x.verdict),
|
|
142
|
+
verdicts: Verdicts.parse(JSON.parse(r.stdout)).map((x) => x.verdict),
|
|
122
143
|
tail: r.stderr.trim().split("\n").pop(),
|
|
123
|
-
cached: Object.keys(
|
|
144
|
+
cached: Object.keys(
|
|
145
|
+
Cache.parse(JSON.parse(readFileSync(CACHE, "utf8"))),
|
|
146
|
+
).sort(),
|
|
124
147
|
},
|
|
125
148
|
{
|
|
126
149
|
status: 1,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Types for verify-cites.mjs, for the exports `src/` imports — the same arrangement as
|
|
2
|
+
// `skills/paper-pipeline/scripts/consumer.d.mts`. Without this file TypeScript sees an untyped
|
|
3
|
+
// module, and every import needed a `@ts-expect-error` plus a cast at each call.
|
|
4
|
+
//
|
|
5
|
+
// Only what `src/` imports is declared. The `.mjs` is not typechecked, so a declaration nobody
|
|
6
|
+
// compiles against would drift without anyone noticing.
|
|
7
|
+
|
|
8
|
+
/** One entry as `parseBib` returns it: the BibTeX key as `id`, plus the fields it recognised. */
|
|
9
|
+
export interface Citation {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly [field: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Every entry of a BibTeX text, `@comment`/`@string`/`@preamble` skipped. */
|
|
15
|
+
export declare function parseBib(text: string): Citation[];
|
|
16
|
+
|
|
17
|
+
/** The title reduced to what identifies it: case, braces and punctuation removed. */
|
|
18
|
+
export declare function titleIdentity(title: string): string;
|
|
19
|
+
|
|
20
|
+
/** Answers the resolvers gave, keyed by request — shared between citations and runs. */
|
|
21
|
+
export type ResponseCache = Record<string, unknown>;
|
|
22
|
+
|
|
23
|
+
/** Would verifying `citation` make a request, given `cache`? Answers from the cache only. */
|
|
24
|
+
export declare function wouldAsk(
|
|
25
|
+
citation: Citation,
|
|
26
|
+
cache: ResponseCache,
|
|
27
|
+
): Promise<boolean>;
|
|
28
|
+
|
|
29
|
+
/** One run's memory of which service refused: a refusing service is not asked again. */
|
|
30
|
+
export interface Breaker {
|
|
31
|
+
call<T>(
|
|
32
|
+
service: string,
|
|
33
|
+
attempt: () => Promise<
|
|
34
|
+
{ ok: true; value: T } | { ok: false; reason: string }
|
|
35
|
+
>,
|
|
36
|
+
): Promise<{ ok: true; value: T } | { ok: false; reason: string }>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export declare function createBreaker(): Breaker;
|
|
40
|
+
|
|
41
|
+
/** The verdict on one citation. */
|
|
42
|
+
export interface CiteVerdict {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly verdict: "true" | "false" | "unresolvable";
|
|
45
|
+
readonly reason?: string;
|
|
46
|
+
/** "<service>: <reason>" for each question a refusing service did not answer. */
|
|
47
|
+
readonly refused?: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Verify ONE citation live, answering from `cache` first and writing new answers into it. */
|
|
51
|
+
export declare function verifyCitationLive(
|
|
52
|
+
citation: Citation,
|
|
53
|
+
options?: {
|
|
54
|
+
readonly cache?: ResponseCache;
|
|
55
|
+
readonly offline?: boolean;
|
|
56
|
+
readonly breaker?: Breaker;
|
|
57
|
+
},
|
|
58
|
+
): Promise<CiteVerdict>;
|
|
@@ -156,13 +156,23 @@ export function normalizeArxiv(arxiv) {
|
|
|
156
156
|
.trim();
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/** arXiv registers a DataCite DOI for every paper: `10.48550/arXiv.<id>`. */
|
|
160
|
+
const ARXIV_DOI = /^10\.48550\/arxiv\.(.+)$/i;
|
|
161
|
+
|
|
159
162
|
/** Shallow copy of a citation with doi/arxiv identifiers normalized. Applied at the
|
|
160
163
|
* verify boundary so BOTH JSON input and .bib-parsed input are clean before any
|
|
161
|
-
* network / authority lookup.
|
|
164
|
+
* network / authority lookup.
|
|
165
|
+
*
|
|
166
|
+
* An arXiv DOI also yields the arXiv id it names, when the entry has none of its own: without
|
|
167
|
+
* it arXiv is asked only by title, and only for an entry with no DOI, so a registry that maps
|
|
168
|
+
* the DOI to the wrong work goes uncontradicted (OpenAlex did, for SWE-bench, on 2026-09-27). */
|
|
162
169
|
export function normalizeIdentifiers(citation) {
|
|
163
170
|
const c = { ...citation };
|
|
164
171
|
if (c.doi) c.doi = normalizeDoi(c.doi);
|
|
165
172
|
if (c.arxiv) c.arxiv = normalizeArxiv(c.arxiv);
|
|
173
|
+
// Decided on the NORMALIZED id: a blank field normalizes to "" and must not block the DOI's id.
|
|
174
|
+
const fromDoi = c.doi ? ARXIV_DOI.exec(c.doi)?.[1] : undefined;
|
|
175
|
+
if (!c.arxiv && fromDoi) c.arxiv = fromDoi;
|
|
166
176
|
return c;
|
|
167
177
|
}
|
|
168
178
|
|
|
@@ -271,6 +271,41 @@ test("a corrected arXiv id is asked again even when the DOI is unchanged", async
|
|
|
271
271
|
);
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
+
// Guards: an arXiv DOI (10.48550/arXiv.<id>) names an arXiv id, so arXiv is asked by that id.
|
|
275
|
+
// Measured 2026-09-27 on a real bibliography: OpenAlex maps 10.48550/arXiv.2310.06770 (SWE-bench)
|
|
276
|
+
// to an unrelated work; with Crossref and Semantic Scholar refusing and arXiv asked only by title
|
|
277
|
+
// "when there is no DOI", nothing contradicted it and the paper came out `exists: false`.
|
|
278
|
+
test("an arXiv DOI is asked of arXiv by its id, whose match outweighs a registry's wrong record", async () => {
|
|
279
|
+
const calls = fakeFetch({
|
|
280
|
+
[CR]: () => json(429),
|
|
281
|
+
[OA]: () =>
|
|
282
|
+
json(200, {
|
|
283
|
+
id: "W1",
|
|
284
|
+
display_name: "GardenBench",
|
|
285
|
+
publication_year: 2023,
|
|
286
|
+
}),
|
|
287
|
+
[S2]: () => json(429),
|
|
288
|
+
[AX]: () => text(200, feed(TITLE, 2017)),
|
|
289
|
+
[DOI]: () => json(200, { responseCode: 1 }),
|
|
290
|
+
});
|
|
291
|
+
const v = await verifyCitationLive(
|
|
292
|
+
{ id: "c", doi: "10.48550/arXiv.1706.03762", title: TITLE, year: "2017" },
|
|
293
|
+
{ cache: {} },
|
|
294
|
+
);
|
|
295
|
+
assert.deepEqual(
|
|
296
|
+
{
|
|
297
|
+
verdict: v.verdict,
|
|
298
|
+
matched: v.matched_db,
|
|
299
|
+
arxiv: calls.filter((u) => u.startsWith(AX)),
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
verdict: "true",
|
|
303
|
+
matched: "arxiv",
|
|
304
|
+
arxiv: [`${AX}?id_list=1706.03762&max_results=1`],
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
274
309
|
// Guards: NVD's "not found" must not be cached. A CVE cited before NVD indexes it would otherwise
|
|
275
310
|
// stay "fabricated" in the committed cache forever; only positive existence is kept, as for doi.org.
|
|
276
311
|
test("an NVD not-found answer is not cached, so a later build asks again", async () => {
|
|
@@ -738,6 +738,20 @@ console.log("\n[S4] .bib parser edge cases:");
|
|
|
738
738
|
|
|
739
739
|
// ── Edges of the pure layer: the defensive arms, each with the input that reaches it ──────────
|
|
740
740
|
|
|
741
|
+
// Guards: the arXiv id is read out of an arXiv DOI whenever the entry has no USABLE arXiv id, so
|
|
742
|
+
// a blank `arxiv` field (JSON input) does not keep arXiv from being asked.
|
|
743
|
+
test.each([
|
|
744
|
+
[{ doi: "10.48550/arXiv.2310.06770" }, "2310.06770"],
|
|
745
|
+
[{ doi: "10.48550/arXiv.2310.06770", arxiv: " " }, "2310.06770"],
|
|
746
|
+
[
|
|
747
|
+
{ doi: "10.48550/arXiv.2310.06770", arxiv: "arXiv:2401.00001" },
|
|
748
|
+
"2401.00001",
|
|
749
|
+
],
|
|
750
|
+
[{ doi: "10.1145/3548606" }, undefined],
|
|
751
|
+
])("normalizeIdentifiers(%o).arxiv is %s", (citation, arxiv) => {
|
|
752
|
+
assert.equal(normalizeIdentifiers(citation).arxiv || undefined, arxiv);
|
|
753
|
+
});
|
|
754
|
+
|
|
741
755
|
test("levenshtein, normalizeTitle and titleIdentity take a missing string as empty", () => {
|
|
742
756
|
assert.deepEqual(
|
|
743
757
|
[
|
package/src/CLAUDE.md
CHANGED
|
@@ -35,7 +35,7 @@ through the `io` file category in `boundaries/dependencies`; the `process` and `
|
|
|
35
35
|
carries `// eslint-disable-next-line <rule> -- legacy <I/O|layer>, moves behind a port in #76`.
|
|
36
36
|
Unused directives are errors (`reportUnusedDisableDirectives`), so a fixed site must drop its
|
|
37
37
|
comment; the per-file counts are frozen in `scripts/layer-legacy.frozen.json` and checked by
|
|
38
|
-
`scripts/layer-legacy-frozen.
|
|
38
|
+
`scripts/layer-legacy-frozen.ts` (`npm run check`, CI), which counts ESLint's own
|
|
39
39
|
`suppressedMessages` and fails when a file's count grows, a file not in the list carries one, or
|
|
40
40
|
anything under `src/domain/`, `src/ports/` or `src/adapters/` does. A NEW module that needs the
|
|
41
41
|
world is a new `*.io.ts` in the adapter of the program it talks to — never a new directive.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `adapters/banal/index.ts` — banal as the two ports the app sees. What `run.test.ts` checks in
|
|
3
3
|
* banal's own terms is checked here as the domain receives it: a `Geometry` whose provenance names
|
|
4
4
|
* banal and the rule that found it, a reason that is one line of text, a `Ready` that says what was
|
|
5
|
-
* verified. In-memory ports throughout; the real banal is `test/e2e/banal.
|
|
5
|
+
* verified. In-memory ports throughout; the real banal is `test/e2e/banal.ts`.
|
|
6
6
|
*/
|
|
7
7
|
import assert from "node:assert/strict";
|
|
8
8
|
import { test } from "vitest";
|
|
@@ -44,15 +44,12 @@ test("with $BANAL: banal gets the .xml and a quoted $PDFTOHTML, and the geometry
|
|
|
44
44
|
const { m, run } = measurer({ BANAL: "/own/banal" });
|
|
45
45
|
const g = m.measure([]);
|
|
46
46
|
assert.equal(g.kind, "measured");
|
|
47
|
-
assert.deepEqual(g.
|
|
47
|
+
assert.deepEqual(g.by, {
|
|
48
48
|
tool: "banal",
|
|
49
49
|
path: "/own/banal",
|
|
50
50
|
how: "$BANAL",
|
|
51
51
|
});
|
|
52
|
-
assert.deepEqual(
|
|
53
|
-
g.kind === "measured" && [g.geometry.columns, g.geometry.bodyPages],
|
|
54
|
-
[2, 1],
|
|
55
|
-
);
|
|
52
|
+
assert.deepEqual([g.geometry.columns, g.geometry.bodyPages], [2, 1]);
|
|
56
53
|
const c = run.calls[0];
|
|
57
54
|
// Guards: the poppler-free path — banal is handed paperlint's XML, never the PDF.
|
|
58
55
|
assert.match(c?.args.at(-1) ?? "", /\.xml$/);
|
|
@@ -65,12 +65,15 @@ export function banalMeasurer(
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** The one place banal's `Ready` is minted: from an `Installed`, whose `PinnedBanal` only run.ts mints. */
|
|
68
|
-
const ready = (i: Installed): Ready =>
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const ready = (i: Installed): Ready => {
|
|
69
|
+
// Widened by annotation: the brand of the path is not part of `Ready`'s shape.
|
|
70
|
+
const where: string = i.banal.path;
|
|
71
|
+
return {
|
|
72
|
+
where,
|
|
71
73
|
fresh: i.fresh,
|
|
72
74
|
verified: "sha256 verified, and it measured a probe page",
|
|
73
|
-
}
|
|
75
|
+
} as Ready;
|
|
76
|
+
};
|
|
74
77
|
|
|
75
78
|
/**
|
|
76
79
|
* banal as the `ToolInstaller` port: download at the pin, check the sha256, accept by measuring a
|
|
@@ -51,7 +51,9 @@ test("shQuote survives a single quote", () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-stage-test-")));
|
|
54
|
-
after(() =>
|
|
54
|
+
after(() => {
|
|
55
|
+
rmSync(root, { recursive: true, force: true });
|
|
56
|
+
});
|
|
55
57
|
|
|
56
58
|
test("🔴 stage: on a real scratch directory the stub answers `-v` through the shell, from a path with a space and a quote", () => {
|
|
57
59
|
const awkward = join(root, "it's a dir");
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* (`Error: Failed to run pdftohtml`, measured); told a version below 0.85 it applies a larger
|
|
8
8
|
* correction and every font size moves. So `$PDFTOHTML` (banal's own override, line 166) points at a
|
|
9
9
|
* stub that answers `-v` with `XML_DIALECT.version` — the pdftohtml whose XML `xml.ts` writes — and
|
|
10
|
-
* refuses anything else. `test/e2e/banal.
|
|
10
|
+
* refuses anything else. `test/e2e/banal.ts` shows both failures with the real banal.
|
|
11
11
|
*/
|
|
12
12
|
import type { Opaque } from "ts-essentials";
|
|
13
13
|
import type { AbsolutePath } from "../../domain/paths.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `output.ts` — every way a banal run can end, as a table of `ProcessExit` → `Result`, and the
|
|
3
|
-
* measurement → geometry projection. Pure: no perl, no disk. The real banal is `test/e2e/banal.
|
|
3
|
+
* measurement → geometry projection. Pure: no perl, no disk. The real banal is `test/e2e/banal.ts`.
|
|
4
4
|
*/
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { test } from "vitest";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `adapters/banal/run.ts` — running and installing banal, on in-memory ports: which `Command` it builds, what it stages,
|
|
3
3
|
* what it downloads and writes, and what it concludes. No perl, no disk, no network: the adapters
|
|
4
|
-
* record, and each assertion reads the record. Real perl and the real banal are `test/e2e/banal.
|
|
4
|
+
* record, and each assertion reads the record. Real perl and the real banal are `test/e2e/banal.ts`;
|
|
5
5
|
* the adapters' own behaviour is `src/adapters/node/*.test.ts`.
|
|
6
6
|
*/
|
|
7
7
|
import assert from "node:assert/strict";
|
|
@@ -45,10 +45,10 @@ test("measureGeometry: perl runs the found banal on the staged XML, and the geom
|
|
|
45
45
|
});
|
|
46
46
|
const g = measureGeometry(io, s, project, []);
|
|
47
47
|
assert.equal(g.source, "banal");
|
|
48
|
-
assert.equal(g.
|
|
48
|
+
assert.equal(g.geometry.bodyPt, 10.3);
|
|
49
49
|
const [c] = run.calls;
|
|
50
50
|
assert.equal(c?.file, "perl");
|
|
51
|
-
assert.equal(c
|
|
51
|
+
assert.equal(c.args.at(-1), "/scratch/paper.xml");
|
|
52
52
|
// Guards: both files staged, once, and the scratch scope ended normally (cleanup ran).
|
|
53
53
|
assert.deepEqual(
|
|
54
54
|
workspace.written.map((w) => [w.name, w.mode]),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Every page here is written by hand. The real-PDF half — pdf.js boxes of a committed fixture,
|
|
6
6
|
* written as XML, measured by the real banal and compared with banal on real pdftohtml — is
|
|
7
|
-
* `test/e2e/banal.
|
|
7
|
+
* `test/e2e/banal.ts`, which runs where `paperlint toolchain` has installed banal.
|
|
8
8
|
*/
|
|
9
9
|
import assert from "node:assert/strict";
|
|
10
10
|
import { test } from "vitest";
|
|
@@ -23,7 +23,9 @@ import { spawnProcess } from "../node/index.ts";
|
|
|
23
23
|
const root = realpathSync(
|
|
24
24
|
mkdtempSync(join(tmpdir(), "paperlint-download-test-")),
|
|
25
25
|
);
|
|
26
|
-
after(() =>
|
|
26
|
+
after(() => {
|
|
27
|
+
rmSync(root, { recursive: true, force: true });
|
|
28
|
+
});
|
|
27
29
|
const at = (...p: string[]) => join(root, ...p) as AbsolutePath;
|
|
28
30
|
|
|
29
31
|
const dl = (env: Record<string, string>) => {
|
|
@@ -45,7 +47,7 @@ test("Download: a URL that does not answer is a failure with curl's words", () =
|
|
|
45
47
|
const { d } = dl({ PATH: "/usr/bin:/bin" });
|
|
46
48
|
const r = d.fetch(pathToFileURL(at("missing")).href, 10_000);
|
|
47
49
|
assert.equal(r.ok, false);
|
|
48
|
-
assert.ok(
|
|
50
|
+
assert.ok(r.error.detail.length > 0);
|
|
49
51
|
});
|
|
50
52
|
|
|
51
53
|
test("🔴 Download: no curl on PATH is named as that, not as a failed URL", () => {
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
import { dirname } from "node:path";
|
|
11
11
|
import type { AbsolutePath } from "../../domain/paths.ts";
|
|
12
12
|
import type { Files } from "../../ports/files.ts";
|
|
13
|
+
import { codeOf } from "../../domain/text.ts";
|
|
13
14
|
|
|
14
15
|
/** An error's errno code; anything thrown that is not an Error has none. */
|
|
15
|
-
export const errorCode = (e: unknown): unknown =>
|
|
16
|
-
e instanceof Error ? (e as NodeJS.ErrnoException).code : undefined;
|
|
16
|
+
export const errorCode = (e: unknown): unknown => codeOf(e);
|
|
17
17
|
|
|
18
18
|
export const nodeFiles: Files = {
|
|
19
19
|
isFile(p: AbsolutePath): boolean {
|
|
@@ -8,7 +8,9 @@ import type { AbsolutePath } from "../../domain/paths.ts";
|
|
|
8
8
|
import { errorCode, nodeFiles } from "./files.io.ts";
|
|
9
9
|
|
|
10
10
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-files-test-")));
|
|
11
|
-
after(() =>
|
|
11
|
+
after(() => {
|
|
12
|
+
rmSync(root, { recursive: true, force: true });
|
|
13
|
+
});
|
|
12
14
|
const at = (...p: string[]) => join(root, ...p) as AbsolutePath;
|
|
13
15
|
|
|
14
16
|
test("Files: a missing file reads as null and is not a file", () => {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* it — `error`, `status`, `signal`, all nullable — and turns it into a `ProcessExit`, so no caller
|
|
4
4
|
* re-derives "what happened" from three fields.
|
|
5
5
|
*/
|
|
6
|
+
import { codeOf, printed } from "../../domain/text.ts";
|
|
6
7
|
import {
|
|
7
8
|
spawnSync,
|
|
8
9
|
type SpawnSyncOptionsWithStringEncoding,
|
|
@@ -14,13 +15,13 @@ import type { Command, ProcessExit, RunProcess } from "../../ports/process.ts";
|
|
|
14
15
|
export type SpawnSync = typeof spawnSync;
|
|
15
16
|
|
|
16
17
|
const errno = (r: SpawnSyncReturns<string>): string | undefined =>
|
|
17
|
-
(r.error
|
|
18
|
+
codeOf(r.error);
|
|
18
19
|
|
|
19
20
|
/** What a finished `spawnSync` came to. ENOENT is the executable itself: `c.file` was not found. */
|
|
20
21
|
export function exitOf(r: SpawnSyncReturns<string>, c: Command): ProcessExit {
|
|
21
22
|
const out = {
|
|
22
|
-
stdout:
|
|
23
|
-
stderr:
|
|
23
|
+
stdout: printed(r.stdout),
|
|
24
|
+
stderr: printed(r.stderr),
|
|
24
25
|
};
|
|
25
26
|
const code = errno(r);
|
|
26
27
|
if (code === "ENOENT") return { kind: "not-found", file: c.file };
|
|
@@ -58,7 +58,7 @@ test("a program killed by a signal is `signalled`, with what it printed first",
|
|
|
58
58
|
test("output past maxOutputBytes is a spawn failure, named by Node's own message", () => {
|
|
59
59
|
const r = run.run({ ...sh("head -c 100000 /dev/zero"), maxOutputBytes: 10 });
|
|
60
60
|
assert.equal(r.kind, "spawn-failed");
|
|
61
|
-
assert.match(r.
|
|
61
|
+
assert.match(r.message, /ENOBUFS/);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test("cwd is the Command's when it names one", () => {
|
|
@@ -20,7 +20,9 @@ import { afterAll as after, test } from "vitest";
|
|
|
20
20
|
import { tmpWorkspace } from "./workspace.io.ts";
|
|
21
21
|
|
|
22
22
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-ws-test-")));
|
|
23
|
-
after(() =>
|
|
23
|
+
after(() => {
|
|
24
|
+
rmSync(root, { recursive: true, force: true });
|
|
25
|
+
});
|
|
24
26
|
|
|
25
27
|
test("the directory exists inside the scope and is gone after it", () => {
|
|
26
28
|
const ws = tmpWorkspace(root);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fill.ts` — what is recovered from pdf.js's exports for each text item: whether it is upright, and
|
|
3
3
|
* the fill it was drawn in (the operator-list walk). Operator lists here are written by hand; the
|
|
4
|
-
* real-PDF half runs in `pdf-facts.harness.
|
|
4
|
+
* real-PDF half runs in `pdf-facts.harness.ts` and `test/e2e/banal.ts`.
|
|
5
5
|
*/
|
|
6
6
|
import assert from "node:assert/strict";
|
|
7
7
|
import { test } from "vitest";
|
|
@@ -34,7 +34,7 @@ const OPS = {
|
|
|
34
34
|
showText: 5,
|
|
35
35
|
showSpacedText: 6,
|
|
36
36
|
};
|
|
37
|
-
const glyphs = (s: string) => [
|
|
37
|
+
const glyphs = (s: string) => [Array.from(s, (unicode) => ({ unicode }))];
|
|
38
38
|
const ops = (...pairs: [number, unknown?][]): OperatorList => ({
|
|
39
39
|
fnArray: pairs.map((p) => p[0]),
|
|
40
40
|
argsArray: pairs.map((p) => p[1] ?? null),
|
|
@@ -33,9 +33,12 @@ export interface OperatorList {
|
|
|
33
33
|
/** Text as the walk compares it: normalised, with no whitespace (items and glyphs split spaces differently). */
|
|
34
34
|
const squeeze = (s: string): string => s.normalize("NFKC").replace(/\s+/g, "");
|
|
35
35
|
|
|
36
|
+
/** An operator's argument list; pdf.js types it `any`, and a malformed one reads as empty. */
|
|
37
|
+
const listOf = (v: unknown): readonly unknown[] => (Array.isArray(v) ? v : []);
|
|
38
|
+
|
|
36
39
|
/** `setFillRGBColor`'s argument: a `#rrggbb` string in current pdf.js, three 0-255 numbers in older ones. */
|
|
37
40
|
export function hexOf(args: unknown): string {
|
|
38
|
-
const a =
|
|
41
|
+
const a = listOf(args);
|
|
39
42
|
if (typeof a[0] === "string") return a[0].toLowerCase();
|
|
40
43
|
const byte = (v: unknown): string =>
|
|
41
44
|
Math.round(typeof v === "number" ? v : 0)
|
|
@@ -46,12 +49,13 @@ export function hexOf(args: unknown): string {
|
|
|
46
49
|
|
|
47
50
|
/** The characters a show-text operator draws, from its glyph objects. */
|
|
48
51
|
function glyphText(args: unknown): string {
|
|
49
|
-
const
|
|
50
|
-
const glyphs = Array.isArray(a[0]) ? (a[0] as unknown[]) : [];
|
|
52
|
+
const glyphs = listOf(listOf(args)[0]);
|
|
51
53
|
return glyphs
|
|
52
54
|
.map((g) =>
|
|
53
55
|
typeof g === "object" && g !== null && "unicode" in g
|
|
54
|
-
?
|
|
56
|
+
? typeof g.unicode === "string"
|
|
57
|
+
? g.unicode
|
|
58
|
+
: ""
|
|
55
59
|
: "",
|
|
56
60
|
)
|
|
57
61
|
.join("");
|
|
@@ -76,7 +80,7 @@ function applyState(
|
|
|
76
80
|
else if (fn === ops.setFillRGBColor)
|
|
77
81
|
s.cur.fill = { kind: "rgb", hex: hexOf(args) };
|
|
78
82
|
else if (fn === ops.setTextRenderingMode)
|
|
79
|
-
s.cur.mode = Number((args
|
|
83
|
+
s.cur.mode = Number(listOf(args)[0] ?? 0);
|
|
80
84
|
else return false;
|
|
81
85
|
return true;
|
|
82
86
|
}
|
|
@@ -89,7 +93,10 @@ interface Drawn {
|
|
|
89
93
|
|
|
90
94
|
/** Every drawn character, in content order, with the fill it was drawn in. */
|
|
91
95
|
export function drawnCharacters(ops: ColourOps, list: OperatorList): Drawn[] {
|
|
92
|
-
const s
|
|
96
|
+
const s: { cur: State; stack: State[] } = {
|
|
97
|
+
cur: { fill: { kind: "unknown" }, mode: 0 },
|
|
98
|
+
stack: [],
|
|
99
|
+
};
|
|
93
100
|
const drawn: Drawn[] = [];
|
|
94
101
|
list.fnArray.forEach((fn, i) => {
|
|
95
102
|
const args = list.argsArray[i];
|
|
@@ -149,7 +156,7 @@ export function fillsFor<T>(
|
|
|
149
156
|
const drawn = drawnCharacters(ops, list);
|
|
150
157
|
let at = 0;
|
|
151
158
|
const fillOf = (text: string): Fill => {
|
|
152
|
-
const want =
|
|
159
|
+
const want = Array.from(squeeze(text));
|
|
153
160
|
if (want.length === 0) return { kind: "unknown" };
|
|
154
161
|
const hit = findRun(drawn, want, at);
|
|
155
162
|
if (hit === null) return { kind: "unknown" };
|