paperlint 6.2.3 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +4 -2
- package/README.md +10 -3
- package/dist/adapters/hotcrp/index.d.ts +7 -0
- package/dist/adapters/hotcrp/index.d.ts.map +1 -0
- package/dist/adapters/hotcrp/index.js +7 -0
- package/dist/adapters/hotcrp/index.js.map +1 -0
- package/dist/adapters/hotcrp/portal.io.d.ts +20 -0
- package/dist/adapters/hotcrp/portal.io.d.ts.map +1 -0
- package/dist/adapters/hotcrp/portal.io.js +90 -0
- package/dist/adapters/hotcrp/portal.io.js.map +1 -0
- package/dist/adapters/hotcrp/response.d.ts +13 -0
- package/dist/adapters/hotcrp/response.d.ts.map +1 -0
- package/dist/adapters/hotcrp/response.js +138 -0
- package/dist/adapters/hotcrp/response.js.map +1 -0
- 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 +4 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +126 -37
- package/dist/cli.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/result.d.ts +4 -0
- package/dist/domain/result.d.ts.map +1 -1
- package/dist/domain/result.js +2 -0
- package/dist/domain/result.js.map +1 -1
- package/dist/domain/submission.d.ts +106 -0
- package/dist/domain/submission.d.ts.map +1 -0
- package/dist/domain/submission.js +39 -0
- package/dist/domain/submission.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/domain/venue-name.d.ts +38 -0
- package/dist/domain/venue-name.d.ts.map +1 -0
- package/dist/domain/venue-name.js +71 -0
- package/dist/domain/venue-name.js.map +1 -0
- 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.d.ts +51 -0
- package/dist/folder-venue-rule.d.ts.map +1 -0
- package/dist/folder-venue-rule.js +96 -0
- package/dist/folder-venue-rule.js.map +1 -0
- 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/init.d.ts +6 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -3
- package/dist/init.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +5 -1
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/new-paper.d.ts +19 -5
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +23 -4
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-settings.d.ts +3 -0
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +20 -4
- package/dist/paper-settings.js.map +1 -1
- package/dist/ports/latex.d.ts +7 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/ports/submission-portal.d.ts +19 -0
- package/dist/ports/submission-portal.d.ts.map +1 -0
- package/dist/ports/submission-portal.js +2 -0
- package/dist/ports/submission-portal.js.map +1 -0
- 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 +43 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +130 -63
- package/dist/presets.js.map +1 -1
- package/dist/rules-config.js +1 -1
- package/dist/submission.d.ts +61 -0
- package/dist/submission.d.ts.map +1 -0
- package/dist/submission.js +211 -0
- package/dist/submission.js.map +1 -0
- package/dist/tex-requirements.d.ts +36 -3
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +30 -2
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +7 -8
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +10 -14
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/configuration.md +30 -11
- package/docs/optional-rules.md +4 -3
- package/docs/rules/format/page-limit.md +1 -1
- package/docs/rules/paper/folder-venue-leftover.md +104 -0
- package/docs/rules/talk/captions-cover.md +1 -1
- package/docs/rules/talk/duration-floor.md +1 -1
- package/docs/rules/talk/profile.md +1 -1
- package/docs/rules/tex/heading-case.md +424 -0
- package/docs/rules.md +64 -53
- package/docs/skills.md +5 -4
- package/docs/submission.md +64 -0
- package/docs/talk.md +1 -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/lib/paper-config.ts +6 -1
- package/lib/skill-checks.d.mts +7 -0
- package/lib/skill-checks.mjs +31 -0
- package/lib/skill-trigger-cases.mjs +11 -0
- package/package.json +8 -2
- 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 +12 -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/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 +57 -2
- 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/paper-pipeline.harness.mjs +4 -0
- 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 +69 -0
- package/skills/submission-portal/SKILL.md.spec.ts +71 -0
- package/skills/submission-portal/submission-portal.eval.mjs +15 -0
- package/skills/submission-portal/submission-portal.test.ts +81 -0
- package/skills/submit-paper/SKILL.md +27 -22
- package/skills/submit-paper/SKILL.md.spec.ts +26 -21
- package/skills/submit-paper/references/publishers/acm.md +1 -1
- package/src/adapters/hotcrp/cassette/README.md +24 -0
- package/src/adapters/hotcrp/cassette/get-noauth.json +10 -0
- package/src/adapters/hotcrp/cassette/get-paper.json +39 -0
- package/src/adapters/hotcrp/cassette/post-dot.json +21 -0
- package/src/adapters/hotcrp/cassette/post-ok.json +12 -0
- package/src/adapters/hotcrp/index.ts +11 -0
- package/src/adapters/hotcrp/portal.io.ts +130 -0
- package/src/adapters/hotcrp/portal.test.ts +503 -0
- package/src/adapters/hotcrp/response.ts +181 -0
- 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 +155 -3
- package/src/cli.ts +161 -25
- package/src/domain/heading-case.test.ts +660 -0
- package/src/domain/heading-case.ts +538 -0
- package/src/domain/result.test.ts +9 -1
- package/src/domain/result.ts +5 -0
- package/src/domain/submission.test.ts +45 -0
- package/src/domain/submission.ts +141 -0
- package/src/domain/tex-document.ts +36 -0
- package/src/domain/venue-name.test.ts +121 -0
- package/src/domain/venue-name.ts +129 -0
- package/src/facts-file.test.ts +4 -4
- package/src/facts-file.ts +6 -6
- package/src/folder-venue-rule.test.ts +182 -0
- package/src/folder-venue-rule.ts +141 -0
- package/src/heading-case.test.ts +500 -0
- package/src/heading-case.ts +275 -0
- package/src/init.test.ts +22 -0
- package/src/init.ts +9 -2
- package/src/new-paper.harness.mjs +11 -9
- package/src/new-paper.test.ts +135 -10
- package/src/new-paper.ts +38 -7
- package/src/paper-settings-commands.test.ts +25 -10
- package/src/paper-settings.test.ts +12 -1
- package/src/paper-settings.ts +25 -5
- package/src/parse-args.test.ts +19 -0
- package/src/ports/latex.ts +7 -0
- package/src/ports/submission-portal.ts +26 -0
- package/src/preset-card-rule.test.ts +266 -0
- package/src/preset-card-rule.ts +384 -0
- package/src/presets.test.ts +212 -37
- package/src/presets.ts +183 -71
- package/src/rule-cases.test.ts +147 -42
- package/src/rules-config.ts +1 -1
- package/src/submission.test.ts +497 -0
- package/src/submission.ts +311 -0
- 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 +78 -8
- package/src/tex-venue-rules.test.ts +36 -22
- package/src/tex-venue-rules.ts +14 -26
- package/src/types.ts +13 -0
- package/src/venue-rules.test.ts +7 -6
- 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,19 +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; so does `name`, and `required_sections`, `register
|
|
27
|
+
* rules per rule id, the child wins; `ruleOrigins` keeps which file set each
|
|
28
|
+
* template the child wins; so does `name`, and `required_sections`, `register`, `talk` and
|
|
29
|
+
* `portal` (each whole)
|
|
23
30
|
* aliases union, with every `name` — what the venue is called along the chain
|
|
24
31
|
*
|
|
25
|
-
* ── THE LABEL
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
28
36
|
* (`paperlint:agenticdev` → `agenticdev`, `./venues/usenix-sec.jsonc` → `usenix-sec`). Display
|
|
29
37
|
* only: nothing is ever resolved by it.
|
|
30
38
|
*/
|
|
@@ -37,12 +45,13 @@ import {
|
|
|
37
45
|
profileFileOf,
|
|
38
46
|
venueNames,
|
|
39
47
|
type PresetFile,
|
|
48
|
+
type PresetIdentity,
|
|
40
49
|
type RequiredSection,
|
|
41
50
|
type TexRequirements,
|
|
42
51
|
type VenueFormat,
|
|
43
52
|
} from "./tex-requirements.ts";
|
|
44
53
|
import { callerPath } from "./caller-path.ts";
|
|
45
|
-
import { err, ok, type Result } from "./domain/result.ts";
|
|
54
|
+
import { err, isOk, ok, type Result } from "./domain/result.ts";
|
|
46
55
|
import type { Files } from "./ports/files.ts";
|
|
47
56
|
import {
|
|
48
57
|
readPaperSettings,
|
|
@@ -53,6 +62,7 @@ import { CONFIG_FILE } from "#lib/paper-config";
|
|
|
53
62
|
import { messageOf } from "./domain/text.ts";
|
|
54
63
|
import type { RegisterAnchor } from "./domain/register.ts";
|
|
55
64
|
import type { VenueTalk } from "./domain/talk.ts";
|
|
65
|
+
import type { VenuePortal } from "./domain/submission.ts";
|
|
56
66
|
|
|
57
67
|
/** The prefix of a shipped preset's spec. */
|
|
58
68
|
export const SHIPPED_PREFIX = "paperlint:";
|
|
@@ -73,10 +83,17 @@ export interface PresetTemplate {
|
|
|
73
83
|
|
|
74
84
|
/** A resolved chain, merged. */
|
|
75
85
|
export interface Preset {
|
|
76
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* The preset's id: the spec's file name without its extension (`agenticdev`, `usenix-sec`). The
|
|
88
|
+
* stable key a machine reads — the facts file and the build plan carry it — whatever `name` says.
|
|
89
|
+
*/
|
|
90
|
+
readonly id: string;
|
|
91
|
+
/** The word for this venue in messages: the venue's `name`, else the id. */
|
|
77
92
|
readonly label: string;
|
|
78
93
|
/** Every file of the chain, root first. */
|
|
79
94
|
readonly chain: readonly string[];
|
|
95
|
+
/** What the spec names — the leaf file of the chain: a venue (with its call), or a family. */
|
|
96
|
+
readonly identity: PresetIdentity;
|
|
80
97
|
/**
|
|
81
98
|
* The `\\documentclass` the venue's template uses, as the preset spells it, and the file that
|
|
82
99
|
* spells it; null when no file of the chain names one. Read by the rules and `paperlint new`,
|
|
@@ -91,10 +108,14 @@ export interface Preset {
|
|
|
91
108
|
readonly tex: TexRequirements;
|
|
92
109
|
readonly format: VenueFormat;
|
|
93
110
|
readonly rules: Readonly<Record<string, unknown>>;
|
|
111
|
+
/** Rule id → the file of the chain that set its entry in `rules` (the last one to name it). */
|
|
112
|
+
readonly ruleOrigins: Readonly<Record<string, string>>;
|
|
94
113
|
/** The accepted papers the register rules measure a body against; empty when no file names any. */
|
|
95
114
|
readonly registerAnchors: readonly RegisterAnchor[];
|
|
96
115
|
/** What the venue asks a presenter to send (`talk/*`); null when no file of the chain says. */
|
|
97
116
|
readonly talk: VenueTalk | null;
|
|
117
|
+
/** Where the venue takes submissions (`paperlint submission`); null when no file of the chain says. */
|
|
118
|
+
readonly portal: VenuePortal | null;
|
|
98
119
|
}
|
|
99
120
|
|
|
100
121
|
/** Why a spec does not resolve. */
|
|
@@ -108,6 +129,14 @@ export type PresetProblem =
|
|
|
108
129
|
}
|
|
109
130
|
| { readonly kind: "cycle"; readonly chain: readonly string[] }
|
|
110
131
|
| { readonly kind: "too-deep"; readonly chain: readonly string[] }
|
|
132
|
+
/** `file` extends `parent`, which a preset of its type may not extend. */
|
|
133
|
+
| {
|
|
134
|
+
readonly kind: "wrong-parent";
|
|
135
|
+
readonly file: string;
|
|
136
|
+
readonly parent: string;
|
|
137
|
+
/** A family extending a venue, or anything extending the base set. */
|
|
138
|
+
readonly parentType: "venue" | "base";
|
|
139
|
+
}
|
|
111
140
|
| { readonly kind: "broken"; readonly file: string; readonly why: string };
|
|
112
141
|
|
|
113
142
|
/** The display label of a spec: the file name without its extension. Pure. */
|
|
@@ -162,27 +191,47 @@ function readPreset(
|
|
|
162
191
|
}
|
|
163
192
|
}
|
|
164
193
|
|
|
165
|
-
/**
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
194
|
+
/** One file of a chain: where it is, and what it says. */
|
|
195
|
+
interface Link {
|
|
196
|
+
readonly file: string;
|
|
197
|
+
readonly preset: PresetFile;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The chain from `child` up, leaf first: `below` is every file read so far, `child` the last of them.
|
|
202
|
+
* Each parent is refused when it closes a cycle, makes the chain too long, or is of a type `child`
|
|
203
|
+
* may not extend.
|
|
204
|
+
*/
|
|
205
|
+
function chainUp(
|
|
206
|
+
below: readonly Link[],
|
|
207
|
+
child: Link,
|
|
169
208
|
deps: PresetDeps,
|
|
170
|
-
): Result<
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
209
|
+
): Result<readonly Link[], PresetProblem> {
|
|
210
|
+
const up = child.preset.extends;
|
|
211
|
+
if (up === null) return ok(below);
|
|
212
|
+
const r = readPreset(up, child.file, deps);
|
|
213
|
+
if (!r.ok) return r;
|
|
214
|
+
const files = below.map((x) => x.file);
|
|
215
|
+
if (files.includes(r.value.file))
|
|
216
|
+
return err({ kind: "cycle", chain: [...files, r.value.file] });
|
|
217
|
+
if (below.length === MAX_PRESET_DEPTH)
|
|
218
|
+
return err({ kind: "too-deep", chain: [...files, r.value.file] });
|
|
219
|
+
const parentType = r.value.preset.identity.type;
|
|
220
|
+
if (parentType === "venue" && child.preset.identity.type === "family")
|
|
221
|
+
return err({
|
|
222
|
+
kind: "wrong-parent",
|
|
223
|
+
file: child.file,
|
|
224
|
+
parent: r.value.file,
|
|
225
|
+
parentType,
|
|
226
|
+
});
|
|
227
|
+
if (parentType === "base")
|
|
228
|
+
return err({
|
|
229
|
+
kind: "wrong-parent",
|
|
230
|
+
file: child.file,
|
|
231
|
+
parent: r.value.file,
|
|
232
|
+
parentType,
|
|
233
|
+
});
|
|
234
|
+
return chainUp([...below, r.value], r.value, deps);
|
|
186
235
|
}
|
|
187
236
|
|
|
188
237
|
/** A child's format over its parent's: per key the child wins, kinds by name. Pure. */
|
|
@@ -209,50 +258,68 @@ export function mergeFormat(
|
|
|
209
258
|
};
|
|
210
259
|
}
|
|
211
260
|
|
|
261
|
+
/** A venue's name; a family and the base set have none. Pure. */
|
|
262
|
+
const nameOf = (i: PresetIdentity): string | null =>
|
|
263
|
+
i.type === "venue" ? i.name : null;
|
|
264
|
+
|
|
265
|
+
/** A venue's name as a list, empty for a family and the base set. Pure. */
|
|
266
|
+
const namesOf = (i: PresetIdentity): readonly string[] =>
|
|
267
|
+
i.type === "venue" ? [i.name] : [];
|
|
268
|
+
|
|
269
|
+
/** What the merge carries from file to file: the preset without what only the whole chain knows. */
|
|
270
|
+
type Merging = Omit<Preset, "id" | "label" | "chain" | "identity"> & {
|
|
271
|
+
readonly name: string | null;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const NOTHING_MERGED: Merging = {
|
|
275
|
+
name: null,
|
|
276
|
+
template: null,
|
|
277
|
+
aliases: [],
|
|
278
|
+
blind: false,
|
|
279
|
+
requiredSections: [],
|
|
280
|
+
tex: NO_REQUIREMENTS,
|
|
281
|
+
format: NO_FORMAT,
|
|
282
|
+
rules: {},
|
|
283
|
+
ruleOrigins: {},
|
|
284
|
+
registerAnchors: [],
|
|
285
|
+
talk: null,
|
|
286
|
+
portal: null,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** One file of the chain, `file`, merged over what its parents gave. Pure. */
|
|
290
|
+
const mergeOne = (
|
|
291
|
+
acc: Merging,
|
|
292
|
+
{ preset: p, file }: { readonly preset: PresetFile; readonly file: string },
|
|
293
|
+
): Merging => ({
|
|
294
|
+
name: nameOf(p.identity) ?? acc.name,
|
|
295
|
+
template: p.template === null ? acc.template : { text: p.template, file },
|
|
296
|
+
aliases: [...new Set([...acc.aliases, ...namesOf(p.identity), ...p.aliases])],
|
|
297
|
+
blind: p.blind ?? acc.blind,
|
|
298
|
+
requiredSections: p.requiredSections ?? acc.requiredSections,
|
|
299
|
+
tex: p.tex ? mergeRequirements(acc.tex, p.tex) : acc.tex,
|
|
300
|
+
format: mergeFormat(acc.format, p.format),
|
|
301
|
+
rules: { ...acc.rules, ...p.rules },
|
|
302
|
+
ruleOrigins: {
|
|
303
|
+
...acc.ruleOrigins,
|
|
304
|
+
...Object.fromEntries(Object.keys(p.rules).map((id) => [id, file])),
|
|
305
|
+
},
|
|
306
|
+
registerAnchors: p.registerAnchors ?? acc.registerAnchors,
|
|
307
|
+
talk: p.talk ?? acc.talk,
|
|
308
|
+
portal: p.portal ?? acc.portal,
|
|
309
|
+
});
|
|
310
|
+
|
|
212
311
|
/** The chain, root first, merged into one preset. Pure. */
|
|
213
312
|
function merged(
|
|
214
313
|
spec: string,
|
|
215
314
|
rootFirst: readonly { readonly preset: PresetFile; readonly file: string }[],
|
|
315
|
+
leaf: { readonly preset: PresetFile },
|
|
216
316
|
): Preset {
|
|
217
|
-
const
|
|
218
|
-
readonly name: string | null;
|
|
219
|
-
} = {
|
|
220
|
-
name: null,
|
|
221
|
-
template: null,
|
|
222
|
-
aliases: [],
|
|
223
|
-
blind: false,
|
|
224
|
-
requiredSections: [],
|
|
225
|
-
tex: NO_REQUIREMENTS,
|
|
226
|
-
format: NO_FORMAT,
|
|
227
|
-
rules: {},
|
|
228
|
-
registerAnchors: [],
|
|
229
|
-
talk: null,
|
|
230
|
-
};
|
|
231
|
-
const m = rootFirst.reduce(
|
|
232
|
-
(acc, { preset: p, file }) => ({
|
|
233
|
-
name: p.name ?? acc.name,
|
|
234
|
-
template: p.template === null ? acc.template : { text: p.template, file },
|
|
235
|
-
aliases: [
|
|
236
|
-
...new Set([
|
|
237
|
-
...acc.aliases,
|
|
238
|
-
...(p.name === null ? [] : [p.name]),
|
|
239
|
-
...p.aliases,
|
|
240
|
-
]),
|
|
241
|
-
],
|
|
242
|
-
blind: p.blind ?? acc.blind,
|
|
243
|
-
requiredSections: p.requiredSections ?? acc.requiredSections,
|
|
244
|
-
tex: p.tex ? mergeRequirements(acc.tex, p.tex) : acc.tex,
|
|
245
|
-
format: mergeFormat(acc.format, p.format),
|
|
246
|
-
rules: { ...acc.rules, ...p.rules },
|
|
247
|
-
registerAnchors: p.registerAnchors ?? acc.registerAnchors,
|
|
248
|
-
talk: p.talk ?? acc.talk,
|
|
249
|
-
}),
|
|
250
|
-
base,
|
|
251
|
-
);
|
|
252
|
-
const { name, ...rest } = m;
|
|
317
|
+
const { name, ...rest } = rootFirst.reduce(mergeOne, NOTHING_MERGED);
|
|
253
318
|
return {
|
|
319
|
+
id: labelOf(spec),
|
|
254
320
|
label: name ?? labelOf(spec),
|
|
255
321
|
chain: rootFirst.map((x) => x.file),
|
|
322
|
+
identity: leaf.preset.identity,
|
|
256
323
|
...rest,
|
|
257
324
|
};
|
|
258
325
|
}
|
|
@@ -266,13 +333,17 @@ export function resolvePreset(
|
|
|
266
333
|
fromFile: string,
|
|
267
334
|
deps: PresetDeps,
|
|
268
335
|
): Result<Preset, PresetProblem> {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
336
|
+
const refused = (p: PresetProblem): Result<Preset, PresetProblem> =>
|
|
337
|
+
err(
|
|
338
|
+
p.kind === "not-found"
|
|
339
|
+
? { ...p, shipped: shippedPresets(deps.venuesDir) }
|
|
340
|
+
: p,
|
|
341
|
+
);
|
|
342
|
+
const leaf = readPreset(spec, fromFile, deps);
|
|
343
|
+
if (!leaf.ok) return refused(leaf.error);
|
|
344
|
+
const chain = chainUp([leaf.value], leaf.value, deps);
|
|
345
|
+
if (!chain.ok) return refused(chain.error);
|
|
346
|
+
return ok(merged(spec, [...chain.value].reverse(), leaf.value));
|
|
276
347
|
}
|
|
277
348
|
|
|
278
349
|
/** The shipped presets' names (each is spelled `paperlint:<name>`). */
|
|
@@ -280,11 +351,48 @@ export function shippedPresets(venuesDir: string): string[] {
|
|
|
280
351
|
return venueNames(venuesDir);
|
|
281
352
|
}
|
|
282
353
|
|
|
354
|
+
/** The spec a paper writes in `extends` for the shipped preset `name`: `paperlint:<name>`. */
|
|
355
|
+
export const shippedSpec = (name: string): string => `${SHIPPED_PREFIX}${name}`;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Every shipped preset that resolves, each read the way a paper extending it would read it. The one
|
|
359
|
+
* piece that reads the presets directory. One that does not resolve is left out: `pdf/profile`
|
|
360
|
+
* owns that.
|
|
361
|
+
*/
|
|
362
|
+
export function resolveShipped(deps: PresetDeps): readonly Preset[] {
|
|
363
|
+
const from = join(deps.venuesDir, CONFIG_FILE);
|
|
364
|
+
return shippedPresets(deps.venuesDir)
|
|
365
|
+
.map((name) => resolvePreset(shippedSpec(name), from, deps))
|
|
366
|
+
.filter(isOk)
|
|
367
|
+
.map((r) => r.value);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** A shipped venue as a name knows it, and the files of its chain: what tells it from a paper's own. */
|
|
371
|
+
export type ShippedVenue = Pick<Preset, "label" | "aliases" | "chain">;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* A resolved preset as a name knows it: its label, its aliases, its chain. Pure. (Not Remeda's
|
|
375
|
+
* `pick`: the app layer may not import it — `boundaries/dependencies`, `APP_EXTERNALS`.)
|
|
376
|
+
*/
|
|
377
|
+
export const venueOf = ({ label, aliases, chain }: Preset): ShippedVenue => ({
|
|
378
|
+
label,
|
|
379
|
+
aliases,
|
|
380
|
+
chain,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* THE ONE SOURCE OF VENUE NAMES: every shipped venue's label and `aliases` (with each `name` along
|
|
385
|
+
* its chain). Read by `paperlint new`, which refuses a folder name naming one, and through
|
|
386
|
+
* `otherVenues` by `tex/venue-leftover` and `paper/folder-venue-leftover`.
|
|
387
|
+
*/
|
|
388
|
+
export const shippedVenueNames = (deps: PresetDeps): readonly ShippedVenue[] =>
|
|
389
|
+
resolveShipped(deps).map(venueOf);
|
|
390
|
+
|
|
283
391
|
/** One line for a problem, naming what to change. Pure. */
|
|
284
392
|
export function presetProblemText(p: PresetProblem): string {
|
|
285
393
|
switch (p.kind) {
|
|
286
394
|
case "unsupported":
|
|
287
|
-
return `"extends": "${p.spec}" — a preset is \`paperlint:<name>\` (shipped) or a path starting with ./ or ../ (your own); npm presets: not yet supported. Did you mean "${
|
|
395
|
+
return `"extends": "${p.spec}" — a preset is \`paperlint:<name>\` (shipped) or a path starting with ./ or ../ (your own); npm presets: not yet supported. Did you mean "${shippedSpec(p.spec)}"?`;
|
|
288
396
|
case "not-found":
|
|
289
397
|
return `"extends": "${p.spec}" names no preset (${p.file}); shipped presets (${SHIPPED_PREFIX}<name>): ${p.shipped.join(", ")}`;
|
|
290
398
|
case "cycle":
|
|
@@ -293,6 +401,10 @@ export function presetProblemText(p: PresetProblem): string {
|
|
|
293
401
|
return `the preset chain is longer than ${String(MAX_PRESET_DEPTH)}: ${p.chain.join(" → ")}`;
|
|
294
402
|
case "broken":
|
|
295
403
|
return `the preset ${p.file} does not parse: ${p.why}`;
|
|
404
|
+
case "wrong-parent":
|
|
405
|
+
return p.parentType === "base"
|
|
406
|
+
? `the preset ${p.file} extends ${p.parent}, the TeX base set — every paper gets that set already, and no preset extends it`
|
|
407
|
+
: `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`;
|
|
296
408
|
}
|
|
297
409
|
}
|
|
298
410
|
|