paperlint 4.1.0 → 5.0.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.
Files changed (202) hide show
  1. package/CLAUDE.md +26 -2
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +15 -4
  4. package/action.harness.mjs +54 -36
  5. package/action.yml +12 -27
  6. package/dist/adapters/latex/document-class.d.ts +6 -5
  7. package/dist/adapters/latex/document-class.d.ts.map +1 -1
  8. package/dist/adapters/latex/document-class.js +18 -10
  9. package/dist/adapters/latex/document-class.js.map +1 -1
  10. package/dist/adapters/latex/includes.d.ts +16 -0
  11. package/dist/adapters/latex/includes.d.ts.map +1 -0
  12. package/dist/adapters/latex/includes.js +48 -0
  13. package/dist/adapters/latex/includes.js.map +1 -0
  14. package/dist/adapters/latex/index.d.ts +9 -5
  15. package/dist/adapters/latex/index.d.ts.map +1 -1
  16. package/dist/adapters/latex/index.js +8 -1
  17. package/dist/adapters/latex/index.js.map +1 -1
  18. package/dist/adapters/latex/nodes.d.ts +5 -1
  19. package/dist/adapters/latex/nodes.d.ts.map +1 -1
  20. package/dist/adapters/latex/nodes.js +6 -2
  21. package/dist/adapters/latex/nodes.js.map +1 -1
  22. package/dist/adapters/latex/parse.d.ts.map +1 -1
  23. package/dist/adapters/latex/parse.js +1 -0
  24. package/dist/adapters/latex/parse.js.map +1 -1
  25. package/dist/adapters/latex/prose.d.ts +8 -0
  26. package/dist/adapters/latex/prose.d.ts.map +1 -0
  27. package/dist/adapters/latex/prose.js +353 -0
  28. package/dist/adapters/latex/prose.js.map +1 -0
  29. package/dist/build.d.ts.map +1 -1
  30. package/dist/build.js +7 -3
  31. package/dist/build.js.map +1 -1
  32. package/dist/claim-provenance.d.ts +34 -0
  33. package/dist/claim-provenance.d.ts.map +1 -0
  34. package/dist/claim-provenance.js +146 -0
  35. package/dist/claim-provenance.js.map +1 -0
  36. package/dist/cli.d.ts +2 -8
  37. package/dist/cli.d.ts.map +1 -1
  38. package/dist/cli.js +81 -31
  39. package/dist/cli.js.map +1 -1
  40. package/dist/domain/paper-source.d.ts +58 -0
  41. package/dist/domain/paper-source.d.ts.map +1 -0
  42. package/dist/domain/paper-source.js +109 -0
  43. package/dist/domain/paper-source.js.map +1 -0
  44. package/dist/domain/sentences.d.ts +26 -0
  45. package/dist/domain/sentences.d.ts.map +1 -0
  46. package/dist/domain/sentences.js +48 -0
  47. package/dist/domain/sentences.js.map +1 -0
  48. package/dist/domain/tex-document.d.ts +39 -4
  49. package/dist/domain/tex-document.d.ts.map +1 -1
  50. package/dist/domain/tex-document.js.map +1 -1
  51. package/dist/eslint-rules/latex-language.d.ts +5 -1
  52. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  53. package/dist/eslint-rules/latex-language.js +6 -3
  54. package/dist/eslint-rules/latex-language.js.map +1 -1
  55. package/dist/eslint-rules/paper-context.d.ts +20 -0
  56. package/dist/eslint-rules/paper-context.d.ts.map +1 -0
  57. package/dist/eslint-rules/paper-context.js +44 -0
  58. package/dist/eslint-rules/paper-context.js.map +1 -0
  59. package/dist/eslint-rules/paper-typography.d.ts +8 -1
  60. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  61. package/dist/eslint-rules/paper-typography.js +85 -19
  62. package/dist/eslint-rules/paper-typography.js.map +1 -1
  63. package/dist/eslint-rules/tex-build.d.ts.map +1 -1
  64. package/dist/eslint-rules/tex-build.js +7 -1
  65. package/dist/eslint-rules/tex-build.js.map +1 -1
  66. package/dist/init.d.ts +4 -19
  67. package/dist/init.d.ts.map +1 -1
  68. package/dist/init.js +24 -29
  69. package/dist/init.js.map +1 -1
  70. package/dist/package-dirs.d.ts +2 -0
  71. package/dist/package-dirs.d.ts.map +1 -1
  72. package/dist/package-dirs.js +8 -0
  73. package/dist/package-dirs.js.map +1 -1
  74. package/dist/paper-files.d.ts +2 -2
  75. package/dist/paper-includes.d.ts +17 -0
  76. package/dist/paper-includes.d.ts.map +1 -0
  77. package/dist/paper-includes.js +51 -0
  78. package/dist/paper-includes.js.map +1 -0
  79. package/dist/ports/latex.d.ts +12 -1
  80. package/dist/ports/latex.d.ts.map +1 -1
  81. package/dist/register.d.ts +40 -0
  82. package/dist/register.d.ts.map +1 -0
  83. package/dist/register.js +134 -0
  84. package/dist/register.js.map +1 -0
  85. package/dist/rules-config.d.ts +1 -0
  86. package/dist/rules-config.d.ts.map +1 -1
  87. package/dist/rules-config.js.map +1 -1
  88. package/dist/structure.js +1 -1
  89. package/dist/structure.js.map +1 -1
  90. package/dist/tex-paper.d.ts +33 -0
  91. package/dist/tex-paper.d.ts.map +1 -0
  92. package/dist/tex-paper.js +103 -0
  93. package/dist/tex-paper.js.map +1 -0
  94. package/dist/tex-venue-rules.d.ts +7 -2
  95. package/dist/tex-venue-rules.d.ts.map +1 -1
  96. package/dist/tex-venue-rules.js +31 -17
  97. package/dist/tex-venue-rules.js.map +1 -1
  98. package/docs/configuration.md +5 -4
  99. package/docs/install.md +8 -8
  100. package/docs/prior-art/paper-folder-scaffolding.md +4 -7
  101. package/docs/rules/tex/claim-provenance.md +83 -0
  102. package/docs/rules/tex/register.md +77 -0
  103. package/docs/rules/tex/required-section.md +3 -1
  104. package/docs/rules/tex/template.md +12 -0
  105. package/docs/rules/tex/venue-leftover.md +4 -2
  106. package/docs/rules.md +17 -10
  107. package/eslint-rules/latex-language.ts +10 -4
  108. package/eslint-rules/paper-context.test.ts +47 -0
  109. package/eslint-rules/paper-context.ts +57 -0
  110. package/eslint-rules/paper-typography.test.mjs +48 -1
  111. package/eslint-rules/paper-typography.ts +139 -24
  112. package/eslint-rules/tex-build.ts +8 -1
  113. package/eslint.config.mjs +1 -0
  114. package/fixtures/real-markdown-paper/baseline.json +1 -2
  115. package/fixtures/real-markdown-paper/baseline.ts +8 -7
  116. package/package.json +2 -1
  117. package/scripts/check.ts +2 -2
  118. package/scripts/eslint-report-guard.mjs +5 -2
  119. package/scripts/harness-api.frozen.json +0 -1
  120. package/skills/cold-read-diff/SKILL.md +31 -10
  121. package/skills/cold-read-diff/SKILL.md.spec.ts +30 -9
  122. package/skills/draft-paper/SKILL.md +2 -2
  123. package/skills/draft-paper/SKILL.md.spec.ts +1 -1
  124. package/skills/grade-paper-writing/SKILL.md +109 -44
  125. package/skills/grade-paper-writing/SKILL.md.spec.ts +108 -43
  126. package/skills/harden-paper/SKILL.md +35 -36
  127. package/skills/harden-paper/SKILL.md.spec.ts +34 -35
  128. package/skills/paper-adversarial-review/SKILL.md +2 -2
  129. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  130. package/skills/paper-pipeline/SKILL.md +3 -3
  131. package/skills/paper-pipeline/SKILL.md.spec.ts +2 -2
  132. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +6 -6
  133. package/skills/paper-pipeline/references/pipeline-status-template.md +2 -16
  134. package/skills/paper-pipeline/references/writing-craft.md +53 -18
  135. package/skills/pc-panel-review/SKILL.md +36 -11
  136. package/skills/pc-panel-review/SKILL.md.spec.ts +35 -10
  137. package/src/adapters/latex/document-class.ts +20 -12
  138. package/src/adapters/latex/includes.ts +74 -0
  139. package/src/adapters/latex/index.test.ts +183 -1
  140. package/src/adapters/latex/index.ts +14 -4
  141. package/src/adapters/latex/nodes.ts +11 -2
  142. package/src/adapters/latex/parse.ts +1 -0
  143. package/src/adapters/latex/prose.ts +484 -0
  144. package/src/build.harness.mjs +4 -2
  145. package/src/build.test.ts +18 -1
  146. package/src/build.ts +7 -3
  147. package/src/claim-provenance.test.ts +352 -0
  148. package/src/claim-provenance.ts +217 -0
  149. package/src/cli.harness.mjs +22 -54
  150. package/src/cli.test.ts +2 -18
  151. package/src/cli.ts +92 -36
  152. package/src/domain/paper-source.test.ts +156 -0
  153. package/src/domain/paper-source.ts +233 -0
  154. package/src/domain/sentences.ts +67 -0
  155. package/src/domain/tex-document.ts +36 -3
  156. package/src/init.test.ts +33 -13
  157. package/src/init.ts +25 -48
  158. package/src/linted-set.test.ts +11 -1
  159. package/src/new-paper.harness.mjs +1 -5
  160. package/src/package-dirs.ts +9 -0
  161. package/src/paper-files.ts +2 -2
  162. package/src/paper-includes.test.ts +161 -0
  163. package/src/paper-includes.ts +77 -0
  164. package/src/ports/latex.ts +13 -0
  165. package/src/register.test.ts +165 -0
  166. package/src/register.ts +177 -0
  167. package/src/rule-cases.test.ts +43 -23
  168. package/src/rule-docs.test.ts +0 -1
  169. package/src/rules-config.ts +1 -0
  170. package/src/structure.harness.ts +1 -3
  171. package/src/structure.ts +1 -1
  172. package/src/tex-paper.test.ts +148 -0
  173. package/src/tex-paper.ts +161 -0
  174. package/src/tex-venue-rules.test.ts +87 -5
  175. package/src/tex-venue-rules.ts +62 -30
  176. package/templates/paper/PIPELINE-STATUS.md +0 -4
  177. package/dist/action-ref.d.ts +0 -12
  178. package/dist/action-ref.d.ts.map +0 -1
  179. package/dist/action-ref.js +0 -16
  180. package/dist/action-ref.js.map +0 -1
  181. package/dist/eslint-rules/paper-research-question.d.ts +0 -31
  182. package/dist/eslint-rules/paper-research-question.d.ts.map +0 -1
  183. package/dist/eslint-rules/paper-research-question.js +0 -188
  184. package/dist/eslint-rules/paper-research-question.js.map +0 -1
  185. package/eslint-rules/paper-research-question.harness.mjs +0 -212
  186. package/eslint-rules/paper-research-question.ts +0 -199
  187. package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +0 -9
  188. package/fixtures/paper-research-question/comment-only/paper.tex +0 -7
  189. package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +0 -10
  190. package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +0 -6
  191. package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +0 -6
  192. package/fixtures/paper-research-question/draft/paper.tex +0 -2
  193. package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +0 -9
  194. package/fixtures/paper-research-question/markdown-no-rq/paper.md +0 -4
  195. package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +0 -12
  196. package/fixtures/paper-research-question/shipped-no-rq/paper.tex +0 -3
  197. package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +0 -10
  198. package/fixtures/paper-research-question/shipped-with-rq/paper.tex +0 -2
  199. package/skills/harden-paper/fixtures/uncited-assertions-sample.md +0 -43
  200. package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +0 -77
  201. package/src/action-ref.test.ts +0 -26
  202. package/src/action-ref.ts +0 -15
