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
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { test } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
identityHint,
|
|
5
|
+
identityHints,
|
|
6
|
+
loadTypescript,
|
|
7
|
+
parsePreset,
|
|
8
|
+
} from "./tex-requirements.ts";
|
|
4
9
|
|
|
5
10
|
test("loadTypescript refuses a module without the two functions a profile is read with", () => {
|
|
6
11
|
assert.throws(
|
|
@@ -19,3 +24,124 @@ test("loadTypescript hands back what `require` loaded when it has them", () => {
|
|
|
19
24
|
ts,
|
|
20
25
|
);
|
|
21
26
|
});
|
|
27
|
+
|
|
28
|
+
// ── what a preset is: the identity its `type` declares ─────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/** What `parsePreset` throws for `preset`, as text; "" when it parses. */
|
|
31
|
+
function refusalOf(name: string, preset: object): string {
|
|
32
|
+
try {
|
|
33
|
+
parsePreset(JSON.stringify(preset), name);
|
|
34
|
+
return "";
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return e instanceof Error ? e.message : String(e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test("a venue without the link to its call for papers is refused at load, saying what to add", () => {
|
|
41
|
+
// Guards: the wrong state the identity exists to make unrepresentable — a venue nobody can check.
|
|
42
|
+
assert.equal(
|
|
43
|
+
refusalOf("v.jsonc", {
|
|
44
|
+
type: "venue",
|
|
45
|
+
name: "X",
|
|
46
|
+
extends: "paperlint:acm-sigconf",
|
|
47
|
+
}),
|
|
48
|
+
[
|
|
49
|
+
"v.jsonc does not match venue-profile.schema.json:",
|
|
50
|
+
' a venue preset names its venue and links its call for papers: "name": "<the venue, as its call spells it>", "url": "https://<the call for papers>"',
|
|
51
|
+
" v.jsonc: (top level) should have required property 'url'",
|
|
52
|
+
' v.jsonc: (top level) should match "then" schema',
|
|
53
|
+
].join("\n"),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("a template family with a venue's name and url is refused, naming each key", () => {
|
|
58
|
+
assert.equal(
|
|
59
|
+
refusalOf("f.jsonc", {
|
|
60
|
+
type: "family",
|
|
61
|
+
name: "ACM",
|
|
62
|
+
url: "https://acm.org/",
|
|
63
|
+
extends: "paperlint:acm-sigconf",
|
|
64
|
+
}),
|
|
65
|
+
[
|
|
66
|
+
"f.jsonc does not match venue-profile.schema.json:",
|
|
67
|
+
' a template family names no venue: drop "name" — "name" and "url" belong to the venue presets that extend it',
|
|
68
|
+
' a template family names no venue: drop "url" — "name" and "url" belong to the venue presets that extend it',
|
|
69
|
+
" f.jsonc: .name boolean schema is false",
|
|
70
|
+
" f.jsonc: .url boolean schema is false",
|
|
71
|
+
' f.jsonc: (top level) should match "then" schema',
|
|
72
|
+
].join("\n"),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("a preset that does not say what it is is refused, with the three answers", () => {
|
|
77
|
+
assert.equal(
|
|
78
|
+
refusalOf("n.jsonc", { extends: "paperlint:acm-sigconf" }),
|
|
79
|
+
[
|
|
80
|
+
"n.jsonc does not match venue-profile.schema.json:",
|
|
81
|
+
' say what this preset is: "type": "venue" (one venue\'s call for papers), "family" (a publisher\'s template that venues extend) or "base" (the TeX base set)',
|
|
82
|
+
" n.jsonc: (top level) should have required property 'type'",
|
|
83
|
+
].join("\n"),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("the TeX base set holds its packages and nothing else", () => {
|
|
88
|
+
assert.equal(
|
|
89
|
+
refusalOf("b.jsonc", {
|
|
90
|
+
type: "base",
|
|
91
|
+
tex: { packages: { a: ["a.sty"] } },
|
|
92
|
+
format: { columns: 2 },
|
|
93
|
+
}),
|
|
94
|
+
[
|
|
95
|
+
"b.jsonc does not match venue-profile.schema.json:",
|
|
96
|
+
' the TeX base set holds "type" and "tex" only — a venue\'s or a family\'s settings go in its own preset',
|
|
97
|
+
" b.jsonc: (top level) should NOT have more than 2 properties",
|
|
98
|
+
' b.jsonc: (top level) should match "then" schema',
|
|
99
|
+
].join("\n"),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a venue is parsed into its identity; a family and the base set carry none of a venue's fields", () => {
|
|
104
|
+
const read = (p: object) =>
|
|
105
|
+
parsePreset(JSON.stringify(p), "p.jsonc").identity;
|
|
106
|
+
assert.deepEqual(
|
|
107
|
+
[
|
|
108
|
+
read({
|
|
109
|
+
type: "venue",
|
|
110
|
+
name: "X",
|
|
111
|
+
url: "https://x.org/cfp",
|
|
112
|
+
extends: "paperlint:acm-sigconf",
|
|
113
|
+
}),
|
|
114
|
+
read({ type: "family", extends: "paperlint:acm-sigconf" }),
|
|
115
|
+
read({ type: "base", tex: { packages: { a: ["a.sty"] } } }),
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
{ type: "venue", name: "X", url: "https://x.org/cfp" },
|
|
119
|
+
{ type: "family" },
|
|
120
|
+
{ type: "base" },
|
|
121
|
+
],
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("identityHint has nothing to add to a violation that is not about the identity", () => {
|
|
126
|
+
assert.equal(
|
|
127
|
+
identityHint({
|
|
128
|
+
keyword: "additionalProperties",
|
|
129
|
+
dataPath: "",
|
|
130
|
+
params: { additionalProperty: "colums" },
|
|
131
|
+
}),
|
|
132
|
+
null,
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("identityHints: Ajv's null errors give none; a hint two violations share is given once", () => {
|
|
137
|
+
assert.deepEqual(identityHints({ errors: null }), []);
|
|
138
|
+
const missing = {
|
|
139
|
+
keyword: "required",
|
|
140
|
+
dataPath: "",
|
|
141
|
+
schemaPath: "#/required",
|
|
142
|
+
params: { missingProperty: "type" },
|
|
143
|
+
};
|
|
144
|
+
const hints = identityHints({ errors: [missing, missing] });
|
|
145
|
+
assert.equal(hints.length, 1);
|
|
146
|
+
assert.deepEqual(hints, [identityHint(missing)]);
|
|
147
|
+
});
|
package/src/tex-requirements.ts
CHANGED
|
@@ -140,6 +140,11 @@ export interface VenueFormat {
|
|
|
140
140
|
readonly fontsTitle: string | null;
|
|
141
141
|
/** How the body is counted: null is banal's count; `references`, the pages before the references. */
|
|
142
142
|
readonly bodyEndsAt: "references" | null;
|
|
143
|
+
/**
|
|
144
|
+
* With `bodyEndsAt`: appendices count as body wherever they sit, and the reference pages are the
|
|
145
|
+
* ones holding only references. Null: the appendix ends the body, as `references` alone counts it.
|
|
146
|
+
*/
|
|
147
|
+
readonly appendixInBody: boolean | null;
|
|
143
148
|
readonly kinds: ReadonlyMap<string, KindLimits>;
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -155,6 +160,7 @@ export const NO_FORMAT: VenueFormat = {
|
|
|
155
160
|
fontsText: null,
|
|
156
161
|
fontsTitle: null,
|
|
157
162
|
bodyEndsAt: null,
|
|
163
|
+
appendixInBody: null,
|
|
158
164
|
kinds: new Map(),
|
|
159
165
|
};
|
|
160
166
|
|
|
@@ -165,21 +171,38 @@ export interface RequiredSection {
|
|
|
165
171
|
readonly position: "last" | null;
|
|
166
172
|
}
|
|
167
173
|
|
|
174
|
+
/**
|
|
175
|
+
* What a preset file IS, as its `type` declares it. A venue names itself and its call for papers; a
|
|
176
|
+
* template family and the base set name no venue, so they cannot carry either — the union has no
|
|
177
|
+
* field to put them in, the way the schema refuses them.
|
|
178
|
+
*/
|
|
179
|
+
export type PresetIdentity =
|
|
180
|
+
| { readonly type: "venue"; readonly name: string; readonly url: string }
|
|
181
|
+
| { readonly type: "family" }
|
|
182
|
+
| { readonly type: "base" };
|
|
183
|
+
|
|
184
|
+
/** A preset's kind of file: `venue`, `family` or `base`. */
|
|
185
|
+
export type PresetType = PresetIdentity["type"];
|
|
186
|
+
|
|
168
187
|
/**
|
|
169
188
|
* One preset FILE, parsed — before its `extends` chain is resolved (`src/presets.ts` does that).
|
|
170
|
-
* A preset is a venue
|
|
189
|
+
* A preset is a venue, a template family, or the TeX base set (`identity`).
|
|
171
190
|
*/
|
|
172
191
|
export interface PresetFile {
|
|
192
|
+
/** What the file is, and for a venue, its name and call for papers. */
|
|
193
|
+
readonly identity: PresetIdentity;
|
|
173
194
|
/** The preset this one builds on: `paperlint:<name>` or a relative path. */
|
|
174
195
|
readonly extends: string | null;
|
|
175
|
-
/** A display name for messages; the file name otherwise. */
|
|
176
|
-
readonly name: string | null;
|
|
177
196
|
/** The `\\documentclass` the venue's template uses, as the file spells it; null when it names none. */
|
|
178
197
|
readonly template: string | null;
|
|
198
|
+
/** Class options the venue forbids, read with `template`; empty when the file names none. */
|
|
199
|
+
readonly templateForbids: readonly string[];
|
|
179
200
|
/** Other names the venue goes by in a paper's text; empty when the file names none. */
|
|
180
201
|
readonly aliases: readonly string[];
|
|
181
202
|
/** Whether the venue reviews double-blind; null when the file does not say. */
|
|
182
203
|
readonly blind: boolean | null;
|
|
204
|
+
/** When the venue's names count as a mention in a paper's text; null when the file does not say. */
|
|
205
|
+
readonly mentions: "with-year" | null;
|
|
183
206
|
/** Sections the venue requires; null when the file names none (a child's list replaces its parent's). */
|
|
184
207
|
readonly requiredSections: readonly RequiredSection[] | null;
|
|
185
208
|
/** Null when the file declares no `tex` block (allowed only with `extends`). */
|
|
@@ -214,16 +237,28 @@ interface FormatJson {
|
|
|
214
237
|
readonly fonts_text?: string;
|
|
215
238
|
readonly fonts_title?: string;
|
|
216
239
|
readonly body_ends_at?: "references";
|
|
240
|
+
readonly appendix_in_body?: boolean;
|
|
217
241
|
readonly kinds?: KindsJson;
|
|
218
242
|
}
|
|
219
243
|
|
|
220
|
-
/**
|
|
221
|
-
|
|
244
|
+
/**
|
|
245
|
+
* The preset's JSON after the schema accepted it — the shape `venue-profile.schema.json` allows,
|
|
246
|
+
* the identity included: a venue has its `name` and `url`, a family and the base set have neither.
|
|
247
|
+
*/
|
|
248
|
+
type PresetJson = PresetBodyJson &
|
|
249
|
+
(
|
|
250
|
+
| { readonly type: "venue"; readonly name: string; readonly url: string }
|
|
251
|
+
| { readonly type: "family" | "base" }
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
/** Every key of a preset but its identity. */
|
|
255
|
+
interface PresetBodyJson {
|
|
222
256
|
readonly extends?: string;
|
|
223
|
-
readonly name?: string;
|
|
224
257
|
readonly template?: string;
|
|
258
|
+
readonly template_forbids?: readonly string[];
|
|
225
259
|
readonly aliases?: readonly string[];
|
|
226
260
|
readonly blind?: boolean;
|
|
261
|
+
readonly mentions?: "with-year";
|
|
227
262
|
readonly required_sections?: readonly {
|
|
228
263
|
readonly title: string;
|
|
229
264
|
readonly position?: "last";
|
|
@@ -270,6 +305,7 @@ function formatOf(j: FormatJson = {}): VenueFormat {
|
|
|
270
305
|
fontsText: orNull(j.fonts_text),
|
|
271
306
|
fontsTitle: orNull(j.fonts_title),
|
|
272
307
|
bodyEndsAt: orNull(j.body_ends_at),
|
|
308
|
+
appendixInBody: orNull(j.appendix_in_body),
|
|
273
309
|
kinds: kindsOf(j.kinds),
|
|
274
310
|
};
|
|
275
311
|
}
|
|
@@ -282,6 +318,46 @@ const sectionsOf = (
|
|
|
282
318
|
? null
|
|
283
319
|
: r.map((x) => ({ title: x.title, position: orNull(x.position) }));
|
|
284
320
|
|
|
321
|
+
/** The identity a preset's `type` declares. Pure. */
|
|
322
|
+
const identityOf = (j: PresetJson): PresetIdentity =>
|
|
323
|
+
j.type === "venue"
|
|
324
|
+
? { type: "venue", name: j.name, url: j.url }
|
|
325
|
+
: { type: j.type };
|
|
326
|
+
|
|
327
|
+
/** One ajv error, as far as the hints read it. */
|
|
328
|
+
interface SchemaError {
|
|
329
|
+
readonly keyword: string;
|
|
330
|
+
readonly dataPath: string;
|
|
331
|
+
readonly params: object;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* What to write instead, for the violations of a preset's identity — ajv's own words for them
|
|
336
|
+
* ("should have required property 'url'", "boolean schema is false") name the key but not why.
|
|
337
|
+
* Read off the error's keyword and parameters, never its message text. Pure.
|
|
338
|
+
*/
|
|
339
|
+
export function identityHint(e: SchemaError): string | null {
|
|
340
|
+
const missing = fieldOf(e.params, "missingProperty");
|
|
341
|
+
if (e.keyword === "required" && missing === "type")
|
|
342
|
+
return 'say what this preset is: "type": "venue" (one venue\'s call for papers), "family" (a publisher\'s template that venues extend) or "base" (the TeX base set)';
|
|
343
|
+
if (e.keyword === "required" && (missing === "name" || missing === "url"))
|
|
344
|
+
return 'a venue preset names its venue and links its call for papers: "name": "<the venue, as its call spells it>", "url": "https://<the call for papers>"';
|
|
345
|
+
if (e.keyword === "false schema")
|
|
346
|
+
return `a template family names no venue: drop "${e.dataPath.slice(1)}" — "name" and "url" belong to the venue presets that extend it`;
|
|
347
|
+
if (e.keyword === "maxProperties")
|
|
348
|
+
return 'the TeX base set holds "type" and "tex" only — a venue\'s or a family\'s settings go in its own preset';
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** The hints for every violation that has one, each once. Ajv's null `errors` has none. */
|
|
353
|
+
export const identityHints = (
|
|
354
|
+
v: Readonly<Pick<Validate, "errors">>,
|
|
355
|
+
): readonly string[] => [
|
|
356
|
+
...new Set(
|
|
357
|
+
(v.errors ?? []).map(identityHint).filter((h): h is string => h !== null),
|
|
358
|
+
),
|
|
359
|
+
];
|
|
360
|
+
|
|
285
361
|
/** A preset's JSONC text → its value, or an Error naming the syntax problem. */
|
|
286
362
|
function jsoncOf(text: string, file: string): unknown {
|
|
287
363
|
const parsed = typescript().parseConfigFileTextToJson(file, text);
|
|
@@ -314,6 +390,10 @@ const anchorsOf = (
|
|
|
314
390
|
},
|
|
315
391
|
}));
|
|
316
392
|
|
|
393
|
+
/** A preset's `portal`, or null when it names none. */
|
|
394
|
+
const portalOf = (p: PresetJson["portal"]): Readonly<VenuePortal> | null =>
|
|
395
|
+
p === undefined ? null : { kind: p.kind, url: p.url };
|
|
396
|
+
|
|
317
397
|
/**
|
|
318
398
|
* The text of one preset file → the typed file, or an Error naming every problem. The ONE parser
|
|
319
399
|
* of a preset: the toolchain reads its `tex`, the venue rules its `format`, the config its `rules`.
|
|
@@ -331,25 +411,24 @@ export function parsePreset(
|
|
|
331
411
|
const validate = validatorFor(dir);
|
|
332
412
|
if (!matchesSchema(config, validate))
|
|
333
413
|
throw new Error(
|
|
334
|
-
`${file} does not match ${SCHEMA_FILE}:\n ${violations(file, validate).join("\n ")}`,
|
|
414
|
+
`${file} does not match ${SCHEMA_FILE}:\n ${[...identityHints(validate), ...violations(file, validate)].join("\n ")}`,
|
|
335
415
|
);
|
|
336
416
|
const j = config;
|
|
337
417
|
return {
|
|
418
|
+
identity: identityOf(j),
|
|
338
419
|
extends: orNull(j.extends),
|
|
339
|
-
name: orNull(j.name),
|
|
340
420
|
template: orNull(j.template),
|
|
421
|
+
templateForbids: j.template_forbids ?? [],
|
|
341
422
|
aliases: j.aliases ?? [],
|
|
342
423
|
blind: orNull(j.blind),
|
|
424
|
+
mentions: orNull(j.mentions),
|
|
343
425
|
requiredSections: sectionsOf(j.required_sections),
|
|
344
426
|
tex: texOf(j.tex),
|
|
345
427
|
format: formatOf(j.format),
|
|
346
428
|
rules: j.rules ?? {},
|
|
347
429
|
registerAnchors: anchorsOf(j.register),
|
|
348
430
|
talk: j.talk === undefined ? null : venueTalkOf(j.talk),
|
|
349
|
-
portal:
|
|
350
|
-
j.portal === undefined
|
|
351
|
-
? null
|
|
352
|
-
: { kind: j.portal.kind, url: j.portal.url },
|
|
431
|
+
portal: portalOf(j.portal),
|
|
353
432
|
};
|
|
354
433
|
}
|
|
355
434
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "./tex-venue-rules.ts";
|
|
21
21
|
import { resolvePreset } from "./presets.ts";
|
|
22
22
|
import { buildConfig, SHIPPED_RULES } from "./cli.ts";
|
|
23
|
+
import { venuePreset } from "../test/support.ts";
|
|
23
24
|
|
|
24
25
|
const VENUES = presetsDir();
|
|
25
26
|
const PAPER = "/work/papers/p";
|
|
@@ -136,13 +137,13 @@ describe("tex/template — the class and every option the preset names", () => {
|
|
|
136
137
|
"[conference]{IEEEtran} — compsoc missing",
|
|
137
138
|
"\\documentclass[conference]{IEEEtran}",
|
|
138
139
|
["tex/template:missingOption"],
|
|
139
|
-
/the class option `compsoc` is missing:
|
|
140
|
+
/the class option `compsoc` is missing: AIDC requires `\\documentclass\[conference,compsoc\]\{IEEEtran\}`/,
|
|
140
141
|
],
|
|
141
142
|
[
|
|
142
143
|
"{acmart} under an IEEE preset",
|
|
143
144
|
"\\documentclass[sigconf]{acmart}",
|
|
144
145
|
["tex/template:wrongClass"],
|
|
145
|
-
/the class is `acmart`, and
|
|
146
|
+
/the class is `acmart`, and AIDC requires/,
|
|
146
147
|
],
|
|
147
148
|
[
|
|
148
149
|
"[journal,compsoc]{IEEEtran} — conference missing",
|
|
@@ -183,7 +184,7 @@ describe("tex/template — the class line's states: missing, empty, commented ou
|
|
|
183
184
|
expect(ids(fs)).toEqual(["tex/template:emptyClass"]);
|
|
184
185
|
expect(fs[0]?.line).toBe(2);
|
|
185
186
|
expect(fs[0]?.message).toBe(
|
|
186
|
-
"the \\documentclass names no class;
|
|
187
|
+
"the \\documentclass names no class; AIDC requires `\\documentclass[conference,compsoc]{IEEEtran}`",
|
|
187
188
|
);
|
|
188
189
|
});
|
|
189
190
|
|
|
@@ -258,7 +259,7 @@ describe("tex/template — several \\documentclass lines behind a TeX switch", (
|
|
|
258
259
|
// Line 4: the first candidate, after the comment, `\\def` and `\\if` lines.
|
|
259
260
|
expect(fs[0]?.line).toBe(4);
|
|
260
261
|
expect(fs[0]?.message).toBe(
|
|
261
|
-
"none of the 2 \\documentclass lines is `\\documentclass[conference,compsoc]{IEEEtran}`, which
|
|
262
|
+
"none of the 2 \\documentclass lines is `\\documentclass[conference,compsoc]{IEEEtran}`, which AIDC requires: `\\documentclass{article}`, `\\documentclass[conference]{IEEEtran}`. The source picks one behind a TeX switch, which is not evaluated; make one of them the venue's",
|
|
262
263
|
);
|
|
263
264
|
});
|
|
264
265
|
|
|
@@ -309,13 +310,131 @@ describe("tex/template — a project's own preset, and nothing to judge", () =>
|
|
|
309
310
|
paper("\\documentclass{whatever}"),
|
|
310
311
|
{ extends: "./own.jsonc" },
|
|
311
312
|
{
|
|
312
|
-
extra: {
|
|
313
|
+
extra: {
|
|
314
|
+
[own]: JSON.stringify({
|
|
315
|
+
type: "family",
|
|
316
|
+
tex: { packages: { x: ["x"] } },
|
|
317
|
+
}),
|
|
318
|
+
},
|
|
313
319
|
},
|
|
314
320
|
),
|
|
315
321
|
).toEqual([]);
|
|
316
322
|
});
|
|
317
323
|
});
|
|
318
324
|
|
|
325
|
+
/** A project's own venue on IEEEtran's conference class that forbids the two compsoc options. */
|
|
326
|
+
const forbidding = (body: Record<string, unknown> = {}) => ({
|
|
327
|
+
[`${PAPER}/own.jsonc`]: JSON.stringify(
|
|
328
|
+
venuePreset("Own", {
|
|
329
|
+
extends: "paperlint:ieee-conference",
|
|
330
|
+
template: "\\documentclass[10pt,conference]{IEEEtran}",
|
|
331
|
+
template_forbids: ["compsoc", "compsocconf"],
|
|
332
|
+
...body,
|
|
333
|
+
}),
|
|
334
|
+
),
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("tex/template — class options the preset forbids (`template_forbids`)", () => {
|
|
338
|
+
const judge = (cls: string, extra = forbidding()) =>
|
|
339
|
+
lint(paper(cls, "Text."), { extends: "./own.jsonc" }, { extra });
|
|
340
|
+
|
|
341
|
+
it("the template's own line passes, and so does an extra option the preset does not forbid", () => {
|
|
342
|
+
expect(judge("\\documentclass[10pt,conference]{IEEEtran}")).toEqual([]);
|
|
343
|
+
expect(
|
|
344
|
+
judge("\\documentclass[10pt,conference,letterpaper]{IEEEtran}"),
|
|
345
|
+
).toEqual([]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("🔴 a forbidden option is reported at the class line, naming it and the venue's line", () => {
|
|
349
|
+
const fs = judge("\\documentclass[10pt,conference,compsoc]{IEEEtran}");
|
|
350
|
+
expect(ids(fs)).toEqual(["tex/template:forbiddenOption"]);
|
|
351
|
+
expect(fs[0]?.message).toBe(
|
|
352
|
+
"the class option `compsoc` is forbidden: Own requires `\\documentclass[10pt,conference]{IEEEtran}` without `compsoc`, `compsocconf`",
|
|
353
|
+
);
|
|
354
|
+
expect(fs[0]?.line).toBe(2);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("each forbidden option is one finding, beside a missing one", () => {
|
|
358
|
+
expect(
|
|
359
|
+
ids(judge("\\documentclass[compsoc,compsocconf,conference]{IEEEtran}")),
|
|
360
|
+
).toEqual([
|
|
361
|
+
"tex/template:forbiddenOption",
|
|
362
|
+
"tex/template:forbiddenOption",
|
|
363
|
+
"tex/template:missingOption",
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe("tex/template under paperlint:msr — `[10pt,conference]`, without compsoc or compsocconf", () => {
|
|
369
|
+
const MSR = { extends: "paperlint:msr", kind: "technical" };
|
|
370
|
+
|
|
371
|
+
it("the class line MSR's call names passes", () => {
|
|
372
|
+
expect(
|
|
373
|
+
lint(paper("\\documentclass[10pt,conference]{IEEEtran}", "Text."), MSR),
|
|
374
|
+
).toEqual([]);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it.each([["compsoc"], ["compsocconf"]])(
|
|
378
|
+
"🔴 `%s` in the class options fails tex/template",
|
|
379
|
+
(option) => {
|
|
380
|
+
const fs = lint(
|
|
381
|
+
paper(`\\documentclass[10pt,conference,${option}]{IEEEtran}`, "Text."),
|
|
382
|
+
MSR,
|
|
383
|
+
);
|
|
384
|
+
expect(ids(fs)).toEqual(["tex/template:forbiddenOption"]);
|
|
385
|
+
expect(fs[0]?.message).toMatch(
|
|
386
|
+
`the class option \`${option}\` is forbidden: MSR requires`,
|
|
387
|
+
);
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
it("AIDC's class line fails MSR on both counts: compsoc forbidden, 10pt missing", () => {
|
|
392
|
+
expect(ids(lint(OFFICIAL, MSR))).toEqual([
|
|
393
|
+
"tex/template:forbiddenOption",
|
|
394
|
+
"tex/template:missingOption",
|
|
395
|
+
]);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe("tex/template — forbidden options behind a switch, and down a chain", () => {
|
|
400
|
+
it("behind a TeX switch, a candidate with a forbidden option is not the venue's class", () => {
|
|
401
|
+
const two = (a: string, b: string) =>
|
|
402
|
+
`\\def\\v{1}\n\\if\\v1 ${a} \\fi\n\\if\\v2 ${b} \\fi\n\\begin{document}\nText.\n\\end{document}\n`;
|
|
403
|
+
const compsoc = "\\documentclass[10pt,conference,compsoc]{IEEEtran}";
|
|
404
|
+
const opts = { extra: forbidding() };
|
|
405
|
+
expect(
|
|
406
|
+
ids(lint(two(compsoc, compsoc), { extends: "./own.jsonc" }, opts)),
|
|
407
|
+
).toEqual(["tex/template:noCandidate"]);
|
|
408
|
+
expect(
|
|
409
|
+
lint(
|
|
410
|
+
two(compsoc, "\\documentclass[10pt,conference]{IEEEtran}"),
|
|
411
|
+
{ extends: "./own.jsonc" },
|
|
412
|
+
opts,
|
|
413
|
+
),
|
|
414
|
+
).toEqual([]);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("a child's template replaces its parent's, the forbidden options with it", () => {
|
|
418
|
+
const child = `${PAPER}/child.jsonc`;
|
|
419
|
+
const extra = {
|
|
420
|
+
...forbidding(),
|
|
421
|
+
[child]: JSON.stringify(
|
|
422
|
+
venuePreset("Child", {
|
|
423
|
+
extends: "./own.jsonc",
|
|
424
|
+
template: "\\documentclass[conference]{IEEEtran}",
|
|
425
|
+
}),
|
|
426
|
+
),
|
|
427
|
+
};
|
|
428
|
+
expect(
|
|
429
|
+
lint(
|
|
430
|
+
paper("\\documentclass[conference,compsoc]{IEEEtran}", "Text."),
|
|
431
|
+
{ extends: "./child.jsonc" },
|
|
432
|
+
{ extra },
|
|
433
|
+
),
|
|
434
|
+
).toEqual([]);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
319
438
|
describe("tex/template — a template the reader cannot read, a bare class name, nothing to judge", () => {
|
|
320
439
|
it("a template the reader cannot read is said once, naming its file — the class is not judged", () => {
|
|
321
440
|
const own = `${PAPER}/own.jsonc`;
|
|
@@ -325,6 +444,7 @@ describe("tex/template — a template the reader cannot read, a bare class name,
|
|
|
325
444
|
{
|
|
326
445
|
extra: {
|
|
327
446
|
[own]: JSON.stringify({
|
|
447
|
+
type: "family",
|
|
328
448
|
template: "\\documentclass[a]{}",
|
|
329
449
|
tex: { packages: { x: ["x"] } },
|
|
330
450
|
}),
|
|
@@ -341,6 +461,7 @@ describe("tex/template — a template the reader cannot read, a bare class name,
|
|
|
341
461
|
const own = `${PAPER}/own.jsonc`;
|
|
342
462
|
const extra = {
|
|
343
463
|
[own]: JSON.stringify({
|
|
464
|
+
type: "family",
|
|
344
465
|
template: "article",
|
|
345
466
|
tex: { packages: { x: ["x"] } },
|
|
346
467
|
}),
|
|
@@ -353,7 +474,9 @@ describe("tex/template — a template the reader cannot read, a bare class name,
|
|
|
353
474
|
ids(lint(paper("\\documentclass{report}"), settings, { extra })),
|
|
354
475
|
).toEqual(["tex/template:wrongClass"]);
|
|
355
476
|
});
|
|
477
|
+
});
|
|
356
478
|
|
|
479
|
+
describe("tex/template — nothing to judge", () => {
|
|
357
480
|
it.each<[string, object | undefined, string]>([
|
|
358
481
|
["no paperlint.json", undefined, `${PAPER}/paper.tex`],
|
|
359
482
|
[
|
|
@@ -428,7 +551,7 @@ describe("tex/required-section — what fails", () => {
|
|
|
428
551
|
const fs = required(lint(tex, AIDC));
|
|
429
552
|
expect(ids(fs)).toEqual(["tex/required-section:missing"]);
|
|
430
553
|
expect(fs[0]?.message).toBe(
|
|
431
|
-
"
|
|
554
|
+
"AIDC requires a section titled «LLM Usage Statement» and there is none — add `\\section*{LLM Usage Statement}` (the title exactly; a bold paragraph does not count)",
|
|
432
555
|
);
|
|
433
556
|
// At `\end{document}`, where the section would go.
|
|
434
557
|
expect(fs[0]?.line).toBe(tex.split("\n").indexOf("\\end{document}") + 1);
|
|
@@ -503,10 +626,12 @@ describe("tex/required-section — the edges", () => {
|
|
|
503
626
|
it("a position other than last (none): anywhere will do; a child's list replaces its parent's", () => {
|
|
504
627
|
const own = `${PAPER}/own.jsonc`;
|
|
505
628
|
const extra = {
|
|
506
|
-
[own]: JSON.stringify(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
629
|
+
[own]: JSON.stringify(
|
|
630
|
+
venuePreset("own", {
|
|
631
|
+
extends: "paperlint:aidc",
|
|
632
|
+
required_sections: [{ title: "Ethics" }],
|
|
633
|
+
}),
|
|
634
|
+
),
|
|
510
635
|
};
|
|
511
636
|
const settings = { extends: "./own.jsonc", kind: "regular" };
|
|
512
637
|
expect(
|
|
@@ -528,8 +653,8 @@ describe("tex/venue-leftover — another venue named in the text", () => {
|
|
|
528
653
|
const tex = aidcPaper(`${INSTITUTION}\n${BODY}${STATEMENT}`);
|
|
529
654
|
const fs = leftovers(lint(tex, AIDC));
|
|
530
655
|
expect(fs.map((f) => f.message)).toEqual([
|
|
531
|
-
"«AISec» names
|
|
532
|
-
expect.stringContaining("«ACM CCS» names
|
|
656
|
+
"«AISec» names AISec, and this paper extends AIDC — a leftover from an earlier submission? A reviewer reads it before the abstract. Comments and citation keys are not reported; a sentence that names the other venue on purpose can keep it with a disable directive",
|
|
657
|
+
expect.stringContaining("«ACM CCS» names AISec"),
|
|
533
658
|
]);
|
|
534
659
|
const line = tex.split("\n").indexOf(INSTITUTION) + 1;
|
|
535
660
|
expect(fs.map((f) => f.line)).toEqual([line, line]);
|
|
@@ -568,6 +693,53 @@ describe("tex/venue-leftover — another venue named in the text", () => {
|
|
|
568
693
|
});
|
|
569
694
|
});
|
|
570
695
|
|
|
696
|
+
describe("tex/venue-leftover — MSR, a name that is also an acronym, counts only with a year (`mentions`)", () => {
|
|
697
|
+
const under = (text: string) =>
|
|
698
|
+
leftovers(lint(aidcPaper(`${text}\n${BODY}${STATEMENT}`), AIDC));
|
|
699
|
+
|
|
700
|
+
it.each([
|
|
701
|
+
[
|
|
702
|
+
"an acronym the paper defines",
|
|
703
|
+
"We score it by Manual Speech Recognition (MSR), and MSR scores rise.",
|
|
704
|
+
],
|
|
705
|
+
[
|
|
706
|
+
"Microsoft Research",
|
|
707
|
+
"Researchers at Microsoft Research (MSR) built it, and MSR released it.",
|
|
708
|
+
],
|
|
709
|
+
[
|
|
710
|
+
"a citation of an MSR paper: the key, and the entry in the bibliography",
|
|
711
|
+
"As shown~\\cite{msr2024}.\n\\begin{thebibliography}{1}\\bibitem{msr2024} A. Author, ``Mining,'' in \\emph{Proc. MSR 2024}, 2024.\\end{thebibliography}",
|
|
712
|
+
],
|
|
713
|
+
["a year that is not beside the name", "MSR, in 2027, scored best."],
|
|
714
|
+
])("silent: %s", (_, text) => {
|
|
715
|
+
expect(under(text)).toEqual([]);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it.each([
|
|
719
|
+
["First written for MSR 2027.", "MSR"],
|
|
720
|
+
["A version appeared as MSR'27 work.", "MSR"],
|
|
721
|
+
["Submitted to MSR~2027.", "MSR"],
|
|
722
|
+
])("🔴 reported: «%s»", (text, name) => {
|
|
723
|
+
const fs = under(text);
|
|
724
|
+
expect(fs.map((f) => f.message)).toEqual([
|
|
725
|
+
expect.stringMatching(
|
|
726
|
+
new RegExp(`^«${name}» names MSR, and this paper extends AIDC`),
|
|
727
|
+
),
|
|
728
|
+
]);
|
|
729
|
+
expect(fs[0]?.line).toBe(4);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("under msr itself, its own name with a year is never a leftover", () => {
|
|
733
|
+
const tex = paper(
|
|
734
|
+
"\\documentclass[10pt,conference]{IEEEtran}",
|
|
735
|
+
"First written for MSR 2027.",
|
|
736
|
+
);
|
|
737
|
+
expect(
|
|
738
|
+
leftovers(lint(tex, { extends: "paperlint:msr", kind: "technical" })),
|
|
739
|
+
).toEqual([]);
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
|
|
571
743
|
describe("tex/venue-leftover — a URL is not text, a definition body is", () => {
|
|
572
744
|
it("\\href: a name in the URL is not reported, a name in the link text is", () => {
|
|
573
745
|
const inUrl = aidcPaper(
|
|
@@ -591,7 +763,7 @@ describe("tex/venue-leftover — a URL is not text, a definition body is", () =>
|
|
|
591
763
|
);
|
|
592
764
|
const fs = leftovers(lint(tex, AIDC));
|
|
593
765
|
expect(fs.map((f) => [f.line, f.message.slice(0, 16)])).toEqual([
|
|
594
|
-
[4, "«AISec» names
|
|
766
|
+
[4, "«AISec» names AI"],
|
|
595
767
|
]);
|
|
596
768
|
},
|
|
597
769
|
);
|
|
@@ -622,11 +794,12 @@ describe("tex/venue-leftover — where names are found, and whose they are", ()
|
|
|
622
794
|
it("a name a venue shares with this paper's own chain is not a leftover", () => {
|
|
623
795
|
const own = `${PAPER}/own.jsonc`;
|
|
624
796
|
const extra = {
|
|
625
|
-
[own]: JSON.stringify(
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
797
|
+
[own]: JSON.stringify(
|
|
798
|
+
venuePreset("AIDC 2027", {
|
|
799
|
+
extends: "paperlint:aidc",
|
|
800
|
+
aliases: ["ACM CCS"],
|
|
801
|
+
}),
|
|
802
|
+
),
|
|
630
803
|
};
|
|
631
804
|
const tex = aidcPaper(`ACM CCS and AIDC 2027.\n${BODY}${STATEMENT}`);
|
|
632
805
|
expect(
|
|
@@ -721,8 +894,9 @@ describe("otherVenues", () => {
|
|
|
721
894
|
if (!aidc.ok) return;
|
|
722
895
|
expect(otherVenues(aidc.value, deps).map((o) => o.label)).toEqual([
|
|
723
896
|
"acm-sigconf",
|
|
724
|
-
"
|
|
725
|
-
"
|
|
897
|
+
"AgenticDev",
|
|
898
|
+
"AISec",
|
|
899
|
+
"MSR",
|
|
726
900
|
]);
|
|
727
901
|
});
|
|
728
902
|
});
|
|
@@ -790,7 +964,7 @@ describe("format/layout-override reports", () => {
|
|
|
790
964
|
[13, "\\newgeometry"],
|
|
791
965
|
]);
|
|
792
966
|
expect(overrides(tex)[0]?.message).toBe(
|
|
793
|
-
"`\\usepackage{geometry}` changes the page layout
|
|
967
|
+
"`\\usepackage{geometry}` changes the page layout AIDC's template sets (`\\documentclass[conference,compsoc]{IEEEtran}`) — a desk-reject reason at venues that check the format. Remove it; if the venue allows it, disable this line with a comment saying so",
|
|
794
968
|
);
|
|
795
969
|
});
|
|
796
970
|
it("a definition's body counts: the length it sets is set wherever it is used", () => {
|
|
@@ -834,6 +1008,7 @@ describe("format/layout-override stays silent", () => {
|
|
|
834
1008
|
expect(overrides(tex, { extends: null })).toEqual([]);
|
|
835
1009
|
const house = {
|
|
836
1010
|
[`${PAPER}/house.jsonc`]: JSON.stringify({
|
|
1011
|
+
type: "family",
|
|
837
1012
|
tex: { packages: { ieeetran: ["IEEEtran.cls"] } },
|
|
838
1013
|
}),
|
|
839
1014
|
};
|