paperlint 4.0.0 → 4.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 +16 -23
- package/CLAUDE.md +55 -11
- package/CONTRIBUTING.md +30 -7
- package/README.md +11 -8
- package/dist/adapters/banal/invocation.d.ts +1 -1
- package/dist/adapters/latex/document-class.d.ts +23 -0
- package/dist/adapters/latex/document-class.d.ts.map +1 -0
- package/dist/adapters/latex/document-class.js +110 -0
- package/dist/adapters/latex/document-class.js.map +1 -0
- package/dist/adapters/latex/hidden.d.ts +16 -0
- package/dist/adapters/latex/hidden.d.ts.map +1 -0
- package/dist/adapters/latex/hidden.js +49 -0
- package/dist/adapters/latex/hidden.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +12 -0
- package/dist/adapters/latex/includes.d.ts.map +1 -0
- package/dist/adapters/latex/includes.js +42 -0
- package/dist/adapters/latex/includes.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +33 -0
- package/dist/adapters/latex/index.d.ts.map +1 -0
- package/dist/adapters/latex/index.js +25 -0
- package/dist/adapters/latex/index.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts +58 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -0
- package/dist/adapters/latex/nodes.js +97 -0
- package/dist/adapters/latex/nodes.js.map +1 -0
- package/dist/adapters/latex/outline.d.ts +10 -0
- package/dist/adapters/latex/outline.d.ts.map +1 -0
- package/dist/adapters/latex/outline.js +39 -0
- package/dist/adapters/latex/outline.js.map +1 -0
- package/dist/adapters/latex/parse.d.ts +42 -0
- package/dist/adapters/latex/parse.d.ts.map +1 -0
- package/dist/adapters/latex/parse.js +95 -0
- package/dist/adapters/latex/parse.js.map +1 -0
- package/dist/adapters/latex/prose.d.ts +8 -0
- package/dist/adapters/latex/prose.d.ts.map +1 -0
- package/dist/adapters/latex/prose.js +353 -0
- package/dist/adapters/latex/prose.js.map +1 -0
- package/dist/adapters/latex/rendered.d.ts +10 -0
- package/dist/adapters/latex/rendered.d.ts.map +1 -0
- package/dist/adapters/latex/rendered.js +39 -0
- package/dist/adapters/latex/rendered.js.map +1 -0
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +12 -12
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/build.js +4 -4
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +34 -0
- package/dist/claim-provenance.d.ts.map +1 -0
- package/dist/claim-provenance.js +146 -0
- package/dist/claim-provenance.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +49 -5
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +58 -0
- package/dist/domain/paper-source.d.ts.map +1 -0
- package/dist/domain/paper-source.js +109 -0
- package/dist/domain/paper-source.js.map +1 -0
- package/dist/domain/sentences.d.ts +26 -0
- package/dist/domain/sentences.d.ts.map +1 -0
- package/dist/domain/sentences.js +48 -0
- package/dist/domain/sentences.js.map +1 -0
- package/dist/domain/tex-document.d.ts +119 -0
- package/dist/domain/tex-document.d.ts.map +1 -0
- package/dist/domain/tex-document.js +43 -0
- package/dist/domain/tex-document.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +7 -0
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +53 -1
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +2 -2
- package/dist/facts-file.js.map +1 -1
- package/dist/new-paper.d.ts +5 -0
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +3 -0
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts +5 -0
- package/dist/package-dirs.d.ts.map +1 -0
- package/dist/package-dirs.js +14 -0
- package/dist/package-dirs.js.map +1 -0
- package/dist/ports/latex.d.ts +35 -0
- package/dist/ports/latex.d.ts.map +1 -0
- package/dist/ports/latex.js +2 -0
- package/dist/ports/latex.js.map +1 -0
- package/dist/presets.d.ts +15 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/register.d.ts +40 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +134 -0
- package/dist/register.js.map +1 -0
- package/dist/tex-paper.d.ts +19 -0
- package/dist/tex-paper.d.ts.map +1 -0
- package/dist/tex-paper.js +105 -0
- package/dist/tex-paper.js.map +1 -0
- package/dist/tex-requirements.d.ts +11 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +14 -8
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +88 -0
- package/dist/tex-venue-rules.d.ts.map +1 -0
- package/dist/tex-venue-rules.js +268 -0
- package/dist/tex-venue-rules.js.map +1 -0
- package/dist/venue-rules.d.ts +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/docs/configuration.md +3 -2
- package/docs/e2e.md +69 -40
- 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 +21 -10
- package/docs/rules/tex/claim-provenance.md +83 -0
- package/docs/rules/tex/missing-input.md +58 -0
- package/docs/rules/tex/register.md +77 -0
- package/docs/rules/tex/required-section.md +75 -0
- package/docs/rules/tex/template.md +74 -0
- package/docs/rules/tex/venue-leftover.md +73 -0
- package/docs/rules.md +73 -48
- package/docs/testing.md +17 -4
- package/docs/texlive-install-decision.md +1 -1
- package/docs/toolchain.md +2 -2
- package/eslint-rules/paper-typography.test.mjs +48 -1
- package/eslint-rules/paper-typography.ts +72 -1
- package/eslint.config.mjs +156 -15
- package/fixtures/build-e2e/acmart/expect.json +26 -0
- package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/aidc/expect.json +14 -0
- package/fixtures/build-e2e/aidc/paper.tex +46 -0
- package/fixtures/build-e2e/aidc/paperlint.json +1 -0
- package/fixtures/build-e2e/broken/expect.json +11 -0
- package/fixtures/build-e2e/cite/expect.json +27 -0
- package/fixtures/build-e2e/empty/expect.json +12 -0
- package/fixtures/build-e2e/fallback/expect.json +53 -0
- package/fixtures/build-e2e/guards/expect.json +6 -0
- package/fixtures/build-e2e/no-source/expect.json +10 -0
- package/fixtures/build-e2e/unbalanced/expect.json +60 -0
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.ts +8 -7
- package/fixtures/render-paper/build-clean.sh +3 -5
- package/package.json +14 -2
- package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
- package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
- package/presets/aidc.jsonc +73 -0
- package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
- package/presets/ieee-conference.jsonc +66 -0
- package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
- package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
- package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
- package/scripts/check.harness.ts +64 -10
- package/scripts/check.test.ts +60 -13
- package/scripts/check.ts +83 -30
- package/scripts/test.test.ts +1 -1
- package/scripts/test.ts +1 -1
- package/skills/cold-read-diff/SKILL.md +31 -10
- package/skills/cold-read-diff/SKILL.md.spec.ts +30 -9
- package/skills/draft-paper/SKILL.md +2 -2
- package/skills/draft-paper/SKILL.md.spec.ts +1 -1
- package/skills/grade-paper-writing/SKILL.md +109 -44
- package/skills/grade-paper-writing/SKILL.md.spec.ts +108 -43
- package/skills/harden-paper/SKILL.md +35 -36
- package/skills/harden-paper/SKILL.md.spec.ts +34 -35
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
- package/skills/paper-pipeline/SKILL.md +4 -4
- package/skills/paper-pipeline/SKILL.md.spec.ts +3 -3
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +6 -6
- package/skills/paper-pipeline/references/writing-craft.md +53 -18
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
- package/skills/paper-pipeline/scripts/consumer.mjs +1 -15
- package/skills/pc-panel-review/SKILL.md +36 -11
- package/skills/pc-panel-review/SKILL.md.spec.ts +35 -10
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/check-render.sh +16 -63
- package/skills/render-paper/render-paper.harness.mjs +17 -59
- package/src/adapters/banal/index.test.ts +1 -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 +1 -1
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/latex/document-class.ts +155 -0
- package/src/adapters/latex/hidden.ts +70 -0
- package/src/adapters/latex/includes.ts +66 -0
- package/src/adapters/latex/index.test.ts +535 -0
- package/src/adapters/latex/index.ts +65 -0
- package/src/adapters/latex/nodes.ts +150 -0
- package/src/adapters/latex/outline.ts +64 -0
- package/src/adapters/latex/parse.ts +137 -0
- package/src/adapters/latex/prose.ts +484 -0
- package/src/adapters/latex/rendered.ts +59 -0
- package/src/adapters/pdfjs/fill.test.ts +1 -1
- package/src/adapters/pdfjs/fill.ts +18 -10
- package/src/build.harness.mjs +5 -7
- package/src/build.ts +4 -4
- package/src/claim-provenance.test.ts +352 -0
- package/src/claim-provenance.ts +217 -0
- package/src/cli.harness.mjs +1 -1
- package/src/cli.test.ts +2 -8
- package/src/cli.ts +53 -8
- package/src/domain/paper-source.test.ts +156 -0
- package/src/domain/paper-source.ts +233 -0
- package/src/domain/sentences.ts +67 -0
- package/src/domain/tex-document.test.ts +80 -0
- package/src/domain/tex-document.ts +143 -0
- package/src/engine.harness.mjs +1 -1
- package/src/facts-file.test.ts +4 -7
- package/src/facts-file.ts +2 -2
- package/src/latex-loop.harness.ts +1 -1
- package/src/new-paper.test.ts +28 -3
- package/src/new-paper.ts +8 -0
- package/src/package-dirs.ts +17 -0
- package/src/paper-settings-commands.test.ts +6 -1
- package/src/ports/latex.ts +42 -0
- package/src/presets.test.ts +65 -4
- package/src/presets.ts +38 -15
- package/src/register.test.ts +165 -0
- package/src/register.ts +177 -0
- package/src/rule-cases.test.ts +524 -0
- package/src/rule-docs.test.ts +159 -0
- package/src/tex-paper.test.ts +131 -0
- package/src/tex-paper.ts +152 -0
- package/src/tex-requirements.harness.ts +13 -23
- package/src/tex-requirements.ts +35 -8
- package/src/tex-venue-rules.test.ts +741 -0
- package/src/tex-venue-rules.ts +461 -0
- package/src/toolchain.harness.mjs +1 -1
- package/src/venue-rules.test.ts +96 -4
- package/src/venue-rules.ts +1 -1
- package/templates/paper/paper.tex +2 -1
- package/skills/harden-paper/fixtures/uncited-assertions-sample.md +0 -43
- package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +0 -77
- /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
- /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# vigiles lint . README.md 6 s
|
|
20
20
|
# rules-see-files.mjs 5 s
|
|
21
21
|
# layer-legacy-frozen.ts 3 s
|
|
22
|
-
#
|
|
22
|
+
# npm run test:e2e:install (npm+pnpm) 38 s
|
|
23
23
|
# ───────────────────────────────────────────
|
|
24
24
|
# total work ~96 s, plus checkout + install + the claude binary
|
|
25
25
|
#
|
|
@@ -178,10 +178,11 @@ jobs:
|
|
|
178
178
|
run: corepack enable pnpm
|
|
179
179
|
timeout-minutes: 2
|
|
180
180
|
|
|
181
|
-
#
|
|
182
|
-
# contributor's clone and a FAILURE here, because
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
# The vitest project `e2e-install`: pack, install under npm and pnpm, run the binary. A manager
|
|
182
|
+
# that does not launch is a skip on a contributor's clone and a FAILURE here, because `CI` is set
|
|
183
|
+
# and in CI it means a broken environment (test/e2e/need.ts).
|
|
184
|
+
- name: npm run test:e2e:install
|
|
185
|
+
run: npm run test:e2e:install
|
|
185
186
|
timeout-minutes: 10
|
|
186
187
|
|
|
187
188
|
# ── BUILDING THE PAPER WITH REAL pdflatex, ON LINUX AND macOS ──────────────────────────────────
|
|
@@ -199,7 +200,7 @@ jobs:
|
|
|
199
200
|
# 🔴 WHY ON EVERY PUSH. It catches silent font substitution — a class that HAS ALREADY shipped a
|
|
200
201
|
# paper typeset in the wrong font. This repository is public, so the macOS minutes are free.
|
|
201
202
|
#
|
|
202
|
-
#
|
|
203
|
+
# `CI` is set: absence of TeX locally is a legitimate skip; here it is an environment failure.
|
|
203
204
|
build-e2e:
|
|
204
205
|
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
|
|
205
206
|
strategy:
|
|
@@ -258,7 +259,7 @@ jobs:
|
|
|
258
259
|
uses: actions/cache@v6
|
|
259
260
|
with:
|
|
260
261
|
path: ~/.cache/paperlint-texlive
|
|
261
|
-
key: texlive-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('
|
|
262
|
+
key: texlive-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('presets/*.jsonc') }}
|
|
262
263
|
|
|
263
264
|
# The same command a user runs. On a cache hit it verifies and says "nothing to do"; on a miss
|
|
264
265
|
# it installs from an empty directory and verifies every declared file with kpsewhich.
|
|
@@ -271,19 +272,11 @@ jobs:
|
|
|
271
272
|
run: node bin/paperlint.mjs toolchain
|
|
272
273
|
timeout-minutes: 15
|
|
273
274
|
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
# Called directly with --strict (see the install e2e step in the `gates` job).
|
|
283
|
-
- name: build e2e (--strict)
|
|
284
|
-
run: node test/e2e/build.ts --strict
|
|
285
|
-
timeout-minutes: 10
|
|
286
|
-
|
|
287
|
-
- name: toolchain e2e (--strict)
|
|
288
|
-
run: node test/e2e/toolchain.ts --strict
|
|
289
|
-
timeout-minutes: 10
|
|
275
|
+
# Every e2e test under test/e2e/tex/, found by the vitest project `e2e-tex`: the toolchain again
|
|
276
|
+
# (a no-op on the tree above, then a build with only it on PATH), the real banal on the XML
|
|
277
|
+
# paperlint writes (no poppler on this runner), and a real pdflatex build of every fixture in
|
|
278
|
+
# fixtures/build-e2e/, each against its own expect.json. `CI` is set on every runner, so a
|
|
279
|
+
# missing TeX Live or banal FAILS here instead of skipping (test/e2e/need.ts).
|
|
280
|
+
- name: npm run test:e2e:tex
|
|
281
|
+
run: npm run test:e2e:tex
|
|
282
|
+
timeout-minutes: 20
|
package/CLAUDE.md
CHANGED
|
@@ -77,6 +77,13 @@ and stay QUIET on a clean fixture. A check that has only been seen quiet is
|
|
|
77
77
|
indistinguishable from a dead one — silence is its success state. Prove the fire half by
|
|
78
78
|
seeing the test go RED before the fix lands ([`docs/testing.md`](docs/testing.md)).
|
|
79
79
|
|
|
80
|
+
**A new rule ships with its page**, `docs/rules/<group>/<rule>.md`, in these sections: What it
|
|
81
|
+
catches · Why · Examples (failing / passing) · Options / preset fields · What it does not check ·
|
|
82
|
+
How to fix. Its `meta.docs.url` points at that page, and its row in `docs/rules.md` is one line
|
|
83
|
+
linking there. `src/rule-docs.test.ts` holds every rule the config registers to this; the rules
|
|
84
|
+
older than the convention wait in its `AWAITING_PAGE` list, which only shrinks — #131 writes their
|
|
85
|
+
pages.
|
|
86
|
+
|
|
80
87
|
**4. `exit 0` with empty output is NOT "clean".** A rule whose glob matched no files reports
|
|
81
88
|
exactly like a rule that passed. Any rule shipped here must be loud when its input set is
|
|
82
89
|
empty. This is the specific defect that blocks stage 1 of the plan: in the source base a
|
|
@@ -150,13 +157,15 @@ Four proposals were made and withdrawn in one session on 2026-09-17 for exactly
|
|
|
150
157
|
|
|
151
158
|
**10. Effects live in adapters, and WHERE paperlint IS INSTALLED lives in ONE of them.** Rule 6 generalised: the caller's cwd is one case of it. Checking logic — lint rules,
|
|
152
159
|
skills, hooks — must not know its own location, nor its distance from anything else. Every
|
|
153
|
-
answer to _where_ comes from
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
answer to _where_ comes from one of two modules, each for one side: the consumer (its root, the
|
|
161
|
+
package's name) from `skills/paper-pipeline/scripts/consumer.mjs`, which adapts per channel — own
|
|
162
|
+
checkout · `node_modules` · plugin cache · CI; the package's own data (`presets/`, `presets/tex/`)
|
|
163
|
+
from `src/package-dirs.ts`. A skill naming a script by an install-specific path in its own prose
|
|
164
|
+
walks around that door, and 208 such literals across 190 lines do exactly that.
|
|
157
165
|
|
|
158
|
-
<!-- The
|
|
166
|
+
<!-- The ports' paths above are resolved by `npm run check` (vigiles lint), since #67. -->
|
|
159
167
|
<!-- vigiles:file skills/paper-pipeline/scripts/consumer.mjs -->
|
|
168
|
+
<!-- vigiles:file src/package-dirs.ts -->
|
|
160
169
|
|
|
161
170
|
The layer rules for `src/` live in [`src/CLAUDE.md`](src/CLAUDE.md) and are enforced by the linter.
|
|
162
171
|
|
|
@@ -491,20 +500,55 @@ of the account-wide 3000/month shared with every other private repo, and the bud
|
|
|
491
500
|
private again, this section and `.github/dependabot.yml` are revisited together** — the bot is
|
|
492
501
|
justified two hundred lines above precisely by these minutes being free.
|
|
493
502
|
|
|
503
|
+
## Writing checks — what keeps them from pushing papers toward a blog post
|
|
504
|
+
|
|
505
|
+
The rules and skills that judge a paper's prose follow five principles. A new rule, or a new
|
|
506
|
+
criterion in a skill, is held to them.
|
|
507
|
+
|
|
508
|
+
1. **Every writing check is two-sided.** A check with only a ceiling — too long, too many markers,
|
|
509
|
+
too many of X — pushes every paper toward clipped, unsignposted prose, and the one equilibrium of
|
|
510
|
+
a set of ceilings is a blog post. A new check has a floor as well, or measures the distance to a
|
|
511
|
+
reference (accepted papers of the target venue); never only "less is better".
|
|
512
|
+
2. **Rules count, skills judge.** What is countable on the parse tree — a density, a number with no
|
|
513
|
+
owner (`tex/claim-provenance`), a missing section — is a lint rule. What needs reading — register
|
|
514
|
+
relative to a venue, genre, whether a claim changed strength — is a skill criterion. A skill does
|
|
515
|
+
not grep prose for what a rule can count; a rule is not asked to judge register.
|
|
516
|
+
3. **The model reader is the venue's reviewer, not only a smart outsider.** A check that simulates a
|
|
517
|
+
reader includes a skimming reviewer who reads the abstract, the introduction and the conclusion,
|
|
518
|
+
and judges whether it reads as a paper at that venue and whose result each sentence states.
|
|
519
|
+
4. **Thresholds are calibrated, not borrowed.** A threshold is set with the same counter on real
|
|
520
|
+
accepted papers, never taken from a published figure measured by a different counter. Those
|
|
521
|
+
papers are [`fixtures/accepted-papers/`](fixtures/accepted-papers/README.md), and every rule's
|
|
522
|
+
count on them is recorded there.
|
|
523
|
+
5. **A rewrite is accepted on what reviewers see.** That is the combined count of every surface form
|
|
524
|
+
of a tic (a narrow rule is gamed by rewording), plus a claims-preservation check that no claim was
|
|
525
|
+
softened or strengthened — not the one pattern the triggering rule matched.
|
|
526
|
+
|
|
494
527
|
## Testing
|
|
495
528
|
|
|
496
|
-
| you are testing | kind
|
|
497
|
-
| -------------------------------------------------------------------------- |
|
|
498
|
-
| a pure function | unit — vitest, `<module>.test.ts` beside it
|
|
499
|
-
| a use case or adapter that reaches disk, a process, the network, the clock | integration — vitest, fakes through the ports
|
|
500
|
-
| a hook's decision, a skill's contract | harness — vigiles, `<surface>.harness.ts`
|
|
501
|
-
| the installed package, a real TeX build | e2e — `test/e2e
|
|
529
|
+
| you are testing | kind |
|
|
530
|
+
| -------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
531
|
+
| a pure function | unit — vitest, `<module>.test.ts` beside it |
|
|
532
|
+
| a use case or adapter that reaches disk, a process, the network, the clock | integration — vitest, fakes through the ports |
|
|
533
|
+
| a hook's decision, a skill's contract | harness — vigiles, `<surface>.harness.ts` |
|
|
534
|
+
| the installed package, a real TeX build | e2e — `test/e2e/<area>/*.e2e.ts`, `npm run test:e2e:<area>` |
|
|
502
535
|
|
|
503
536
|
Red first · assert the whole value · test what the code does, never what its source says · no test
|
|
504
537
|
touches the real network · 100% coverage, no `c8 ignore`. **Before every push: `npm run check`,
|
|
505
538
|
exit code read without a pipe.** Everything else — where files live, how fakes are injected, the
|
|
506
539
|
`--min=1` trap: [`docs/testing.md`](docs/testing.md).
|
|
507
540
|
|
|
541
|
+
**A new rule** needs unit tests beside it and a reporting and a silent case in
|
|
542
|
+
`src/rule-cases.test.ts`, which runs every registered rule through real ESLint and fails on one
|
|
543
|
+
without cases. An e2e fixture only when [`docs/e2e.md`](docs/e2e.md)'s triggers say so.
|
|
544
|
+
|
|
545
|
+
**New code is functional.** In `src/`, `eslint-rules/` and `lib/` (tests aside) there is no
|
|
546
|
+
`let`, no mutation, no loop, and types are read-only (`eslint-plugin-functional`); a `switch` over a
|
|
547
|
+
union names every member. Collections go through [Remeda](https://remedajs.com/) — `lodash`,
|
|
548
|
+
`lodash-es` and `ramda` are refused by `npm run lint`. Old code is listed in
|
|
549
|
+
`eslint-suppressions.json`, ESLint's own bulk suppressions, which may only shrink: fixing a listed
|
|
550
|
+
site without `npx eslint . --prune-suppressions` fails lint, and #134 burns the list down.
|
|
551
|
+
|
|
508
552
|
## Commits
|
|
509
553
|
|
|
510
554
|
Conventional-commit subject, body says what was MEASURED, not what was intended. A number in
|
package/CONTRIBUTING.md
CHANGED
|
@@ -37,12 +37,14 @@ install e2e under npm and pnpm, and a real `pdflatex` build — and ends
|
|
|
37
37
|
by printing **which CI jobs it does not reproduce, and why**.
|
|
38
38
|
|
|
39
39
|
🔴 **There is no `--fast` flag, and that is the point.** On 2026-09-19 a rule change was pushed
|
|
40
|
-
that broke the suite: the gates were run afterwards and were green, but `npm test` and
|
|
41
|
-
|
|
40
|
+
that broke the suite: the gates were run afterwards and were green, but `npm test` and the
|
|
41
|
+
install e2e were not among them, because there were eleven separate scripts and the only way
|
|
42
42
|
to run them all was from memory. A subset flag re-creates exactly that — the cheap half gets run
|
|
43
43
|
and reported as "the gates". If a step genuinely cannot run here, it says so out loud rather than
|
|
44
|
-
being skipped quietly: an e2e that finds no TeX or no pnpm
|
|
45
|
-
`npm run check`
|
|
44
|
+
being skipped quietly: an e2e test that finds no TeX or no pnpm is reported SKIPPED by vitest,
|
|
45
|
+
and `npm run check` reads that count from vitest's JSON report and lists the gate as skipped
|
|
46
|
+
instead of counting it as passed. The e2e areas also run alone: `npm run test:e2e:install`,
|
|
47
|
+
`npm run test:e2e:tex`, or both with `npm run test:e2e`.
|
|
46
48
|
|
|
47
49
|
Each gate's command is listed in `scripts/check.ts`; run one of them directly while iterating on
|
|
48
50
|
one rule. They are not what you run before pushing.
|
|
@@ -75,6 +77,20 @@ Most of the package's rules run on users' papers and are described for users in
|
|
|
75
77
|
[`docs/rules.md`](docs/rules.md); the rest lint this package's own source and never see a user's
|
|
76
78
|
files.
|
|
77
79
|
|
|
80
|
+
A new rule ships with its page, `docs/rules/<group>/<rule>.md`, in these sections: What it
|
|
81
|
+
catches · Why · Examples (failing / passing) · Options / preset fields · What it does not check ·
|
|
82
|
+
How to fix. Its `meta.docs.url` points at that page, and its row in `docs/rules.md` is one line
|
|
83
|
+
linking there. `src/rule-docs.test.ts` holds every rule the config registers to this; the rules
|
|
84
|
+
older than the convention wait in its `AWAITING_PAGE` list, which only shrinks — #131 writes their
|
|
85
|
+
pages.
|
|
86
|
+
|
|
87
|
+
**A rule or a skill criterion that judges prose is two-sided.** A check that only caps something —
|
|
88
|
+
length, markers, hedges — drives papers toward clipped, unsignposted prose that reviewers call a
|
|
89
|
+
blog post, so a new one also has a floor, or measures the distance to accepted papers of the target
|
|
90
|
+
venue, with its threshold measured by the same counter on those papers. What the parse tree can
|
|
91
|
+
count is a rule; what needs reading (register, genre) is a skill criterion. The five principles are
|
|
92
|
+
in [`CLAUDE.md`](CLAUDE.md#writing-checks--what-keeps-them-from-pushing-papers-toward-a-blog-post).
|
|
93
|
+
|
|
78
94
|
## Maintainer docs
|
|
79
95
|
|
|
80
96
|
The README links only what a user needs. These are for people changing the package:
|
|
@@ -102,6 +118,13 @@ npm run coverage # the same, under c8, failing below the thresholds in .
|
|
|
102
118
|
None of these are needed to USE the tool — they are here because the gates are part of the
|
|
103
119
|
argument, not decoration.
|
|
104
120
|
|
|
121
|
+
**New code is functional.** In `src/`, `eslint-rules/` and `lib/` (tests aside) there is no
|
|
122
|
+
`let`, no mutation, no loop, and types are read-only (`eslint-plugin-functional`); a `switch` over a
|
|
123
|
+
union names every member. Collections go through [Remeda](https://remedajs.com/) — `lodash`,
|
|
124
|
+
`lodash-es` and `ramda` are refused by `npm run lint`. Old code is listed in
|
|
125
|
+
`eslint-suppressions.json`, ESLint's own bulk suppressions, which may only shrink: fixing a listed
|
|
126
|
+
site without `npx eslint . --prune-suppressions` fails lint, and #134 burns the list down.
|
|
127
|
+
|
|
105
128
|
New code is TypeScript (#78). The last block of [`eslint.config.mjs`](eslint.config.mjs) makes any
|
|
106
129
|
`.js`, `.mjs` or `.cjs` file an error in the directories already converted (`TYPESCRIPT_ONLY`), so
|
|
107
130
|
`npm run lint` fails on one; a directory that still mixes the two is not listed yet, and the change
|
|
@@ -139,17 +162,17 @@ change to `.c8rc.json`'s exclude list that was not made there on purpose.
|
|
|
139
162
|
|
|
140
163
|
## Adding a venue
|
|
141
164
|
|
|
142
|
-
A venue is a **preset**, a JSONC file in `
|
|
165
|
+
A venue is a **preset**, a JSONC file in `presets/`, validated by
|
|
143
166
|
`venue-profile.schema.json` beside it. Adding one is three files and no code:
|
|
144
167
|
|
|
145
|
-
1. **The preset, thin.** `
|
|
168
|
+
1. **The preset, thin.** `presets/<name>.jsonc` extends the template family it is built on
|
|
146
169
|
(`"extends": "paperlint:acm-sigconf"` for an ACM venue) and adds only what the call for papers
|
|
147
170
|
sets: `format.kinds` (the page limit of each kind of paper) and, when the venue's producer asks
|
|
148
171
|
for something an optional rule checks, `rules`. Every number carries the quote it came from.
|
|
149
172
|
A venue on a template with no family yet stands alone (`template`, `tex`, `format`) — or, better,
|
|
150
173
|
add the family first: measured on a real template build (banal + pdf.js), not copied from
|
|
151
174
|
documentation ([#88](https://github.com/zernie/paperlint/issues/88)).
|
|
152
|
-
2. **The card.** `venues/<name>.md` — prose about the venue: deadlines, tracks, the blind model,
|
|
175
|
+
2. **The card.** `skills/submit-paper/references/venues/<name>.md` — prose about the venue: deadlines, tracks, the blind model,
|
|
153
176
|
what the form asks.
|
|
154
177
|
3. **A test.** A case in `src/presets.test.ts` that `paperlint:<name>` resolves over its family with
|
|
155
178
|
the kinds you declared; `src/tex-requirements.harness.ts` already checks every shipped preset
|
package/README.md
CHANGED
|
@@ -69,14 +69,17 @@ build: compiles and measures the PDF; lint then checks pages, fonts, references
|
|
|
69
69
|
A **venue preset** holds a venue's format and page limits. A paper's **kind** picks which limit
|
|
70
70
|
applies: at AgenticDev a `short` paper gets 5 pages and a `full` one 10. You choose it once per paper.
|
|
71
71
|
|
|
72
|
-
| preset
|
|
73
|
-
|
|
|
74
|
-
| `paperlint:acm-sigconf`
|
|
75
|
-
| `paperlint:agenticdev`
|
|
76
|
-
| `paperlint:aisec`
|
|
77
|
-
| `paperlint:realm`
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
| preset | venue | format | kinds and page limits |
|
|
73
|
+
| --------------------------- | ------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `paperlint:acm-sigconf` | any ACM conference | ACM two-column | no kinds: the format only, no page limit |
|
|
75
|
+
| `paperlint:agenticdev` | AgenticDev @ ASE | ACM two-column | `short` 5, `full` 10, `demo` 5 pages, + 2 pages of references |
|
|
76
|
+
| `paperlint:aisec` | AISec @ ACM CCS | ACM two-column | `research`, `benchmark`, `position`, `sok`: 10 pages + 2 pages of references |
|
|
77
|
+
| `paperlint:realm` | REALM @ EMNLP | ACL two-column, A4 | `long` 8, `short` 4 — recorded, not checked ([why](docs/rules.md#checks-against-the-venue)) |
|
|
78
|
+
| `paperlint:ieee-conference` | any IEEE conference | IEEE two-column | no kinds: the format only, no page limit |
|
|
79
|
+
| `paperlint:aidc` | AIDC @ IEEE ACSAC | IEEE two-column, `compsoc` | `regular` 12, `short` 6 — recorded, not checked ([why](docs/rules.md#checks-against-the-venue)); requires the «LLM Usage Statement» |
|
|
80
|
+
|
|
81
|
+
Not shipped yet: USENIX, NeurIPS, Springer, IEEE venues other than AIDC, and ACL venues other than
|
|
82
|
+
REALM.
|
|
80
83
|
[Add yours](#-add-a-venue-that-isnt-listed) in one small file.
|
|
81
84
|
|
|
82
85
|
## 🚀 Getting started
|
|
@@ -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.ts` shows both failures with the real banal.
|
|
10
|
+
* refuses anything else. `test/e2e/tex/banal.e2e.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";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The paper's `\documentclass`, a preset's template, and setting a class in a source. */
|
|
2
|
+
import { type ClassLine, type DocumentClass } from "../../domain/tex-document.ts";
|
|
3
|
+
import { type ParsedTex } from "./parse.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The `\documentclass` at the top of the tree: missing; empty (`\documentclass{}`); the class with
|
|
6
|
+
* each of its options as written (`foo={a,b}` stays one option, braces kept), whitespace collapsed,
|
|
7
|
+
* its place spanning the macro and its arguments; or, when there are several (behind a TeX switch),
|
|
8
|
+
* each of them as a candidate.
|
|
9
|
+
*/
|
|
10
|
+
export declare function documentClassOf(t: ParsedTex): ClassLine;
|
|
11
|
+
/**
|
|
12
|
+
* A preset's `template` → the class it names: a whole `\documentclass[…]{…}` line, or a bare class
|
|
13
|
+
* name (`article`), which names no option. Anything else is null.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseTemplate(text: string): DocumentClass | null;
|
|
16
|
+
/**
|
|
17
|
+
* The source with its `\documentclass` line changed to satisfy `want` (see `venueClass`): another
|
|
18
|
+
* class is replaced by the template's line; the same class gains the options it lacks in place. A
|
|
19
|
+
* source whose class already satisfies `want`, that has no class line the parser could place, or
|
|
20
|
+
* that has several, is returned as it is.
|
|
21
|
+
*/
|
|
22
|
+
export declare function replaceDocumentClass(t: ParsedTex, want: DocumentClass): string;
|
|
23
|
+
//# sourceMappingURL=document-class.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-class.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/document-class.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAUtC,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAqDxD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAOvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAShE;AA0BD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,SAAS,EACZ,IAAI,EAAE,aAAa,GAClB,MAAM,CAqBR"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** The paper's `\documentclass`, a preset's template, and setting a class in a source. */
|
|
2
|
+
import { documentClassLine, venueClass, } from "../../domain/tex-document.js";
|
|
3
|
+
import { collapse, macroPlace, mandatory, optional, textOf, } from "./nodes.js";
|
|
4
|
+
import { parseLatex } from "./parse.js";
|
|
5
|
+
const isDocumentClass = (n) => n.type === "macro" && n.content === "documentclass";
|
|
6
|
+
const isComma = (n) => n.type === "string" && n.content === ",";
|
|
7
|
+
/** Nodes split wherever `at` holds, the separators dropped: a comma inside braces is its group's. */
|
|
8
|
+
function splitWhere(nodes, at) {
|
|
9
|
+
const cuts = nodes.flatMap((n, i) => (at(n) ? [i] : []));
|
|
10
|
+
return [-1, ...cuts].map((from, k) => nodes.slice(from + 1, cuts[k] ?? nodes.length));
|
|
11
|
+
}
|
|
12
|
+
/** The source text a run of nodes spans; its markup-free text if unplaced. */
|
|
13
|
+
function sourceOf(src, nodes) {
|
|
14
|
+
const start = nodes[0]?.position?.start.offset;
|
|
15
|
+
const end = nodes.at(-1)?.position?.end.offset;
|
|
16
|
+
return start === undefined || end === undefined
|
|
17
|
+
? textOf(nodes)
|
|
18
|
+
: src.slice(start, end);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One option as TeX reads it, whitespace collapsed. A line comment is not part of it, and it joins
|
|
22
|
+
* its two sides with no space: `foo=bar% note⏎baz` is `foo=barbaz`.
|
|
23
|
+
*/
|
|
24
|
+
const optionText = (src, part) => collapse(splitWhere(part, (n) => n.type === "comment")
|
|
25
|
+
.map((run) => sourceOf(src, run))
|
|
26
|
+
.join(""));
|
|
27
|
+
/** Every `\documentclass` at the top of the tree, in source order. */
|
|
28
|
+
const classNodes = (t) => t.root.content.filter(isDocumentClass);
|
|
29
|
+
/** A `\documentclass` node as a class line: empty, or the class with its options as read. */
|
|
30
|
+
function classLine(src, node) {
|
|
31
|
+
const place = macroPlace(node);
|
|
32
|
+
const cls = collapse(textOf(mandatory(node)[0]?.content));
|
|
33
|
+
if (cls === "")
|
|
34
|
+
return { kind: "empty", place };
|
|
35
|
+
const options = splitWhere(optional(node)?.content ?? [], isComma)
|
|
36
|
+
.map((part) => optionText(src, part))
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
return { kind: "class", cls, options, place };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `\documentclass` at the top of the tree: missing; empty (`\documentclass{}`); the class with
|
|
42
|
+
* each of its options as written (`foo={a,b}` stays one option, braces kept), whitespace collapsed,
|
|
43
|
+
* its place spanning the macro and its arguments; or, when there are several (behind a TeX switch),
|
|
44
|
+
* each of them as a candidate.
|
|
45
|
+
*/
|
|
46
|
+
export function documentClassOf(t) {
|
|
47
|
+
const lines = classNodes(t).map((n) => classLine(t.src, n));
|
|
48
|
+
const [first, second, ...rest] = lines;
|
|
49
|
+
if (first === undefined)
|
|
50
|
+
return { kind: "missing" };
|
|
51
|
+
return second === undefined
|
|
52
|
+
? first
|
|
53
|
+
: { kind: "ambiguous", candidates: [first, second, ...rest] };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A preset's `template` → the class it names: a whole `\documentclass[…]{…}` line, or a bare class
|
|
57
|
+
* name (`article`), which names no option. Anything else is null.
|
|
58
|
+
*/
|
|
59
|
+
export function parseTemplate(text) {
|
|
60
|
+
const t = parseLatex(text);
|
|
61
|
+
const [only, ...rest] = t.root.content.filter((n) => n.type !== "whitespace" && n.type !== "comment");
|
|
62
|
+
if (only === undefined || rest.length > 0)
|
|
63
|
+
return null;
|
|
64
|
+
const line = documentClassOf(t);
|
|
65
|
+
if (line.kind === "class")
|
|
66
|
+
return { cls: line.cls, options: line.options };
|
|
67
|
+
return only.type === "string" ? { cls: only.content, options: [] } : null;
|
|
68
|
+
}
|
|
69
|
+
/** `src` with `text` in place of `[from, to)`. */
|
|
70
|
+
const splice = (src, from, to, text) => src.slice(0, from) + text + src.slice(to);
|
|
71
|
+
/**
|
|
72
|
+
* `options` written into the class line's own source: before the `]` of its `[…]` — right after the
|
|
73
|
+
* last node inside, or right after the `[` when there is none — or as a new `[…]` after the name.
|
|
74
|
+
* The bytes between `[` and `]` stay as the author wrote them, comments and braces included.
|
|
75
|
+
*/
|
|
76
|
+
function addOptions(src, node, line, options) {
|
|
77
|
+
const list = options.join(",");
|
|
78
|
+
const nameEnd = line.start + 1 + node.content.length; // `\` and the macro's name
|
|
79
|
+
const opt = optional(node);
|
|
80
|
+
if (opt === undefined)
|
|
81
|
+
return splice(src, nameEnd, nameEnd, `[${list}]`);
|
|
82
|
+
const close = opt.content.at(-1)?.position?.end.offset ?? src.indexOf("[", nameEnd) + 1;
|
|
83
|
+
return splice(src, close, close, line.empty ? list : `,${list}`);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The source with its `\documentclass` line changed to satisfy `want` (see `venueClass`): another
|
|
87
|
+
* class is replaced by the template's line; the same class gains the options it lacks in place. A
|
|
88
|
+
* source whose class already satisfies `want`, that has no class line the parser could place, or
|
|
89
|
+
* that has several, is returned as it is.
|
|
90
|
+
*/
|
|
91
|
+
export function replaceDocumentClass(t, want) {
|
|
92
|
+
// Several lines behind a switch: which one builds is not known, so none is rewritten.
|
|
93
|
+
const [node, ...others] = classNodes(t);
|
|
94
|
+
if (node === undefined || others.length > 0)
|
|
95
|
+
return t.src;
|
|
96
|
+
const line = classLine(t.src, node);
|
|
97
|
+
if (line.kind !== "class" || line.place.kind === "unplaced")
|
|
98
|
+
return t.src;
|
|
99
|
+
const { start, end } = line.place.span;
|
|
100
|
+
const change = venueClass(line, want);
|
|
101
|
+
switch (change.kind) {
|
|
102
|
+
case "keep":
|
|
103
|
+
return t.src;
|
|
104
|
+
case "replace":
|
|
105
|
+
return splice(t.src, start, end, documentClassLine(change.by));
|
|
106
|
+
case "add":
|
|
107
|
+
return addOptions(t.src, node, { start, empty: line.options.length === 0 }, change.options);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=document-class.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-class.js","sourceRoot":"","sources":["../../../src/adapters/latex/document-class.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a reader of the PDF never sees, as a walk over the tree prunes it: comments, math and code,
|
|
3
|
+
* the bibliography's body, and the arguments that are keys, labels, files or URLs. The rendered
|
|
4
|
+
* text and the outline both prune with it.
|
|
5
|
+
*
|
|
6
|
+
* A macro definition's body is NOT pruned here. It is text once the macro is used, and nothing here
|
|
7
|
+
* expands macros, so the rendered text reads the body where it is written — a definition naming
|
|
8
|
+
* another venue is a leftover whether it is used or not. The outline prunes it on top
|
|
9
|
+
* (`isDefinition`): a heading in a definition body is not a section where it is written.
|
|
10
|
+
*/
|
|
11
|
+
import { type Visited } from "./nodes.ts";
|
|
12
|
+
/** Whether the walk prunes `v` (given the node that holds it): see the module comment. */
|
|
13
|
+
export declare const isUnrendered: (v: Visited, parent: Visited | undefined) => boolean;
|
|
14
|
+
/** A macro that defines another macro or an environment (`\newcommand`, `\def`, …). */
|
|
15
|
+
export declare const isDefinition: (v: Visited) => boolean;
|
|
16
|
+
//# sourceMappingURL=hidden.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hidden.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/hidden.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAKL,KAAK,OAAO,EACb,MAAM,YAAY,CAAC;AA4CpB,0FAA0F;AAC1F,eAAO,MAAM,YAAY,GACvB,GAAG,OAAO,EACV,QAAQ,OAAO,GAAG,SAAS,KAC1B,OACsE,CAAC;AAE1E,uFAAuF;AACvF,eAAO,MAAM,YAAY,GAAI,GAAG,OAAO,KAAG,OAC2B,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a reader of the PDF never sees, as a walk over the tree prunes it: comments, math and code,
|
|
3
|
+
* the bibliography's body, and the arguments that are keys, labels, files or URLs. The rendered
|
|
4
|
+
* text and the outline both prune with it.
|
|
5
|
+
*
|
|
6
|
+
* A macro definition's body is NOT pruned here. It is text once the macro is used, and nothing here
|
|
7
|
+
* expands macros, so the rendered text reads the body where it is written — a definition naming
|
|
8
|
+
* another venue is a leftover whether it is used or not. The outline prunes it on top
|
|
9
|
+
* (`isDefinition`): a heading in a definition body is not a section where it is written.
|
|
10
|
+
*/
|
|
11
|
+
import { isArgument, isNode, } from "./nodes.js";
|
|
12
|
+
import { DEFINITION_MACROS, KEY_ARGUMENT, KEY_SIGNATURES } from "./parse.js";
|
|
13
|
+
/** Environments whose body is not text a reader sees as prose: code, the bibliography, comments. */
|
|
14
|
+
const HIDDEN_ENVS = new Set([
|
|
15
|
+
"thebibliography",
|
|
16
|
+
"verbatim",
|
|
17
|
+
"Verbatim",
|
|
18
|
+
"lstlisting",
|
|
19
|
+
"minted",
|
|
20
|
+
"comment",
|
|
21
|
+
]);
|
|
22
|
+
/** Node types a reader does not see as prose: comments, code, math. */
|
|
23
|
+
const HIDDEN_TYPES = new Set([
|
|
24
|
+
"comment",
|
|
25
|
+
"verbatim",
|
|
26
|
+
"verb",
|
|
27
|
+
"inlinemath",
|
|
28
|
+
"displaymath",
|
|
29
|
+
"mathenv",
|
|
30
|
+
]);
|
|
31
|
+
const KEY_MACROS = new Set(Object.keys(KEY_SIGNATURES));
|
|
32
|
+
/** A key macro that prints none of its arguments (`\label`, `\url`, …). */
|
|
33
|
+
const isHiddenMacro = (name) => KEY_MACROS.has(name) && !KEY_ARGUMENT.has(name);
|
|
34
|
+
const isHiddenNode = (n) => HIDDEN_TYPES.has(n.type) ||
|
|
35
|
+
(n.type === "environment" && HIDDEN_ENVS.has(n.env)) ||
|
|
36
|
+
(n.type === "macro" && isHiddenMacro(n.content));
|
|
37
|
+
/**
|
|
38
|
+
* The one argument of a partly printed macro (`KEY_ARGUMENT`) that is a key or a URL. What holds an
|
|
39
|
+
* argument is a macro or an environment; the first three tests only narrow the type.
|
|
40
|
+
*/
|
|
41
|
+
const isKeyArgument = (a, parent) => parent !== undefined &&
|
|
42
|
+
isNode(parent) &&
|
|
43
|
+
parent.type === "macro" &&
|
|
44
|
+
parent.args?.indexOf(a) === KEY_ARGUMENT.get(parent.content);
|
|
45
|
+
/** Whether the walk prunes `v` (given the node that holds it): see the module comment. */
|
|
46
|
+
export const isUnrendered = (v, parent) => isArgument(v) ? isKeyArgument(v, parent) : isNode(v) && isHiddenNode(v);
|
|
47
|
+
/** A macro that defines another macro or an environment (`\newcommand`, `\def`, …). */
|
|
48
|
+
export const isDefinition = (v) => isNode(v) && v.type === "macro" && DEFINITION_MACROS.has(v.content);
|
|
49
|
+
//# sourceMappingURL=hidden.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hidden.js","sourceRoot":"","sources":["../../../src/adapters/latex/hidden.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,UAAU,EACV,MAAM,GAIP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7E,oGAAoG;AACpG,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC;IAC/C,iBAAiB;IACjB,UAAU;IACV,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,SAAS;CACV,CAAC,CAAC;AAEH,uEAAuE;AACvE,MAAM,YAAY,GAA8B,IAAI,GAAG,CAAe;IACpE,SAAS;IACT,UAAU;IACV,MAAM;IACN,YAAY;IACZ,aAAa;IACb,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,UAAU,GAAwB,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;AAE7E,2EAA2E;AAC3E,MAAM,aAAa,GAAG,CAAC,IAAY,EAAW,EAAE,CAC9C,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAElD,MAAM,YAAY,GAAG,CAAC,CAAO,EAAW,EAAE,CACxC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACxB,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,aAAa,GAAG,CAAC,CAAW,EAAE,MAA2B,EAAW,EAAE,CAC1E,MAAM,KAAK,SAAS;IACpB,MAAM,CAAC,MAAM,CAAC;IACd,MAAM,CAAC,IAAI,KAAK,OAAO;IACvB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAE/D,0FAA0F;AAC1F,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,CAAU,EACV,MAA2B,EAClB,EAAE,CACX,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;AAE1E,uFAAuF;AACvF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAU,EAAW,EAAE,CAClD,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Include } from "../../domain/paper-source.ts";
|
|
2
|
+
import type { Span } from "../../domain/tex-document.ts";
|
|
3
|
+
import type { ParsedTex } from "./parse.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Every `\input`, `\include` and `\subfile` of the source, in source order — not in a comment (a
|
|
6
|
+
* comment is a node of its own) and not in a macro definition's body, which is read where the macro
|
|
7
|
+
* is used, and nothing here expands macros.
|
|
8
|
+
*/
|
|
9
|
+
export declare const includesOf: (t: ParsedTex) => readonly Include[];
|
|
10
|
+
/** The span of the `document` environment's body, or null when the source has none. */
|
|
11
|
+
export declare const documentBodyOf: (t: ParsedTex) => Span | null;
|
|
12
|
+
//# sourceMappingURL=includes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"includes.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/includes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAYzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AA6B5C;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,GAAG,SAAS,KAAG,SAAS,OAAO,EAGU,CAAC;AAKrE,uFAAuF;AACvF,eAAO,MAAM,cAAc,GAAI,GAAG,SAAS,KAAG,IAAI,GAAG,IAOhD,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isDefinition } from "./hidden.js";
|
|
2
|
+
import { inPlace, isNode, macroPlace, mandatory, placeOf, visited, } from "./nodes.js";
|
|
3
|
+
const INCLUDE_MACROS = new Map([
|
|
4
|
+
["input", "input"],
|
|
5
|
+
["include", "include"],
|
|
6
|
+
["subfile", "subfile"],
|
|
7
|
+
]);
|
|
8
|
+
/**
|
|
9
|
+
* The path an include names, as written: its braced argument, or — TeX's own form, `\input name` —
|
|
10
|
+
* the name up to the next space. unified-latex gives the unbraced form an argument of the first
|
|
11
|
+
* token only (`sections` of `sections/a`), so the source is read for it.
|
|
12
|
+
*/
|
|
13
|
+
function argumentText(src, m) {
|
|
14
|
+
const start = mandatory(m)[0]?.content[0]?.position?.start.offset;
|
|
15
|
+
if (start === undefined)
|
|
16
|
+
return null;
|
|
17
|
+
const rest = src.slice(start);
|
|
18
|
+
const stop = rest.search(src[start - 1] === "{" ? /\}/u : /[\s{}%]/u);
|
|
19
|
+
return stop < 0 ? rest : rest.slice(0, stop);
|
|
20
|
+
}
|
|
21
|
+
/** One include macro as an `Include`, or nothing when it names no file or has no place. */
|
|
22
|
+
function includeOf(src, m) {
|
|
23
|
+
const macro = INCLUDE_MACROS.get(m.content);
|
|
24
|
+
const target = argumentText(src, m);
|
|
25
|
+
if (macro === undefined || target === null || target.trim() === "")
|
|
26
|
+
return [];
|
|
27
|
+
return inPlace(macroPlace(m), (span) => [{ macro, target, span }]);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every `\input`, `\include` and `\subfile` of the source, in source order — not in a comment (a
|
|
31
|
+
* comment is a node of its own) and not in a macro definition's body, which is read where the macro
|
|
32
|
+
* is used, and nothing here expands macros.
|
|
33
|
+
*/
|
|
34
|
+
export const includesOf = (t) => visited(t.root, isDefinition)
|
|
35
|
+
.filter(isNode)
|
|
36
|
+
.flatMap((n) => (n.type === "macro" ? includeOf(t.src, n) : []));
|
|
37
|
+
const isDocument = (n) => n.type === "environment" && n.env === "document";
|
|
38
|
+
/** The span of the `document` environment's body, or null when the source has none. */
|
|
39
|
+
export const documentBodyOf = (t) => (t.root.content.find(isDocument)?.content ?? [])
|
|
40
|
+
.flatMap((n) => inPlace(placeOf(n), (span) => [span]))
|
|
41
|
+
.reduce((body, span) => body === null ? span : { start: body.start, end: span.end }, null);
|
|
42
|
+
//# sourceMappingURL=includes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"includes.js","sourceRoot":"","sources":["../../../src/adapters/latex/includes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a paper.tex and answers four questions — checks nothing itself: `documentClassOf` (which
|
|
3
|
+
* class, with which options, and where), `outlineOf` (the sections in order, where the back matter
|
|
4
|
+
* starts, where the document ends), `renderedRuns` (the text a reader sees, each character with
|
|
5
|
+
* its source offset) and `bodyProse` (the body's prose as passages, with its citation and
|
|
6
|
+
* cross-reference marks). Used by `tex/template`, `tex/required-section`, `tex/venue-leftover`,
|
|
7
|
+
* `tex/claim-provenance` and `paperlint new`, through the `LatexReader` port (`src/ports/latex.ts`);
|
|
8
|
+
* `src/cli.ts` wires it.
|
|
9
|
+
*
|
|
10
|
+
* ── WHY THE PARSE TREE AND NOT THE `tex/latex` PROJECTION ────────────────────────
|
|
11
|
+
* The ESLint language's projection (`eslint-rules/latex-language.ts`) blanks the preamble,
|
|
12
|
+
* `\documentclass`, `\author` and every other macro in its OPAQUE list, because prose rules must
|
|
13
|
+
* not see markup. These questions are about exactly that markup: the class line, a `\section*`
|
|
14
|
+
* title, an `\institution{…}` in the author block. So the source is parsed here with unified-latex,
|
|
15
|
+
* the parser the language already uses.
|
|
16
|
+
*
|
|
17
|
+
* ── STATES, NOT NULL ─────────────────────────────────────────────────────────────
|
|
18
|
+
* A class line is missing, empty or a class; a node the parser gave no position is `unplaced`, not
|
|
19
|
+
* offset 0; a run of text is a non-empty list of contiguous segments, and a range outside it has
|
|
20
|
+
* no span (`spanIn` in `src/domain/tex-document.ts`). A tree travels only with its own source
|
|
21
|
+
* (`ParsedTex`, minted by `parseLatex` alone).
|
|
22
|
+
*/
|
|
23
|
+
import type { LatexReader } from "../../ports/latex.ts";
|
|
24
|
+
export { documentClassOf, parseTemplate, replaceDocumentClass, } from "./document-class.ts";
|
|
25
|
+
export { collapse, inPlace, macroPlace, mandatory, optional, placeOf, textOf, } from "./nodes.ts";
|
|
26
|
+
export { documentBodyOf, includesOf } from "./includes.ts";
|
|
27
|
+
export { outlineOf } from "./outline.ts";
|
|
28
|
+
export { parseLatex, type ParsedTex } from "./parse.ts";
|
|
29
|
+
export { bodyProse } from "./prose.ts";
|
|
30
|
+
export { renderedRuns } from "./rendered.ts";
|
|
31
|
+
/** The `LatexReader` port over unified-latex: each call parses its source once. */
|
|
32
|
+
export declare const latexReader: LatexReader;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|