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.
- package/CLAUDE.md +26 -2
- package/CONTRIBUTING.md +7 -0
- package/README.md +15 -4
- package/action.harness.mjs +54 -36
- package/action.yml +12 -27
- package/dist/adapters/latex/document-class.d.ts +6 -5
- package/dist/adapters/latex/document-class.d.ts.map +1 -1
- package/dist/adapters/latex/document-class.js +18 -10
- package/dist/adapters/latex/document-class.js.map +1 -1
- package/dist/adapters/latex/includes.d.ts +16 -0
- package/dist/adapters/latex/includes.d.ts.map +1 -0
- package/dist/adapters/latex/includes.js +48 -0
- package/dist/adapters/latex/includes.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +9 -5
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +8 -1
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +5 -1
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +6 -2
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +1 -0
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/latex/prose.d.ts +8 -0
- package/dist/adapters/latex/prose.d.ts.map +1 -0
- package/dist/adapters/latex/prose.js +353 -0
- package/dist/adapters/latex/prose.js.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +7 -3
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +34 -0
- package/dist/claim-provenance.d.ts.map +1 -0
- package/dist/claim-provenance.js +146 -0
- package/dist/claim-provenance.js.map +1 -0
- package/dist/cli.d.ts +2 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +81 -31
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +58 -0
- package/dist/domain/paper-source.d.ts.map +1 -0
- package/dist/domain/paper-source.js +109 -0
- package/dist/domain/paper-source.js.map +1 -0
- package/dist/domain/sentences.d.ts +26 -0
- package/dist/domain/sentences.d.ts.map +1 -0
- package/dist/domain/sentences.js +48 -0
- package/dist/domain/sentences.js.map +1 -0
- package/dist/domain/tex-document.d.ts +39 -4
- package/dist/domain/tex-document.d.ts.map +1 -1
- package/dist/domain/tex-document.js.map +1 -1
- package/dist/eslint-rules/latex-language.d.ts +5 -1
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +6 -3
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts +20 -0
- package/dist/eslint-rules/paper-context.d.ts.map +1 -0
- package/dist/eslint-rules/paper-context.js +44 -0
- package/dist/eslint-rules/paper-context.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +8 -1
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +85 -19
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +7 -1
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +4 -19
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +24 -29
- package/dist/init.js.map +1 -1
- package/dist/package-dirs.d.ts +2 -0
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +8 -0
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-files.d.ts +2 -2
- package/dist/paper-includes.d.ts +17 -0
- package/dist/paper-includes.d.ts.map +1 -0
- package/dist/paper-includes.js +51 -0
- package/dist/paper-includes.js.map +1 -0
- package/dist/ports/latex.d.ts +12 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/register.d.ts +40 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +134 -0
- package/dist/register.js.map +1 -0
- package/dist/rules-config.d.ts +1 -0
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-paper.d.ts +33 -0
- package/dist/tex-paper.d.ts.map +1 -0
- package/dist/tex-paper.js +103 -0
- package/dist/tex-paper.js.map +1 -0
- package/dist/tex-venue-rules.d.ts +7 -2
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +31 -17
- package/dist/tex-venue-rules.js.map +1 -1
- package/docs/configuration.md +5 -4
- package/docs/install.md +8 -8
- package/docs/prior-art/paper-folder-scaffolding.md +4 -7
- package/docs/rules/tex/claim-provenance.md +83 -0
- package/docs/rules/tex/register.md +77 -0
- package/docs/rules/tex/required-section.md +3 -1
- package/docs/rules/tex/template.md +12 -0
- package/docs/rules/tex/venue-leftover.md +4 -2
- package/docs/rules.md +17 -10
- package/eslint-rules/latex-language.ts +10 -4
- package/eslint-rules/paper-context.test.ts +47 -0
- package/eslint-rules/paper-context.ts +57 -0
- package/eslint-rules/paper-typography.test.mjs +48 -1
- package/eslint-rules/paper-typography.ts +139 -24
- package/eslint-rules/tex-build.ts +8 -1
- package/eslint.config.mjs +1 -0
- package/fixtures/real-markdown-paper/baseline.json +1 -2
- package/fixtures/real-markdown-paper/baseline.ts +8 -7
- package/package.json +2 -1
- package/scripts/check.ts +2 -2
- package/scripts/eslint-report-guard.mjs +5 -2
- package/scripts/harness-api.frozen.json +0 -1
- package/skills/cold-read-diff/SKILL.md +31 -10
- package/skills/cold-read-diff/SKILL.md.spec.ts +30 -9
- package/skills/draft-paper/SKILL.md +2 -2
- package/skills/draft-paper/SKILL.md.spec.ts +1 -1
- package/skills/grade-paper-writing/SKILL.md +109 -44
- package/skills/grade-paper-writing/SKILL.md.spec.ts +108 -43
- package/skills/harden-paper/SKILL.md +35 -36
- package/skills/harden-paper/SKILL.md.spec.ts +34 -35
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +3 -3
- package/skills/paper-pipeline/SKILL.md.spec.ts +2 -2
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +6 -6
- package/skills/paper-pipeline/references/pipeline-status-template.md +2 -16
- package/skills/paper-pipeline/references/writing-craft.md +53 -18
- package/skills/pc-panel-review/SKILL.md +36 -11
- package/skills/pc-panel-review/SKILL.md.spec.ts +35 -10
- package/src/adapters/latex/document-class.ts +20 -12
- package/src/adapters/latex/includes.ts +74 -0
- package/src/adapters/latex/index.test.ts +183 -1
- package/src/adapters/latex/index.ts +14 -4
- package/src/adapters/latex/nodes.ts +11 -2
- package/src/adapters/latex/parse.ts +1 -0
- package/src/adapters/latex/prose.ts +484 -0
- package/src/build.harness.mjs +4 -2
- package/src/build.test.ts +18 -1
- package/src/build.ts +7 -3
- package/src/claim-provenance.test.ts +352 -0
- package/src/claim-provenance.ts +217 -0
- package/src/cli.harness.mjs +22 -54
- package/src/cli.test.ts +2 -18
- package/src/cli.ts +92 -36
- package/src/domain/paper-source.test.ts +156 -0
- package/src/domain/paper-source.ts +233 -0
- package/src/domain/sentences.ts +67 -0
- package/src/domain/tex-document.ts +36 -3
- package/src/init.test.ts +33 -13
- package/src/init.ts +25 -48
- package/src/linted-set.test.ts +11 -1
- package/src/new-paper.harness.mjs +1 -5
- package/src/package-dirs.ts +9 -0
- package/src/paper-files.ts +2 -2
- package/src/paper-includes.test.ts +161 -0
- package/src/paper-includes.ts +77 -0
- package/src/ports/latex.ts +13 -0
- package/src/register.test.ts +165 -0
- package/src/register.ts +177 -0
- package/src/rule-cases.test.ts +43 -23
- package/src/rule-docs.test.ts +0 -1
- package/src/rules-config.ts +1 -0
- package/src/structure.harness.ts +1 -3
- package/src/structure.ts +1 -1
- package/src/tex-paper.test.ts +148 -0
- package/src/tex-paper.ts +161 -0
- package/src/tex-venue-rules.test.ts +87 -5
- package/src/tex-venue-rules.ts +62 -30
- package/templates/paper/PIPELINE-STATUS.md +0 -4
- package/dist/action-ref.d.ts +0 -12
- package/dist/action-ref.d.ts.map +0 -1
- package/dist/action-ref.js +0 -16
- package/dist/action-ref.js.map +0 -1
- package/dist/eslint-rules/paper-research-question.d.ts +0 -31
- package/dist/eslint-rules/paper-research-question.d.ts.map +0 -1
- package/dist/eslint-rules/paper-research-question.js +0 -188
- package/dist/eslint-rules/paper-research-question.js.map +0 -1
- package/eslint-rules/paper-research-question.harness.mjs +0 -212
- package/eslint-rules/paper-research-question.ts +0 -199
- package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +0 -9
- package/fixtures/paper-research-question/comment-only/paper.tex +0 -7
- package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +0 -10
- package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +0 -6
- package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +0 -6
- package/fixtures/paper-research-question/draft/paper.tex +0 -2
- package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +0 -9
- package/fixtures/paper-research-question/markdown-no-rq/paper.md +0 -4
- package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +0 -12
- package/fixtures/paper-research-question/shipped-no-rq/paper.tex +0 -3
- package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +0 -10
- package/fixtures/paper-research-question/shipped-with-rq/paper.tex +0 -2
- package/skills/harden-paper/fixtures/uncited-assertions-sample.md +0 -43
- package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +0 -77
- package/src/action-ref.test.ts +0 -26
- 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
|
|
213
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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?**
|
package/action.harness.mjs
CHANGED
|
@@ -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
|
-
|
|
37
|
-
`expected
|
|
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: "
|
|
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
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
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
|
-
|
|
180
|
-
"`paths` must be
|
|
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.
|
|
203
|
-
|
|
204
|
-
"
|
|
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
|
|
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.
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
required:
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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`,
|
|
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,
|
|
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
|
-
|
|
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
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
|
46
|
-
|
|
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`,
|
|
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
|
-
|
|
86
|
-
|
|
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":"AAAA,0FAA0F;AAC1F,OAAO,EACL,iBAAiB,EACjB,UAAU,
|
|
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":"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"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reads a paper.tex and answers
|
|
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)
|
|
5
|
-
* its source offset)
|
|
6
|
-
*
|
|
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
|
|
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":"
|
|
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,
|
|
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
|
|
13
|
-
export const optional = (m) => (m
|
|
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 };
|