package/CLAUDE.md CHANGED
@@ -209,8 +209,8 @@ again on every build, so a checker fix reaches every paper ([`docs/references.md
209
209
  then does what only a command can, because it depends on the project it lands in: it finds the
210
210
  papers directory and declares it in a root `paperlint.json` when it is not the default `papers`, links each skill into `.claude/skills/`, writes
211
211
  the hook commands into `.claude/settings.json` (vigiles' `mergeRegistrations`, reading
212
- `plugin/hooks/hooks.json` as the one source), and offers a CI workflow pinned to the installed
213
- release's tag. `paperlint doctor` reads all of it back. Details: `docs/install.md`.
212
+ `plugin/hooks/hooks.json` as the one source), and offers a CI workflow that runs the action from
213
+ `node_modules` after `npm ci`. `paperlint doctor` reads all of it back. Details: `docs/install.md`.
214
214
 
215
215
  **There is no Claude Code plugin or marketplace entry; it was removed in the release after 1.0.0
216
216
  (#82).** Do not bring it back without answering these, each measured:
@@ -500,6 +500,30 @@ of the account-wide 3000/month shared with every other private repo, and the bud
500
500
  private again, this section and `.github/dependabot.yml` are revisited together** — the bot is
501
501
  justified two hundred lines above precisely by these minutes being free.
502
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
+
503
527
  ## Testing
504
528
 
505
529
  | you are testing | kind |
package/CONTRIBUTING.md CHANGED
@@ -84,6 +84,13 @@ linking there. `src/rule-docs.test.ts` holds every rule the config registers to
84
84
  older than the convention wait in its `AWAITING_PAGE` list, which only shrinks — #131 writes their
85
85
  pages.
86
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
+
87
94
  ## Maintainer docs
88
95
 
89
96
  The README links only what a user needs. These are for people changing the package:
package/README.md CHANGED
@@ -190,6 +190,9 @@ Every skill, by stage: [`docs/skills.md`](docs/skills.md).
190
190
  - Commit `repro/references-cache.json`: it keeps what the citation services answered, so a build
191
191
  asks only about new or edited entries (and answers older than 30 days), so an unchanged
192
192
  bibliography usually builds without the network ([`docs/references.md`](docs/references.md)).
193
+ - A paper whose body is in other files (`\input{sections/intro}`) is linted file by file: a finding
194
+ lands at the file and line it is in, and `--fix` edits that file. The files are the ones
195
+ `paper.tex` includes from its body, found where the build finds them — not every `.tex` beside it.
193
196
  - Lint also checks the pipeline's own records: `PIPELINE-STATUS.md`, reviews, notes on related
194
197
  papers ([`docs/rules.md`](docs/rules.md)).
195
198
  - The rules run on ESLint, so a deliberate exception is a comment on the line above:
@@ -274,14 +277,22 @@ jobs:
274
277
  - run: npx paperlint lint
275
278
  ```
276
279
 
277
- Lint only — each paper then gets one warning that it was not built:
280
+ Lint only — each paper then gets one warning that it was not built. Call the action from the
281
+ installed package, after `npm ci`: the action and the CLI it runs are then one revision, pinned by
282
+ your lockfile.
278
283
 
279
284
  ```yaml
280
- - uses: zernie/paperlint@v3.0.0
281
- with:
282
- paths: papers # your papersDir
285
+ steps:
286
+ - uses: actions/checkout@v4
287
+ - uses: actions/setup-node@v4
288
+ with:
289
+ node-version: 22
290
+ - run: npm ci
291
+ - uses: ./node_modules/paperlint
283
292
  ```
284
293
 
294
+ With no `paths` it lints the `papersDir` of your `paperlint.json`; pass `paths:` only to lint less.
295
+
285
296
  ## ❓ FAQ
286
297
 
287
298
  **Does it install anything without asking?**
@@ -33,8 +33,8 @@ assert.equal(action.runs.using, "composite", "the action must be composite");
33
33
  const names = action.runs.steps.map((s) => s.name);
34
34
  assert.equal(
35
35
  action.runs.steps.length,
36
- 4,
37
- `expected 4 steps, got ${action.runs.steps.length}: ${names}`,
36
+ 3,
37
+ `expected 3 steps, got ${action.runs.steps.length}: ${names}`,
38
38
  );
39
39
 
40
40
  const eslintStep = action.runs.steps.find((s) => s.name === "paperlint lint");
@@ -92,7 +92,7 @@ assert.match(
92
92
  //
93
93
  // So: execute the REAL `run:` block under the REAL flags, with a stub standing in for `npx`, and
94
94
  // require that the guard was reached and that ESLint's code came through it.
95
- const stubbedStepRun = (stubRc) => {
95
+ const stubbedStepRun = (stubRc, paths = ".") => {
96
96
  const bin = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-bin-")));
97
97
  writeFileSync(
98
98
  join(bin, "npx"),
@@ -100,6 +100,7 @@ const stubbedStepRun = (stubRc) => {
100
100
  // `-o`, it REDIRECTS output. So the stub also checks the redirect itself — if it disappears
101
101
  // from the step, the file stays empty and the guard says "nothing was measured".
102
102
  `#!/usr/bin/env bash\n` +
103
+ `printf '%s\\n' "$@" > "$RUNNER_TEMP/npx-args"\n` +
103
104
  `printf '%s' "$REPORT_JSON"\n` +
104
105
  `exit "$STUB_RC"\n`,
105
106
  { mode: 0o755 },
@@ -115,8 +116,8 @@ const stubbedStepRun = (stubRc) => {
115
116
  PATH: `${bin}:${process.env.PATH}`,
116
117
  RUNNER_TEMP: TMP,
117
118
  GITHUB_ACTION_PATH: HERE,
118
- PAPERLINT_CONFIG: "eslint.config.mjs",
119
- PAPERLINT_PATHS: ".",
119
+ PAPERLINT_CONFIG: "",
120
+ PAPERLINT_PATHS: paths,
120
121
  PAPERLINT_MAXWARN: "-1",
121
122
  STUB_RC: String(stubRc),
122
123
  REPORT_JSON: JSON.stringify([{ filePath: "/x/a.md", messages: [] }]),
@@ -169,41 +170,46 @@ for (const key of [
169
170
  ])
170
171
  assert.ok(action.inputs[key], `input \`${key}\` is missing`);
171
172
 
172
- // ── `paths` is REQUIRED, and the requirement is ENFORCED, not merely declared ─────────────────
173
- // 🔴 GitHub does not enforce `required: true` for COMPOSITE actions. A caller who omits the
174
- // input reaches the first step with an empty string and no error at all. So two separate things
175
- // are asserted here, and neither implies the other: that the contract is DECLARED, and that a
176
- // step exists which can actually fail on it.
173
+ // ── `paths` is OPTIONAL: with none, the CLI lints the papersDir its paperlint.json declares ──────
174
+ // `paperlint lint` with no path finds the root paperlint.json and lints its `papersDir`, so that is
175
+ // the normal call and the action must not refuse it. What is asserted is the behaviour: the real
176
+ // `run:` block, handed an empty `paths`, calls the CLI with no path argument at all, and the guard
177
+ // still runs over what the CLI reported.
177
178
  assert.equal(
178
179
  action.inputs.paths.required,
179
- true,
180
- "`paths` must be declared required",
180
+ false,
181
+ "`paths` must be optional — the CLI finds papersDir itself",
181
182
  );
182
- assert.ok(
183
- !("default" in action.inputs.paths),
184
- "`paths` must have NO default — the old default `.` linted the whole checkout, so a caller " +
185
- "who never chose a scope still got a green job over one",
186
- );
187
- const pathsGuard = action.runs.steps.find((s) =>
188
- /paths was actually given/i.test(s.name ?? ""),
189
- );
190
- assert.ok(
191
- pathsGuard,
192
- `no step enforcing the \`paths\` contract among: ${names}`,
193
- );
194
- assert.match(
195
- pathsGuard.run,
196
- /exit 1/,
197
- "the `paths` guard must FAIL, not warn — a declaration nobody checks is documentation",
198
- );
199
- // And it must come FIRST: checking scope after texlive setup would burn apt minutes
200
- // for a call already wrong.
201
183
  assert.equal(
202
- action.runs.steps[0].name,
203
- pathsGuard.name,
204
- "the `paths` guard must run FIRST — checking scope after an apt install burns minutes on a " +
205
- "call that was already wrong",
184
+ action.inputs.paths.default,
185
+ "",
186
+ "`paths` must default to empty, which means: the papersDir of paperlint.json",
206
187
  );
188
+ {
189
+ const r = stubbedStepRun(0, "");
190
+ assert.equal(r.status, 0, `an empty \`paths\` must run clean: ${r.stderr}`);
191
+ assert.deepEqual(
192
+ readFileSync(join(TMP, "npx-args"), "utf8").trim().split("\n"),
193
+ ["paperlint", "lint", "--max-warnings=-1", "--json"],
194
+ "with no `paths` the CLI must get no path argument — not an empty string, not `.`",
195
+ );
196
+ assert.match(r.stdout, /linted 1 file\(s\)/, "the guard must still run");
197
+ }
198
+ {
199
+ stubbedStepRun(0, "papers/a papers/b");
200
+ assert.deepEqual(
201
+ readFileSync(join(TMP, "npx-args"), "utf8").trim().split("\n"),
202
+ [
203
+ "paperlint",
204
+ "lint",
205
+ "papers/a",
206
+ "papers/b",
207
+ "--max-warnings=-1",
208
+ "--json",
209
+ ],
210
+ "named paths are handed to the CLI one argument each",
211
+ );
212
+ }
207
213
 
208
214
  // ── II. BEHAVIOUR of the guard — both halves, on fixtures ─────────────────────────────────────
209
215
  const fixture = (name, body) => {
@@ -225,6 +231,18 @@ const fixture = (name, body) => {
225
231
  );
226
232
  assert.match(lines.join("\n"), /linted ZERO files/);
227
233
  }
234
+ // With no paths given, the guard names where the scope came from instead of printing `paths=`.
235
+ {
236
+ const { lines } = guard(fixture("empty2.json", "[]"), 0, {
237
+ paths: "",
238
+ config: "paperlint.json",
239
+ });
240
+ assert.match(
241
+ lines.join("\n"),
242
+ /paths=\(none: the papersDir of paperlint\.json\)/,
243
+ "an empty paths value must be named as the papersDir, not printed blank",
244
+ );
245
+ }
228
246
  // FIRES: the report is absent or unparsable — nothing was measured either way.
229
247
  assert.equal(
230
248
  guard(join(TMP, "absent.json"), 0).code,
@@ -283,5 +301,5 @@ assert.equal(
283
301
  }
284
302
 
285
303
  console.log(
286
- "✓ action.yml shape (parsed, not grepped) + guard behaviour, both halves — 20 assertions",
304
+ "✓ action.yml shape (parsed, not grepped) + guard behaviour, both halves",
287
305
  );
package/action.yml CHANGED
@@ -7,11 +7,12 @@
7
7
  # which is the one place it has to hold. The same bypass also meant the consumer's settings were never read and
8
8
  # the caller still had to keep an ESLint flat config — the exact thing the CLI exists to remove.
9
9
  #
10
- # WHY AN ACTION AND NOT A DOCUMENTED `run:` BLOCK. Two steps have to happen in this order and
11
- # neither is guessable from the outside: `texcount` must exist on PATH before linting starts, and
12
- # the scope has to be checked before any install spends minutes on a call that was already wrong.
13
- # A consumer who copies only the last step gets a green job over rules that silently degraded —
14
- # see the `texcount` note below.
10
+ # WHY AN ACTION AND NOT A DOCUMENTED `run:` BLOCK. `texcount` must exist on PATH before linting
11
+ # starts, and that is not guessable from the outside. A consumer who copies only the last step gets
12
+ # a green job over rules that silently degraded — see the `texcount` note below.
13
+ #
14
+ # CALL IT AS `uses: ./node_modules/paperlint`, after `npm ci`: the action and the CLI it runs are
15
+ # then one revision, pinned by the consumer's lockfile.
15
16
  #
16
17
  # 🔴 THE GREEN-ZERO GUARD IS PART OF THIS ACTION, NOT AN OPTIONAL EXTRA. `eslint` prints findings
17
18
  # and exits 0 when there are none — and "no findings" is byte-identical to "not one rule was handed
@@ -33,11 +34,12 @@ inputs:
33
34
  default: ""
34
35
  paths:
35
36
  description: >-
36
- What to lint, as ESLint would take it on the command line. REQUIRED, and deliberately
37
- without a default: the previous default was "." — the whole checkout — so a caller who
38
- never thought about scope still got a green job, over whatever happened to be in the
39
- repository. Scope is the one thing this action cannot guess.
40
- required: true
37
+ What to lint, as `paperlint lint` takes it on the command line. Optional: empty — the
38
+ default — runs `paperlint lint` with no path, and the CLI lints the `papersDir` its root
39
+ `paperlint.json` declares (default `papers`), and fails when that directory holds no paper.
40
+ Name paths only to lint less than that.
41
+ required: false
42
+ default: ""
41
43
  max-warnings:
42
44
  description: >-
43
45
  Passed through to ESLint. The default -1 means warnings never fail the job: in this rule set
@@ -62,23 +64,6 @@ inputs:
62
64
  runs:
63
65
  using: composite
64
66
  steps:
65
- # 🔴 `required: true` ABOVE IS A DECLARATION, NOT AN ENFORCEMENT. GitHub does not enforce
66
- # required inputs for COMPOSITE actions — a caller who omits `paths` reaches this step with
67
- # an empty string and no error of any kind. So the contract is checked here, where it can
68
- # actually fail, rather than trusted where it is merely written down. Same reasoning as the
69
- # green-zero guard below: the declaration and the check are different things.
70
- - name: paths was actually given
71
- shell: bash
72
- env:
73
- PAPERLINT_PATHS: ${{ inputs.paths }}
74
- run: |
75
- if [ -z "${PAPERLINT_PATHS// /}" ]; then
76
- echo "::error::input \`paths\` is required and was empty. Name what to lint, e.g." \
77
- "\`paths: papers\`. There is no default on purpose: linting the whole checkout" \
78
- "by accident produces a green job over a scope nobody chose."
79
- exit 1
80
- fi
81
-
82
67
  # texlive-extra-utils is what carries /usr/bin/texcount. Cached, because the apt fetch is the
83
68
  # only network hop in this action and a slow mirror once cost a caller 14 minutes.
84
69
  - name: texcount (for a CONSUMER's texcount/* rules — this package ships none)
@@ -2,9 +2,10 @@
2
2
  import { type ClassLine, type DocumentClass } from "../../domain/tex-document.ts";
3
3
  import { type ParsedTex } from "./parse.ts";
4
4
  /**
5
- * The first `\documentclass` at the top of the tree: missing; empty (`\documentclass{}`); or the
6
- * class with each of its options as written (`foo={a,b}` stays one option, braces kept), whitespace
7
- * collapsed. Its place spans the macro and its arguments.
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.
8
9
  */
9
10
  export declare function documentClassOf(t: ParsedTex): ClassLine;
10
11
  /**
@@ -15,8 +16,8 @@ export declare function parseTemplate(text: string): DocumentClass | null;
15
16
  /**
16
17
  * The source with its `\documentclass` line changed to satisfy `want` (see `venueClass`): another
17
18
  * class is replaced by the template's line; the same class gains the options it lacks in place. A
18
- * source whose class already satisfies `want`, or that has no class line the parser could place, is
19
- * returned as it is.
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.
20
21
  */
21
22
  export declare function replaceDocumentClass(t: ParsedTex, want: DocumentClass): string;
22
23
  //# sourceMappingURL=document-class.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"document-class.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/document-class.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAUtC,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAoDxD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,SAAS,CAGvD;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,CAoBR"}
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"}
@@ -24,7 +24,8 @@ function sourceOf(src, nodes) {
24
24
  const optionText = (src, part) => collapse(splitWhere(part, (n) => n.type === "comment")
25
25
  .map((run) => sourceOf(src, run))
26
26
  .join(""));
27
- const classNode = (t) => t.root.content.find(isDocumentClass);
27
+ /** Every `\documentclass` at the top of the tree, in source order. */
28
+ const classNodes = (t) => t.root.content.filter(isDocumentClass);
28
29
  /** A `\documentclass` node as a class line: empty, or the class with its options as read. */
29
30
  function classLine(src, node) {
30
31
  const place = macroPlace(node);
@@ -37,13 +38,19 @@ function classLine(src, node) {
37
38
  return { kind: "class", cls, options, place };
38
39
  }
39
40
  /**
40
- * The first `\documentclass` at the top of the tree: missing; empty (`\documentclass{}`); or the
41
- * class with each of its options as written (`foo={a,b}` stays one option, braces kept), whitespace
42
- * collapsed. Its place spans the macro and its arguments.
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.
43
45
  */
44
46
  export function documentClassOf(t) {
45
- const node = classNode(t);
46
- return node === undefined ? { kind: "missing" } : classLine(t.src, node);
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] };
47
54
  }
48
55
  /**
49
56
  * A preset's `template` → the class it names: a whole `\documentclass[…]{…}` line, or a bare class
@@ -78,12 +85,13 @@ function addOptions(src, node, line, options) {
78
85
  /**
79
86
  * The source with its `\documentclass` line changed to satisfy `want` (see `venueClass`): another
80
87
  * class is replaced by the template's line; the same class gains the options it lacks in place. A
81
- * source whose class already satisfies `want`, or that has no class line the parser could place, is
82
- * returned as it is.
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.
83
90
  */
84
91
  export function replaceDocumentClass(t, want) {
85
- const node = classNode(t);
86
- if (node === undefined)
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)
87
95
  return t.src;
88
96
  const line = classLine(t.src, node);
89
97
  if (line.kind !== "class" || line.place.kind === "unplaced")
@@ -1 +1 @@
1
- {"version":3,"file":"document-class.js","sourceRoot":"","sources":["../../../src/adapters/latex/document-class.ts"],"names":[],"mappings":"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"}
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
+ 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
+ /**
11
+ * The span of the `document` environment's body, or null when the source has none. A macro's own
12
+ * position ends at its name, so its place is taken with its arguments: a body ending in
13
+ * `\input{sections/a}` ends at the closing brace.
14
+ */
15
+ export declare const documentBodyOf: (t: ParsedTex) => Span | null;
16
+ //# 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;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,GAAG,SAAS,KAAG,IAAI,GAAG,IAWhD,CAAC"}
@@ -0,0 +1,48 @@
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
+ /**
39
+ * The span of the `document` environment's body, or null when the source has none. A macro's own
40
+ * position ends at its name, so its place is taken with its arguments: a body ending in
41
+ * `\input{sections/a}` ends at the closing brace.
42
+ */
43
+ export const documentBodyOf = (t) => (t.root.content.find(isDocument)?.content ?? [])
44
+ .flatMap((n) => inPlace(n.type === "macro" ? macroPlace(n) : placeOf(n), (span) => [
45
+ span,
46
+ ]))
47
+ .reduce((body, span) => body === null ? span : { start: body.start, end: span.end }, null);
48
+ //# sourceMappingURL=includes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"includes.js","sourceRoot":"","sources":["../../../src/adapters/latex/includes.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,OAAO,EACP,MAAM,EACN,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,GAGR,MAAM,YAAY,CAAC;AAGpB,MAAM,cAAc,GAA0C,IAAI,GAAG,CAAC;IACpE,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,SAAS,EAAE,SAAS,CAAC;CACvB,CAAC,CAAC;AAEH;;;;GAIG;AACH,SAAS,YAAY,CAAC,GAAW,EAAE,CAAQ;IACzC,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;IAClE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACtE,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,2FAA2F;AAC3F,SAAS,SAAS,CAAC,GAAW,EAAE,CAAQ;IACtC,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpC,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC9E,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAY,EAAsB,EAAE,CAC7D,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,YAAY,CAAC;KAC1B,MAAM,CAAC,MAAM,CAAC;KACd,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAErE,MAAM,UAAU,GAAG,CAAC,CAAO,EAAkC,EAAE,CAC7D,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,GAAG,KAAK,UAAU,CAAC;AAEnD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAY,EAAe,EAAE,CAC1D,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;KAC7C,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACb,OAAO,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;IACjE,IAAI;CACL,CAAC,CACH;KACA,MAAM,CACL,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CACb,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAC7D,IAAI,CACL,CAAC"}
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Reads a paper.tex and answers three questions — checks nothing itself: `documentClassOf` (which
2
+ * Reads a paper.tex and answers four questions — checks nothing itself: `documentClassOf` (which
3
3
  * class, with which options, and where), `outlineOf` (the sections in order, where the back matter
4
- * starts, where the document ends) and `renderedRuns` (the text a reader sees, each character with
5
- * its source offset). Used by `tex/template`, `tex/required-section`, `tex/venue-leftover` and
6
- * `paperlint new`, through the `LatexReader` port (`src/ports/latex.ts`); `src/cli.ts` wires it.
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.
7
9
  *
8
10
  * ── WHY THE PARSE TREE AND NOT THE `tex/latex` PROJECTION ────────────────────────
9
11
  * The ESLint language's projection (`eslint-rules/latex-language.ts`) blanks the preamble,
@@ -20,9 +22,11 @@
20
22
  */
21
23
  import type { LatexReader } from "../../ports/latex.ts";
22
24
  export { documentClassOf, parseTemplate, replaceDocumentClass, } from "./document-class.ts";
23
- export { collapse, macroPlace, mandatory, optional, placeOf, textOf, } from "./nodes.ts";
25
+ export { collapse, inPlace, macroPlace, mandatory, optional, placeOf, textOf, } from "./nodes.ts";
26
+ export { documentBodyOf, includesOf } from "./includes.ts";
24
27
  export { outlineOf } from "./outline.ts";
25
28
  export { parseLatex, type ParsedTex } from "./parse.ts";
29
+ export { bodyProse } from "./prose.ts";
26
30
  export { renderedRuns } from "./rendered.ts";
27
31
  /** The `LatexReader` port over unified-latex: each call parses its source once. */
28
32
  export declare const latexReader: LatexReader;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAUxD,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,UAAU,EACV,SAAS,EACT,QAAQ,EACR,OAAO,EACP,MAAM,GACP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,mFAAmF;AACnF,eAAO,MAAM,WAAW,EAAE,WAMzB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAYxD,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,OAAO,EACP,UAAU,EACV,SAAS,EACT,QAAQ,EACR,OAAO,EACP,MAAM,GACP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,mFAAmF;AACnF,eAAO,MAAM,WAAW,EAAE,WASzB,CAAC"}
@@ -1,17 +1,24 @@
1
1
  import { documentClassOf, parseTemplate, replaceDocumentClass, } from "./document-class.js";
2
+ import { documentBodyOf, includesOf } from "./includes.js";
2
3
  import { outlineOf } from "./outline.js";
3
4
  import { parseLatex } from "./parse.js";
5
+ import { bodyProse } from "./prose.js";
4
6
  import { renderedRuns } from "./rendered.js";
5
7
  export { documentClassOf, parseTemplate, replaceDocumentClass, } from "./document-class.js";
6
- export { collapse, macroPlace, mandatory, optional, placeOf, textOf, } from "./nodes.js";
8
+ export { collapse, inPlace, macroPlace, mandatory, optional, placeOf, textOf, } from "./nodes.js";
9
+ export { documentBodyOf, includesOf } from "./includes.js";
7
10
  export { outlineOf } from "./outline.js";
8
11
  export { parseLatex } from "./parse.js";
12
+ export { bodyProse } from "./prose.js";
9
13
  export { renderedRuns } from "./rendered.js";
10
14
  /** The `LatexReader` port over unified-latex: each call parses its source once. */
11
15
  export const latexReader = {
12
16
  documentClass: (src) => documentClassOf(parseLatex(src)),
13
17
  outline: (src) => outlineOf(parseLatex(src)),
14
18
  renderedRuns: (src) => renderedRuns(parseLatex(src)),
19
+ bodyProse: (src) => bodyProse(parseLatex(src)),
20
+ includes: (src) => includesOf(parseLatex(src)),
21
+ documentBody: (src) => documentBodyOf(parseLatex(src)),
15
22
  template: parseTemplate,
16
23
  withDocumentClass: (src, want) => replaceDocumentClass(parseLatex(src), want),
17
24
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/latex/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,UAAU,EACV,SAAS,EACT,QAAQ,EACR,OAAO,EACP,MAAM,GACP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5C,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACpD,QAAQ,EAAE,aAAa;IACvB,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;CAC9E,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/latex/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EACL,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,OAAO,EACP,UAAU,EACV,SAAS,EACT,QAAQ,EACR,OAAO,EACP,MAAM,GACP,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5C,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACpD,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC9C,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC9C,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACtD,QAAQ,EAAE,aAAa;IACvB,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;CAC9E,CAAC"}
@@ -1,6 +1,6 @@
1
1
  /** What the three readers share about unified-latex nodes: places, text, and the walk. */
2
2
  import type * as Ast from "@unified-latex/unified-latex-types";
3
- import type { Place } from "../../domain/tex-document.ts";
3
+ import type { Place, Span } from "../../domain/tex-document.ts";
4
4
  export { collapse } from "../../domain/tex-document.ts";
5
5
  /** unified-latex's node types, read-only: nothing here writes to a tree. */
6
6
  export type Node = Readonly<Ast.Node>;
@@ -19,6 +19,10 @@ interface Positioned {
19
19
  }
20
20
  /** Where a node stands, or that it has no position. */
21
21
  export declare const placeOf: (n: Positioned) => Place;
22
+ /** What `f` makes of a place's span, or nothing for a node the parser gave no position. */
23
+ export declare const inPlace: <T>(p: Place, f: (span: Span) => readonly T[]) => readonly T[];
24
+ /** A macro's arguments, in order: none when the parser attached none. */
25
+ export declare const argumentsOf: (m: Macro) => readonly Argument[];
22
26
  /** The first mandatory (`{…}`) and optional (`[…]`) arguments' contents, and the last mandatory one's. */
23
27
  export declare const mandatory: (m: Macro) => readonly Argument[];
24
28
  export declare const optional: (m: Macro) => Argument | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/nodes.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,KAAK,KAAK,GAAG,MAAM,oCAAoC,CAAC;AAE/D,OAAO,KAAK,EAAE,KAAK,EAAQ,MAAM,8BAA8B,CAAC;AAGhE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAExD,4EAA4E;AAC5E,MAAM,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACtC,MAAM,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACxC,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAE9C,uGAAuG;AACvG,UAAU,UAAU;IAClB,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5C,QAAQ,CAAC,GAAG,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC3C,CAAC;CACH;AAED,uDAAuD;AACvD,eAAO,MAAM,OAAO,GAAI,GAAG,UAAU,KAAG,KAMjC,CAAC;AAER,0GAA0G;AAC1G,eAAO,MAAM,SAAS,GAAI,GAAG,KAAK,KAAG,SAAS,QAAQ,EACJ,CAAC;AACnD,eAAO,MAAM,QAAQ,GAAI,GAAG,KAAK,KAAG,QAAQ,GAAG,SACC,CAAC;AAEjD,iGAAiG;AACjG,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,MACZ,CAAC;AA+BlC;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,SAAS,IAAI,EAAE,GAAG,SAAS,KAAG,MAU/C,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAS1C;AAED,mGAAmG;AACnG,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,OAAO,CAErB,IAAI,EAAE,GAAG,CAAC,GAAG,EACb,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,KAAK,OAAO,GACzD,SAAS,OAAO,EAAE,CAapB;AAED,yFAAyF;AACzF,eAAO,MAAM,MAAM,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC,EACjD,CAAC;AAEnB,+CAA+C;AAC/C,eAAO,MAAM,UAAU,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,QACR,CAAC;AAEtC,mFAAmF;AACnF,eAAO,MAAM,MAAM,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,IACiB,CAAC"}
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../../../src/adapters/latex/nodes.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,KAAK,KAAK,GAAG,MAAM,oCAAoC,CAAC;AAE/D,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAGhE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAExD,4EAA4E;AAC5E,MAAM,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACtC,MAAM,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACxC,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAE9C,uGAAuG;AACvG,UAAU,UAAU;IAClB,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5C,QAAQ,CAAC,GAAG,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC3C,CAAC;CACH;AAED,uDAAuD;AACvD,eAAO,MAAM,OAAO,GAAI,GAAG,UAAU,KAAG,KAMjC,CAAC;AAER,2FAA2F;AAC3F,eAAO,MAAM,OAAO,GAAI,CAAC,EACvB,GAAG,KAAK,EACR,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,EAAE,KAC9B,SAAS,CAAC,EAAwC,CAAC;AAEtD,yEAAyE;AACzE,eAAO,MAAM,WAAW,GAAI,GAAG,KAAK,KAAG,SAAS,QAAQ,EAAkB,CAAC;AAE3E,0GAA0G;AAC1G,eAAO,MAAM,SAAS,GAAI,GAAG,KAAK,KAAG,SAAS,QAAQ,EACJ,CAAC;AACnD,eAAO,MAAM,QAAQ,GAAI,GAAG,KAAK,KAAG,QAAQ,GAAG,SACC,CAAC;AAEjD,iGAAiG;AACjG,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,MACZ,CAAC;AA+BlC;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,SAAS,IAAI,EAAE,GAAG,SAAS,KAAG,MAU/C,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAS1C;AAED,mGAAmG;AACnG,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC;AAEvE;;;;GAIG;AACH,wBAAgB,OAAO,CAErB,IAAI,EAAE,GAAG,CAAC,GAAG,EACb,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,KAAK,OAAO,GACzD,SAAS,OAAO,EAAE,CAapB;AAED,yFAAyF;AACzF,eAAO,MAAM,MAAM,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC,EACjD,CAAC;AAEnB,+CAA+C;AAC/C,eAAO,MAAM,UAAU,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,QACR,CAAC;AAEtC,mFAAmF;AACnF,eAAO,MAAM,MAAM,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,IACiB,CAAC"}
@@ -8,9 +8,13 @@ export const placeOf = (n) => n.position === undefined
8
8
  kind: "at",
9
9
  span: { start: n.position.start.offset, end: n.position.end.offset },
10
10
  };
11
+ /** What `f` makes of a place's span, or nothing for a node the parser gave no position. */
12
+ export const inPlace = (p, f) => (p.kind === "at" ? f(p.span) : []);
13
+ /** A macro's arguments, in order: none when the parser attached none. */
14
+ export const argumentsOf = (m) => m.args ?? [];
11
15
  /** The first mandatory (`{…}`) and optional (`[…]`) arguments' contents, and the last mandatory one's. */
12
- export const mandatory = (m) => (m.args ?? []).filter((a) => a.openMark === "{");
13
- export const optional = (m) => (m.args ?? []).find((a) => a.openMark === "[");
16
+ export const mandatory = (m) => argumentsOf(m).filter((a) => a.openMark === "{");
17
+ export const optional = (m) => argumentsOf(m).find((a) => a.openMark === "[");
14
18
  /** What a string node reads as: itself, except a tie (`~`), which is a space the reader sees. */
15
19
  export const stringText = (content) => content === "~" ? " " : content;
16
20
  const LAST = { kind: "argument", at: -1 };