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.ts
CHANGED
|
@@ -12,20 +12,27 @@
|
|
|
12
12
|
* Anything else — an npm package name, a bare name — is refused by name. npm presets are not
|
|
13
13
|
* supported yet; a bare name is almost always a shipped name missing its prefix, and the message says so.
|
|
14
14
|
*
|
|
15
|
+
* ── WHAT EACH FILE IS ─────────────────────────────────────────────────────────────
|
|
16
|
+
* Every preset declares its `type` (`venue-profile.schema.json`): a `venue` names itself and links
|
|
17
|
+
* its call for papers, a template `family` names no venue, and the TeX `base` set is extended by
|
|
18
|
+
* nothing. A chain must read top-down as template → venue: a family extends only a family, and
|
|
19
|
+
* nothing extends the base set. A chain that does not is refused naming both files.
|
|
20
|
+
*
|
|
15
21
|
* ── THE CHAIN ─────────────────────────────────────────────────────────────────────
|
|
16
22
|
* At most `MAX_PRESET_DEPTH` files, root to leaf. A cycle (relative paths can form one) is refused
|
|
17
23
|
* naming the chain. The chain is merged block by block:
|
|
18
24
|
*
|
|
19
25
|
* tex union — a child never removes a package its parent needs
|
|
20
26
|
* format per key, the child wins; `kinds` by kind name, a child's kind replaces that kind
|
|
21
|
-
* rules per rule id, the child wins
|
|
22
|
-
* template the child wins
|
|
27
|
+
* rules per rule id, the child wins; `ruleOrigins` keeps which file set each
|
|
28
|
+
* template the child wins, with its `template_forbids`; so does `name`, and `required_sections`, `register`, `talk` and
|
|
23
29
|
* `portal` (each whole)
|
|
24
30
|
* aliases union, with every `name` — what the venue is called along the chain
|
|
25
31
|
*
|
|
26
|
-
* ── THE LABEL
|
|
27
|
-
*
|
|
28
|
-
*
|
|
32
|
+
* ── THE ID AND THE LABEL ──────────────────────────────────────────────────────────
|
|
33
|
+
* The facts file and the build plan carry the preset's `id`: the spec's file name without its
|
|
34
|
+
* extension, a stable key consumers match against preset ids. Messages say the `label`: the most
|
|
35
|
+
* derived venue's `name` when the chain has a venue, else the id
|
|
29
36
|
* (`paperlint:agenticdev` → `agenticdev`, `./venues/usenix-sec.jsonc` → `usenix-sec`). Display
|
|
30
37
|
* only: nothing is ever resolved by it.
|
|
31
38
|
*/
|
|
@@ -38,6 +45,7 @@ import {
|
|
|
38
45
|
profileFileOf,
|
|
39
46
|
venueNames,
|
|
40
47
|
type PresetFile,
|
|
48
|
+
type PresetIdentity,
|
|
41
49
|
type RequiredSection,
|
|
42
50
|
type TexRequirements,
|
|
43
51
|
type VenueFormat,
|
|
@@ -67,18 +75,27 @@ export interface PresetDeps {
|
|
|
67
75
|
readonly venuesDir: string;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
/** A preset's `template`, as its file spells it. */
|
|
78
|
+
/** A preset's `template`, as its file spells it, and the class options that file forbids. */
|
|
71
79
|
export interface PresetTemplate {
|
|
72
80
|
readonly text: string;
|
|
73
81
|
readonly file: string;
|
|
82
|
+
/** `template_forbids` of the same file: options the paper's class line must not carry. */
|
|
83
|
+
readonly forbids: readonly string[];
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
/** A resolved chain, merged. */
|
|
77
87
|
export interface Preset {
|
|
78
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* The preset's id: the spec's file name without its extension (`agenticdev`, `usenix-sec`). The
|
|
90
|
+
* stable key a machine reads — the facts file and the build plan carry it — whatever `name` says.
|
|
91
|
+
*/
|
|
92
|
+
readonly id: string;
|
|
93
|
+
/** The word for this venue in messages: the venue's `name`, else the id. */
|
|
79
94
|
readonly label: string;
|
|
80
95
|
/** Every file of the chain, root first. */
|
|
81
96
|
readonly chain: readonly string[];
|
|
97
|
+
/** What the spec names — the leaf file of the chain: a venue (with its call), or a family. */
|
|
98
|
+
readonly identity: PresetIdentity;
|
|
82
99
|
/**
|
|
83
100
|
* The `\\documentclass` the venue's template uses, as the preset spells it, and the file that
|
|
84
101
|
* spells it; null when no file of the chain names one. Read by the rules and `paperlint new`,
|
|
@@ -89,10 +106,17 @@ export interface Preset {
|
|
|
89
106
|
readonly aliases: readonly string[];
|
|
90
107
|
/** Whether the venue reviews double-blind (`anonymity/identity`); false when no file of the chain says. */
|
|
91
108
|
readonly blind: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* When the venue's names count as a mention in a paper's text (`tex/venue-leftover`): `any`
|
|
111
|
+
* whole-word use, or `with-year` beside it; `any` when no file of the chain says.
|
|
112
|
+
*/
|
|
113
|
+
readonly mentions: "any" | "with-year";
|
|
92
114
|
readonly requiredSections: readonly RequiredSection[];
|
|
93
115
|
readonly tex: TexRequirements;
|
|
94
116
|
readonly format: VenueFormat;
|
|
95
117
|
readonly rules: Readonly<Record<string, unknown>>;
|
|
118
|
+
/** Rule id → the file of the chain that set its entry in `rules` (the last one to name it). */
|
|
119
|
+
readonly ruleOrigins: Readonly<Record<string, string>>;
|
|
96
120
|
/** The accepted papers the register rules measure a body against; empty when no file names any. */
|
|
97
121
|
readonly registerAnchors: readonly RegisterAnchor[];
|
|
98
122
|
/** What the venue asks a presenter to send (`talk/*`); null when no file of the chain says. */
|
|
@@ -112,6 +136,14 @@ export type PresetProblem =
|
|
|
112
136
|
}
|
|
113
137
|
| { readonly kind: "cycle"; readonly chain: readonly string[] }
|
|
114
138
|
| { readonly kind: "too-deep"; readonly chain: readonly string[] }
|
|
139
|
+
/** `file` extends `parent`, which a preset of its type may not extend. */
|
|
140
|
+
| {
|
|
141
|
+
readonly kind: "wrong-parent";
|
|
142
|
+
readonly file: string;
|
|
143
|
+
readonly parent: string;
|
|
144
|
+
/** A family extending a venue, or anything extending the base set. */
|
|
145
|
+
readonly parentType: "venue" | "base";
|
|
146
|
+
}
|
|
115
147
|
| { readonly kind: "broken"; readonly file: string; readonly why: string };
|
|
116
148
|
|
|
117
149
|
/** The display label of a spec: the file name without its extension. Pure. */
|
|
@@ -166,27 +198,47 @@ function readPreset(
|
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
/** One file of a chain: where it is, and what it says. */
|
|
202
|
+
interface Link {
|
|
203
|
+
readonly file: string;
|
|
204
|
+
readonly preset: PresetFile;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The chain from `child` up, leaf first: `below` is every file read so far, `child` the last of them.
|
|
209
|
+
* Each parent is refused when it closes a cycle, makes the chain too long, or is of a type `child`
|
|
210
|
+
* may not extend.
|
|
211
|
+
*/
|
|
212
|
+
function chainUp(
|
|
213
|
+
below: readonly Link[],
|
|
214
|
+
child: Link,
|
|
173
215
|
deps: PresetDeps,
|
|
174
|
-
): Result<
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
216
|
+
): Result<readonly Link[], PresetProblem> {
|
|
217
|
+
const up = child.preset.extends;
|
|
218
|
+
if (up === null) return ok(below);
|
|
219
|
+
const r = readPreset(up, child.file, deps);
|
|
220
|
+
if (!r.ok) return r;
|
|
221
|
+
const files = below.map((x) => x.file);
|
|
222
|
+
if (files.includes(r.value.file))
|
|
223
|
+
return err({ kind: "cycle", chain: [...files, r.value.file] });
|
|
224
|
+
if (below.length === MAX_PRESET_DEPTH)
|
|
225
|
+
return err({ kind: "too-deep", chain: [...files, r.value.file] });
|
|
226
|
+
const parentType = r.value.preset.identity.type;
|
|
227
|
+
if (parentType === "venue" && child.preset.identity.type === "family")
|
|
228
|
+
return err({
|
|
229
|
+
kind: "wrong-parent",
|
|
230
|
+
file: child.file,
|
|
231
|
+
parent: r.value.file,
|
|
232
|
+
parentType,
|
|
233
|
+
});
|
|
234
|
+
if (parentType === "base")
|
|
235
|
+
return err({
|
|
236
|
+
kind: "wrong-parent",
|
|
237
|
+
file: child.file,
|
|
238
|
+
parent: r.value.file,
|
|
239
|
+
parentType,
|
|
240
|
+
});
|
|
241
|
+
return chainUp([...below, r.value], r.value, deps);
|
|
190
242
|
}
|
|
191
243
|
|
|
192
244
|
/** A child's format over its parent's: per key the child wins, kinds by name. Pure. */
|
|
@@ -209,56 +261,78 @@ export function mergeFormat(
|
|
|
209
261
|
fontsText: pick("fontsText"),
|
|
210
262
|
fontsTitle: pick("fontsTitle"),
|
|
211
263
|
bodyEndsAt: pick("bodyEndsAt"),
|
|
264
|
+
appendixInBody: pick("appendixInBody"),
|
|
212
265
|
kinds: new Map([...parent.kinds, ...child.kinds]),
|
|
213
266
|
};
|
|
214
267
|
}
|
|
215
268
|
|
|
269
|
+
/** A venue's name; a family and the base set have none. Pure. */
|
|
270
|
+
const nameOf = (i: PresetIdentity): string | null =>
|
|
271
|
+
i.type === "venue" ? i.name : null;
|
|
272
|
+
|
|
273
|
+
/** A venue's name as a list, empty for a family and the base set. Pure. */
|
|
274
|
+
const namesOf = (i: PresetIdentity): readonly string[] =>
|
|
275
|
+
i.type === "venue" ? [i.name] : [];
|
|
276
|
+
|
|
277
|
+
/** What the merge carries from file to file: the preset without what only the whole chain knows. */
|
|
278
|
+
type Merging = Omit<Preset, "id" | "label" | "chain" | "identity"> & {
|
|
279
|
+
readonly name: string | null;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const NOTHING_MERGED: Merging = {
|
|
283
|
+
name: null,
|
|
284
|
+
template: null,
|
|
285
|
+
aliases: [],
|
|
286
|
+
blind: false,
|
|
287
|
+
mentions: "any",
|
|
288
|
+
requiredSections: [],
|
|
289
|
+
tex: NO_REQUIREMENTS,
|
|
290
|
+
format: NO_FORMAT,
|
|
291
|
+
rules: {},
|
|
292
|
+
ruleOrigins: {},
|
|
293
|
+
registerAnchors: [],
|
|
294
|
+
talk: null,
|
|
295
|
+
portal: null,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/** One file of the chain, `file`, merged over what its parents gave. Pure. */
|
|
299
|
+
const mergeOne = (
|
|
300
|
+
acc: Merging,
|
|
301
|
+
{ preset: p, file }: { readonly preset: PresetFile; readonly file: string },
|
|
302
|
+
): Merging => ({
|
|
303
|
+
name: nameOf(p.identity) ?? acc.name,
|
|
304
|
+
template:
|
|
305
|
+
p.template === null
|
|
306
|
+
? acc.template
|
|
307
|
+
: { text: p.template, file, forbids: p.templateForbids },
|
|
308
|
+
aliases: [...new Set([...acc.aliases, ...namesOf(p.identity), ...p.aliases])],
|
|
309
|
+
blind: p.blind ?? acc.blind,
|
|
310
|
+
mentions: p.mentions ?? acc.mentions,
|
|
311
|
+
requiredSections: p.requiredSections ?? acc.requiredSections,
|
|
312
|
+
tex: p.tex ? mergeRequirements(acc.tex, p.tex) : acc.tex,
|
|
313
|
+
format: mergeFormat(acc.format, p.format),
|
|
314
|
+
rules: { ...acc.rules, ...p.rules },
|
|
315
|
+
ruleOrigins: {
|
|
316
|
+
...acc.ruleOrigins,
|
|
317
|
+
...Object.fromEntries(Object.keys(p.rules).map((id) => [id, file])),
|
|
318
|
+
},
|
|
319
|
+
registerAnchors: p.registerAnchors ?? acc.registerAnchors,
|
|
320
|
+
talk: p.talk ?? acc.talk,
|
|
321
|
+
portal: p.portal ?? acc.portal,
|
|
322
|
+
});
|
|
323
|
+
|
|
216
324
|
/** The chain, root first, merged into one preset. Pure. */
|
|
217
325
|
function merged(
|
|
218
326
|
spec: string,
|
|
219
327
|
rootFirst: readonly { readonly preset: PresetFile; readonly file: string }[],
|
|
328
|
+
leaf: { readonly preset: PresetFile },
|
|
220
329
|
): Preset {
|
|
221
|
-
const
|
|
222
|
-
readonly name: string | null;
|
|
223
|
-
} = {
|
|
224
|
-
name: null,
|
|
225
|
-
template: null,
|
|
226
|
-
aliases: [],
|
|
227
|
-
blind: false,
|
|
228
|
-
requiredSections: [],
|
|
229
|
-
tex: NO_REQUIREMENTS,
|
|
230
|
-
format: NO_FORMAT,
|
|
231
|
-
rules: {},
|
|
232
|
-
registerAnchors: [],
|
|
233
|
-
talk: null,
|
|
234
|
-
portal: null,
|
|
235
|
-
};
|
|
236
|
-
const m = rootFirst.reduce(
|
|
237
|
-
(acc, { preset: p, file }) => ({
|
|
238
|
-
name: p.name ?? acc.name,
|
|
239
|
-
template: p.template === null ? acc.template : { text: p.template, file },
|
|
240
|
-
aliases: [
|
|
241
|
-
...new Set([
|
|
242
|
-
...acc.aliases,
|
|
243
|
-
...(p.name === null ? [] : [p.name]),
|
|
244
|
-
...p.aliases,
|
|
245
|
-
]),
|
|
246
|
-
],
|
|
247
|
-
blind: p.blind ?? acc.blind,
|
|
248
|
-
requiredSections: p.requiredSections ?? acc.requiredSections,
|
|
249
|
-
tex: p.tex ? mergeRequirements(acc.tex, p.tex) : acc.tex,
|
|
250
|
-
format: mergeFormat(acc.format, p.format),
|
|
251
|
-
rules: { ...acc.rules, ...p.rules },
|
|
252
|
-
registerAnchors: p.registerAnchors ?? acc.registerAnchors,
|
|
253
|
-
talk: p.talk ?? acc.talk,
|
|
254
|
-
portal: p.portal ?? acc.portal,
|
|
255
|
-
}),
|
|
256
|
-
base,
|
|
257
|
-
);
|
|
258
|
-
const { name, ...rest } = m;
|
|
330
|
+
const { name, ...rest } = rootFirst.reduce(mergeOne, NOTHING_MERGED);
|
|
259
331
|
return {
|
|
332
|
+
id: labelOf(spec),
|
|
260
333
|
label: name ?? labelOf(spec),
|
|
261
334
|
chain: rootFirst.map((x) => x.file),
|
|
335
|
+
identity: leaf.preset.identity,
|
|
262
336
|
...rest,
|
|
263
337
|
};
|
|
264
338
|
}
|
|
@@ -272,13 +346,17 @@ export function resolvePreset(
|
|
|
272
346
|
fromFile: string,
|
|
273
347
|
deps: PresetDeps,
|
|
274
348
|
): Result<Preset, PresetProblem> {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
349
|
+
const refused = (p: PresetProblem): Result<Preset, PresetProblem> =>
|
|
350
|
+
err(
|
|
351
|
+
p.kind === "not-found"
|
|
352
|
+
? { ...p, shipped: shippedPresets(deps.venuesDir) }
|
|
353
|
+
: p,
|
|
354
|
+
);
|
|
355
|
+
const leaf = readPreset(spec, fromFile, deps);
|
|
356
|
+
if (!leaf.ok) return refused(leaf.error);
|
|
357
|
+
const chain = chainUp([leaf.value], leaf.value, deps);
|
|
358
|
+
if (!chain.ok) return refused(chain.error);
|
|
359
|
+
return ok(merged(spec, [...chain.value].reverse(), leaf.value));
|
|
282
360
|
}
|
|
283
361
|
|
|
284
362
|
/** The shipped presets' names (each is spelled `paperlint:<name>`). */
|
|
@@ -303,17 +381,21 @@ export function resolveShipped(deps: PresetDeps): readonly Preset[] {
|
|
|
303
381
|
}
|
|
304
382
|
|
|
305
383
|
/** A shipped venue as a name knows it, and the files of its chain: what tells it from a paper's own. */
|
|
306
|
-
export type ShippedVenue = Pick<
|
|
384
|
+
export type ShippedVenue = Pick<
|
|
385
|
+
Preset,
|
|
386
|
+
"label" | "aliases" | "chain" | "mentions"
|
|
387
|
+
>;
|
|
307
388
|
|
|
308
389
|
/**
|
|
309
390
|
* A resolved preset as a name knows it: its label, its aliases, its chain. Pure. (Not Remeda's
|
|
310
391
|
* `pick`: the app layer may not import it — `boundaries/dependencies`, `APP_EXTERNALS`.)
|
|
311
392
|
*/
|
|
312
|
-
export const venueOf = ({
|
|
393
|
+
export const venueOf = ({
|
|
313
394
|
label,
|
|
314
395
|
aliases,
|
|
315
396
|
chain,
|
|
316
|
-
|
|
397
|
+
mentions,
|
|
398
|
+
}: Preset): ShippedVenue => ({ label, aliases, chain, mentions });
|
|
317
399
|
|
|
318
400
|
/**
|
|
319
401
|
* THE ONE SOURCE OF VENUE NAMES: every shipped venue's label and `aliases` (with each `name` along
|
|
@@ -336,6 +418,10 @@ export function presetProblemText(p: PresetProblem): string {
|
|
|
336
418
|
return `the preset chain is longer than ${String(MAX_PRESET_DEPTH)}: ${p.chain.join(" → ")}`;
|
|
337
419
|
case "broken":
|
|
338
420
|
return `the preset ${p.file} does not parse: ${p.why}`;
|
|
421
|
+
case "wrong-parent":
|
|
422
|
+
return p.parentType === "base"
|
|
423
|
+
? `the preset ${p.file} extends ${p.parent}, the TeX base set — every paper gets that set already, and no preset extends it`
|
|
424
|
+
: `the preset ${p.file} is a template family and extends ${p.parent}, a venue — a family builds only on a family; a venue extends a family or another venue`;
|
|
339
425
|
}
|
|
340
426
|
}
|
|
341
427
|
|