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
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type * as Ast from "@unified-latex/unified-latex-types";
|
|
8
8
|
import { describe, expect, it } from "vitest";
|
|
9
|
-
import { runText, spanIn } from "../../domain/tex-document.ts";
|
|
9
|
+
import { runText, spanIn, type ProsePiece } from "../../domain/tex-document.ts";
|
|
10
10
|
import {
|
|
11
|
+
bodyProse,
|
|
12
|
+
documentBodyOf,
|
|
11
13
|
documentClassOf,
|
|
14
|
+
includesOf,
|
|
15
|
+
inPlace,
|
|
12
16
|
latexReader,
|
|
13
17
|
macroPlace,
|
|
14
18
|
mandatory,
|
|
@@ -45,6 +49,16 @@ describe("documentClassOf — the class line's three states", () => {
|
|
|
45
49
|
});
|
|
46
50
|
});
|
|
47
51
|
|
|
52
|
+
it("ambiguous: two \\documentclass lines behind a switch, each read as a candidate in source order", () => {
|
|
53
|
+
const src =
|
|
54
|
+
"\\if\\x1\\documentclass{article}\\fi\\if\\x2\\documentclass[a]{b}\\fi";
|
|
55
|
+
const line = documentClassOf(parseLatex(src));
|
|
56
|
+
expect(
|
|
57
|
+
line.kind === "ambiguous" &&
|
|
58
|
+
line.candidates.map((c) => c.kind === "class" && c.cls),
|
|
59
|
+
).toEqual(["article", "b"]);
|
|
60
|
+
});
|
|
61
|
+
|
|
48
62
|
it("missing: no \\documentclass at the top of the tree", () => {
|
|
49
63
|
expect(
|
|
50
64
|
documentClassOf(parseLatex("\\begin{document}\\end{document}")),
|
|
@@ -158,6 +172,12 @@ describe("replaceDocumentClass — the class `paperlint new` writes", () => {
|
|
|
158
172
|
);
|
|
159
173
|
});
|
|
160
174
|
|
|
175
|
+
it("🔴 a source with several \\documentclass lines is returned as it is: which branch builds is not known", () => {
|
|
176
|
+
const src =
|
|
177
|
+
"\\if\\x1\\documentclass{article}\\fi\\if\\x2\\documentclass{IEEEtran}\\fi";
|
|
178
|
+
expect(replaced(src)).toBe(src);
|
|
179
|
+
});
|
|
180
|
+
|
|
161
181
|
it("no class, or an empty one, is returned as it is", () => {
|
|
162
182
|
expect(replaced("just text")).toBe("just text");
|
|
163
183
|
expect(replaced("\\documentclass{}\nx")).toBe("\\documentclass{}\nx");
|
|
@@ -359,3 +379,165 @@ describe("renderedRuns", () => {
|
|
|
359
379
|
).toBe(false);
|
|
360
380
|
});
|
|
361
381
|
});
|
|
382
|
+
|
|
383
|
+
describe("inPlace — a node the parser gave no position yields nothing, never offset 0", () => {
|
|
384
|
+
it("a span is handed to f; an unplaced node gives the empty list", () => {
|
|
385
|
+
const f = (s: { start: number; end: number }) => [s.start];
|
|
386
|
+
expect(inPlace({ kind: "at", span: { start: 3, end: 5 } }, f)).toEqual([3]);
|
|
387
|
+
expect(inPlace({ kind: "unplaced" }, f)).toEqual([]);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
/** A prose piece, readable in an expectation: text as itself, a mark or math in angle brackets. */
|
|
392
|
+
const pieceShape = (x: ProsePiece): string =>
|
|
393
|
+
x.kind === "text"
|
|
394
|
+
? x.segment.text
|
|
395
|
+
: x.kind === "owner"
|
|
396
|
+
? `<${x.owner}@${String(x.span.start)}>`
|
|
397
|
+
: `<math ${x.tex}>`;
|
|
398
|
+
|
|
399
|
+
describe("bodyProse — passages a sentence cannot cross, with the marks that say where a claim comes from", () => {
|
|
400
|
+
it("text, a citation mark, a reference mark and math, in source order; a footnote is its own passage", () => {
|
|
401
|
+
const src =
|
|
402
|
+
"\\begin{document}\nA~\\cite{k} B \\ref{s} $x$.\\footnote{F.}\n\n\\section{H}C\\%\n\\end{document}";
|
|
403
|
+
const at = (s: string) => src.indexOf(s);
|
|
404
|
+
const shape = bodyProse(parseLatex(src)).map((p) =>
|
|
405
|
+
p.pieces.map(pieceShape),
|
|
406
|
+
);
|
|
407
|
+
expect(shape).toEqual([
|
|
408
|
+
[
|
|
409
|
+
"A",
|
|
410
|
+
" ",
|
|
411
|
+
`<citation@${String(at("\\cite"))}>`,
|
|
412
|
+
" ",
|
|
413
|
+
"B",
|
|
414
|
+
" ",
|
|
415
|
+
`<reference@${String(at("\\ref"))}>`,
|
|
416
|
+
" ",
|
|
417
|
+
"<math $x$>",
|
|
418
|
+
".",
|
|
419
|
+
],
|
|
420
|
+
["F", "."],
|
|
421
|
+
["C", "%"],
|
|
422
|
+
]);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("a character macro's character stands at the macro's last source character", () => {
|
|
426
|
+
const src = "C\\%";
|
|
427
|
+
const [p] = bodyProse(parseLatex(src));
|
|
428
|
+
expect(p?.pieces.at(-1)).toEqual({
|
|
429
|
+
kind: "text",
|
|
430
|
+
segment: { text: "%", at: 2 },
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe("bodyProse — run-in headings and list items", () => {
|
|
436
|
+
/** The text of each passage of `src`'s body, marks and math left out. */
|
|
437
|
+
const texts = (src: string): readonly string[] =>
|
|
438
|
+
bodyProse(parseLatex(src)).map((p) =>
|
|
439
|
+
p.pieces
|
|
440
|
+
.map((x) => (x.kind === "text" ? x.segment.text : ""))
|
|
441
|
+
.join("")
|
|
442
|
+
.trim(),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
it("🔴 a run-in heading — a bold, italic or emphasised phrase ending in `.` or `:` that opens a paragraph or an item — is a heading, left out", () => {
|
|
446
|
+
expect(
|
|
447
|
+
texts(
|
|
448
|
+
"\\begin{document}\n\\section{Method}\\label{sec:m}\n\\vspace{2pt}\\textbf{Correctness gate.} Each task carries a check.\n\n\\noindent \\textit{\\textbf{VPNs:}} GPT recommended them.\n\\begin{itemize}\n\\item \\emph{Scale.} It grows.\n\\end{itemize}\n\\end{document}",
|
|
449
|
+
),
|
|
450
|
+
).toEqual([
|
|
451
|
+
"Each task carries a check.",
|
|
452
|
+
"GPT recommended them.",
|
|
453
|
+
"It grows.",
|
|
454
|
+
]);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("🔴 a list item's text is prose, each item its own passage; its label is not — and an \\item outside a list ends a passage", () => {
|
|
458
|
+
expect(
|
|
459
|
+
texts(
|
|
460
|
+
"\\begin{document}\n\\begin{itemize}\n\\item[A:] It grows.\n\\item It holds.\n\\end{itemize}\nAfter \\item the list.\n\\end{document}",
|
|
461
|
+
),
|
|
462
|
+
).toEqual(["It grows.", "It holds.", "After", "the list."]);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("a run-in heading holding a footnote or a citation is still a heading, left out whole", () => {
|
|
466
|
+
expect(
|
|
467
|
+
texts(
|
|
468
|
+
"\\begin{document}\n\\textbf{Scale\\footnote{F.}.} It grows.\n\n\\textbf{See~\\cite{k}:} It holds.\n\\end{document}",
|
|
469
|
+
),
|
|
470
|
+
).toEqual(["It grows.", "It holds."]);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("the same markup inside a sentence, or without the closing mark, is prose", () => {
|
|
474
|
+
expect(
|
|
475
|
+
texts(
|
|
476
|
+
"\\begin{document}\nWe \\textbf{do not.} stop.\n\n\\textbf{Bold} opens this one.\n\\end{document}",
|
|
477
|
+
),
|
|
478
|
+
).toEqual(["We do not. stop.", "Bold opens this one."]);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe("includesOf — the files a source pulls in", () => {
|
|
483
|
+
it("each of \\input, \\include and \\subfile, with the path as written and the macro's span", () => {
|
|
484
|
+
const src = "a\\input{sections/1-intro}\n\\include{b.tex}\\subfile{c_d}";
|
|
485
|
+
expect(includesOf(parseLatex(src))).toEqual([
|
|
486
|
+
{
|
|
487
|
+
macro: "input",
|
|
488
|
+
target: "sections/1-intro",
|
|
489
|
+
span: { start: 1, end: 25 },
|
|
490
|
+
},
|
|
491
|
+
{ macro: "include", target: "b.tex", span: { start: 26, end: 41 } },
|
|
492
|
+
{ macro: "subfile", target: "c_d", span: { start: 41, end: 54 } },
|
|
493
|
+
]);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("\\input without braces (TeX's own form) names the file up to the next space", () => {
|
|
497
|
+
const targets = (src: string) =>
|
|
498
|
+
includesOf(parseLatex(src)).map((i) => i.target);
|
|
499
|
+
expect(targets("\\input sections/a \n")).toEqual(["sections/a"]);
|
|
500
|
+
// At the end of the source, the name runs to it.
|
|
501
|
+
expect(targets("\\input sections/a")).toEqual(["sections/a"]);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("an \\input with nothing after it names no file", () => {
|
|
505
|
+
expect(includesOf(parseLatex("\\input"))).toEqual([]);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("not in a comment, not in a macro definition's body, not an empty argument", () => {
|
|
509
|
+
const src = "% \\input{old}\n\\newcommand{\\x}{\\input{y}}\\input{}";
|
|
510
|
+
expect(includesOf(parseLatex(src))).toEqual([]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("the port answers the same over a source", () => {
|
|
514
|
+
expect(latexReader.includes("\\input{a}").map((i) => i.target)).toEqual([
|
|
515
|
+
"a",
|
|
516
|
+
]);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
describe("documentBodyOf — the body of a source that is a document of its own", () => {
|
|
521
|
+
it("from the first to the last node inside the document environment", () => {
|
|
522
|
+
const src =
|
|
523
|
+
"\\documentclass{x}\\begin{document}BODY and more\\end{document}";
|
|
524
|
+
const body = documentBodyOf(parseLatex(src));
|
|
525
|
+
expect(body && src.slice(body.start, body.end)).toBe("BODY and more");
|
|
526
|
+
expect(latexReader.documentBody(src)).toEqual(body);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("a body ending in a macro ends after that macro's arguments, not after its name", () => {
|
|
530
|
+
const src = "\\begin{document}\nText.\n\\input{sections/a}\\end{document}";
|
|
531
|
+
const body = documentBodyOf(parseLatex(src));
|
|
532
|
+
expect(body && src.slice(body.start, body.end)).toBe(
|
|
533
|
+
"Text.\n\\input{sections/a}",
|
|
534
|
+
);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("none without a document environment, or with an empty one", () => {
|
|
538
|
+
expect(documentBodyOf(parseLatex("just text"))).toBe(null);
|
|
539
|
+
expect(documentBodyOf(parseLatex("\\begin{document}\\end{document}"))).toBe(
|
|
540
|
+
null,
|
|
541
|
+
);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
@@ -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,
|
|
@@ -24,8 +26,10 @@ import {
|
|
|
24
26
|
parseTemplate,
|
|
25
27
|
replaceDocumentClass,
|
|
26
28
|
} from "./document-class.ts";
|
|
29
|
+
import { documentBodyOf, includesOf } from "./includes.ts";
|
|
27
30
|
import { outlineOf } from "./outline.ts";
|
|
28
31
|
import { parseLatex } from "./parse.ts";
|
|
32
|
+
import { bodyProse } from "./prose.ts";
|
|
29
33
|
import { renderedRuns } from "./rendered.ts";
|
|
30
34
|
|
|
31
35
|
export {
|
|
@@ -35,14 +39,17 @@ export {
|
|
|
35
39
|
} from "./document-class.ts";
|
|
36
40
|
export {
|
|
37
41
|
collapse,
|
|
42
|
+
inPlace,
|
|
38
43
|
macroPlace,
|
|
39
44
|
mandatory,
|
|
40
45
|
optional,
|
|
41
46
|
placeOf,
|
|
42
47
|
textOf,
|
|
43
48
|
} from "./nodes.ts";
|
|
49
|
+
export { documentBodyOf, includesOf } from "./includes.ts";
|
|
44
50
|
export { outlineOf } from "./outline.ts";
|
|
45
51
|
export { parseLatex, type ParsedTex } from "./parse.ts";
|
|
52
|
+
export { bodyProse } from "./prose.ts";
|
|
46
53
|
export { renderedRuns } from "./rendered.ts";
|
|
47
54
|
|
|
48
55
|
/** The `LatexReader` port over unified-latex: each call parses its source once. */
|
|
@@ -50,6 +57,9 @@ export const latexReader: LatexReader = {
|
|
|
50
57
|
documentClass: (src) => documentClassOf(parseLatex(src)),
|
|
51
58
|
outline: (src) => outlineOf(parseLatex(src)),
|
|
52
59
|
renderedRuns: (src) => renderedRuns(parseLatex(src)),
|
|
60
|
+
bodyProse: (src) => bodyProse(parseLatex(src)),
|
|
61
|
+
includes: (src) => includesOf(parseLatex(src)),
|
|
62
|
+
documentBody: (src) => documentBodyOf(parseLatex(src)),
|
|
53
63
|
template: parseTemplate,
|
|
54
64
|
withDocumentClass: (src, want) => replaceDocumentClass(parseLatex(src), want),
|
|
55
65
|
};
|
|
@@ -28,11 +28,20 @@ export const placeOf = (n: Positioned): Place =>
|
|
|
28
28
|
span: { start: n.position.start.offset, end: n.position.end.offset },
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/** What `f` makes of a place's span, or nothing for a node the parser gave no position. */
|
|
32
|
+
export const inPlace = <T>(
|
|
33
|
+
p: Place,
|
|
34
|
+
f: (span: Span) => readonly T[],
|
|
35
|
+
): readonly T[] => (p.kind === "at" ? f(p.span) : []);
|
|
36
|
+
|
|
37
|
+
/** A macro's arguments, in order: none when the parser attached none. */
|
|
38
|
+
export const argumentsOf = (m: Macro): readonly Argument[] => m.args ?? [];
|
|
39
|
+
|
|
31
40
|
/** The first mandatory (`{…}`) and optional (`[…]`) arguments' contents, and the last mandatory one's. */
|
|
32
41
|
export const mandatory = (m: Macro): readonly Argument[] =>
|
|
33
|
-
(m
|
|
42
|
+
argumentsOf(m).filter((a) => a.openMark === "{");
|
|
34
43
|
export const optional = (m: Macro): Argument | undefined =>
|
|
35
|
-
(m
|
|
44
|
+
argumentsOf(m).find((a) => a.openMark === "[");
|
|
36
45
|
|
|
37
46
|
/** What a string node reads as: itself, except a tie (`~`), which is a space the reader sees. */
|
|
38
47
|
export const stringText = (content: string): string =>
|