paperlint 3.0.0 → 3.1.1
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 +28 -41
- package/.github/workflows/dependabot-automerge.yml +1 -1
- package/CLAUDE.md +51 -56
- package/CONTRIBUTING.md +30 -18
- package/README.md +179 -180
- package/bin/paperlint.test.mjs +45 -0
- package/dist/adapters/node/files.io.d.ts +2 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +3 -2
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts +15 -4
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +31 -21
- 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 +14 -5
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts +8 -0
- package/dist/adapters/references/pool.d.ts.map +1 -0
- package/dist/adapters/references/pool.js +19 -0
- package/dist/adapters/references/pool.js.map +1 -0
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +6 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +10 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +29 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +93 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +296 -80
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +27 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +4 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/result.d.ts +0 -2
- package/dist/domain/result.d.ts.map +1 -1
- package/dist/domain/result.js +0 -4
- package/dist/domain/result.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -1
- package/dist/hooks-settings.d.ts +8 -5
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +32 -32
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +4 -4
- package/dist/init.js.map +1 -1
- package/dist/latex-log.d.ts +0 -2
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +7 -13
- package/dist/latex-log.js.map +1 -1
- package/dist/link-skills.d.ts +15 -2
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +36 -20
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts +8 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +50 -15
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +30 -8
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +59 -30
- package/dist/paper-files.js.map +1 -1
- package/dist/pdf-facts.d.ts +11 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +16 -18
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -1
- package/dist/presets.js.map +1 -1
- package/dist/references.js +2 -2
- package/dist/references.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +8 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +6 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +8 -3
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +26 -10
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +12 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +48 -4
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/test-tooling.md +7 -30
- package/docs/rules.md +29 -25
- package/docs/skills.md +80 -0
- package/eslint-rules/install-path-literals.harness.mjs +17 -1
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.mjs +12 -9
- package/eslint-rules/latex-language.test.mjs +200 -0
- package/eslint-rules/paper-research-question.harness.mjs +73 -7
- package/eslint-rules/paper-stages.harness.mjs +9 -14
- package/eslint-rules/paper-stages.test.mjs +135 -0
- package/eslint-rules/paper-typography.mjs +56 -9
- package/eslint-rules/paper-typography.test.mjs +186 -1
- package/eslint-rules/pdf-last-page-balance.harness.mjs +63 -7
- package/eslint-rules/review-frontmatter.mjs +2 -1
- package/eslint-rules/review-frontmatter.schema.json +1 -1
- package/eslint-rules/review-frontmatter.test.mjs +37 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +49 -0
- package/eslint-rules/tex-build.mjs +13 -13
- package/eslint.config.mjs +6 -2
- package/fixtures/toolchain-mirror/catalog.txt +1 -0
- package/fixtures/toolchain-mirror/install-tl +4 -1
- package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
- package/hooks/hooks.harness.mjs +6 -11
- package/hooks/paper-edit-guard.hook.mjs +2 -2
- package/hooks/paper-edit-guard.test.mjs +61 -0
- package/hooks/paper-skills-nudge.hook.mjs +2 -2
- package/hooks/paper-skills-nudge.test.mjs +38 -0
- package/hooks/paper-status-gates.test.mjs +42 -0
- package/lib/agent-cli-version.harness.mjs +7 -0
- package/lib/check.d.mts +17 -0
- package/lib/check.mjs +50 -0
- package/lib/check.test.ts +67 -0
- package/lib/markdown.mjs +16 -69
- package/lib/markdown.test.mjs +246 -0
- package/lib/paper-config.harness.mjs +28 -9
- package/lib/paper-config.mjs +7 -2
- package/lib/skill-checks.mjs +18 -46
- package/lib/skill-checks.test.mjs +297 -0
- package/lib/skill-corpus.mjs +11 -13
- package/lib/skill-corpus.test.mjs +54 -0
- package/lib/skill-trigger-cases.harness.mjs +57 -4
- package/package.json +6 -5
- package/scripts/check.harness.mjs +4 -10
- package/scripts/check.mjs +45 -42
- package/scripts/check.test.mjs +104 -0
- package/scripts/coverage-config.test.ts +406 -0
- package/scripts/eslint-report-guard.test.mjs +48 -0
- package/scripts/harness-api.frozen.json +0 -2
- package/scripts/layer-legacy-frozen.d.mts +9 -1
- package/scripts/layer-legacy-frozen.mjs +28 -11
- package/scripts/layer-legacy-frozen.test.ts +143 -1
- package/scripts/rules-are-content-only.harness.mjs +3 -11
- package/scripts/rules-are-content-only.mjs +29 -19
- package/scripts/rules-are-content-only.test.mjs +61 -0
- package/scripts/rules-see-files.mjs +11 -9
- package/scripts/rules-see-files.test.mjs +71 -0
- package/scripts/test.mjs +44 -0
- package/scripts/test.test.mjs +52 -0
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.mjs.snap +57 -0
- package/skills/grade-paper-writing/prose-lint.mjs +31 -12
- package/skills/grade-paper-writing/prose-lint.test.mjs +153 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +3 -3
- package/skills/paper-pipeline/scripts/README.md +13 -132
- package/skills/paper-pipeline/scripts/announce.test.mjs +102 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.test.mjs +86 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +8 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/consumer.mjs +13 -25
- package/skills/paper-pipeline/scripts/consumer.test.mjs +113 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +9 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +380 -0
- package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +71 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +4 -4
- package/skills/paper-pipeline/scripts/generated-code.mjs +3 -2
- package/skills/paper-pipeline/scripts/generated-code.test.mjs +62 -0
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +302 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +1 -1
- package/skills/paper-pipeline/scripts/{ledger.selftest.mjs → ledger.test.mjs} +44 -76
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +474 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -63
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +37 -21
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +73 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +4 -47
- package/skills/paper-pipeline/scripts/population-map.mjs +12 -2
- package/skills/paper-pipeline/scripts/population-map.test.mjs +154 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +26 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +1 -2
- package/skills/paper-pipeline/scripts/round-diff.mjs +13 -6
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +159 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +30 -6
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +468 -0
- package/skills/paper-pipeline/scripts/status.mjs +2 -2
- package/skills/paper-pipeline/scripts/status.test.mjs +232 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +2 -2
- package/skills/render-paper/extract-pdf-facts.harness.mjs +56 -7
- package/skills/tighten-paper/__snapshots__/structure.test.mjs.snap +133 -0
- package/skills/tighten-paper/structure.test.mjs +116 -0
- package/skills/verify-citations/SKILL.md +5 -5
- package/skills/verify-citations/SKILL.md.spec.ts +4 -4
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +235 -0
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +274 -0
- package/skills/verify-citations/scripts/bib-authors.cli.test.mjs +178 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +2 -1
- package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +96 -96
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +287 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +149 -5
- package/src/adapters/banal/failure.test.ts +70 -1
- package/src/adapters/banal/index.test.ts +16 -0
- package/src/adapters/banal/output.test.ts +5 -0
- package/src/adapters/curl/download.test.ts +43 -1
- package/src/adapters/memory/index.test.ts +25 -0
- package/src/adapters/node/files.io.ts +3 -2
- package/src/adapters/node/files.test.ts +13 -1
- package/src/adapters/node/process.test.ts +38 -0
- package/src/adapters/pdfjs/fill.test.ts +34 -0
- package/src/adapters/pdfjs/fill.ts +44 -25
- package/src/adapters/references/index.test.ts +228 -0
- package/src/adapters/references/index.ts +21 -6
- package/src/adapters/references/pool.test.ts +31 -0
- package/src/adapters/references/pool.ts +24 -0
- package/src/build-engine.harness.mjs +3 -7
- package/src/build-engine.test.ts +137 -0
- package/src/build-engine.ts +6 -1
- package/src/build.harness.mjs +51 -11
- package/src/build.test.ts +362 -0
- package/src/build.ts +40 -14
- package/src/cli.harness.mjs +12 -19
- package/src/cli.test.ts +584 -0
- package/src/cli.ts +409 -94
- package/src/doctor.harness.mjs +3 -8
- package/src/doctor.test.ts +64 -0
- package/src/doctor.ts +12 -4
- package/src/domain/result.test.ts +4 -0
- package/src/domain/result.ts +0 -5
- package/src/engine.harness.mjs +77 -7
- package/src/engine.ts +35 -0
- package/src/facts-file.test.ts +110 -0
- package/src/hooks-settings.harness.mjs +43 -13
- package/src/hooks-settings.ts +41 -35
- package/src/init.test.ts +308 -5
- package/src/init.ts +4 -4
- package/src/latex-log.harness.mjs +32 -7
- package/src/latex-log.ts +6 -13
- package/src/latex-loop.harness.mjs +8 -7
- package/src/link-skills.harness.mjs +3 -8
- package/src/link-skills.test.ts +105 -0
- package/src/link-skills.ts +75 -21
- package/src/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +5 -7
- package/src/new-paper.test.ts +203 -2
- package/src/new-paper.ts +84 -17
- package/src/paper-files.test.ts +122 -5
- package/src/paper-files.ts +99 -39
- package/src/paper-settings-commands.test.ts +41 -5
- package/src/paper-settings.test.ts +20 -0
- package/src/parse-args.test.ts +48 -0
- package/src/pdf-facts.harness.mjs +3 -6
- package/src/pdf-facts.test.ts +144 -0
- package/src/pdf-facts.ts +17 -19
- package/src/pdf-geometry.harness.mjs +3 -7
- package/src/pdf-geometry.ts +1 -1
- package/src/presets.test.ts +2 -0
- package/src/presets.ts +7 -1
- package/src/references.test.ts +97 -0
- package/src/references.ts +2 -2
- package/src/rules-config.harness.mjs +10 -8
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +5 -9
- package/src/structure.ts +1 -1
- package/src/tex-requirements.harness.mjs +34 -7
- package/src/tex-requirements.ts +12 -6
- package/src/toolchain.harness.mjs +44 -13
- package/src/toolchain.test.ts +280 -0
- package/src/toolchain.ts +37 -12
- package/src/types.ts +6 -0
- package/src/venue-rules.test.ts +63 -0
- package/src/venue-rules.ts +14 -12
- package/templates/paper/paperlint.json +1 -1
- package/action.mutations.mjs +0 -162
- package/eslint-rules/install-path-literals.mutations.mjs +0 -62
- package/eslint-rules/latex-language.mutations.mjs +0 -196
- package/eslint-rules/paper-research-question.mutations.mjs +0 -127
- package/eslint-rules/paper-stages.mutations.mjs +0 -81
- package/eslint-rules/papers.mutations.mjs +0 -186
- package/eslint-rules/temp-root-realpath.mutations.mjs +0 -99
- package/eslint-rules/tex-build.mutations.mjs +0 -258
- package/hooks/hooks.mutations.mjs +0 -337
- package/lib/agent-cli-version.mutations.mjs +0 -109
- package/lib/mutation-driver.harness.mjs +0 -227
- package/lib/mutation-driver.mjs +0 -397
- package/lib/mutation-driver.mutations.mjs +0 -68
- package/lib/paper-config.mutations.mjs +0 -143
- package/lib/skill-trigger-cases.mutations.mjs +0 -65
- package/scripts/check.mutations.mjs +0 -110
- package/scripts/exclusive.mjs +0 -138
- package/scripts/mutation-batteries-frozen.harness.mjs +0 -204
- package/scripts/mutation-batteries-frozen.mjs +0 -238
- package/scripts/mutation-batteries.frozen.json +0 -108
- package/scripts/rules-are-content-only.mutations.mjs +0 -81
- package/scripts/rules-see-files.mutations.mjs +0 -131
- package/scripts/run-mutations.mjs +0 -100
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +0 -218
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +0 -190
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +0 -65
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +0 -122
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +0 -59
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +0 -276
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +0 -65
- package/skills/skill-contract.mutations.mjs +0 -179
- package/src/build.mutations.mjs +0 -196
- package/src/cli.mutations.mjs +0 -414
- package/src/doctor.mutations.mjs +0 -175
- package/src/hooks-settings.mutations.mjs +0 -99
- package/src/link-skills.mutations.mjs +0 -140
- package/src/new-paper.mutations.mjs +0 -79
- package/src/structure.mutations.mjs +0 -83
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# What we test with
|
|
1
|
+
# What we test with
|
|
2
2
|
|
|
3
|
-
**Question this file answers:**
|
|
4
|
-
|
|
5
|
-
Is that a gap? Should an npm package's install be tested through a local registry?
|
|
3
|
+
**Question this file answers:** should an npm package's install be tested through a local
|
|
4
|
+
registry, and which tests belong to which tool?
|
|
6
5
|
|
|
7
|
-
**Verdict
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
**Verdict.** Unit tests run on vitest (`*.test.ts` / `*.test.mjs`); agent-facing checks are vigiles
|
|
7
|
+
harnesses; the install and build are end-to-end scripts. The install is tested by packing a
|
|
8
|
+
tarball and installing it by path, not through a local registry: a registry is what
|
|
9
|
+
MULTI-package repositories reach for. That conclusion has named triggers for revisiting, below.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -27,10 +27,6 @@ So the genuinely agent-facing harnesses are three: `hooks/hooks.harness.mjs` (`r
|
|
|
27
27
|
(`runHarnessTest`, which spawns the real `claude` binary against a scripted model), and the skill
|
|
28
28
|
contract checks. Everything else is ordinary Node testing that happens to carry the suffix.
|
|
29
29
|
|
|
30
|
-
⚠️ **The suffix is a CONTRACT, not a style choice.** `lib/mutation-driver.mjs` pairs batteries to
|
|
31
|
-
harnesses by it. Renaming files for a new runner would break that, which has nothing to do with
|
|
32
|
-
running tests.
|
|
33
|
-
|
|
34
30
|
## Why the install e2e stays a script
|
|
35
31
|
|
|
36
32
|
`test/e2e/install.mjs` is one linear scenario per package manager with strictly dependent steps
|
|
@@ -81,25 +77,6 @@ answering. Recording this so the question "would it even run here" never has to
|
|
|
81
77
|
documented install becoming a cold `npx <name>`. Then: verdaccio as a devDependency spawned by the
|
|
82
78
|
script, Angular-style — not a Docker service in CI.
|
|
83
79
|
|
|
84
|
-
## Why no vitest, and no `node:test` either
|
|
85
|
-
|
|
86
|
-
vitest's value is its Vite transform pipeline, jsdom, module mocking and a watch UI. The tests here
|
|
87
|
-
are plain `.mjs`, the sources are compiled by `tsc`, there is no DOM, and the design deliberately
|
|
88
|
-
does not mock — it spawns real processes into temp directories, which is the right shape for a CLI.
|
|
89
|
-
So vitest would add vite and a native esbuild binary to a package whose install weight is itself
|
|
90
|
-
under scrutiny, and nothing breaks without it.
|
|
91
|
-
|
|
92
|
-
`node:test` costs no dependency but creates the two-answers problem verbatim: a second discovery
|
|
93
|
-
mechanism (`--test` globs against the `.harness.mjs` suffix) and a second exit code.
|
|
94
|
-
|
|
95
|
-
**What staying honestly costs:** a top-level `assert` stops at the FIRST failure, so a table-driven
|
|
96
|
-
test over many fixtures reports one row per run. Also no name-level filtering (file-level exists:
|
|
97
|
-
`vigiles test <file>`).
|
|
98
|
-
|
|
99
|
-
**Trigger to revisit:** when continue-after-failure is measurably costing iteration time on a
|
|
100
|
-
table-driven test. The answer then is `node:test`, never vitest, and it must run under the same
|
|
101
|
-
`npm test` line rather than beside it.
|
|
102
|
-
|
|
103
80
|
## One known limit, recorded so it is not claimed
|
|
104
81
|
|
|
105
82
|
**Yarn Berry (PnP) cannot be supported as things stand.** `plugin/hooks/hooks.json` addresses
|
package/docs/rules.md
CHANGED
|
@@ -3,30 +3,30 @@
|
|
|
3
3
|
The README lists what each check catches in one line. This page says exactly which file each one
|
|
4
4
|
reads and when it fails. Errors fail `paperlint lint`; warnings print and do not.
|
|
5
5
|
|
|
6
|
-
| Rule | Level | Reads | Fails when
|
|
7
|
-
| ------------------------------ | ----- | --------------------------------------------- |
|
|
8
|
-
| `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares
|
|
9
|
-
| `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it)
|
|
10
|
-
| `paper/author-list` | error | `paper.tex` → `_build/references.json` | an entry's authors are not those of the version it cites — typically the arXiv preprint's list under a published venue. Checked online by `paperlint build` (DBLP), reported on the entry's own line
|
|
11
|
-
| `paper/research-question` | warn | `paper.tex`, `paper.md`, `PIPELINE-STATUS.md` | a stage is declared and either the scorecard has no `researchQuestion` field, or it has one the paper does not contain. The declared sentence is compared against the source with whitespace collapsed — your words, not a pattern. Advisory, because nothing here can judge whether what you declared _is_ a research question
|
|
12
|
-
| `paper/section-word` | warn | `paper.tex`, `paper.md` | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5`
|
|
13
|
-
| `paper/leading-zero` | warn | `paper.tex`, `paper.md` | a decimal below 1 without its leading zero (`.05` instead of `0.05`, IEEE / ISO 80000-1 style), counted only where the reader sees it — prose, math and table cells, not macro options, column specs, comments, code or tikz. **Fixable**
|
|
14
|
-
| `paper/figure-ref-style` | warn | `paper.tex` | `Fig.~\ref` and `Figure~\ref` mixed in one document; each minority occurrence is reported. **Fixable:** `--fix` writes the majority form
|
|
15
|
-
| `bib/reachable-entry` | warn | `paper.tex` (its `filecontents` bibliography) | an entry has no doi, url or arXiv id — a reader has nothing to follow. Reported on the entry; no fix, the link has to be looked up
|
|
16
|
-
| `paper/cite-exists` | error | `paper.tex` → `_build/references.json` | an entry's doi or arXiv id resolves to nothing, or to a different work. Checked online by `paperlint build`; a work that was merely not found is not a finding
|
|
17
|
-
| `paper/refs-fresh` | error | `paper.tex` → `_build/references.json` | the bibliography changed since the build checked it (its SHA-256 differs) — run `npx paperlint build`
|
|
18
|
-
| `paper/refs-checked` | warn | `paper.tex` → `_build/references.json` | the paper has a bibliography and no build has checked it, or the last build could not (no network) — so the two rules above did not run
|
|
19
|
-
| `tex/future-promise` | warn | `paper.tex` | a camera-ready build still says "will be released" about something already handed over
|
|
20
|
-
| `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1
|
|
21
|
-
| `review/frontmatter` | error | `reviews/*.md` | the review's frontmatter fails paperlint's JSON Schema — a `findings` record without `id`/`status`, an unknown field, or an **open** finding with no `cause` (`skill-defect` · `missing-skill` · `hook` · `rule`). A review with no `findings` key is not checked for findings
|
|
22
|
-
| `sibling/frontmatter` | warn | `siblings/*.md` (not `README.md`) | a sibling card's frontmatter has no `read:` saying how much of the competing paper was read (`full` · `abstract` · `none`)
|
|
23
|
-
| `pdf/fresh` | error | `paper.tex` → `_build/paper.facts.json` | the paper names a venue and the facts cannot be judged: not JSON, a schema other than 2, or they describe a PDF that is gone or differs from the one on disk (its SHA-256)
|
|
24
|
-
| `pdf/profile` | error | `paper.tex` → `paperlint.json` | `paperlint.json` is not JSON or has an unknown key, its `extends` does not resolve (not found, a cycle, a chain longer than four, a preset that fails the schema, an npm name), it names no `kind` while the preset has kinds, or names a kind the preset does not have
|
|
25
|
-
| `pdf/fonts` | error | `paper.tex` → `_build/paper.facts.json` | a font the pages draw is Type 3 or not embedded, or no font starts with the family the venue preset names for body text (`fonts_text`) or headings (`fonts_title`)
|
|
26
|
-
| `pdf/geometry` | error | `paper.tex` → `_build/paper.facts.json` | the page width or height is more than `dimTol` (default 0.05 in) off the preset's, or the column count differs
|
|
27
|
-
| `pdf/limits` | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limit of the paper's kind, or the reference font size is outside the preset's range widened by `body_pt_tol`
|
|
28
|
-
| `pdf/body-size` | warn | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's. A warning: banal measures the mode of the rendered text, not the declared size (9.30 pt measured at a declared 9)
|
|
29
|
-
| `pdf/measured` | warn | `paper.tex` → `_build/paper.facts.json` | the paper's `paperlint.json` names no venue preset yet; or it names one and there are no facts (it was not built), or the facts carry no page geometry (banal was not found) — so the checks above did not run
|
|
6
|
+
| Rule | Level | Reads | Fails when |
|
|
7
|
+
| ------------------------------ | ----- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares |
|
|
9
|
+
| `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it) |
|
|
10
|
+
| `paper/author-list` | error | `paper.tex` → `_build/references.json` | an entry's authors are not those of the version it cites — typically the arXiv preprint's list under a published venue. Checked online by `paperlint build` (DBLP), reported on the entry's own line |
|
|
11
|
+
| `paper/research-question` | warn | `paper.tex`, `paper.md`, `PIPELINE-STATUS.md` | a stage is declared and either the scorecard has no `researchQuestion` field, or it has one the paper does not contain. The declared sentence is compared against the source with whitespace collapsed — your words, not a pattern. Advisory, because nothing here can judge whether what you declared _is_ a research question |
|
|
12
|
+
| `paper/section-word` | warn | `paper.tex`, `paper.md` | `§` or `\S\ref` instead of the word Section. **Fixable:** `--fix` writes `Section~\ref{…}` / `Section 5` |
|
|
13
|
+
| `paper/leading-zero` | warn | `paper.tex`, `paper.md` | a decimal below 1 without its leading zero (`.05` instead of `0.05`, IEEE / ISO 80000-1 style), counted only where the reader sees it — prose, math and table cells, not macro options, column specs, comments, code or tikz. A number right after `¶` or `§` (`¶¶.42`) is a paragraph designator and is not reported; in a paragraph that cites numbers that way, another bare decimal is reported without a fix, with the zero as a suggestion. **Fixable** |
|
|
14
|
+
| `paper/figure-ref-style` | warn | `paper.tex` | `Fig.~\ref` and `Figure~\ref` mixed in one document; each minority occurrence is reported. **Fixable:** `--fix` writes the majority form |
|
|
15
|
+
| `bib/reachable-entry` | warn | `paper.tex` (its `filecontents` bibliography) | an entry has no doi, url or arXiv id — a reader has nothing to follow. Reported on the entry; no fix, the link has to be looked up |
|
|
16
|
+
| `paper/cite-exists` | error | `paper.tex` → `_build/references.json` | an entry's doi or arXiv id resolves to nothing, or to a different work. Checked online by `paperlint build`; a work that was merely not found is not a finding |
|
|
17
|
+
| `paper/refs-fresh` | error | `paper.tex` → `_build/references.json` | the bibliography changed since the build checked it (its SHA-256 differs) — run `npx paperlint build` |
|
|
18
|
+
| `paper/refs-checked` | warn | `paper.tex` → `_build/references.json` | the paper has a bibliography and no build has checked it, or the last build could not (no network) — so the two rules above did not run |
|
|
19
|
+
| `tex/future-promise` | warn | `paper.tex` | a camera-ready build still says "will be released" about something already handed over |
|
|
20
|
+
| `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1 |
|
|
21
|
+
| `review/frontmatter` | error | `reviews/*.md` | the review's frontmatter fails paperlint's JSON Schema — a `findings` record without `id`/`status`, an unknown field, or an **open** finding with no `cause` (`skill-defect` · `missing-skill` · `hook` · `rule`). A review with no `findings` key is not checked for findings |
|
|
22
|
+
| `sibling/frontmatter` | warn | `siblings/*.md` (not `README.md`) | a sibling card's frontmatter has no `read:` saying how much of the competing paper was read (`full` · `abstract` · `none`) |
|
|
23
|
+
| `pdf/fresh` | error | `paper.tex` → `_build/paper.facts.json` | the paper names a venue and the facts cannot be judged: not JSON, a schema other than 2, or they describe a PDF that is gone or differs from the one on disk (its SHA-256) |
|
|
24
|
+
| `pdf/profile` | error | `paper.tex` → `paperlint.json` | `paperlint.json` is not JSON or has an unknown key, its `extends` does not resolve (not found, a cycle, a chain longer than four, a preset that fails the schema, an npm name), it names no `kind` while the preset has kinds, or names a kind the preset does not have |
|
|
25
|
+
| `pdf/fonts` | error | `paper.tex` → `_build/paper.facts.json` | a font the pages draw is Type 3 or not embedded, or no font starts with the family the venue preset names for body text (`fonts_text`) or headings (`fonts_title`) |
|
|
26
|
+
| `pdf/geometry` | error | `paper.tex` → `_build/paper.facts.json` | the page width or height is more than `dimTol` (default 0.05 in) off the preset's, or the column count differs |
|
|
27
|
+
| `pdf/limits` | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limit of the paper's kind, or the reference font size is outside the preset's range widened by `body_pt_tol` |
|
|
28
|
+
| `pdf/body-size` | warn | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's. A warning: banal measures the mode of the rendered text, not the declared size (9.30 pt measured at a declared 9) |
|
|
29
|
+
| `pdf/measured` | warn | `paper.tex` → `_build/paper.facts.json` | the paper's `paperlint.json` names no venue preset yet; or it names one and there are no facts (it was not built), or the facts carry no page geometry (banal was not found) — so the checks above did not run |
|
|
30
30
|
|
|
31
31
|
Optional rules — off unless you turn them on in the `rules` setting, because only some venues need
|
|
32
32
|
them — are on their own page: [`optional-rules.md`](optional-rules.md). Today there is one,
|
|
@@ -200,7 +200,11 @@ rules, which is why they list it above. Markdown papers are deprecated and being
|
|
|
200
200
|
|
|
201
201
|
## The scorecard's `bytes:` and `sourceBytes:`
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
**When you submit,** copy the PDF and `paper.tex` you sent into the paper's `versions/` folder and
|
|
204
|
+
record the stage in the front matter of `PIPELINE-STATUS.md` (the paper's scorecard). From then on
|
|
205
|
+
`paper/stages` fails if that PDF goes missing or changes size, and `paper/source` if its source was
|
|
206
|
+
not kept. Today you record it by hand; a command for it is planned
|
|
207
|
+
([#100](https://github.com/zernie/paperlint/issues/100)). A stage looks like this:
|
|
204
208
|
|
|
205
209
|
```yaml
|
|
206
210
|
---
|
package/docs/skills.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Skills
|
|
2
|
+
|
|
3
|
+
Every skill paperlint ships, by the stages of [the pipeline](../README.md#-the-pipeline). Each line
|
|
4
|
+
says what you get; the skill's name is the second column.
|
|
5
|
+
|
|
6
|
+
You do not call them by name: you ask Claude Code for the thing ("is this idea worth a paper?"), and
|
|
7
|
+
the skill whose description matches starts. `paperlint init` installs them into
|
|
8
|
+
`.claude/skills/` ([`install.md`](install.md)). Each one's full instructions are its
|
|
9
|
+
`skills/<name>/SKILL.md`.
|
|
10
|
+
|
|
11
|
+
`test/skills-catalog.test.ts` fails when this list and the shipped `skills/` directory disagree.
|
|
12
|
+
|
|
13
|
+
## Across every stage
|
|
14
|
+
|
|
15
|
+
| you get | skill |
|
|
16
|
+
| ----------------------------------------------------------------------------------------- | ---------------- |
|
|
17
|
+
| a guided path through the stages: what to do now, and which skill does it | `paper-pipeline` |
|
|
18
|
+
| where the paper stands, measured: pages from the real build, what is checked, what blocks | `paper-status` |
|
|
19
|
+
|
|
20
|
+
## 1 · Idea
|
|
21
|
+
|
|
22
|
+
| you get | skill |
|
|
23
|
+
| --------------------------------------------------------------------------------------- | ----------------------- |
|
|
24
|
+
| a go / no-go on the idea, with the reason and the smallest result that makes a paper | `research-ideate` |
|
|
25
|
+
| who already works on it, dated against your deadline, and what you can still claim | `map-prior-work` |
|
|
26
|
+
| one close competitor read in full: is yours "just X?", and the sentence that answers it | `analyze-sibling-paper` |
|
|
27
|
+
| candidate mechanisms from other fields, when the work needs a design, not a measurement | `sweep-design-space` |
|
|
28
|
+
|
|
29
|
+
## 2 · Venue
|
|
30
|
+
|
|
31
|
+
| you get | skill |
|
|
32
|
+
| ------------------------------------------------------------------------------ | ----------------------- |
|
|
33
|
+
| a ranked list of venues that fit: deadline, page limit, indexing, remote talks | `find-venue` |
|
|
34
|
+
| what papers accepted at that venue do well, and what would make yours stronger | `study-accepted-papers` |
|
|
35
|
+
| the deadlines worked backwards, as events in your calendar | `plan-paper-timeline` |
|
|
36
|
+
|
|
37
|
+
## 3 · Study
|
|
38
|
+
|
|
39
|
+
| you get | skill |
|
|
40
|
+
| ------------------------------------------------------------------------------------------ | ----------------- |
|
|
41
|
+
| a study design with honest statistics, and a reproduction artifact that checks its numbers | `build-benchmark` |
|
|
42
|
+
|
|
43
|
+
## 4 · Draft
|
|
44
|
+
|
|
45
|
+
| you get | skill |
|
|
46
|
+
| ---------------------------------------------------------------------------------- | ---------------- |
|
|
47
|
+
| a first draft from your results: claims sized, threats to validity written | `draft-paper` |
|
|
48
|
+
| an outline where each section follows from the one before | `argument-arc` |
|
|
49
|
+
| a cut plan: what to cut, merge or move to the appendix | `tighten-paper` |
|
|
50
|
+
| what a reader with no context understood from your last edit, sentence by sentence | `cold-read-diff` |
|
|
51
|
+
| the compiled pages as images, to read on a screen or a phone | `render-paper` |
|
|
52
|
+
|
|
53
|
+
## 5 · Review
|
|
54
|
+
|
|
55
|
+
| you get | skill |
|
|
56
|
+
| ----------------------------------------------------------------------------- | -------------------------- |
|
|
57
|
+
| a writing grade, with the sentence to fix for each point | `grade-paper-writing` |
|
|
58
|
+
| every cited work confirmed to exist, with the right authors, title and year | `verify-citations` |
|
|
59
|
+
| one hostile review: overclaims, missing baselines, holes in the method | `paper-adversarial-review` |
|
|
60
|
+
| a simulated program committee: several reviews, a decision and the must-fixes | `pc-panel-review` |
|
|
61
|
+
| ready or not ready to submit, worst problem first | `harden-paper` |
|
|
62
|
+
|
|
63
|
+
## 6 · Submit
|
|
64
|
+
|
|
65
|
+
| you get | skill |
|
|
66
|
+
| ------------------------------------------------------------------- | --------------------- |
|
|
67
|
+
| the submission, step by step: anonymization, the form, the artifact | `submit-paper` |
|
|
68
|
+
| the reproduction artifact uploaded to OSF for anonymous review | `osf-artifact-upload` |
|
|
69
|
+
|
|
70
|
+
## 7 · Camera-ready
|
|
71
|
+
|
|
72
|
+
| you get | skill |
|
|
73
|
+
| ------------------------------------------------------------------------------------------ | -------------- |
|
|
74
|
+
| the final version: authors back, the artifact public with a DOI, any disclosure done first | `camera-ready` |
|
|
75
|
+
|
|
76
|
+
## 8 · Extend
|
|
77
|
+
|
|
78
|
+
| you get | skill |
|
|
79
|
+
| ------------------------------------------------------------- | -------------- |
|
|
80
|
+
| a plan for the next, stronger paper built on the accepted one | `extend-paper` |
|
|
@@ -118,4 +118,20 @@ const cases = [];
|
|
|
118
118
|
cases.push("js quiet on a module that asks the port");
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
// ── 5. A TAGGED TEMPLATE WITH AN INVALID ESCAPE has no cooked value, only the raw one — and the
|
|
122
|
+
// path in it is still a path. (`String.raw` is exactly where such a template is legal.)
|
|
123
|
+
{
|
|
124
|
+
const [res] = await jsLinter.lintText(
|
|
125
|
+
"export const p = String.raw`node_modules/paperlint/\\unicode`;\n",
|
|
126
|
+
{ filePath: join(ROOT, "tagged.probe.mjs") },
|
|
127
|
+
);
|
|
128
|
+
const m = res.messages.filter((x) => x.ruleId === RULE_JS);
|
|
129
|
+
assert.equal(
|
|
130
|
+
m.length,
|
|
131
|
+
1,
|
|
132
|
+
`a raw-only template quasi must be read, got ${JSON.stringify(res.messages)}`,
|
|
133
|
+
);
|
|
134
|
+
cases.push("js fires on a tagged template whose quasi has no cooked value");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
recordCheck(cases.length);
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* clean.tex` goes through the repository's real `eslint.config.mjs` and must be silent.
|
|
12
12
|
* II. THE PROJECTION ACTUALLY DID ITS WORK — headings (including SYNTHESISED ones), caption
|
|
13
13
|
* and footnote prose kept, float markup blanked, opaque macros blanked, math blanked,
|
|
14
|
-
* comments recognised. This is the half
|
|
15
|
-
* language could be gutted and part I would stay green, because a
|
|
16
|
-
* everything is also silent.
|
|
14
|
+
* comments recognised. This is the half a gutted projection could still hide behind;
|
|
15
|
+
* without it the language could be gutted and part I would stay green, because a
|
|
16
|
+
* projection that blanks everything is also silent.
|
|
17
17
|
* III. FIRES ON A PLANTED DEFECT — `fixtures/latex-language/defect.tex`, through the real
|
|
18
18
|
* config, must produce findings, and at the right addresses.
|
|
19
19
|
* IV. THE LANGUAGE'S BOUNDARIES AS ASSERTIONS. A known limitation recorded only in prose
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* arguments of those two macros.
|
|
34
34
|
* 🔴 Removing `table` / `figure` from `CODEISH` was NOT an option — rules would then receive
|
|
35
35
|
* `&`, `\\`, the column specification and tikz nodes, i.e. findings on markup. That is pinned
|
|
36
|
-
* by the harness
|
|
36
|
+
* by the harness.
|
|
37
37
|
* Measured after the fix: nine new findings across three papers, all of them captions and
|
|
38
38
|
* footnotes, reviewed one by one; zero false positives on markup.
|
|
39
39
|
*
|
|
@@ -143,8 +143,13 @@ const argEnd = (node) => {
|
|
|
143
143
|
return end;
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The projection of `src`. `parse` is unified-latex by default; a test passes one returning the
|
|
148
|
+
* node shapes today's parser never emits (a macro without `args`, an argument without `content`, an
|
|
149
|
+
* `env` given as nodes), which the fallbacks below keep reading instead of throwing on.
|
|
150
|
+
*/
|
|
151
|
+
export function texToMdast(src, { parse = (s) => getParser().parse(s) } = {}) {
|
|
152
|
+
const ast = parse(src);
|
|
148
153
|
const chars = src.split("");
|
|
149
154
|
const blank = (from, to) => {
|
|
150
155
|
for (let i = from; i < to && i < chars.length; i++)
|
|
@@ -264,15 +269,13 @@ export function texToMdast(src) {
|
|
|
264
269
|
return;
|
|
265
270
|
}
|
|
266
271
|
if (env === "thebibliography" && pos) {
|
|
267
|
-
|
|
272
|
+
// `\begin{thebibliography}` (23 characters) always holds `## References` (13).
|
|
273
|
+
synthHeading(
|
|
268
274
|
pos.start.offset,
|
|
269
275
|
pos.start.offset + "\\begin{thebibliography}".length,
|
|
270
276
|
"References",
|
|
271
277
|
);
|
|
272
|
-
blank(
|
|
273
|
-
pos.start.offset + (h ? "## References".length : 0),
|
|
274
|
-
pos.end.offset,
|
|
275
|
-
);
|
|
278
|
+
blank(pos.start.offset + "## References".length, pos.end.offset);
|
|
276
279
|
children.push({
|
|
277
280
|
type: "code",
|
|
278
281
|
lang: env,
|
|
@@ -282,7 +285,7 @@ export function texToMdast(src) {
|
|
|
282
285
|
return;
|
|
283
286
|
}
|
|
284
287
|
if (CODEISH.test(env) && pos) {
|
|
285
|
-
// 🔴 THERE WERE TWO LOCKS, AND LIFTING ONE DID NOT HELP
|
|
288
|
+
// 🔴 THERE WERE TWO LOCKS, AND LIFTING ONE DID NOT HELP: a
|
|
286
289
|
// caption was blanked by `OPAQUE` as a macro, and inside `table` / `figure` it was
|
|
287
290
|
// blanked AGAIN by the whole environment. So what is blanked here is not the float's
|
|
288
291
|
// borders but the float MINUS the arguments of `\caption{}` / `\footnote{}`.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* texToMdast / texLanguage on the shapes the harness does not carry: a `thebibliography`
|
|
3
|
+
* environment, a float on the file's last line with no newline after it, a heading whose title
|
|
4
|
+
* holds a macro, an unknown macro in prose, and a parse that fails (nesting deep enough to exhaust the parser).
|
|
5
|
+
*/
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { test } from "vitest";
|
|
8
|
+
import { texLanguage, texToMdast } from "./latex-language.mjs";
|
|
9
|
+
|
|
10
|
+
/** The projection plus each node's type, lang/title and start–end offsets. */
|
|
11
|
+
const project = (src) => {
|
|
12
|
+
const { root, text } = texToMdast(src);
|
|
13
|
+
return {
|
|
14
|
+
text,
|
|
15
|
+
nodes: root.children.map((c) => [
|
|
16
|
+
c.type,
|
|
17
|
+
c.lang ?? c.title,
|
|
18
|
+
c.position.start.offset,
|
|
19
|
+
c.position.end.offset,
|
|
20
|
+
]),
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
test("thebibliography: a References heading over `\\begin`, the rest one blanked code node", () => {
|
|
25
|
+
assert.deepEqual(
|
|
26
|
+
project(
|
|
27
|
+
"\\begin{thebibliography}{9}\n\\bibitem{a} A.\n\\end{thebibliography}\n",
|
|
28
|
+
),
|
|
29
|
+
{
|
|
30
|
+
text: "## References \n \n \n",
|
|
31
|
+
nodes: [
|
|
32
|
+
["heading", "References", 0, 13],
|
|
33
|
+
["code", "thebibliography", 14, 63],
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("a float on the last line with no newline after it ends at the end of the file", () => {
|
|
40
|
+
assert.deepEqual(project("x\n\\begin{table}y\\end{table}"), {
|
|
41
|
+
text: "x\n ",
|
|
42
|
+
nodes: [["code", "table", 2, 27]],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("a heading's title drops an argument-less macro inside it; an unknown macro in prose loses only its name", () => {
|
|
47
|
+
assert.deepEqual(
|
|
48
|
+
project("\\section{A \\LaTeX{} b}\nsome \\foo{bar} text\n"),
|
|
49
|
+
{
|
|
50
|
+
text: "## A b \nsome {bar} text\n",
|
|
51
|
+
nodes: [["heading", "A b", 0, 22]],
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a heading's title keeps the text of a macro with an argument", () => {
|
|
57
|
+
assert.deepEqual(project("\\section{A \\emph{b} c}\n"), {
|
|
58
|
+
text: "## A b c \n",
|
|
59
|
+
nodes: [["heading", "A b c", 0, 22]],
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a body the parser cannot take (nesting deep enough to exhaust its stack) is a parse failure, not a throw", () => {
|
|
64
|
+
const r = texLanguage.parse({ body: "{".repeat(20_000) });
|
|
65
|
+
assert.deepEqual(
|
|
66
|
+
[r.ok, r.errors.map((e) => e.constructor.name)],
|
|
67
|
+
[false, ["RangeError"]],
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/** A node position on line 1, from offset `a` to offset `b`. */
|
|
72
|
+
const at = (a, b) => ({
|
|
73
|
+
start: { offset: a, line: 1, column: a + 1 },
|
|
74
|
+
end: { offset: b, line: 1, column: b + 1 },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("node shapes today's parser never emits are read, not thrown on", () => {
|
|
78
|
+
// Each node lacks what unified-latex always gives: `args`, an argument's `content`, a string `env`.
|
|
79
|
+
const src = " ".repeat(200);
|
|
80
|
+
const ast = {
|
|
81
|
+
type: "root",
|
|
82
|
+
content: [
|
|
83
|
+
// a heading macro with no args at all: an empty title
|
|
84
|
+
{ type: "macro", content: "section", position: at(0, 8) },
|
|
85
|
+
// a heading whose title holds a macro whose argument has no content
|
|
86
|
+
{
|
|
87
|
+
type: "macro",
|
|
88
|
+
content: "subsection",
|
|
89
|
+
position: at(10, 40),
|
|
90
|
+
args: [
|
|
91
|
+
{
|
|
92
|
+
type: "argument",
|
|
93
|
+
openMark: "{",
|
|
94
|
+
closeMark: "}",
|
|
95
|
+
content: [
|
|
96
|
+
{ type: "string", content: "T", position: at(22, 23) },
|
|
97
|
+
{ type: "macro", content: "emph", args: [{ type: "argument" }] },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
// an environment named by nodes rather than a string
|
|
103
|
+
{
|
|
104
|
+
type: "environment",
|
|
105
|
+
env: [{ type: "string", content: "abstract" }],
|
|
106
|
+
content: [],
|
|
107
|
+
position: at(50, 80),
|
|
108
|
+
},
|
|
109
|
+
// a float whose caption has no args, and whose footnote's argument has no content
|
|
110
|
+
{
|
|
111
|
+
type: "environment",
|
|
112
|
+
env: "table",
|
|
113
|
+
position: at(90, 120),
|
|
114
|
+
content: [
|
|
115
|
+
{ type: "macro", content: "caption", position: at(95, 100) },
|
|
116
|
+
{
|
|
117
|
+
type: "macro",
|
|
118
|
+
content: "footnote",
|
|
119
|
+
position: at(101, 110),
|
|
120
|
+
args: [{ type: "argument" }],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
// texttt and a first-column textbf with no args; an ordinary macro whose argument is empty
|
|
125
|
+
{ type: "macro", content: "texttt", position: at(125, 132) },
|
|
126
|
+
{
|
|
127
|
+
type: "macro",
|
|
128
|
+
content: "textbf",
|
|
129
|
+
position: {
|
|
130
|
+
start: { offset: 133, line: 2, column: 1 },
|
|
131
|
+
end: { offset: 140, line: 2, column: 8 },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: "macro",
|
|
136
|
+
content: "foo",
|
|
137
|
+
position: at(141, 150),
|
|
138
|
+
args: [{ type: "argument" }],
|
|
139
|
+
},
|
|
140
|
+
// the same macros with an argument that has no content
|
|
141
|
+
{
|
|
142
|
+
type: "macro",
|
|
143
|
+
content: "subsubsection",
|
|
144
|
+
position: at(170, 180),
|
|
145
|
+
args: [{ type: "argument", openMark: "{", closeMark: "}" }],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: "macro",
|
|
149
|
+
content: "texttt",
|
|
150
|
+
position: at(181, 185),
|
|
151
|
+
args: [{ type: "argument" }],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: "macro",
|
|
155
|
+
content: "emph",
|
|
156
|
+
position: {
|
|
157
|
+
start: { offset: 186, line: 3, column: 1 },
|
|
158
|
+
end: { offset: 190, line: 3, column: 5 },
|
|
159
|
+
},
|
|
160
|
+
args: [{ type: "argument" }],
|
|
161
|
+
},
|
|
162
|
+
// `\bibliography` squeezed into one character: too short for `## References`, so no heading
|
|
163
|
+
{ type: "macro", content: "bibliography", position: at(160, 160) },
|
|
164
|
+
// a node the walk meets without a position
|
|
165
|
+
{ type: "environment", env: "abstract", content: [] },
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
const { root } = texToMdast(src, { parse: () => ast });
|
|
169
|
+
assert.deepEqual(
|
|
170
|
+
root.children.map((c) => [c.type, c.title ?? c.lang ?? ""]),
|
|
171
|
+
// Sorted by offset: the float's code node starts at its (single) line's start.
|
|
172
|
+
[
|
|
173
|
+
["heading", ""],
|
|
174
|
+
["code", "table"],
|
|
175
|
+
["heading", "T"],
|
|
176
|
+
["heading", "Abstract"],
|
|
177
|
+
["strong", ""],
|
|
178
|
+
["heading", ""],
|
|
179
|
+
["strong", ""],
|
|
180
|
+
],
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("a source code made from a parse result without the raw text reads the projection; parents are recorded", () => {
|
|
185
|
+
const body = "\\section{A}\n";
|
|
186
|
+
const parsed = texLanguage.parse({ body });
|
|
187
|
+
const code = texLanguage.createSourceCode(
|
|
188
|
+
{ body },
|
|
189
|
+
{ ast: parsed.ast, projected: parsed.projected },
|
|
190
|
+
);
|
|
191
|
+
const [heading] = parsed.ast.children;
|
|
192
|
+
assert.deepEqual(
|
|
193
|
+
[
|
|
194
|
+
code.raw === parsed.projected,
|
|
195
|
+
code.getParent(parsed.ast),
|
|
196
|
+
code.getParent(heading) === parsed.ast,
|
|
197
|
+
],
|
|
198
|
+
[true, undefined, true],
|
|
199
|
+
);
|
|
200
|
+
});
|
|
@@ -13,10 +13,19 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import assert from "node:assert/strict";
|
|
15
15
|
import { join, dirname } from "node:path";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
mkdirSync,
|
|
18
|
+
mkdtempSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
realpathSync,
|
|
21
|
+
rmSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
17
25
|
import { fileURLToPath } from "node:url";
|
|
18
26
|
import { Linter } from "eslint";
|
|
19
27
|
import markdown from "@eslint/markdown";
|
|
28
|
+
import { createChecker } from "../lib/check.mjs";
|
|
20
29
|
|
|
21
30
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
22
31
|
const FIX = join(HERE, "..", "fixtures", "paper-research-question");
|
|
@@ -24,11 +33,7 @@ const FIX = join(HERE, "..", "fixtures", "paper-research-question");
|
|
|
24
33
|
const { texLanguage } = await import(join(HERE, "latex-language.mjs"));
|
|
25
34
|
const rq = (await import(join(HERE, "paper-research-question.mjs"))).default;
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
const check = (label, cond) => {
|
|
29
|
-
assert.ok(cond, label);
|
|
30
|
-
n++;
|
|
31
|
-
};
|
|
36
|
+
const check = createChecker();
|
|
32
37
|
|
|
33
38
|
const linter = new Linter();
|
|
34
39
|
|
|
@@ -141,6 +146,67 @@ check(
|
|
|
141
146
|
.length === 1,
|
|
142
147
|
);
|
|
143
148
|
|
|
149
|
+
// ── a scorecard that cannot be read is no scorecard: silence, never a crash ────────────
|
|
150
|
+
// A directory where the file should be (EISDIR), and front matter that is not YAML — which
|
|
151
|
+
// `paper/stages` reports on the scorecard itself, so this rule does not report it twice.
|
|
152
|
+
{
|
|
153
|
+
const work = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-rq-")));
|
|
154
|
+
try {
|
|
155
|
+
const paperIn = (name, status) => {
|
|
156
|
+
const dir = join(work, name);
|
|
157
|
+
mkdirSync(dir);
|
|
158
|
+
status(join(dir, "PIPELINE-STATUS.md"));
|
|
159
|
+
const path = join(dir, "paper.md");
|
|
160
|
+
writeFileSync(path, "# A paper\n");
|
|
161
|
+
// A linter rooted at the scratch directory: files outside `cwd` match no config.
|
|
162
|
+
return new Linter({ cwd: work }).verify(
|
|
163
|
+
"# A paper\n",
|
|
164
|
+
[
|
|
165
|
+
{
|
|
166
|
+
files: ["**/*.md"],
|
|
167
|
+
plugins: { markdown, paper: rq },
|
|
168
|
+
language: "markdown/gfm",
|
|
169
|
+
rules: { "paper/research-question": "error" },
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
path,
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
check(
|
|
176
|
+
"a scorecard path that is a directory: no finding, no crash",
|
|
177
|
+
JSON.stringify(paperIn("dir", (p) => mkdirSync(p))) === "[]",
|
|
178
|
+
() => paperIn("dir2", (p) => mkdirSync(p)),
|
|
179
|
+
);
|
|
180
|
+
check(
|
|
181
|
+
"a scorecard whose front matter is not YAML: no finding, no crash",
|
|
182
|
+
JSON.stringify(
|
|
183
|
+
paperIn("yaml", (p) =>
|
|
184
|
+
writeFileSync(p, "---\nstages: [submitted\n---\n"),
|
|
185
|
+
),
|
|
186
|
+
) === "[]",
|
|
187
|
+
);
|
|
188
|
+
} finally {
|
|
189
|
+
rmSync(work, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── a language whose source code carries no text at all: nothing to compare, silence ───
|
|
194
|
+
{
|
|
195
|
+
const reports = [];
|
|
196
|
+
rq.rules["research-question"]
|
|
197
|
+
.create({
|
|
198
|
+
options: [],
|
|
199
|
+
filename: join(FIX, "shipped-no-rq", "paper.tex"),
|
|
200
|
+
sourceCode: {},
|
|
201
|
+
report: (r) => reports.push(r),
|
|
202
|
+
})
|
|
203
|
+
["root:exit"]({});
|
|
204
|
+
check(
|
|
205
|
+
"a source code with neither `raw` nor `text` is skipped, even for a shipped paper",
|
|
206
|
+
reports.length === 0,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
144
210
|
console.log(
|
|
145
|
-
`✓ ${String(
|
|
211
|
+
`✓ ${String(check.count)} assertions passed — paper/research-question, the debt of a shipped paper`,
|
|
146
212
|
);
|