paperlint 6.3.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +4 -2
- package/README.md +20 -13
- package/dist/adapters/latex/conditionals.d.ts +21 -0
- package/dist/adapters/latex/conditionals.d.ts.map +1 -0
- package/dist/adapters/latex/conditionals.js +75 -0
- package/dist/adapters/latex/conditionals.js.map +1 -0
- package/dist/adapters/latex/headings.d.ts +10 -0
- package/dist/adapters/latex/headings.d.ts.map +1 -0
- package/dist/adapters/latex/headings.js +129 -0
- package/dist/adapters/latex/headings.js.map +1 -0
- 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/parse.d.ts +1 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +3 -2
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/build.d.ts +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1 -1
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -5
- package/dist/cli.js.map +1 -1
- package/dist/domain/body-pages.d.ts +29 -0
- package/dist/domain/body-pages.d.ts.map +1 -1
- package/dist/domain/body-pages.js +25 -0
- package/dist/domain/body-pages.js.map +1 -1
- package/dist/domain/heading-case.d.ts +100 -0
- package/dist/domain/heading-case.d.ts.map +1 -0
- package/dist/domain/heading-case.js +365 -0
- package/dist/domain/heading-case.js.map +1 -0
- package/dist/domain/tex-document.d.ts +32 -0
- package/dist/domain/tex-document.d.ts.map +1 -1
- package/dist/domain/tex-document.js +11 -0
- package/dist/domain/tex-document.js.map +1 -1
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +16 -3
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/facts-file.d.ts +3 -3
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +3 -3
- package/dist/facts-file.js.map +1 -1
- package/dist/folder-venue-rule.js +6 -6
- package/dist/folder-venue-rule.js.map +1 -1
- package/dist/heading-case.d.ts +48 -0
- package/dist/heading-case.d.ts.map +1 -0
- package/dist/heading-case.js +191 -0
- package/dist/heading-case.js.map +1 -0
- package/dist/paper-includes.d.ts +3 -0
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +17 -2
- package/dist/paper-includes.js.map +1 -1
- package/dist/ports/latex.d.ts +7 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/preset-card-rule.d.ts +100 -0
- package/dist/preset-card-rule.d.ts.map +1 -0
- package/dist/preset-card-rule.js +221 -0
- package/dist/preset-card-rule.js.map +1 -0
- package/dist/presets.d.ts +29 -5
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +104 -68
- package/dist/presets.js.map +1 -1
- package/dist/submission.d.ts +8 -0
- package/dist/submission.d.ts.map +1 -1
- package/dist/submission.js +19 -7
- package/dist/submission.js.map +1 -1
- package/dist/tex-requirements.d.ts +42 -3
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +34 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +6 -3
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +29 -10
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/venue-rules.d.ts +2 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +55 -7
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +13 -1
- package/docs/e2e.md +14 -12
- package/docs/optional-rules.md +4 -3
- package/docs/rules/format/page-limit.md +19 -2
- package/docs/rules/paper/folder-venue-leftover.md +1 -1
- package/docs/rules/talk/profile.md +1 -1
- package/docs/rules/tex/heading-case.md +424 -0
- package/docs/rules/tex/template.md +25 -5
- package/docs/rules/tex/venue-leftover.md +13 -0
- package/docs/rules.md +74 -57
- package/docs/submission.md +5 -1
- package/eslint-rules/latex-language.test.mjs +12 -0
- package/eslint-rules/latex-language.ts +17 -4
- package/eslint-rules/paper-typography.test.mjs +12 -0
- package/eslint.config.mjs +59 -1
- package/fixtures/build-e2e/acmart/paper.tex +1 -1
- package/fixtures/build-e2e/fallback/expect.json +3 -3
- package/fixtures/build-e2e/msr/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/msr/expect.json +18 -0
- package/fixtures/build-e2e/msr/paper.tex +49 -0
- package/fixtures/build-e2e/msr/paperlint.json +1 -0
- package/fixtures/build-e2e/msr-appendix-after/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/msr-appendix-after/expect.json +20 -0
- package/fixtures/build-e2e/msr-appendix-after/paper.tex +48 -0
- package/fixtures/build-e2e/msr-appendix-after/paperlint.json +1 -0
- package/lib/skill-checks.mjs +31 -0
- package/package.json +7 -1
- package/presets/acm-sigconf.jsonc +5 -0
- package/presets/acm-sigconf.md +43 -0
- package/presets/agenticdev.jsonc +27 -6
- package/presets/agenticdev.md +151 -0
- package/presets/aidc.jsonc +6 -0
- package/presets/aidc.md +75 -0
- package/presets/aisec.jsonc +5 -1
- package/presets/aisec.md +117 -0
- package/presets/ieee-conference.jsonc +5 -0
- package/presets/ieee-conference.md +38 -0
- package/presets/msr.jsonc +71 -0
- package/presets/msr.md +94 -0
- package/presets/realm.jsonc +7 -2
- package/presets/realm.md +166 -0
- package/presets/tex/agenticdev.tex +1 -1
- package/presets/tex-base.jsonc +6 -0
- package/presets/tex-base.md +30 -0
- package/presets/venue-profile.schema.json +65 -1
- package/skills/camera-ready/SKILL.md +8 -1
- package/skills/camera-ready/SKILL.md.spec.ts +7 -0
- package/skills/find-venue/SKILL.md +6 -5
- package/skills/find-venue/SKILL.md.spec.ts +5 -4
- package/skills/paper-pipeline/SKILL.md +5 -6
- package/skills/paper-pipeline/SKILL.md.spec.ts +4 -5
- package/skills/paper-pipeline/references/acl-venue-rules.md +1 -1
- package/skills/study-accepted-papers/SKILL.md +3 -3
- package/skills/study-accepted-papers/SKILL.md.spec.ts +2 -2
- package/skills/submission-portal/SKILL.md +2 -2
- package/skills/submission-portal/SKILL.md.spec.ts +1 -1
- package/skills/submit-paper/SKILL.md +34 -22
- package/skills/submit-paper/SKILL.md.spec.ts +33 -21
- package/skills/submit-paper/references/publishers/acm.md +1 -1
- package/src/adapters/latex/conditionals.ts +130 -0
- package/src/adapters/latex/headings.ts +163 -0
- package/src/adapters/latex/index.test.ts +65 -0
- package/src/adapters/latex/index.ts +3 -0
- package/src/adapters/latex/parse.ts +3 -2
- package/src/build.ts +2 -2
- package/src/cli.test.ts +50 -7
- package/src/cli.ts +28 -6
- package/src/domain/body-pages.test.ts +144 -0
- package/src/domain/body-pages.ts +61 -1
- package/src/domain/heading-case.test.ts +660 -0
- package/src/domain/heading-case.ts +538 -0
- package/src/domain/tex-document.ts +36 -0
- package/src/domain/venue-name.test.ts +13 -7
- package/src/facts-file.test.ts +4 -4
- package/src/facts-file.ts +6 -6
- package/src/folder-venue-rule.test.ts +10 -10
- package/src/folder-venue-rule.ts +6 -6
- package/src/heading-case.test.ts +500 -0
- package/src/heading-case.ts +275 -0
- package/src/init.test.ts +1 -1
- package/src/new-paper.test.ts +37 -6
- package/src/paper-includes.test.ts +26 -0
- package/src/paper-includes.ts +20 -2
- package/src/paper-settings-commands.test.ts +26 -11
- package/src/ports/latex.ts +7 -0
- package/src/preset-card-rule.test.ts +267 -0
- package/src/preset-card-rule.ts +384 -0
- package/src/presets.test.ts +259 -41
- package/src/presets.ts +161 -75
- package/src/rule-cases.test.ts +136 -50
- package/src/submission.test.ts +44 -4
- package/src/submission.ts +28 -7
- package/src/talk-rules.test.ts +6 -3
- package/src/tex-requirements.harness.ts +25 -11
- package/src/tex-requirements.test.ts +127 -1
- package/src/tex-requirements.ts +91 -12
- package/src/tex-venue-rules.test.ts +196 -21
- package/src/tex-venue-rules.ts +47 -16
- package/src/venue-rules.test.ts +180 -8
- package/src/venue-rules.ts +71 -10
- package/templates/paper/paper.tex +1 -1
- package/skills/submit-paper/references/venues/agenticdev.md +0 -171
- package/skills/submit-paper/references/venues/aisec.md +0 -105
- package/skills/submit-paper/references/venues/realm.md +0 -155
package/src/presets.test.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* resolving shows here.
|
|
8
8
|
*/
|
|
9
9
|
import { readdirSync, readFileSync } from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
10
|
+
import { join, relative } from "node:path";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
12
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
13
13
|
import { presetsDir } from "./package-dirs.ts";
|
|
@@ -35,6 +35,17 @@ const deps = (extra: Record<string, string> = {}) => ({
|
|
|
35
35
|
});
|
|
36
36
|
const resolve = (spec: string, extra: Record<string, string> = {}) =>
|
|
37
37
|
resolvePreset(spec, PAPER_FILE, deps(extra));
|
|
38
|
+
/** A venue preset of a project's own: the identity every venue declares, then `body`. */
|
|
39
|
+
const venue = (body: Record<string, unknown>, name = "Our Workshop"): string =>
|
|
40
|
+
JSON.stringify({
|
|
41
|
+
type: "venue",
|
|
42
|
+
name,
|
|
43
|
+
url: "https://example.org/cfp",
|
|
44
|
+
...body,
|
|
45
|
+
});
|
|
46
|
+
/** A template family of a project's own. */
|
|
47
|
+
const family = (body: Record<string, unknown>): string =>
|
|
48
|
+
JSON.stringify({ type: "family", ...body });
|
|
38
49
|
|
|
39
50
|
describe("shipped presets", () => {
|
|
40
51
|
it("paperlint:agenticdev → agenticdev over acm-sigconf: the family's format, the venue's kinds", () => {
|
|
@@ -52,11 +63,53 @@ describe("shipped presets", () => {
|
|
|
52
63
|
expect(r.value.template).toEqual({
|
|
53
64
|
text: "\\documentclass[sigconf]{acmart}",
|
|
54
65
|
file: join(VENUES, "acm-sigconf.jsonc"),
|
|
66
|
+
forbids: [],
|
|
55
67
|
});
|
|
56
68
|
expect("acmart" in r.value.tex.packages).toBe(true);
|
|
57
|
-
|
|
69
|
+
// The label is the venue's own name, from its file's `name`; the id stays the file name — the
|
|
70
|
+
// stable key the facts file and the build plan carry.
|
|
71
|
+
expect(r.value.label).toBe("AgenticDev");
|
|
72
|
+
expect(r.value.id).toBe("agenticdev");
|
|
73
|
+
expect(r.value.identity).toEqual({
|
|
74
|
+
type: "venue",
|
|
75
|
+
name: "AgenticDev",
|
|
76
|
+
url: "https://conf.researchr.org/home/ase-2026/agenticdev-2026",
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("shipped presets: paperlint:msr", () => {
|
|
82
|
+
it("paperlint:msr → msr over ieee-conference: one kind, appendices counted as body, compsoc forbidden", () => {
|
|
83
|
+
const r = resolve("paperlint:msr");
|
|
84
|
+
expect(r.ok).toBe(true);
|
|
85
|
+
if (!r.ok) return;
|
|
86
|
+
expect(r.value.chain.map((f) => f.split("/").pop())).toEqual([
|
|
87
|
+
"ieee-conference.jsonc",
|
|
88
|
+
"msr.jsonc",
|
|
89
|
+
]);
|
|
90
|
+
expect([...r.value.format.kinds]).toEqual([
|
|
91
|
+
["technical", { bodyPagesMax: 10, refPagesMax: 2 }],
|
|
92
|
+
]);
|
|
93
|
+
expect(r.value.format.bodyEndsAt).toBe("references");
|
|
94
|
+
expect(r.value.format.appendixInBody).toBe(true);
|
|
95
|
+
expect(r.value.format.pageWidthIn).toBe(8.5);
|
|
96
|
+
expect(r.value.template).toEqual({
|
|
97
|
+
text: "\\documentclass[10pt,conference]{IEEEtran}",
|
|
98
|
+
file: join(VENUES, "msr.jsonc"),
|
|
99
|
+
forbids: ["compsoc", "compsocconf"],
|
|
100
|
+
});
|
|
101
|
+
expect(r.value.blind).toBe(true);
|
|
102
|
+
expect(r.value.portal).toEqual({
|
|
103
|
+
kind: "hotcrp",
|
|
104
|
+
url: "https://msr2027.hotcrp.com",
|
|
105
|
+
});
|
|
106
|
+
// No rule turned on: the IEEE capitalization rule has not been read and quoted.
|
|
107
|
+
expect(r.value.rules).toEqual({});
|
|
108
|
+
expect(r.value.registerAnchors).toEqual([]);
|
|
58
109
|
});
|
|
110
|
+
});
|
|
59
111
|
|
|
112
|
+
describe("shipped presets: a family, and a venue that stands alone", () => {
|
|
60
113
|
it("a paper may extend a family directly: format and fonts, and no kinds", () => {
|
|
61
114
|
const r = resolve("paperlint:acm-sigconf");
|
|
62
115
|
expect(r.ok && r.value.format.kinds.size).toBe(0);
|
|
@@ -78,19 +131,77 @@ describe("shipped presets", () => {
|
|
|
78
131
|
});
|
|
79
132
|
});
|
|
80
133
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
// Each shipped preset, parsed: its file name and what it extends. The families are DERIVED — a
|
|
135
|
+
// shipped preset another shipped preset extends — so a new family is checked the day it ships.
|
|
136
|
+
const PARSED = shippedPresets(VENUES).map((name) => {
|
|
137
|
+
const file = join(VENUES, `${name}.jsonc`);
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
preset: parsePreset(readFileSync(file, "utf8"), file, VENUES),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
const FAMILIES = PARSED.filter((p) =>
|
|
144
|
+
PARSED.some((q) => q.preset.extends === `${SHIPPED_PREFIX}${p.name}`),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
describe("what each shipped preset declares itself to be", () => {
|
|
148
|
+
it("every shipped preset loads and declares what it is; a venue links its call", () => {
|
|
149
|
+
const declared = Object.fromEntries(
|
|
150
|
+
readdirSync(VENUES)
|
|
151
|
+
.filter((f) => f.endsWith(".jsonc"))
|
|
152
|
+
.map((f) => [
|
|
153
|
+
f,
|
|
154
|
+
parsePreset(readFileSync(join(VENUES, f), "utf8"), f, VENUES)
|
|
155
|
+
.identity,
|
|
156
|
+
]),
|
|
157
|
+
);
|
|
158
|
+
expect(declared).toEqual({
|
|
159
|
+
"acm-sigconf.jsonc": { type: "family" },
|
|
160
|
+
"agenticdev.jsonc": {
|
|
161
|
+
type: "venue",
|
|
162
|
+
name: "AgenticDev",
|
|
163
|
+
url: "https://conf.researchr.org/home/ase-2026/agenticdev-2026",
|
|
164
|
+
},
|
|
165
|
+
"aidc.jsonc": {
|
|
166
|
+
type: "venue",
|
|
167
|
+
name: "AIDC",
|
|
168
|
+
url: "https://aidcworkshop.github.io/",
|
|
169
|
+
},
|
|
170
|
+
"aisec.jsonc": {
|
|
171
|
+
type: "venue",
|
|
172
|
+
name: "AISec",
|
|
173
|
+
url: "https://aisec.cc/",
|
|
174
|
+
},
|
|
175
|
+
"ieee-conference.jsonc": { type: "family" },
|
|
176
|
+
"msr.jsonc": {
|
|
177
|
+
type: "venue",
|
|
178
|
+
name: "MSR",
|
|
179
|
+
url: "https://2027.msrconf.org/track/msr-2027-technical-papers",
|
|
180
|
+
},
|
|
181
|
+
"realm.jsonc": {
|
|
182
|
+
type: "venue",
|
|
183
|
+
name: "REALM",
|
|
184
|
+
url: "https://realm-workshop.github.io/call_for_papers",
|
|
185
|
+
},
|
|
186
|
+
"tex-base.jsonc": { type: "base" },
|
|
187
|
+
});
|
|
90
188
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
189
|
+
|
|
190
|
+
it("the families another preset extends are exactly the ones that declare `family`", () => {
|
|
191
|
+
expect(
|
|
192
|
+
PARSED.filter((p) => p.preset.identity.type === "family").map(
|
|
193
|
+
(p) => p.name,
|
|
194
|
+
),
|
|
195
|
+
).toEqual(FAMILIES.map((f) => f.name));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("the naming convention of shipped presets (docs/rules.md)", () => {
|
|
200
|
+
const families = FAMILIES;
|
|
201
|
+
const presets = PARSED.map((p) => ({
|
|
202
|
+
name: p.name,
|
|
203
|
+
extends: p.preset.extends,
|
|
204
|
+
}));
|
|
94
205
|
const publishers = families.map((f) => f.name.slice(0, f.name.indexOf("-")));
|
|
95
206
|
|
|
96
207
|
it("there are families to check (else the checks below see nothing)", () => {
|
|
@@ -118,16 +229,18 @@ describe("the naming convention of shipped presets (docs/rules.md)", () => {
|
|
|
118
229
|
});
|
|
119
230
|
|
|
120
231
|
describe("a project's own preset, by relative path", () => {
|
|
121
|
-
const usenix =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
232
|
+
const usenix = venue(
|
|
233
|
+
{
|
|
234
|
+
extends: "paperlint:acm-sigconf",
|
|
235
|
+
format: {
|
|
236
|
+
columns: 1,
|
|
237
|
+
kinds: { full: { body_pages_max: 13, ref_pages_max: 0 } },
|
|
238
|
+
},
|
|
239
|
+
tex: { packages: { usenix: ["usenix.sty"] } },
|
|
240
|
+
rules: { "pdf/body-size": "off" },
|
|
127
241
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
242
|
+
"USENIX Security",
|
|
243
|
+
);
|
|
131
244
|
|
|
132
245
|
it("./ is relative to the file that says it; merged per block, child wins", () => {
|
|
133
246
|
const r = resolve("../../venues/usenix-sec.jsonc", {
|
|
@@ -147,18 +260,21 @@ describe("a project's own preset, by relative path", () => {
|
|
|
147
260
|
expect(r.value.label).toBe("USENIX Security");
|
|
148
261
|
});
|
|
149
262
|
|
|
150
|
-
it("
|
|
263
|
+
it("a family names no venue: its label is the file name without its extension", () => {
|
|
151
264
|
const r = resolve("./venues/usenix-sec.jsonc", {
|
|
152
|
-
"/work/papers/p/venues/usenix-sec.jsonc":
|
|
265
|
+
"/work/papers/p/venues/usenix-sec.jsonc": family({
|
|
153
266
|
extends: "paperlint:acm-sigconf",
|
|
154
267
|
}),
|
|
155
268
|
});
|
|
156
269
|
expect(r.ok && r.value.label).toBe("usenix-sec");
|
|
270
|
+
expect(r.ok && r.value.identity).toEqual({ type: "family" });
|
|
157
271
|
});
|
|
272
|
+
});
|
|
158
273
|
|
|
274
|
+
describe("a project's own preset: what its children replace", () => {
|
|
159
275
|
it("a child kind replaces that kind wholesale; other kinds are kept", () => {
|
|
160
276
|
const r = resolve("./mine.jsonc", {
|
|
161
|
-
"/work/papers/p/mine.jsonc":
|
|
277
|
+
"/work/papers/p/mine.jsonc": venue({
|
|
162
278
|
extends: "paperlint:agenticdev",
|
|
163
279
|
format: { kinds: { short: { body_pages_max: 6 } } },
|
|
164
280
|
}),
|
|
@@ -174,13 +290,40 @@ describe("a project's own preset, by relative path", () => {
|
|
|
174
290
|
|
|
175
291
|
it("rules: the leaf wins over its parent, per rule id", () => {
|
|
176
292
|
const r = resolve("./mine.jsonc", {
|
|
177
|
-
"/work/papers/p/mine.jsonc":
|
|
293
|
+
"/work/papers/p/mine.jsonc": venue({
|
|
178
294
|
extends: "paperlint:agenticdev",
|
|
179
295
|
rules: { "pdf/last-page-balance": "off" },
|
|
180
296
|
}),
|
|
181
297
|
});
|
|
182
298
|
expect(r.ok && r.value.rules["pdf/last-page-balance"]).toBe("off");
|
|
183
299
|
});
|
|
300
|
+
|
|
301
|
+
it("ruleOrigins: each rule's entry names the file of the chain that set it", () => {
|
|
302
|
+
const r = resolve("./mine.jsonc", {
|
|
303
|
+
"/work/papers/p/mine.jsonc": venue({
|
|
304
|
+
extends: "paperlint:agenticdev",
|
|
305
|
+
rules: { "pdf/body-size": "off" },
|
|
306
|
+
}),
|
|
307
|
+
});
|
|
308
|
+
// Guards: an own rule is the leaf's, an inherited one names the parent that set it.
|
|
309
|
+
expect(r.ok && r.value.ruleOrigins).toEqual({
|
|
310
|
+
"pdf/last-page-balance": join(VENUES, "agenticdev.jsonc"),
|
|
311
|
+
"tex/heading-case": join(VENUES, "agenticdev.jsonc"),
|
|
312
|
+
"pdf/body-size": "/work/papers/p/mine.jsonc",
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("ruleOrigins: a rule the leaf sets again is the leaf's, not its parent's", () => {
|
|
317
|
+
const r = resolve("./mine.jsonc", {
|
|
318
|
+
"/work/papers/p/mine.jsonc": venue({
|
|
319
|
+
extends: "paperlint:agenticdev",
|
|
320
|
+
rules: { "tex/heading-case": "off" },
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
expect(r.ok && r.value.ruleOrigins["tex/heading-case"]).toBe(
|
|
324
|
+
"/work/papers/p/mine.jsonc",
|
|
325
|
+
);
|
|
326
|
+
});
|
|
184
327
|
});
|
|
185
328
|
|
|
186
329
|
describe("what does not resolve, and says why", () => {
|
|
@@ -196,7 +339,7 @@ describe("what does not resolve, and says why", () => {
|
|
|
196
339
|
"a shipped name with a typo",
|
|
197
340
|
"paperlint:agenticdve",
|
|
198
341
|
"not-found",
|
|
199
|
-
/acm-sigconf, agenticdev, aidc, aisec, ieee-conference, realm/,
|
|
342
|
+
/acm-sigconf, agenticdev, aidc, aisec, ieee-conference, msr, realm/,
|
|
200
343
|
],
|
|
201
344
|
["the base TeX set", "paperlint:tex-base", "not-found", /agenticdev/],
|
|
202
345
|
[
|
|
@@ -217,8 +360,8 @@ describe("what does not resolve, and says why", () => {
|
|
|
217
360
|
describe("chains that do not resolve", () => {
|
|
218
361
|
it("a cycle is refused, naming the chain", () => {
|
|
219
362
|
const r = resolve("./a.jsonc", {
|
|
220
|
-
"/work/papers/p/a.jsonc":
|
|
221
|
-
"/work/papers/p/b.jsonc":
|
|
363
|
+
"/work/papers/p/a.jsonc": family({ extends: "./b.jsonc" }),
|
|
364
|
+
"/work/papers/p/b.jsonc": family({ extends: "./a.jsonc" }),
|
|
222
365
|
});
|
|
223
366
|
expect(r.ok).toBe(false);
|
|
224
367
|
if (r.ok) return;
|
|
@@ -231,7 +374,7 @@ describe("chains that do not resolve", () => {
|
|
|
231
374
|
it(`a chain longer than ${String(MAX_PRESET_DEPTH)} is refused, printing it`, () => {
|
|
232
375
|
const files: Record<string, string> = {};
|
|
233
376
|
for (let i = 1; i <= 5; i++)
|
|
234
|
-
files[`/work/papers/p/p${String(i)}.jsonc`] =
|
|
377
|
+
files[`/work/papers/p/p${String(i)}.jsonc`] = venue({
|
|
235
378
|
extends: i < 5 ? `./p${String(i + 1)}.jsonc` : "paperlint:acm-sigconf",
|
|
236
379
|
});
|
|
237
380
|
const r = resolve("./p1.jsonc", files);
|
|
@@ -243,17 +386,17 @@ describe("chains that do not resolve", () => {
|
|
|
243
386
|
|
|
244
387
|
it("a chain of exactly the limit resolves", () => {
|
|
245
388
|
const r = resolve("./p1.jsonc", {
|
|
246
|
-
"/work/papers/p/p1.jsonc":
|
|
247
|
-
"/work/papers/p/p2.jsonc":
|
|
248
|
-
extends: "paperlint:agenticdev",
|
|
249
|
-
}),
|
|
389
|
+
"/work/papers/p/p1.jsonc": venue({ extends: "./p2.jsonc" }),
|
|
390
|
+
"/work/papers/p/p2.jsonc": venue({ extends: "paperlint:agenticdev" }),
|
|
250
391
|
});
|
|
251
392
|
expect(r.ok && r.value.chain.length).toBe(MAX_PRESET_DEPTH);
|
|
252
393
|
});
|
|
394
|
+
});
|
|
253
395
|
|
|
396
|
+
describe("chains that do not resolve: the files themselves", () => {
|
|
254
397
|
it("a preset that fails the schema is named with its file", () => {
|
|
255
398
|
const r = resolve("./bad.jsonc", {
|
|
256
|
-
"/work/papers/p/bad.jsonc":
|
|
399
|
+
"/work/papers/p/bad.jsonc": family({
|
|
257
400
|
extends: "paperlint:acm-sigconf",
|
|
258
401
|
colums: 2,
|
|
259
402
|
}),
|
|
@@ -266,16 +409,51 @@ describe("chains that do not resolve", () => {
|
|
|
266
409
|
|
|
267
410
|
it("a preset with no `extends` must carry `tex` (it is a root)", () => {
|
|
268
411
|
const r = resolve("./root.jsonc", {
|
|
269
|
-
"/work/papers/p/root.jsonc":
|
|
412
|
+
"/work/papers/p/root.jsonc": family({ format: { columns: 1 } }),
|
|
413
|
+
});
|
|
414
|
+
expect(r.ok).toBe(false);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("a family that extends a venue is refused, naming both files", () => {
|
|
418
|
+
const r = resolve("./fam.jsonc", {
|
|
419
|
+
"/work/papers/p/fam.jsonc": family({ extends: "paperlint:aisec" }),
|
|
420
|
+
});
|
|
421
|
+
expect(r.ok).toBe(false);
|
|
422
|
+
if (r.ok) return;
|
|
423
|
+
expect(presetProblemText(r.error)).toBe(
|
|
424
|
+
`the preset /work/papers/p/fam.jsonc is a template family and extends ${join(VENUES, "aisec.jsonc")}, a venue — a family builds only on a family; a venue extends a family or another venue`,
|
|
425
|
+
);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("nothing extends the TeX base set, even by a relative path", () => {
|
|
429
|
+
const r = resolve("./mine.jsonc", {
|
|
430
|
+
"/work/papers/p/mine.jsonc": venue({
|
|
431
|
+
extends: relative("/work/papers/p", join(VENUES, "tex-base.jsonc")),
|
|
432
|
+
}),
|
|
270
433
|
});
|
|
271
434
|
expect(r.ok).toBe(false);
|
|
435
|
+
if (r.ok) return;
|
|
436
|
+
expect(presetProblemText(r.error)).toBe(
|
|
437
|
+
`the preset /work/papers/p/mine.jsonc extends ${join(VENUES, "tex-base.jsonc")}, the TeX base set — every paper gets that set already, and no preset extends it`,
|
|
438
|
+
);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("a venue may extend a venue, and the resolved identity is the leaf's", () => {
|
|
442
|
+
const r = resolve("./mine.jsonc", {
|
|
443
|
+
"/work/papers/p/mine.jsonc": venue({ extends: "paperlint:aisec" }),
|
|
444
|
+
});
|
|
445
|
+
expect(r.ok && r.value.identity).toEqual({
|
|
446
|
+
type: "venue",
|
|
447
|
+
name: "Our Workshop",
|
|
448
|
+
url: "https://example.org/cfp",
|
|
449
|
+
});
|
|
272
450
|
});
|
|
273
451
|
});
|
|
274
452
|
|
|
275
453
|
describe("a preset's template: kept as its file spells it, with that file", () => {
|
|
276
454
|
it("a child's template replaces its parent's, and names its own file", () => {
|
|
277
455
|
const r = resolve("./own.jsonc", {
|
|
278
|
-
"/work/papers/p/own.jsonc":
|
|
456
|
+
"/work/papers/p/own.jsonc": venue({
|
|
279
457
|
extends: "paperlint:aidc",
|
|
280
458
|
template: "\\documentclass[conference]{IEEEtran}",
|
|
281
459
|
}),
|
|
@@ -283,7 +461,31 @@ describe("a preset's template: kept as its file spells it, with that file", () =
|
|
|
283
461
|
expect(r.ok && r.value.template).toEqual({
|
|
284
462
|
text: "\\documentclass[conference]{IEEEtran}",
|
|
285
463
|
file: "/work/papers/p/own.jsonc",
|
|
464
|
+
forbids: [],
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("the options a file forbids travel with its template", () => {
|
|
469
|
+
const r = resolve("./own.jsonc", {
|
|
470
|
+
"/work/papers/p/own.jsonc": venue({
|
|
471
|
+
extends: "paperlint:ieee-conference",
|
|
472
|
+
template: "\\documentclass[conference]{IEEEtran}",
|
|
473
|
+
template_forbids: ["compsoc"],
|
|
474
|
+
}),
|
|
475
|
+
});
|
|
476
|
+
expect(r.ok && r.value.template?.forbids).toEqual(["compsoc"]);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("forbidden options without a template of the same file are refused by the schema", () => {
|
|
480
|
+
const r = resolve("./own.jsonc", {
|
|
481
|
+
"/work/papers/p/own.jsonc": venue({
|
|
482
|
+
extends: "paperlint:aidc",
|
|
483
|
+
template_forbids: ["compsoc"],
|
|
484
|
+
}),
|
|
286
485
|
});
|
|
486
|
+
expect(r.ok).toBe(false);
|
|
487
|
+
if (r.ok) return;
|
|
488
|
+
expect(presetProblemText(r.error)).toMatch(/template_forbids/);
|
|
287
489
|
});
|
|
288
490
|
});
|
|
289
491
|
|
|
@@ -302,8 +504,17 @@ describe("labelOf — display only, never used to resolve", () => {
|
|
|
302
504
|
describe("shippedVenueNames — what each shipped venue is called", () => {
|
|
303
505
|
it("every shipped preset, by its label and the names along its chain", () => {
|
|
304
506
|
const names = shippedVenueNames(deps());
|
|
305
|
-
|
|
306
|
-
expect(names.
|
|
507
|
+
// A venue is labelled by its `name`, a family by its file name.
|
|
508
|
+
expect(names.map((n) => n.label)).toEqual([
|
|
509
|
+
"acm-sigconf",
|
|
510
|
+
"AgenticDev",
|
|
511
|
+
"AIDC",
|
|
512
|
+
"AISec",
|
|
513
|
+
"ieee-conference",
|
|
514
|
+
"MSR",
|
|
515
|
+
"REALM",
|
|
516
|
+
]);
|
|
517
|
+
expect(names.find((n) => n.label === "AISec")?.aliases).toEqual(
|
|
307
518
|
expect.arrayContaining(["AISec", "ACM CCS"]),
|
|
308
519
|
);
|
|
309
520
|
});
|
|
@@ -316,6 +527,13 @@ describe("shippedVenueNames — what each shipped venue is called", () => {
|
|
|
316
527
|
);
|
|
317
528
|
expect(
|
|
318
529
|
shippedVenueNames({ files, venuesDir: VENUES }).map((n) => n.label),
|
|
319
|
-
).toEqual(
|
|
530
|
+
).toEqual([
|
|
531
|
+
"acm-sigconf",
|
|
532
|
+
"AgenticDev",
|
|
533
|
+
"AIDC",
|
|
534
|
+
"AISec",
|
|
535
|
+
"ieee-conference",
|
|
536
|
+
"MSR",
|
|
537
|
+
]);
|
|
320
538
|
});
|
|
321
539
|
});
|