paperlint 4.2.0 → 6.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 +15 -2
- package/CONTRIBUTING.md +3 -0
- package/README.md +23 -12
- package/action.harness.mjs +54 -36
- package/action.yml +12 -27
- package/bin/paperlint.mjs +5 -1
- package/dist/adapters/latex/includes.d.ts +5 -1
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +8 -2
- package/dist/adapters/latex/includes.js.map +1 -1
- package/dist/adapters/latex/index.d.ts +1 -0
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +3 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/layout.d.ts +27 -0
- package/dist/adapters/latex/layout.d.ts.map +1 -0
- package/dist/adapters/latex/layout.js +62 -0
- package/dist/adapters/latex/layout.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +11 -1
- 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 +5 -1
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +7 -3
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +2 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +92 -42
- package/dist/cli.js.map +1 -1
- package/dist/domain/anonymity.d.ts +68 -0
- package/dist/domain/anonymity.d.ts.map +1 -0
- package/dist/domain/anonymity.js +94 -0
- package/dist/domain/anonymity.js.map +1 -0
- package/dist/domain/body-pages.d.ts +69 -0
- package/dist/domain/body-pages.d.ts.map +1 -0
- package/dist/domain/body-pages.js +141 -0
- package/dist/domain/body-pages.js.map +1 -0
- package/dist/domain/paper-source.js +2 -2
- package/dist/domain/tex-document.d.ts +9 -0
- package/dist/domain/tex-document.d.ts.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 +1 -1
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +32 -18
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js +2 -2
- 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/facts-file.d.ts +28 -2
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +54 -9
- package/dist/facts-file.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/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +2 -1
- package/dist/lib/paper-config.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/paper-settings.d.ts +11 -0
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +38 -4
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-content.d.ts +40 -0
- package/dist/pdf-content.d.ts.map +1 -0
- package/dist/pdf-content.js +97 -0
- package/dist/pdf-content.js.map +1 -0
- package/dist/pdf-facts.d.ts +30 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +37 -1
- package/dist/pdf-facts.js.map +1 -1
- package/dist/ports/latex.d.ts +3 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -0
- package/dist/presets.js.map +1 -1
- 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 +17 -3
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +36 -38
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-requirements.d.ts +4 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +3 -0
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +7 -1
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +40 -0
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +38 -8
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +179 -41
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +23 -20
- package/docs/e2e.md +32 -11
- package/docs/install.md +8 -8
- package/docs/prior-art/paper-folder-scaffolding.md +4 -7
- package/docs/rules/anonymity/identity.md +111 -0
- package/docs/rules/format/layout-override.md +61 -0
- package/docs/rules/format/page-limit.md +108 -0
- package/docs/rules/pdf/body-size.md +87 -0
- package/docs/rules.md +55 -46
- 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.ts +67 -23
- package/eslint-rules/pdf-last-page-balance.harness.mjs +6 -1
- package/eslint-rules/pdf-last-page-balance.ts +2 -2
- package/eslint-rules/tex-build.ts +8 -1
- package/fixtures/build-e2e/aidc/expect.json +1 -1
- package/fixtures/build-e2e/aidc/paperlint.json +1 -1
- package/fixtures/build-e2e/anon-ack/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-ack/expect.json +19 -0
- package/fixtures/build-e2e/anon-ack/paper.tex +31 -0
- package/fixtures/build-e2e/anon-ack/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-author/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-author/expect.json +19 -0
- package/fixtures/build-e2e/anon-author/paper.tex +29 -0
- package/fixtures/build-e2e/anon-author/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-bib/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-bib/expect.json +20 -0
- package/fixtures/build-e2e/anon-bib/paper.tex +35 -0
- package/fixtures/build-e2e/anon-bib/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-href/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-href/expect.json +19 -0
- package/fixtures/build-e2e/anon-href/paper.tex +30 -0
- package/fixtures/build-e2e/anon-href/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-macro/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-macro/expect.json +19 -0
- package/fixtures/build-e2e/anon-macro/paper.tex +30 -0
- package/fixtures/build-e2e/anon-macro/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-pdfauthor/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-pdfauthor/expect.json +19 -0
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +31 -0
- package/fixtures/build-e2e/anon-pdfauthor/paperlint.json +1 -0
- package/fixtures/build-e2e/unbalanced/expect.json +2 -2
- package/fixtures/real-markdown-paper/baseline.json +1 -2
- package/lib/paper-config.ts +3 -1
- package/package.json +1 -1
- package/presets/acm-sigconf.jsonc +3 -1
- package/presets/aidc.jsonc +16 -17
- package/presets/ieee-conference.jsonc +8 -0
- package/presets/venue-profile.schema.json +9 -0
- package/scripts/check.ts +2 -2
- package/scripts/eslint-report-guard.mjs +5 -2
- package/scripts/harness-api.frozen.json +0 -1
- package/skills/paper-pipeline/references/pipeline-status-template.md +2 -16
- package/skills/render-paper/extract-pdf-facts.harness.ts +3 -3
- package/src/adapters/latex/includes.ts +10 -2
- package/src/adapters/latex/index.test.ts +8 -0
- package/src/adapters/latex/index.ts +3 -0
- package/src/adapters/latex/layout.ts +106 -0
- package/src/adapters/latex/nodes.ts +11 -3
- package/src/adapters/latex/parse.ts +5 -1
- package/src/build.harness.mjs +6 -4
- package/src/build.test.ts +23 -1
- package/src/build.ts +7 -3
- package/src/cli.harness.mjs +22 -54
- package/src/cli.test.ts +15 -18
- package/src/cli.ts +114 -46
- package/src/domain/anonymity.test.ts +163 -0
- package/src/domain/anonymity.ts +129 -0
- package/src/domain/body-pages.test.ts +300 -0
- package/src/domain/body-pages.ts +223 -0
- package/src/domain/paper-source.ts +2 -2
- package/src/domain/tex-document.ts +10 -0
- package/src/facts-file.test.ts +100 -4
- package/src/facts-file.ts +91 -9
- 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/new-paper.test.ts +7 -1
- 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/paper-settings.test.ts +63 -6
- package/src/paper-settings.ts +53 -5
- package/src/pdf-content.test.ts +71 -0
- package/src/pdf-content.ts +125 -0
- package/src/pdf-facts.test.ts +110 -2
- package/src/pdf-facts.ts +106 -8
- package/src/ports/latex.ts +3 -0
- package/src/presets.ts +8 -8
- package/src/rule-cases.test.ts +29 -33
- package/src/rule-docs.test.ts +0 -3
- 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 +78 -61
- package/src/tex-paper.ts +57 -48
- package/src/tex-requirements.ts +9 -0
- package/src/tex-venue-rules.test.ts +111 -6
- package/src/tex-venue-rules.ts +57 -2
- package/src/types.ts +2 -0
- package/src/venue-rules.test.ts +351 -39
- package/src/venue-rules.ts +247 -47
- 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/docs/rules/tex/missing-input.md +0 -58
- 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/src/action-ref.test.ts +0 -26
- package/src/action-ref.ts +0 -15
package/src/venue-rules.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The venue rules (`pdf/fresh` · `pdf/profile` · `pdf/fonts` · `pdf/geometry` · `pdf/
|
|
3
|
-
* `pdf/
|
|
2
|
+
* The venue rules (`pdf/fresh` · `pdf/profile` · `pdf/fonts` · `pdf/geometry` · `pdf/body-size` ·
|
|
3
|
+
* `pdf/measured` · `format/page-limit` · `anonymity/identity`) over a paper held in memory: `paper.tex`, `paperlint.json`,
|
|
4
4
|
* `_build/paper.facts.json` and the PDF the facts describe. The venue profiles are the SHIPPED
|
|
5
5
|
* ones, read from the package's venues directory, so a profile edit that breaks a check shows here.
|
|
6
6
|
*
|
|
@@ -13,7 +13,17 @@ import { describe, expect, it } from "vitest";
|
|
|
13
13
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
14
14
|
import { sha256Hex } from "./domain/sha256.ts";
|
|
15
15
|
import { presetsDir } from "./package-dirs.ts";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
ANONYMITY_RULE_LEVELS,
|
|
18
|
+
anonymityRules,
|
|
19
|
+
judgeBodyEnd,
|
|
20
|
+
PAGE_LIMIT_RULE_LEVELS,
|
|
21
|
+
pageLimitRules,
|
|
22
|
+
VENUE_RULE_LEVELS,
|
|
23
|
+
venueRules,
|
|
24
|
+
type Resolved,
|
|
25
|
+
} from "./venue-rules.ts";
|
|
26
|
+
import { NO_FORMAT } from "./tex-requirements.ts";
|
|
17
27
|
import { buildConfig, OPTIONAL_RULES, SHIPPED_RULES } from "./cli.ts";
|
|
18
28
|
import { present } from "../test/support.ts";
|
|
19
29
|
|
|
@@ -44,7 +54,12 @@ type Patch = (f: Facts) => void;
|
|
|
44
54
|
/** The facts of a short agenticdev paper that meets every number in its profile. */
|
|
45
55
|
function goodFacts(): Facts {
|
|
46
56
|
return {
|
|
47
|
-
schema:
|
|
57
|
+
schema: 3,
|
|
58
|
+
pages_text: [],
|
|
59
|
+
metadata: {},
|
|
60
|
+
links: [],
|
|
61
|
+
bib_anchor_page: null,
|
|
62
|
+
appendix_anchor_page: null,
|
|
48
63
|
pdf: "paper.pdf",
|
|
49
64
|
pdf_sha256: sha256Hex(PDF_BYTES),
|
|
50
65
|
venue: "agenticdev",
|
|
@@ -122,18 +137,29 @@ function lint(
|
|
|
122
137
|
cwd?: string,
|
|
123
138
|
): Finding[] {
|
|
124
139
|
const files = memoryFiles(paperFiles(p));
|
|
125
|
-
const
|
|
140
|
+
const deps = { files, venuesDir: VENUES };
|
|
141
|
+
const rules = [
|
|
142
|
+
...Object.entries(venueRules(deps)).map(
|
|
143
|
+
([n, r]) => [`pdf/${n}`, r] as const,
|
|
144
|
+
),
|
|
145
|
+
...Object.entries(pageLimitRules(deps)).map(
|
|
146
|
+
([n, r]) => [`format/${n}`, r] as const,
|
|
147
|
+
),
|
|
148
|
+
...Object.entries(anonymityRules(deps)).map(
|
|
149
|
+
([n, r]) => [`anonymity/${n}`, r] as const,
|
|
150
|
+
),
|
|
151
|
+
];
|
|
126
152
|
const out: Finding[] = [];
|
|
127
|
-
for (const [
|
|
153
|
+
for (const [id, rule] of rules) {
|
|
128
154
|
const visitor = rule.create({
|
|
129
155
|
filename,
|
|
130
156
|
...(cwd === undefined ? {} : { cwd }),
|
|
131
157
|
sourceCode: { text: TEX },
|
|
132
158
|
options:
|
|
133
|
-
|
|
159
|
+
id === "pdf/geometry" && Object.keys(options).length ? [options] : [],
|
|
134
160
|
report: (d) =>
|
|
135
161
|
out.push({
|
|
136
|
-
rule:
|
|
162
|
+
rule: id,
|
|
137
163
|
messageId: d.messageId,
|
|
138
164
|
message: Object.entries(d.data ?? {}).reduce(
|
|
139
165
|
(m, [k, v]) => m.replaceAll(`{{${k}}}`, String(v)),
|
|
@@ -456,39 +482,21 @@ describe("pdf/geometry", () => {
|
|
|
456
482
|
});
|
|
457
483
|
});
|
|
458
484
|
|
|
459
|
-
describe("
|
|
485
|
+
describe("format/page-limit", () => {
|
|
460
486
|
it.each<[string, (f: Facts) => void, string[], RegExp | null]>([
|
|
461
487
|
[
|
|
462
488
|
"one body page over the short-paper limit",
|
|
463
489
|
(f: Facts) => (f.body_pages = 6),
|
|
464
|
-
["
|
|
490
|
+
["format/page-limit:pages"],
|
|
465
491
|
/body pages: 6, over the limit 5 for agenticdev\/short/,
|
|
466
492
|
],
|
|
467
493
|
[
|
|
468
494
|
"one reference page over",
|
|
469
495
|
(f: Facts) => (f.ref_pages = 3),
|
|
470
|
-
["
|
|
496
|
+
["format/page-limit:pages"],
|
|
471
497
|
/reference pages: 3, over the limit 2/,
|
|
472
498
|
],
|
|
473
499
|
["exactly at the limit", (f: Facts) => (f.body_pages = 5), [], null],
|
|
474
|
-
[
|
|
475
|
-
"a reference font below the range",
|
|
476
|
-
(f: Facts) => (f.ref_pt = 6),
|
|
477
|
-
["pdf/limits:refPt"],
|
|
478
|
-
/6 pt/,
|
|
479
|
-
],
|
|
480
|
-
[
|
|
481
|
-
"6.8 pt is inside the range once the measuring drift (body_pt_tol) is allowed",
|
|
482
|
-
(f: Facts) => (f.ref_pt = 6.8),
|
|
483
|
-
[],
|
|
484
|
-
null,
|
|
485
|
-
],
|
|
486
|
-
[
|
|
487
|
-
"no reference font measured (no bibliography) is not a finding",
|
|
488
|
-
(f: Facts) => (f.ref_pt = null),
|
|
489
|
-
[],
|
|
490
|
-
null,
|
|
491
|
-
],
|
|
492
500
|
])("%s", (_, patch, want, text) => {
|
|
493
501
|
const fs = lint({ venue: DECL, facts: withFacts(patch) });
|
|
494
502
|
expect(ids(fs)).toEqual(want);
|
|
@@ -497,7 +505,9 @@ describe("pdf/limits", () => {
|
|
|
497
505
|
|
|
498
506
|
it("a longer kind of the same venue passes the same page count", () => {
|
|
499
507
|
const facts = withFacts((f) => (f.body_pages = 9));
|
|
500
|
-
expect(ids(lint({ venue: DECL, facts }))).toEqual([
|
|
508
|
+
expect(ids(lint({ venue: DECL, facts }))).toEqual([
|
|
509
|
+
"format/page-limit:pages",
|
|
510
|
+
]);
|
|
501
511
|
expect(
|
|
502
512
|
lint({ venue: { extends: "paperlint:agenticdev", kind: "full" }, facts }),
|
|
503
513
|
).toEqual([]);
|
|
@@ -517,6 +527,21 @@ describe("pdf/body-size", () => {
|
|
|
517
527
|
(f: Facts) => (f.body_pt = null),
|
|
518
528
|
["pdf/body-size:bodyMissing"],
|
|
519
529
|
],
|
|
530
|
+
[
|
|
531
|
+
"a reference font below the range",
|
|
532
|
+
(f: Facts) => (f.ref_pt = 6),
|
|
533
|
+
["pdf/body-size:refPt"],
|
|
534
|
+
],
|
|
535
|
+
[
|
|
536
|
+
"6.8 pt is inside the reference range once the measuring drift (body_pt_tol) is allowed",
|
|
537
|
+
(f: Facts) => (f.ref_pt = 6.8),
|
|
538
|
+
[],
|
|
539
|
+
],
|
|
540
|
+
[
|
|
541
|
+
"no reference font measured (no bibliography) is not a finding",
|
|
542
|
+
(f: Facts) => (f.ref_pt = null),
|
|
543
|
+
[],
|
|
544
|
+
],
|
|
520
545
|
])("%s", (_, patch, want) => {
|
|
521
546
|
expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
|
|
522
547
|
});
|
|
@@ -537,22 +562,33 @@ describe("paperlint's own config turns the venue rules on for every paper.tex",
|
|
|
537
562
|
"the paper.tex block's rules",
|
|
538
563
|
);
|
|
539
564
|
|
|
540
|
-
it.each(
|
|
565
|
+
it.each(
|
|
566
|
+
Object.entries({
|
|
567
|
+
...VENUE_RULE_LEVELS,
|
|
568
|
+
...PAGE_LIMIT_RULE_LEVELS,
|
|
569
|
+
...ANONYMITY_RULE_LEVELS,
|
|
570
|
+
}),
|
|
571
|
+
)("%s is on at %s", (id, level) => {
|
|
541
572
|
expect(texRules[id]).toBe(level);
|
|
542
573
|
expect(SHIPPED_RULES.has(id)).toBe(true);
|
|
543
574
|
expect(OPTIONAL_RULES.has(id)).toBe(false);
|
|
544
575
|
});
|
|
545
576
|
|
|
546
|
-
it("the
|
|
577
|
+
it("the venue rules are errors except measured, which is a warning", () => {
|
|
547
578
|
expect(VENUE_RULE_LEVELS).toEqual({
|
|
548
579
|
"pdf/fresh": "error",
|
|
549
580
|
"pdf/profile": "error",
|
|
550
581
|
"pdf/fonts": "error",
|
|
551
582
|
"pdf/geometry": "error",
|
|
552
|
-
"pdf/
|
|
553
|
-
"pdf/body-size": "warn",
|
|
583
|
+
"pdf/body-size": "error",
|
|
554
584
|
"pdf/measured": "warn",
|
|
555
585
|
});
|
|
586
|
+
expect(PAGE_LIMIT_RULE_LEVELS).toEqual({ "format/page-limit": "error" });
|
|
587
|
+
expect(ANONYMITY_RULE_LEVELS).toEqual({ "anonymity/identity": "error" });
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it("the rule the page limit was checked by before is not shipped", () => {
|
|
591
|
+
expect(SHIPPED_RULES.has("pdf/limits")).toBe(false);
|
|
556
592
|
});
|
|
557
593
|
|
|
558
594
|
it("pdf/last-page-balance stays optional", () => {
|
|
@@ -573,6 +609,10 @@ const type1 = (names: readonly string[]): Font[] =>
|
|
|
573
609
|
program: "Type1",
|
|
574
610
|
}));
|
|
575
611
|
|
|
612
|
+
/** `n` pages of body text, one per page, as `pages_text` holds them. */
|
|
613
|
+
const bodyPages = (n: number): string[] =>
|
|
614
|
+
Array.from({ length: n }, (_, i) => `Body page ${String(i + 1)}\n`);
|
|
615
|
+
|
|
576
616
|
const ieeeFacts = (patch: Patch = () => {}): Facts =>
|
|
577
617
|
withFacts((f) => {
|
|
578
618
|
f.page_w_in = 8.5;
|
|
@@ -582,6 +622,11 @@ const ieeeFacts = (patch: Patch = () => {}): Facts =>
|
|
|
582
622
|
f.ref_pt = 8.3;
|
|
583
623
|
f.body_pages = 12;
|
|
584
624
|
f.ref_pages = 1;
|
|
625
|
+
// Twelve pages of body, the references heading on the thirteenth.
|
|
626
|
+
f.pages_text = [
|
|
627
|
+
...bodyPages(12),
|
|
628
|
+
"R EFERENCES\n[1] D. E. Knuth, Literate Programming.\nLLM Usage Statement\n",
|
|
629
|
+
];
|
|
585
630
|
f.fonts = type1([
|
|
586
631
|
"NimbusRomNo9L-Medi",
|
|
587
632
|
"NimbusRomNo9L-Regu",
|
|
@@ -593,16 +638,34 @@ const ieeeFacts = (patch: Patch = () => {}): Facts =>
|
|
|
593
638
|
|
|
594
639
|
describe("the IEEE conference family and AIDC", () => {
|
|
595
640
|
const FAMILY = { extends: "paperlint:ieee-conference" };
|
|
596
|
-
const
|
|
641
|
+
const IDENTITY = ["Ada Example", "adaexample", "ada@example.org"];
|
|
642
|
+
const AIDC = {
|
|
643
|
+
extends: "paperlint:aidc",
|
|
644
|
+
kind: "regular",
|
|
645
|
+
identity: IDENTITY,
|
|
646
|
+
};
|
|
597
647
|
|
|
598
648
|
it.each([
|
|
599
649
|
["the family", FAMILY],
|
|
600
650
|
["aidc/regular", AIDC],
|
|
601
|
-
["aidc/short", { extends: "paperlint:aidc", kind: "short" }],
|
|
602
651
|
])("the measured IEEEtran build passes %s", (_, venue) => {
|
|
603
652
|
expect(lint({ venue, facts: ieeeFacts() })).toEqual([]);
|
|
604
653
|
});
|
|
605
654
|
|
|
655
|
+
it("aidc/short: six body pages pass, seven fail", () => {
|
|
656
|
+
const short = { ...AIDC, kind: "short" };
|
|
657
|
+
const pages = (n: number) =>
|
|
658
|
+
ieeeFacts((f) => {
|
|
659
|
+
f.pages_text = [...bodyPages(n), "References\n"];
|
|
660
|
+
});
|
|
661
|
+
expect(lint({ venue: short, facts: pages(6) })).toEqual([]);
|
|
662
|
+
const fs = lint({ venue: short, facts: pages(7) });
|
|
663
|
+
expect(ids(fs)).toEqual(["format/page-limit:pages"]);
|
|
664
|
+
expect(fs[0]?.message).toBe(
|
|
665
|
+
"body pages (up to the references on page 8): 7, over the limit 6 for aidc/short — a desk reject; cut the text",
|
|
666
|
+
);
|
|
667
|
+
});
|
|
668
|
+
|
|
606
669
|
it.each([
|
|
607
670
|
["the family", FAMILY],
|
|
608
671
|
["aidc", AIDC],
|
|
@@ -627,14 +690,52 @@ describe("the IEEE conference family and AIDC", () => {
|
|
|
627
690
|
expect(ids(fs)).toEqual(["pdf/fonts:noFamily"]);
|
|
628
691
|
expect(fs[0]?.message).toMatch(/NimbusRomNo9L/);
|
|
629
692
|
});
|
|
693
|
+
});
|
|
630
694
|
|
|
695
|
+
describe("pdf/body-size on the IEEE family: 10 pt body, 8 pt references", () => {
|
|
631
696
|
it("a body size off the family's 10 pt fails pdf/body-size", () => {
|
|
632
697
|
expect(
|
|
633
|
-
ids(
|
|
698
|
+
ids(
|
|
699
|
+
lint({
|
|
700
|
+
venue: {
|
|
701
|
+
extends: "paperlint:aidc",
|
|
702
|
+
kind: "regular",
|
|
703
|
+
identity: ["Ada Example"],
|
|
704
|
+
},
|
|
705
|
+
facts: ieeeFacts((f) => (f.body_pt = 9.3)),
|
|
706
|
+
}),
|
|
707
|
+
),
|
|
634
708
|
).toEqual(["pdf/body-size:body"]);
|
|
635
709
|
});
|
|
636
710
|
|
|
637
|
-
it(
|
|
711
|
+
it.each([
|
|
712
|
+
["\\scriptsize, 7 pt", 7.3],
|
|
713
|
+
["\\small, 9 pt", 9.3],
|
|
714
|
+
])(
|
|
715
|
+
"a bibliography in %s instead of the class's 8 pt fails pdf/body-size",
|
|
716
|
+
(_, got) => {
|
|
717
|
+
const fs = lint({
|
|
718
|
+
venue: { extends: "paperlint:ieee-conference" },
|
|
719
|
+
facts: ieeeFacts((f) => (f.ref_pt = got)),
|
|
720
|
+
});
|
|
721
|
+
expect(fs.map((f) => f.message)).toEqual([
|
|
722
|
+
`the reference font size is ${String(got)} pt, outside 8 pt for ieee-conference (allowing ±0.5 pt for the measuring drift) — fix the bibliography's font size`,
|
|
723
|
+
]);
|
|
724
|
+
},
|
|
725
|
+
);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
/** An AIDC regular paper (12 body pages), for the page-limit tests. */
|
|
729
|
+
const AIDC_REGULAR = {
|
|
730
|
+
extends: "paperlint:aidc",
|
|
731
|
+
kind: "regular",
|
|
732
|
+
identity: ["Ada Example"],
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
describe("format/page-limit on AIDC: the body is counted up to the references", () => {
|
|
736
|
+
const AIDC = AIDC_REGULAR;
|
|
737
|
+
|
|
738
|
+
it("🔴 AIDC's body is counted before the references, not by banal: banal's 13 — the statement and an appendix after the references counted as body — is not a finding", () => {
|
|
638
739
|
expect(
|
|
639
740
|
lint({
|
|
640
741
|
venue: AIDC,
|
|
@@ -643,6 +744,197 @@ describe("the IEEE conference family and AIDC", () => {
|
|
|
643
744
|
).toEqual([]);
|
|
644
745
|
});
|
|
645
746
|
|
|
747
|
+
it("…while a thirteenth page of body before the references is", () => {
|
|
748
|
+
const fs = lint({
|
|
749
|
+
venue: AIDC,
|
|
750
|
+
facts: ieeeFacts((f) => {
|
|
751
|
+
f.pages_text = [...bodyPages(13), "7 References\n"];
|
|
752
|
+
}),
|
|
753
|
+
});
|
|
754
|
+
expect(ids(fs)).toEqual(["format/page-limit:pages"]);
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it("🔴 a body that runs onto the thirteenth page, the references below it there: 13 pages, a finding", () => {
|
|
758
|
+
const fs = lint({
|
|
759
|
+
venue: AIDC,
|
|
760
|
+
facts: ieeeFacts((f) => {
|
|
761
|
+
f.pages_text = [
|
|
762
|
+
...bodyPages(12),
|
|
763
|
+
"the last lines of the conclusion\nR EFERENCES\n[1] D. E. Knuth.\n",
|
|
764
|
+
];
|
|
765
|
+
}),
|
|
766
|
+
});
|
|
767
|
+
expect(ids(fs)).toEqual(["format/page-limit:pages"]);
|
|
768
|
+
expect(fs[0]?.message).toMatch(
|
|
769
|
+
/^body pages \(up to the references on page 13\): 13, over the limit 12/,
|
|
770
|
+
);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("no references heading on any page: said, not passed", () => {
|
|
774
|
+
const fs = lint({
|
|
775
|
+
venue: AIDC,
|
|
776
|
+
facts: ieeeFacts((f) => (f.pages_text = ["Body\n", "Body\n"])),
|
|
777
|
+
});
|
|
778
|
+
expect(ids(fs)).toEqual(["format/page-limit:noReferences"]);
|
|
779
|
+
expect(fs[0]?.message).toMatch(/NOT counted/);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it("the references count needs no banal: facts without geometry are still judged", () => {
|
|
783
|
+
const fs = lint({
|
|
784
|
+
venue: AIDC,
|
|
785
|
+
facts: ieeeFacts((f) => {
|
|
786
|
+
f.geometry_source = null;
|
|
787
|
+
f.pages_text = [...bodyPages(13), "References\n"];
|
|
788
|
+
}),
|
|
789
|
+
});
|
|
790
|
+
expect(ids(fs)).toEqual([
|
|
791
|
+
"format/page-limit:pages",
|
|
792
|
+
"pdf/measured:noGeometry",
|
|
793
|
+
]);
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
describe("format/page-limit on AIDC: an appendix ends the body too", () => {
|
|
798
|
+
const AIDC = AIDC_REGULAR;
|
|
799
|
+
|
|
800
|
+
it("🔴 an appendix before the references is not body: the body ends where hyperref anchors the appendix", () => {
|
|
801
|
+
const facts = ieeeFacts((f) => {
|
|
802
|
+
f.pages_text = [
|
|
803
|
+
...bodyPages(12),
|
|
804
|
+
"Appendix A.\nDetailed results\n",
|
|
805
|
+
"References\n[1] A.\n",
|
|
806
|
+
];
|
|
807
|
+
f.bib_anchor_page = 14;
|
|
808
|
+
f.appendix_anchor_page = 13;
|
|
809
|
+
});
|
|
810
|
+
expect(ids(lint({ venue: AIDC, facts }))).toEqual([]);
|
|
811
|
+
// Without hyperref's anchor for it, the appendix is body — the count this rule had before.
|
|
812
|
+
facts.appendix_anchor_page = null;
|
|
813
|
+
expect(lint({ venue: AIDC, facts })[0]?.message).toMatch(
|
|
814
|
+
/^body pages \(up to the references on page 14\): 13, over the limit 12/,
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
describe("format/page-limit on AIDC: the two signals of where the references start disagree", () => {
|
|
820
|
+
const AIDC = AIDC_REGULAR;
|
|
821
|
+
|
|
822
|
+
it("hyperref's first entry on a page with no «References» line: unclear, said, not passed", () => {
|
|
823
|
+
const fs = lint({
|
|
824
|
+
venue: AIDC,
|
|
825
|
+
facts: ieeeFacts((f) => {
|
|
826
|
+
f.pages_text = [...bodyPages(12), "References\n[1] A.\n", "[2] B.\n"];
|
|
827
|
+
f.bib_anchor_page = 14;
|
|
828
|
+
}),
|
|
829
|
+
});
|
|
830
|
+
expect(ids(fs)).toEqual(["format/page-limit:unclear"]);
|
|
831
|
+
expect(fs[0]?.message).toBe(
|
|
832
|
+
"could not tell where the body ends, so it was NOT counted against aidc's limit: the first bibliography entry is on page 14 (hyperref's destination), and no line on page 14 reads «References». Check the headings of the bibliography and the appendix, and that nothing before them reads «References» above a [1]",
|
|
833
|
+
);
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it("hyperref anchors the appendix on a page with no line reading «Appendix»: unclear", () => {
|
|
837
|
+
const fs = lint({
|
|
838
|
+
venue: AIDC,
|
|
839
|
+
facts: ieeeFacts((f) => {
|
|
840
|
+
f.appendix_anchor_page = 5;
|
|
841
|
+
}),
|
|
842
|
+
});
|
|
843
|
+
expect(fs[0]?.message).toMatch(
|
|
844
|
+
/: hyperref anchors the appendix on page 5, and no line on page 5 reads «Appendix»\./,
|
|
845
|
+
);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it("a confirmed heading on another page than hyperref's first entry: unclear, naming both", () => {
|
|
849
|
+
const fs = lint({
|
|
850
|
+
venue: AIDC,
|
|
851
|
+
facts: ieeeFacts((f) => {
|
|
852
|
+
f.pages_text = [
|
|
853
|
+
"References\n[1] quoted in the body\n",
|
|
854
|
+
...bodyPages(11),
|
|
855
|
+
"References\n[1] A.\n",
|
|
856
|
+
];
|
|
857
|
+
f.bib_anchor_page = 13;
|
|
858
|
+
}),
|
|
859
|
+
});
|
|
860
|
+
expect(fs[0]?.message).toMatch(
|
|
861
|
+
/and the «References» heading followed by \[1\] is on page 1\./,
|
|
862
|
+
);
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
/** An AIDC paper that declares who wrote it. */
|
|
867
|
+
const BLIND = {
|
|
868
|
+
extends: "paperlint:aidc",
|
|
869
|
+
kind: "regular",
|
|
870
|
+
identity: ["Ada Example", "adaexample", "ada@example.org"],
|
|
871
|
+
};
|
|
872
|
+
const said = (patch: Patch) => ieeeFacts(patch);
|
|
873
|
+
|
|
874
|
+
describe("anonymity/identity — a blind venue (AIDC)", () => {
|
|
875
|
+
it("no identity declared: an error, built or not — a missing input is not a clean pass", () => {
|
|
876
|
+
const noIdentity = { extends: "paperlint:aidc", kind: "regular" };
|
|
877
|
+
const built = lint({ venue: noIdentity, facts: ieeeFacts() });
|
|
878
|
+
expect(ids(built)).toEqual(["anonymity/identity:noIdentity"]);
|
|
879
|
+
expect(built[0]?.message).toMatch(/"identity": \["Your Name"/);
|
|
880
|
+
expect(ids(lint({ venue: noIdentity, facts: null }))).toEqual([
|
|
881
|
+
"anonymity/identity:noIdentity",
|
|
882
|
+
"pdf/measured:unbuilt",
|
|
883
|
+
]);
|
|
884
|
+
expect(
|
|
885
|
+
ids(lint({ venue: { ...noIdentity, identity: [] }, facts: ieeeFacts() })),
|
|
886
|
+
).toEqual(["anonymity/identity:noIdentity"]);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it("declared and not built: silent — pdf/measured already says it was not built", () => {
|
|
890
|
+
expect(ids(lint({ venue: BLIND, facts: null }))).toEqual([
|
|
891
|
+
"pdf/measured:unbuilt",
|
|
892
|
+
]);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
it("a name on a page, the metadata and a link target: one finding each, with the place", () => {
|
|
896
|
+
const fs = lint({
|
|
897
|
+
venue: BLIND,
|
|
898
|
+
facts: said((f) => {
|
|
899
|
+
f.pages_text = [
|
|
900
|
+
"Title\nAda\nExample\n",
|
|
901
|
+
...bodyPages(11),
|
|
902
|
+
"References\n",
|
|
903
|
+
];
|
|
904
|
+
f.metadata = { Author: "Ada Example", Producer: "pdfTeX-1.40.25" };
|
|
905
|
+
f.links = [{ page: 2, uri: "https://github.com/adaexample/tool" }];
|
|
906
|
+
}),
|
|
907
|
+
});
|
|
908
|
+
expect(fs.map((f) => f.message)).toEqual([
|
|
909
|
+
"PDF, page 1: «Ada Example» matches «Ada Example» in `identity`, and aidc reviews double-blind — remove it, or refer to your own work in the third person",
|
|
910
|
+
"PDF metadata: Author: «Ada Example» matches «Ada Example» in `identity`, and aidc reviews double-blind — remove it, or refer to your own work in the third person",
|
|
911
|
+
"link target on page 2 (https://github.com/adaexample/tool): «adaexample» matches «Ada Example» in `identity`, and aidc reviews double-blind — remove it, or refer to your own work in the third person",
|
|
912
|
+
]);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it("an anonymous-hosting link names nobody, and is not a finding", () => {
|
|
916
|
+
expect(
|
|
917
|
+
lint({
|
|
918
|
+
venue: BLIND,
|
|
919
|
+
facts: said((f) => {
|
|
920
|
+
f.links = [
|
|
921
|
+
{ page: 1, uri: "https://anonymous.4open.science/r/tool-1A2B" },
|
|
922
|
+
];
|
|
923
|
+
}),
|
|
924
|
+
}),
|
|
925
|
+
).toEqual([]);
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
it("a venue that is not blind: silent, with or without identity, whatever the PDF says", () => {
|
|
929
|
+
const facts = withFacts((f) => (f.pages_text = ["Ada Example\n"]));
|
|
930
|
+
expect(lint({ venue: DECL, facts })).toEqual([]);
|
|
931
|
+
expect(
|
|
932
|
+
lint({ venue: { ...DECL, identity: ["Ada Example"] }, facts }),
|
|
933
|
+
).toEqual([]);
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
describe("an AIDC kind", () => {
|
|
646
938
|
it("an AIDC kind that does not exist names the two that do", () => {
|
|
647
939
|
const fs = lint({ venue: { extends: "paperlint:aidc", kind: "wip" } });
|
|
648
940
|
expect(ids(fs)).toContain("pdf/profile:kindUnknown");
|
|
@@ -677,7 +969,9 @@ describe("a project's own preset, and the facts' other spellings", () => {
|
|
|
677
969
|
it("a body size with no tolerance is not judged; a reference range with none is judged exactly", () => {
|
|
678
970
|
expect(ids(house((f) => (f.body_pt = 12)))).toEqual([]);
|
|
679
971
|
// 6.9 pt passes agenticdev (its body_pt_tol widens the range) and fails here, where nothing does.
|
|
680
|
-
expect(ids(house((f) => (f.ref_pt = 6.9)))).toEqual([
|
|
972
|
+
expect(ids(house((f) => (f.ref_pt = 6.9)))).toEqual([
|
|
973
|
+
"pdf/body-size:refPt",
|
|
974
|
+
]);
|
|
681
975
|
});
|
|
682
976
|
|
|
683
977
|
it("facts naming the PDF by an absolute path are checked against that file", () => {
|
|
@@ -709,3 +1003,21 @@ describe("a project's own preset, and the facts' other spellings", () => {
|
|
|
709
1003
|
);
|
|
710
1004
|
});
|
|
711
1005
|
});
|
|
1006
|
+
|
|
1007
|
+
describe("judgeBodyEnd, on a venue it does not apply to", () => {
|
|
1008
|
+
const NO_TEXT = { pages: [], bibAnchorPage: null, appendixAnchorPage: null };
|
|
1009
|
+
const venue = (kind: Resolved["kind"]): Resolved => ({
|
|
1010
|
+
venue: "house",
|
|
1011
|
+
format: { ...NO_FORMAT, bodyEndsAt: "references" },
|
|
1012
|
+
kind,
|
|
1013
|
+
kindProblem: null,
|
|
1014
|
+
blind: false,
|
|
1015
|
+
identity: null,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
it("a kind with no body limit, or no kind: nothing to count against", () => {
|
|
1019
|
+
const limits = { bodyPagesMax: null, refPagesMax: null };
|
|
1020
|
+
expect(judgeBodyEnd(NO_TEXT, venue({ name: "long", limits }))).toEqual([]);
|
|
1021
|
+
expect(judgeBodyEnd(NO_TEXT, venue(null))).toEqual([]);
|
|
1022
|
+
});
|
|
1023
|
+
});
|