paperlint 2.1.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +6 -1
- package/README.md +243 -235
- package/action.yml +4 -6
- package/dist/adapters/references/index.d.ts +13 -0
- package/dist/adapters/references/index.d.ts.map +1 -0
- package/dist/adapters/references/index.js +55 -0
- package/dist/adapters/references/index.js.map +1 -0
- package/dist/adapters/references/reach.io.d.ts +3 -0
- package/dist/adapters/references/reach.io.d.ts.map +1 -0
- package/dist/adapters/references/reach.io.js +14 -0
- package/dist/adapters/references/reach.io.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 +15 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +60 -7
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +94 -30
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +500 -288
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +40 -72
- package/dist/doctor.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/facts-file.d.ts +2 -2
- package/dist/facts-file.js +2 -2
- package/dist/hooks-settings.d.ts +0 -21
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +17 -135
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +39 -29
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +144 -173
- package/dist/init.js.map +1 -1
- package/dist/link-skills.d.ts +1 -1
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +11 -29
- 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 +49 -25
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +85 -0
- package/dist/paper-files.d.ts.map +1 -0
- package/dist/paper-files.js +96 -0
- package/dist/paper-files.js.map +1 -0
- package/dist/paper-settings.d.ts +13 -41
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +71 -71
- package/dist/paper-settings.js.map +1 -1
- package/dist/ports/check-references.d.ts +31 -0
- package/dist/ports/check-references.d.ts.map +1 -0
- package/dist/ports/check-references.js +7 -0
- package/dist/ports/check-references.js.map +1 -0
- package/dist/presets.d.ts +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -6
- package/dist/presets.js.map +1 -1
- package/dist/reference-rules.d.ts +47 -0
- package/dist/reference-rules.d.ts.map +1 -0
- package/dist/reference-rules.js +156 -0
- package/dist/reference-rules.js.map +1 -0
- package/dist/references.d.ts +36 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +91 -0
- package/dist/references.js.map +1 -0
- package/dist/rules-config.d.ts +5 -3
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +15 -6
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +3 -3
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.js +2 -2
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +5 -1
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +22 -5
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +26 -18
- 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 +163 -104
- package/docs/e2e.md +1 -1
- package/docs/install.md +25 -40
- package/docs/optional-rules.md +5 -4
- package/docs/rules.md +65 -24
- package/docs/skills.md +80 -0
- package/eslint-rules/bib-reachable-entry.mjs +75 -0
- package/eslint-rules/latex-language.mjs +29 -1
- package/eslint-rules/paper-stages.harness.mjs +1 -117
- package/eslint-rules/paper-stages.mjs +0 -120
- package/eslint-rules/paper-stages.mutations.mjs +0 -76
- package/eslint-rules/paper-typography.mjs +306 -191
- package/eslint-rules/paper-typography.test.mjs +337 -0
- package/eslint-rules/papers.harness.mjs +8 -12
- package/eslint-rules/papers.mjs +17 -31
- package/eslint-rules/pdf-last-page-balance.mjs +4 -7
- package/eslint-rules/review-frontmatter.mjs +118 -0
- package/eslint-rules/review-frontmatter.schema.json +36 -0
- package/eslint-rules/review-frontmatter.test.mjs +118 -0
- package/eslint-rules/sibling-frontmatter.mjs +19 -0
- package/eslint-rules/sibling-frontmatter.schema.json +14 -0
- package/eslint.config.mjs +0 -15
- package/fixtures/real-markdown-paper/baseline.json +6 -7
- 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 +39 -98
- package/hooks/hooks.mutations.mjs +6 -6
- package/hooks/paper-edit-guard.hook.d.mts +2 -3
- package/hooks/paper-edit-guard.hook.mjs +31 -42
- package/hooks/paper-skills-nudge.hook.mjs +21 -19
- package/hooks/paper-status-gates.hook.mjs +21 -19
- package/hooks/paper-status-gates.sh +8 -13
- package/lib/paper-config.d.mts +15 -25
- package/lib/paper-config.harness.mjs +18 -35
- package/lib/paper-config.mjs +83 -105
- package/lib/paper-config.mutations.mjs +2 -2
- package/lib/skill-checks.mjs +1 -1
- package/lib/skill-trigger-cases.harness.mjs +9 -8
- package/lib/skill-trigger-cases.mjs +7 -15
- package/package.json +1 -1
- package/scripts/eslint-report-guard.mjs +1 -1
- package/scripts/harness-api.frozen.json +0 -3
- package/scripts/mutation-batteries.frozen.json +5 -14
- package/skills/analyze-sibling-paper/SKILL.md +16 -12
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +15 -11
- package/skills/cold-read-diff/SKILL.md +20 -1
- package/skills/cold-read-diff/SKILL.md.spec.ts +19 -0
- package/skills/grade-paper-writing/SKILL.md +20 -1
- package/skills/grade-paper-writing/SKILL.md.spec.ts +19 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +8 -12
- package/skills/paper-pipeline/scripts/consumer.mjs +19 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +6 -4
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +2 -11
- package/skills/plan-paper-timeline/SKILL.md +2 -2
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +1 -1
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +1 -1
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +2 -2
- package/skills/render-paper/check-render.sh +2 -3
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- package/skills/verify-citations/SKILL.md +10 -1
- package/skills/verify-citations/SKILL.md.spec.ts +9 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +48 -26
- package/src/adapters/references/index.ts +102 -0
- package/src/adapters/references/reach.io.ts +12 -0
- package/src/build-engine.ts +6 -1
- package/src/build.harness.mjs +54 -6
- package/src/build.ts +89 -11
- package/src/cli.harness.mjs +149 -294
- package/src/cli.mutations.mjs +35 -44
- package/src/cli.ts +637 -328
- package/src/doctor.harness.mjs +41 -84
- package/src/doctor.mutations.mjs +9 -9
- package/src/doctor.ts +44 -89
- package/src/engine.harness.mjs +50 -0
- package/src/engine.ts +35 -0
- package/src/facts-file.test.ts +0 -7
- package/src/facts-file.ts +2 -2
- package/src/hooks-settings.harness.mjs +7 -92
- package/src/hooks-settings.mutations.mjs +2 -17
- package/src/hooks-settings.ts +18 -162
- package/src/init.test.ts +206 -0
- package/src/init.ts +187 -231
- package/src/link-skills.harness.mjs +1 -38
- package/src/link-skills.mutations.mjs +9 -5
- package/src/link-skills.ts +10 -34
- package/src/lint-fix.test.ts +120 -0
- package/src/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +2 -0
- package/src/new-paper.mutations.mjs +0 -14
- package/src/new-paper.test.ts +173 -14
- package/src/new-paper.ts +82 -30
- package/src/paper-files.test.ts +395 -0
- package/src/paper-files.ts +200 -0
- package/src/paper-settings-commands.test.ts +121 -122
- package/src/paper-settings.test.ts +93 -64
- package/src/paper-settings.ts +99 -97
- package/src/parse-args.test.ts +48 -0
- package/src/ports/check-references.ts +28 -0
- package/src/presets.ts +5 -15
- package/src/reference-rules.ts +229 -0
- package/src/references.test.ts +209 -0
- package/src/references.ts +138 -0
- package/src/rules-config.harness.mjs +24 -8
- package/src/rules-config.ts +19 -7
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +4 -2
- package/src/structure.ts +3 -3
- package/src/tex-requirements.ts +2 -2
- package/src/toolchain.harness.mjs +41 -6
- package/src/toolchain.ts +29 -5
- package/src/types.ts +36 -18
- package/src/venue-rules.test.ts +23 -11
- package/src/venue-rules.ts +12 -14
- package/templates/paper/paperlint.json +1 -1
- package/eslint-rules/doc-fields.harness.mjs +0 -336
- package/eslint-rules/doc-fields.mjs +0 -186
- package/eslint-rules/doc-fields.mutations.mjs +0 -96
- package/eslint-rules/paper-typography.harness.mjs +0 -291
- package/eslint-rules/paper-typography.mutations.mjs +0 -131
- package/eslint-rules/review-findings-cause.harness.mjs +0 -228
- package/eslint-rules/review-findings-cause.mjs +0 -135
- package/eslint-rules/review-findings-cause.mutations.mjs +0 -72
- package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +0 -16
- package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +0 -17
- package/fixtures/review-findings-cause/clean.md +0 -17
- package/fixtures/review-findings-cause/defect.md +0 -14
- package/fixtures/review-findings-cause/old-debt.md +0 -14
- package/fixtures/review-findings-cause/quiet-in-fence.md +0 -16
package/docs/install.md
CHANGED
|
@@ -14,21 +14,6 @@ npx paperlint init
|
|
|
14
14
|
Two commands in one terminal, in this order: `init` links the skills and hooks to the copy in
|
|
15
15
|
your project's `node_modules`, so it needs the install first.
|
|
16
16
|
|
|
17
|
-
**Upgrading from `research-paper-pipeline`** (the package's name before 2.0.0):
|
|
18
|
-
`npm rm research-paper-pipeline && npm i -D paperlint`, then `npx paperlint init`. It moves the
|
|
19
|
-
`"research-paper-pipeline"` key in `package.json` to `"paperlint"`, and replaces the hook commands
|
|
20
|
-
and skill links that pointed into the old package. Until then the old key is still read, with a
|
|
21
|
-
warning.
|
|
22
|
-
|
|
23
|
-
**Upgrading from 2.0.0**: 2.0.0 wrote hook commands that run `node_modules/paperlint/bin/rpp.mjs`,
|
|
24
|
-
a file later versions do not ship, so those hooks stop running. `npx paperlint doctor` names them;
|
|
25
|
-
`npx paperlint init` replaces them and keeps your own commands in the same matcher. The TeX Live
|
|
26
|
-
and banal caches moved from `~/.cache/rpp/` to `~/.cache/paperlint/`, so `npx paperlint toolchain`
|
|
27
|
-
downloads TeX Live once more (delete the old directory afterwards), and the environment variables
|
|
28
|
-
are now `PAPERLINT_TEXLIVE_DIR`, `PAPERLINT_BANAL_DIR` and `PAPERLINT_CTAN_MIRROR` — the `RPP_*`
|
|
29
|
-
names are no longer read. A leftover `rpp.json` is ignored; its settings belong under the
|
|
30
|
-
`"paperlint"` key of `package.json`.
|
|
31
|
-
|
|
32
17
|
The npm package carries everything: the `paperlint` command, the ESLint rules, the Claude Code skills
|
|
33
18
|
and hooks, and the scripts the skills run. External programs are separate:
|
|
34
19
|
|
|
@@ -43,15 +28,18 @@ and hooks, and the scripts the skills run. External programs are separate:
|
|
|
43
28
|
|
|
44
29
|
In order:
|
|
45
30
|
|
|
46
|
-
1. **Finds the papers directory**, or asks for it
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
1. **Finds the papers directory**, or asks for it when several directories look like one.
|
|
32
|
+
2. **Declares it**, in a root `paperlint.json`, only when it is not the default `papers`. A project
|
|
33
|
+
on the defaults gets no config file at all.
|
|
49
34
|
3. **Links each shipped skill** into `.claude/skills/<name>`, where Claude Code looks for skills.
|
|
50
35
|
4. **Writes the hook commands** into `.claude/settings.json`, beside your own entries.
|
|
51
36
|
5. **Offers a GitHub Actions workflow**, pinned to the release tag of the version you installed.
|
|
52
37
|
6. **Offers a first paper** (`paperlint new`) if the papers directory has none.
|
|
53
|
-
7. **
|
|
54
|
-
|
|
38
|
+
7. **Offers TeX Live** for `paperlint build` (`paperlint toolchain`, ~270 MB, ~3 min, once), with
|
|
39
|
+
the size in the question and NO as the default. Without a terminal it asks nothing and lists the
|
|
40
|
+
command as the next step. `paperlint lint` needs no TeX.
|
|
41
|
+
8. **Reports missing external programs** and the command that installs each. It installs nothing.
|
|
42
|
+
9. **Runs `paperlint doctor`** and exits with its verdict.
|
|
55
43
|
|
|
56
44
|
It asks only what it cannot guess or what costs something. A human at a terminal is asked; an
|
|
57
45
|
agent, CI or `--yes` takes the defaults, and `init` prints which default it took. An unanswered
|
|
@@ -61,23 +49,23 @@ question (Ctrl+D) also takes the default.
|
|
|
61
49
|
|
|
62
50
|
| what | where | when |
|
|
63
51
|
| ------------------------------------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
-
|
|
|
52
|
+
| `{ "papersDir": … }` naming your papers directory | `paperlint.json` | only when it is not the default `papers` |
|
|
65
53
|
| the three hook commands, merged in beside your own entries | `.claude/settings.json` | by default. A human at a terminal is asked [Y/n]; an agent, CI or `--yes` gets YES; `--no-hooks` skips. Hand-wired under another spelling: nothing written, so nothing runs twice |
|
|
66
54
|
| a GitHub Actions workflow, pinned to `@v<installed version>` | `.github/workflows/` | only if you say yes; it asks once, and only when a human is at a terminal (stdin and stdout, no `CI`, no `--yes`) |
|
|
67
55
|
| a first paper, via `paperlint new` | `<papers>/<name>/` | only when the papers directory holds none: asked of a human at a terminal, otherwise only with `--paper <name>` |
|
|
68
56
|
| one relative symlink per shipped skill, into the installed package | `.claude/skills/<name>` | always — except where that name is already taken (a directory, a file, a link elsewhere): that entry is left as it is and named in the report |
|
|
69
57
|
|
|
70
|
-
It installs no software and touches nothing else. Commit `.claude/settings.json` so every clone
|
|
58
|
+
It installs no software you did not say yes to, and touches nothing else. Commit `.claude/settings.json` so every clone
|
|
71
59
|
gets the hooks; the hook commands run files inside `node_modules`, so a fresh clone needs
|
|
72
60
|
`npm install` first.
|
|
73
61
|
|
|
74
62
|
## Why it is shaped this way
|
|
75
63
|
|
|
76
|
-
**One
|
|
77
|
-
config; it can only read a path it can spell, and
|
|
78
|
-
|
|
79
|
-
`lib/skill-trigger-cases.mjs` and `skills/paper-pipeline/scripts/consumer.mjs
|
|
80
|
-
|
|
64
|
+
**One file, at the project root.** A hook cannot import code or walk up a tree looking for a
|
|
65
|
+
config; it can only read a path it can spell, and `$CLAUDE_PROJECT_DIR/paperlint.json` is one. The
|
|
66
|
+
same file is read by the CLI, the three hooks, `eslint-rules/papers.mjs`,
|
|
67
|
+
`lib/skill-trigger-cases.mjs` and `skills/paper-pipeline/scripts/consumer.mjs`. It is optional:
|
|
68
|
+
absent, every reader takes the same defaults.
|
|
81
69
|
|
|
82
70
|
**Nothing runs at install time.** No postinstall script and no automatic TeX download. npm's rule
|
|
83
71
|
is that _"the only valid use of install or preinstall scripts is for compilation"_; husky removed
|
|
@@ -110,9 +98,7 @@ resolve.
|
|
|
110
98
|
nothing while guarding and says nothing while watching a directory that does not exist, so
|
|
111
99
|
"installed" and "protecting you" look the same from outside. `doctor` prints the papers directory
|
|
112
100
|
`paperlint lint` resolves and the one the hooks resolve, whether they match, whether the hooks are wired
|
|
113
|
-
(once, twice, or not at all), and which external programs are missing.
|
|
114
|
-
project still enables the old `research-paper-pipeline` plugin, because plugin plus settings would
|
|
115
|
-
run every hook twice. A plugin installed at user scope is outside what it can read, and it says so.
|
|
101
|
+
(once, twice, or not at all), and which external programs are missing.
|
|
116
102
|
|
|
117
103
|
### How comparable tools install
|
|
118
104
|
|
|
@@ -166,13 +152,12 @@ Neither `paperlint lint` nor any of the three hooks loads `@ast-grep` or `typesc
|
|
|
166
152
|
|
|
167
153
|
## Troubleshooting
|
|
168
154
|
|
|
169
|
-
| symptom | cause and fix
|
|
170
|
-
| ------------------------------------------------------------ |
|
|
171
|
-
| `init`: skills "nothing linked", or hooks that fail to start | the package is not installed in this project (`npx` ran a temporary copy). Run `npm i -D paperlint`, then `npx paperlint init` again
|
|
172
|
-
| `
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
| every
|
|
177
|
-
|
|
|
178
|
-
| anything else | `npx paperlint doctor` — it checks the setup and exits non-zero on anything miswired |
|
|
155
|
+
| symptom | cause and fix |
|
|
156
|
+
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `init`: skills "nothing linked", or hooks that fail to start | the package is not installed in this project (`npx` ran a temporary copy). Run `npm i -D paperlint`, then `npx paperlint init` again |
|
|
158
|
+
| `paperlint lint`: `no papers in papers/` | there is no paper where `papersDir` points. Create one with `npx paperlint new <name>`, or set `"papersDir"` in `paperlint.json` |
|
|
159
|
+
| hooks fail with `Cannot find module` in a fresh clone | the hook commands run files in `node_modules`: run `npm install` |
|
|
160
|
+
| `paperlint: not built` | installed from git with `--ignore-scripts`, or a clone before building: run `npm run build` in the package |
|
|
161
|
+
| a skill does not show up in Claude Code | its name was already taken in `.claude/skills/`. `init` names it and leaves it alone; rename or remove yours and run `npx paperlint init` again |
|
|
162
|
+
| **every** shell command is blocked in Claude Code | `paper-edit-guard` cannot parse the root `paperlint.json` (merge-conflict markers, a typo), and refuses rather than guess. Fix it with a normal file edit — file edits are not blocked |
|
|
163
|
+
| anything else | `npx paperlint doctor` — it checks the setup and exits non-zero on anything miswired |
|
package/docs/optional-rules.md
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
Some checks matter only for some venues. paperlint ships them **off**. A venue preset that needs one
|
|
4
4
|
turns it on for its papers (AgenticDev's turns on `pdf/last-page-balance`), and a paper can turn one
|
|
5
5
|
on or off for itself in its own `paperlint.json`
|
|
6
|
-
([`configuration.md`](configuration.md#
|
|
7
|
-
rules apply to that paper. The `
|
|
8
|
-
|
|
6
|
+
([`configuration.md`](configuration.md#a-papers-paperlintjson)). No glob is involved: a paper's
|
|
7
|
+
rules apply to that paper. The root `paperlint.json` can turn it on for every paper, or for some
|
|
8
|
+
with a block ([`configuration.md`](configuration.md#the-rules-key-turning-rules-on-and-off)); a
|
|
9
|
+
paper's own file still has the last word for that paper.
|
|
9
10
|
|
|
10
11
|
| rule | what it checks | who needs it |
|
|
11
12
|
| ----------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
|
|
@@ -48,7 +49,7 @@ So: **build, then lint.**
|
|
|
48
49
|
| a review build with numbered lines | nothing — the numbers run down the whole page, so both columns measure full height, and balance is a camera-ready requirement |
|
|
49
50
|
|
|
50
51
|
If you turn the rule on — in a `paperlint.json` beside no `paper.tex`, or with a `files` glob in
|
|
51
|
-
`
|
|
52
|
+
the root `paperlint.json` that reaches none — `paperlint lint` fails and says so, rather than reporting a clean
|
|
52
53
|
run for a rule that never ran.
|
|
53
54
|
|
|
54
55
|
### Which venues need it
|
package/docs/rules.md
CHANGED
|
@@ -3,29 +3,63 @@
|
|
|
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` |
|
|
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/
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `pdf/
|
|
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 |
|
|
24
30
|
|
|
25
31
|
Optional rules — off unless you turn them on in the `rules` setting, because only some venues need
|
|
26
32
|
them — are on their own page: [`optional-rules.md`](optional-rules.md). Today there is one,
|
|
27
33
|
`pdf/last-page-balance`.
|
|
28
34
|
|
|
35
|
+
## A deliberate exception: a disable directive, with the reason
|
|
36
|
+
|
|
37
|
+
When a finding is right in general and wrong for one line — a quoted `.05`, a bibliography entry
|
|
38
|
+
for an invited talk that has no link — keep the exception where it is, with ESLint's own directive
|
|
39
|
+
in a `%` comment (`<!-- … -->` in markdown). The part after `--` is the reason, and it stays in the
|
|
40
|
+
file for the next reader:
|
|
41
|
+
|
|
42
|
+
```latex
|
|
43
|
+
% eslint-disable-next-line paper/leading-zero -- quoted verbatim from the reviewer
|
|
44
|
+
They wrote ".05" in their report.
|
|
45
|
+
Also here: .06 % eslint-disable-line paper/leading-zero -- quoted too
|
|
46
|
+
|
|
47
|
+
% eslint-disable paper/section-word -- a table reproduced from the venue's own template
|
|
48
|
+
…
|
|
49
|
+
% eslint-enable paper/section-word
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This works inside the `filecontents` bibliography of `paper.tex` too, above the entry:
|
|
53
|
+
|
|
54
|
+
```latex
|
|
55
|
+
% eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists
|
|
56
|
+
@misc{smith2024talk, …}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A directive that no longer silences anything is itself reported, so a stale exception does not
|
|
60
|
+
linger. There is no per-paper allowance or counter: the typography rules fix themselves with
|
|
61
|
+
`paperlint lint --fix`, and what is left is either fixed or excepted by name, on its line.
|
|
62
|
+
|
|
29
63
|
Besides these rules, `paperlint lint` reports a paper directory that is missing a required file (by
|
|
30
64
|
default `PIPELINE-STATUS.md`) as an error. Which files are required is configurable — see
|
|
31
65
|
[`configuration.md`](configuration.md#required-files).
|
|
@@ -33,7 +67,7 @@ default `PIPELINE-STATUS.md`) as an error. Which files are required is configura
|
|
|
33
67
|
## Checks against the venue
|
|
34
68
|
|
|
35
69
|
A paper says where it is submitted in a `paperlint.json` beside `paper.tex`
|
|
36
|
-
([`configuration.md`](configuration.md#
|
|
70
|
+
([`configuration.md`](configuration.md#a-papers-paperlintjson)):
|
|
37
71
|
|
|
38
72
|
```json
|
|
39
73
|
{ "extends": "paperlint:aisec", "kind": "research" }
|
|
@@ -69,12 +103,13 @@ and the split into body and reference pages. They report on the paper's `paper.t
|
|
|
69
103
|
| the paper | what you get |
|
|
70
104
|
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
71
105
|
| has a `paperlint.json` that extends no preset (`"extends": null` is what `paperlint new` writes) | `pdf/measured` warning naming the file to set — no venue chosen yet |
|
|
72
|
-
| has no `paperlint.json` at all
|
|
106
|
+
| has no `paperlint.json` at all | nothing |
|
|
73
107
|
| extends a preset that does not resolve (a typo, a missing file) | `pdf/profile` error, listing the shipped presets |
|
|
74
108
|
| has not been built (no facts file) | `pdf/measured` warning — it does not fail the run, because lint often runs where nothing is built (the CI action only lints) |
|
|
75
109
|
| was built without banal | `pdf/measured` warning; fonts are still checked, the rest is not |
|
|
76
110
|
| has facts about another PDF than the one on disk | `pdf/fresh` error, and nothing else is judged |
|
|
77
|
-
| names no `kind
|
|
111
|
+
| names no `kind` while its preset has kinds, or a kind the preset lacks | `pdf/profile` error; everything but the page limit is still checked |
|
|
112
|
+
| names no `kind` and its preset has none (`paperlint:acm-sigconf`) | nothing — that preset has no page limit to check; everything else is |
|
|
78
113
|
|
|
79
114
|
The venue comes from `paperlint.json`, not from the facts, so changing it needs no rebuild: the
|
|
80
115
|
measurements do not depend on it. Messages name the venue by the preset's `name`, else by the file
|
|
@@ -100,6 +135,7 @@ Put it in your repository and extend it by a path relative to the file that name
|
|
|
100
135
|
|
|
101
136
|
```
|
|
102
137
|
package.json
|
|
138
|
+
paperlint.json (optional)
|
|
103
139
|
venues/
|
|
104
140
|
usenix-sec.jsonc
|
|
105
141
|
papers/
|
|
@@ -164,7 +200,11 @@ rules, which is why they list it above. Markdown papers are deprecated and being
|
|
|
164
200
|
|
|
165
201
|
## The scorecard's `bytes:` and `sourceBytes:`
|
|
166
202
|
|
|
167
|
-
|
|
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:
|
|
168
208
|
|
|
169
209
|
```yaml
|
|
170
210
|
---
|
|
@@ -180,8 +220,9 @@ researchQuestion: "Does pruning the state space reduce review cost?"
|
|
|
180
220
|
---
|
|
181
221
|
```
|
|
182
222
|
|
|
183
|
-
|
|
184
|
-
|
|
223
|
+
A stage name may use `a-z`, `0-9`, `.`, `_` and `-`. You write `bytes:` and `sourceBytes:` yourself
|
|
224
|
+
(`wc -c < versions/2026-07-22-submitted.pdf`), or the agent does, once, when the stage is recorded;
|
|
225
|
+
no command in this package generates them. They are not a checksum to maintain: a
|
|
185
226
|
frozen PDF is never supposed to change, so `bytes:` disagreeing with the file means the file was
|
|
186
227
|
replaced after it was declared, and that is exactly the finding. If you really did re-freeze a
|
|
187
228
|
stage, update the number in the same commit.
|
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` |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bib/reachable-entry` — a bibliography entry a reader cannot follow: no doi, no url, no arXiv
|
|
3
|
+
* id. Reported ON THE ENTRY, one finding per entry; there is no fix, because the link has to be
|
|
4
|
+
* looked up. Default severity: warn.
|
|
5
|
+
*
|
|
6
|
+
* Provenance: Reviewer A on HotCRP #20 (2026-08-23), "not every entry has a DOI/link" — one
|
|
7
|
+
* paper had 0 of 47 entries reachable. Until 3.0.0 this was one count inside `paper/typography`,
|
|
8
|
+
* silenced per paper by a declared debt; now each entry is its own finding, and an entry that
|
|
9
|
+
* genuinely has no link (a talk, a personal communication) carries a disable directive with its
|
|
10
|
+
* reason, in the bibliography itself:
|
|
11
|
+
*
|
|
12
|
+
* % eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists
|
|
13
|
+
* @misc{smith2024talk, …}
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ NOT "no doi". Measured 2026-08-24: ICLR/NeurIPS/TMLR issue no DOIs at all, so a doi-only
|
|
16
|
+
* rule would demand something that does not exist and get muted for lying. A url or an arXiv id
|
|
17
|
+
* counts.
|
|
18
|
+
*
|
|
19
|
+
* The bibliography read is the one INSIDE `paper.tex` (`filecontents`), where the papers this
|
|
20
|
+
* package targets keep it. The entry boundaries and fields are found by a line-anchored `@` and
|
|
21
|
+
* a field name followed by `=` — BibTeX's own lexical shape, one lexeme at a time, not a parse:
|
|
22
|
+
* the parser (`@retorquere/bibtex-parser`) is an OPTIONAL peer of this package.
|
|
23
|
+
*/
|
|
24
|
+
import { bibRange } from "./paper-typography.mjs";
|
|
25
|
+
|
|
26
|
+
/** Entry types that are not references. */
|
|
27
|
+
const NOT_AN_ENTRY = /^@(comment|string|preamble)\b/i;
|
|
28
|
+
const HAS_LINK = /\b(doi|url)\s*=/i;
|
|
29
|
+
const HAS_ARXIV = /arxiv[:\s]*\d{4}\.\d{4,5}/i;
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
rules: {
|
|
33
|
+
"reachable-entry": {
|
|
34
|
+
meta: {
|
|
35
|
+
type: "suggestion",
|
|
36
|
+
docs: {
|
|
37
|
+
description:
|
|
38
|
+
"a bibliography entry carries a doi, a url or an arXiv id — something a reader can follow",
|
|
39
|
+
},
|
|
40
|
+
schema: [],
|
|
41
|
+
messages: {
|
|
42
|
+
unreachable:
|
|
43
|
+
"`{{key}}` has no doi, url or arXiv id — a reader has nothing to follow. If none exists, keep the exception with `% eslint-disable-next-line bib/reachable-entry -- <why>` above the entry",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
create(context) {
|
|
47
|
+
return {
|
|
48
|
+
"root:exit"() {
|
|
49
|
+
const sc = context.sourceCode;
|
|
50
|
+
const raw = sc.raw ?? sc.text;
|
|
51
|
+
const bib = typeof raw === "string" ? bibRange(raw) : null;
|
|
52
|
+
if (!bib) return;
|
|
53
|
+
const starts = [...bib.body.matchAll(/^@/gm)].map((m) => m.index);
|
|
54
|
+
starts.forEach((s, i) => {
|
|
55
|
+
const entry = bib.body.slice(s, starts[i + 1] ?? bib.body.length);
|
|
56
|
+
if (NOT_AN_ENTRY.test(entry)) return;
|
|
57
|
+
if (HAS_LINK.test(entry) || HAS_ARXIV.test(entry)) return;
|
|
58
|
+
const head = entry.split("\n")[0];
|
|
59
|
+
const key = /\{\s*([^,\s]+)/.exec(head)?.[1] ?? "?";
|
|
60
|
+
const from = bib.bodyStart + s;
|
|
61
|
+
context.report({
|
|
62
|
+
loc: {
|
|
63
|
+
start: sc.getLocFromIndex(from),
|
|
64
|
+
end: sc.getLocFromIndex(from + head.length),
|
|
65
|
+
},
|
|
66
|
+
messageId: "unreachable",
|
|
67
|
+
data: { key },
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -64,7 +64,9 @@
|
|
|
64
64
|
* is not substituted). Anonymisation is normally done with exactly those, so coinage and
|
|
65
65
|
* jargon rules see less on an anonymised version than on a camera-ready.
|
|
66
66
|
* 8. `% eslint {"rules":…}` inside `.tex` does NOT work: `applyInlineConfig` returns empty.
|
|
67
|
-
* `% eslint-disable-next-line <rule> -- reason
|
|
67
|
+
* The disable directives DO work — `% eslint-disable-next-line <rule> -- reason`,
|
|
68
|
+
* `% eslint-disable-line`, `% eslint-disable` / `% eslint-enable` — and since 3.0.0 also
|
|
69
|
+
* inside a `filecontents` bibliography, whose `%` lines are made comment nodes below.
|
|
68
70
|
* 9. `\section*{…}` is untested and does not occur in the corpus (measured: 0 occurrences).
|
|
69
71
|
* When it appears, `plain()` will glue all arguments together and the heading may come out
|
|
70
72
|
* as `*X`.
|
|
@@ -207,6 +209,32 @@ export function texToMdast(src) {
|
|
|
207
209
|
});
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
// 🔴 A `filecontents` body is not LaTeX — it is the `.bib` the paper writes, usually in the
|
|
213
|
+
// preamble — so the parser returns it as one verbatim node and no comment nodes from it. A
|
|
214
|
+
// disable directive above a bibliography entry (`% eslint-disable-next-line bib/reachable-entry
|
|
215
|
+
// -- …`) was therefore invisible. Its `%` lines are made comments here, the only way
|
|
216
|
+
// `getInlineConfigNodes` sees them. BibTeX ignores text between entries, so the line is
|
|
217
|
+
// harmless to the build.
|
|
218
|
+
for (const n of ast.content)
|
|
219
|
+
if (
|
|
220
|
+
n.type === "verbatim" &&
|
|
221
|
+
/^filecontents\*?$/.test(String(n.env)) &&
|
|
222
|
+
P(n)
|
|
223
|
+
) {
|
|
224
|
+
let at = P(n).start.offset;
|
|
225
|
+
for (const line of src.slice(at, P(n).end.offset).split("\n")) {
|
|
226
|
+
const text = line.replace(/\r$/, "");
|
|
227
|
+
const pct = /^\s*%/.test(text) ? text.indexOf("%") : -1;
|
|
228
|
+
if (pct >= 0)
|
|
229
|
+
children.push({
|
|
230
|
+
type: "html",
|
|
231
|
+
value: text.slice(pct + 1),
|
|
232
|
+
position: { start: loc(at + pct), end: loc(at + text.length) },
|
|
233
|
+
});
|
|
234
|
+
at += line.length + 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
210
238
|
(function walk(node) {
|
|
211
239
|
if (Array.isArray(node)) return node.forEach((n) => walk(n));
|
|
212
240
|
if (!node || typeof node !== "object") return;
|