paperlint 5.0.0 → 6.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/CLAUDE.md +13 -0
- package/CONTRIBUTING.md +3 -0
- package/README.md +8 -8
- package/bin/paperlint.mjs +5 -1
- package/dist/adapters/latex/index.d.ts +2 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +6 -2
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/layout.d.ts +27 -0
- package/dist/adapters/latex/layout.d.ts.map +1 -0
- package/dist/adapters/latex/layout.js +62 -0
- package/dist/adapters/latex/layout.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +11 -1
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +5 -1
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/latex/prose.d.ts +6 -1
- package/dist/adapters/latex/prose.d.ts.map +1 -1
- package/dist/adapters/latex/prose.js +142 -31
- package/dist/adapters/latex/prose.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +35 -18
- package/dist/cli.js.map +1 -1
- package/dist/domain/anonymity.d.ts +68 -0
- package/dist/domain/anonymity.d.ts.map +1 -0
- package/dist/domain/anonymity.js +94 -0
- package/dist/domain/anonymity.js.map +1 -0
- package/dist/domain/body-pages.d.ts +69 -0
- package/dist/domain/body-pages.d.ts.map +1 -0
- package/dist/domain/body-pages.js +141 -0
- package/dist/domain/body-pages.js.map +1 -0
- package/dist/domain/register.d.ts +96 -0
- package/dist/domain/register.d.ts.map +1 -0
- package/dist/domain/register.js +136 -0
- package/dist/domain/register.js.map +1 -0
- package/dist/domain/tex-document.d.ts +28 -0
- package/dist/domain/tex-document.d.ts.map +1 -1
- package/dist/domain/tex-document.js +8 -3
- package/dist/domain/tex-document.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js +2 -2
- package/dist/facts-file.d.ts +28 -2
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +54 -9
- package/dist/facts-file.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +2 -1
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/paper-settings.d.ts +11 -0
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +38 -4
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-content.d.ts +40 -0
- package/dist/pdf-content.d.ts.map +1 -0
- package/dist/pdf-content.js +97 -0
- package/dist/pdf-content.js.map +1 -0
- package/dist/pdf-facts.d.ts +30 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +37 -1
- package/dist/pdf-facts.js.map +1 -1
- package/dist/ports/latex.d.ts +8 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/presets.d.ts +5 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +8 -1
- package/dist/presets.js.map +1 -1
- package/dist/register-bands.d.ts +37 -0
- package/dist/register-bands.d.ts.map +1 -0
- package/dist/register-bands.js +196 -0
- package/dist/register-bands.js.map +1 -0
- package/dist/register.d.ts +4 -2
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +6 -45
- package/dist/register.js.map +1 -1
- package/dist/tex-requirements.d.ts +10 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +29 -6
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +8 -2
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +40 -0
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +38 -8
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +179 -41
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +18 -16
- package/docs/e2e.md +32 -11
- package/docs/rules/anonymity/identity.md +111 -0
- package/docs/rules/format/layout-override.md +61 -0
- package/docs/rules/format/page-limit.md +108 -0
- package/docs/rules/pdf/body-size.md +87 -0
- package/docs/rules/tex/claim-emphasis.md +71 -0
- package/docs/rules/tex/contrast-frames.md +100 -0
- package/docs/rules/tex/relation-markers.md +72 -0
- package/docs/rules.md +59 -50
- package/eslint-rules/pdf-last-page-balance.harness.mjs +6 -1
- package/eslint-rules/pdf-last-page-balance.ts +2 -2
- package/fixtures/build-e2e/aidc/expect.json +1 -1
- package/fixtures/build-e2e/aidc/paperlint.json +1 -1
- package/fixtures/build-e2e/anon-ack/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-ack/expect.json +19 -0
- package/fixtures/build-e2e/anon-ack/paper.tex +31 -0
- package/fixtures/build-e2e/anon-ack/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-author/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-author/expect.json +19 -0
- package/fixtures/build-e2e/anon-author/paper.tex +29 -0
- package/fixtures/build-e2e/anon-author/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-bib/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-bib/expect.json +20 -0
- package/fixtures/build-e2e/anon-bib/paper.tex +35 -0
- package/fixtures/build-e2e/anon-bib/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-href/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-href/expect.json +19 -0
- package/fixtures/build-e2e/anon-href/paper.tex +30 -0
- package/fixtures/build-e2e/anon-href/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-macro/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-macro/expect.json +19 -0
- package/fixtures/build-e2e/anon-macro/paper.tex +30 -0
- package/fixtures/build-e2e/anon-macro/paperlint.json +1 -0
- package/fixtures/build-e2e/anon-pdfauthor/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/anon-pdfauthor/expect.json +19 -0
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +31 -0
- package/fixtures/build-e2e/anon-pdfauthor/paperlint.json +1 -0
- package/fixtures/build-e2e/unbalanced/expect.json +2 -2
- package/lib/paper-config.ts +3 -1
- package/package.json +1 -1
- package/presets/acm-sigconf.jsonc +3 -1
- package/presets/aidc.jsonc +66 -17
- package/presets/ieee-conference.jsonc +8 -0
- package/presets/venue-profile.schema.json +57 -1
- package/skills/grade-paper-writing/SKILL.md +59 -27
- package/skills/grade-paper-writing/SKILL.md.spec.ts +58 -26
- package/skills/render-paper/extract-pdf-facts.harness.ts +3 -3
- package/src/adapters/latex/index.test.ts +91 -0
- package/src/adapters/latex/index.ts +6 -2
- package/src/adapters/latex/layout.ts +106 -0
- package/src/adapters/latex/nodes.ts +11 -3
- package/src/adapters/latex/parse.ts +5 -1
- package/src/adapters/latex/prose.ts +188 -58
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +5 -0
- package/src/cli.test.ts +13 -0
- package/src/cli.ts +50 -17
- package/src/domain/anonymity.test.ts +163 -0
- package/src/domain/anonymity.ts +129 -0
- package/src/domain/body-pages.test.ts +300 -0
- package/src/domain/body-pages.ts +223 -0
- package/src/domain/register.test.ts +153 -0
- package/src/domain/register.ts +261 -0
- package/src/domain/tex-document.ts +47 -3
- package/src/facts-file.test.ts +100 -4
- package/src/facts-file.ts +91 -9
- package/src/new-paper.test.ts +7 -1
- package/src/paper-settings.test.ts +63 -6
- package/src/paper-settings.ts +53 -5
- package/src/pdf-content.test.ts +71 -0
- package/src/pdf-content.ts +125 -0
- package/src/pdf-facts.test.ts +110 -2
- package/src/pdf-facts.ts +106 -8
- package/src/ports/latex.ts +9 -0
- package/src/presets.ts +15 -9
- package/src/register-bands.test.ts +327 -0
- package/src/register-bands.ts +291 -0
- package/src/register.ts +7 -37
- package/src/rule-cases.test.ts +96 -20
- package/src/rule-docs.test.ts +0 -2
- package/src/tex-requirements.ts +56 -8
- package/src/tex-venue-rules.test.ts +110 -5
- package/src/tex-venue-rules.ts +64 -3
- package/src/types.ts +2 -0
- package/src/venue-rules.test.ts +351 -39
- package/src/venue-rules.ts +247 -47
package/src/tex-requirements.ts
CHANGED
|
@@ -28,6 +28,7 @@ import Ajv from "ajv";
|
|
|
28
28
|
import { presetsDir } from "./package-dirs.ts";
|
|
29
29
|
import { CONFIG_FILE } from "#lib/paper-config";
|
|
30
30
|
import { fieldOf } from "./domain/record.ts";
|
|
31
|
+
import type { RegisterAnchor, RegisterMeasureName } from "./domain/register.ts";
|
|
31
32
|
|
|
32
33
|
/** CTAN package name → the names that prove it is installed. */
|
|
33
34
|
export type PackageProofs = Readonly<Record<string, readonly string[]>>;
|
|
@@ -131,6 +132,8 @@ export interface VenueFormat {
|
|
|
131
132
|
/** The prefix a font name of the body text starts with (`LinLibertine`). */
|
|
132
133
|
readonly fontsText: string | null;
|
|
133
134
|
readonly fontsTitle: string | null;
|
|
135
|
+
/** How the body is counted: null is banal's count; `references`, the pages before the references. */
|
|
136
|
+
readonly bodyEndsAt: "references" | null;
|
|
134
137
|
readonly kinds: ReadonlyMap<string, KindLimits>;
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -145,6 +148,7 @@ export const NO_FORMAT: VenueFormat = {
|
|
|
145
148
|
refPtMax: null,
|
|
146
149
|
fontsText: null,
|
|
147
150
|
fontsTitle: null,
|
|
151
|
+
bodyEndsAt: null,
|
|
148
152
|
kinds: new Map(),
|
|
149
153
|
};
|
|
150
154
|
|
|
@@ -168,6 +172,8 @@ export interface PresetFile {
|
|
|
168
172
|
readonly template: string | null;
|
|
169
173
|
/** Other names the venue goes by in a paper's text; empty when the file names none. */
|
|
170
174
|
readonly aliases: readonly string[];
|
|
175
|
+
/** Whether the venue reviews double-blind; null when the file does not say. */
|
|
176
|
+
readonly blind: boolean | null;
|
|
171
177
|
/** Sections the venue requires; null when the file names none (a child's list replaces its parent's). */
|
|
172
178
|
readonly requiredSections: readonly RequiredSection[] | null;
|
|
173
179
|
/** Null when the file declares no `tex` block (allowed only with `extends`). */
|
|
@@ -175,6 +181,11 @@ export interface PresetFile {
|
|
|
175
181
|
readonly format: VenueFormat;
|
|
176
182
|
/** rule id → ESLint entry, checked against the shipped rules where a config is built. */
|
|
177
183
|
readonly rules: Readonly<Record<string, unknown>>;
|
|
184
|
+
/**
|
|
185
|
+
* The accepted papers the register rules measure a body against; null when the file names none
|
|
186
|
+
* (a child's list replaces its parent's).
|
|
187
|
+
*/
|
|
188
|
+
readonly registerAnchors: readonly RegisterAnchor[] | null;
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
type KindsJson = Readonly<
|
|
@@ -192,6 +203,7 @@ interface FormatJson {
|
|
|
192
203
|
readonly ref_pt_max?: number;
|
|
193
204
|
readonly fonts_text?: string;
|
|
194
205
|
readonly fonts_title?: string;
|
|
206
|
+
readonly body_ends_at?: "references";
|
|
195
207
|
readonly kinds?: KindsJson;
|
|
196
208
|
}
|
|
197
209
|
|
|
@@ -201,6 +213,7 @@ interface PresetJson {
|
|
|
201
213
|
readonly name?: string;
|
|
202
214
|
readonly template?: string;
|
|
203
215
|
readonly aliases?: readonly string[];
|
|
216
|
+
readonly blind?: boolean;
|
|
204
217
|
readonly required_sections?: readonly {
|
|
205
218
|
readonly title: string;
|
|
206
219
|
readonly position?: "last";
|
|
@@ -210,6 +223,12 @@ interface PresetJson {
|
|
|
210
223
|
Partial<Pick<TexRequirements, "tools">>;
|
|
211
224
|
readonly format?: FormatJson;
|
|
212
225
|
readonly rules?: Readonly<Record<string, unknown>>;
|
|
226
|
+
readonly register?: {
|
|
227
|
+
readonly anchors: readonly ({
|
|
228
|
+
readonly paper: string;
|
|
229
|
+
readonly words: number;
|
|
230
|
+
} & Readonly<Record<RegisterMeasureName, number>>)[];
|
|
231
|
+
};
|
|
213
232
|
}
|
|
214
233
|
|
|
215
234
|
/** An optional field as the typed preset holds it: absent is null. */
|
|
@@ -238,6 +257,7 @@ function formatOf(j: FormatJson = {}): VenueFormat {
|
|
|
238
257
|
refPtMax: orNull(j.ref_pt_max),
|
|
239
258
|
fontsText: orNull(j.fonts_text),
|
|
240
259
|
fontsTitle: orNull(j.fonts_title),
|
|
260
|
+
bodyEndsAt: orNull(j.body_ends_at),
|
|
241
261
|
kinds: kindsOf(j.kinds),
|
|
242
262
|
};
|
|
243
263
|
}
|
|
@@ -250,6 +270,38 @@ const sectionsOf = (
|
|
|
250
270
|
? null
|
|
251
271
|
: r.map((x) => ({ title: x.title, position: orNull(x.position) }));
|
|
252
272
|
|
|
273
|
+
/** A preset's JSONC text → its value, or an Error naming the syntax problem. */
|
|
274
|
+
function jsoncOf(text: string, file: string): unknown {
|
|
275
|
+
const parsed = typescript().parseConfigFileTextToJson(file, text);
|
|
276
|
+
const config: unknown = parsed.config;
|
|
277
|
+
const error = parsed.error;
|
|
278
|
+
if (error)
|
|
279
|
+
throw new Error(
|
|
280
|
+
`${file}: not valid JSONC — ${typescript().flattenDiagnosticMessageText(error.messageText, " ")}`,
|
|
281
|
+
);
|
|
282
|
+
return config;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** A preset's `tex` block, `tools` defaulted; null when the file declares none. */
|
|
286
|
+
const texOf = (t: PresetJson["tex"]): TexRequirements | null =>
|
|
287
|
+
t ? { packages: t.packages, tools: t.tools ?? {} } : null;
|
|
288
|
+
|
|
289
|
+
/** A preset's `register.anchors`, each with its counts; null when the file names none. */
|
|
290
|
+
const anchorsOf = (
|
|
291
|
+
r: PresetJson["register"],
|
|
292
|
+
): readonly RegisterAnchor[] | null =>
|
|
293
|
+
r === undefined
|
|
294
|
+
? null
|
|
295
|
+
: r.anchors.map((a) => ({
|
|
296
|
+
paper: a.paper,
|
|
297
|
+
words: a.words,
|
|
298
|
+
counts: {
|
|
299
|
+
contrast_frames: a.contrast_frames,
|
|
300
|
+
claim_emphasis: a.claim_emphasis,
|
|
301
|
+
relation_markers: a.relation_markers,
|
|
302
|
+
},
|
|
303
|
+
}));
|
|
304
|
+
|
|
253
305
|
/**
|
|
254
306
|
* The text of one preset file → the typed file, or an Error naming every problem. The ONE parser
|
|
255
307
|
* of a preset: the toolchain reads its `tex`, the venue rules its `format`, the config its `rules`.
|
|
@@ -263,13 +315,7 @@ export function parsePreset(
|
|
|
263
315
|
file: string,
|
|
264
316
|
dir: string = presetsDir(),
|
|
265
317
|
): PresetFile {
|
|
266
|
-
const
|
|
267
|
-
const config: unknown = parsed.config;
|
|
268
|
-
const error = parsed.error;
|
|
269
|
-
if (error)
|
|
270
|
-
throw new Error(
|
|
271
|
-
`${file}: not valid JSONC — ${typescript().flattenDiagnosticMessageText(error.messageText, " ")}`,
|
|
272
|
-
);
|
|
318
|
+
const config = jsoncOf(text, file);
|
|
273
319
|
const validate = validatorFor(dir);
|
|
274
320
|
if (!matchesSchema(config, validate))
|
|
275
321
|
throw new Error(
|
|
@@ -281,10 +327,12 @@ export function parsePreset(
|
|
|
281
327
|
name: orNull(j.name),
|
|
282
328
|
template: orNull(j.template),
|
|
283
329
|
aliases: j.aliases ?? [],
|
|
330
|
+
blind: orNull(j.blind),
|
|
284
331
|
requiredSections: sectionsOf(j.required_sections),
|
|
285
|
-
tex: j.tex
|
|
332
|
+
tex: texOf(j.tex),
|
|
286
333
|
format: formatOf(j.format),
|
|
287
334
|
rules: j.rules ?? {},
|
|
335
|
+
registerAnchors: anchorsOf(j.register),
|
|
288
336
|
};
|
|
289
337
|
}
|
|
290
338
|
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
judgeRequiredSections,
|
|
14
14
|
judgeTemplate,
|
|
15
15
|
otherVenues,
|
|
16
|
+
FORMAT_RULE_LEVELS,
|
|
17
|
+
formatRules,
|
|
16
18
|
TEX_VENUE_RULE_LEVELS,
|
|
17
19
|
texVenueRules,
|
|
18
20
|
} from "./tex-venue-rules.ts";
|
|
@@ -66,9 +68,15 @@ function lint(
|
|
|
66
68
|
...extra,
|
|
67
69
|
});
|
|
68
70
|
const out: Finding[] = [];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
const deps = { files, venuesDir: VENUES, latex: latexReader };
|
|
72
|
+
for (const [id, rule] of [
|
|
73
|
+
...Object.entries(texVenueRules(deps)).map(
|
|
74
|
+
([n, r]) => [`tex/${n}`, r] as const,
|
|
75
|
+
),
|
|
76
|
+
...Object.entries(formatRules(deps)).map(
|
|
77
|
+
([n, r]) => [`format/${n}`, r] as const,
|
|
78
|
+
),
|
|
79
|
+
]) {
|
|
72
80
|
const visitor = rule.create({
|
|
73
81
|
filename,
|
|
74
82
|
sourceCode: {
|
|
@@ -78,7 +86,7 @@ function lint(
|
|
|
78
86
|
},
|
|
79
87
|
report: (d) =>
|
|
80
88
|
out.push({
|
|
81
|
-
rule:
|
|
89
|
+
rule: id,
|
|
82
90
|
// A finding from an included file arrives as a whole message, its file at the front.
|
|
83
91
|
messageId: "messageId" in d ? d.messageId : "(message)",
|
|
84
92
|
message:
|
|
@@ -720,7 +728,7 @@ describe("otherVenues", () => {
|
|
|
720
728
|
});
|
|
721
729
|
|
|
722
730
|
describe("paperlint's own config", () => {
|
|
723
|
-
it.each(Object.entries(TEX_VENUE_RULE_LEVELS))(
|
|
731
|
+
it.each(Object.entries({ ...TEX_VENUE_RULE_LEVELS, ...FORMAT_RULE_LEVELS }))(
|
|
724
732
|
"turns %s on at %s for every paper.tex, and ships it",
|
|
725
733
|
(id, level) => {
|
|
726
734
|
const tex = buildConfig({}, { sentinel: "tex" }).find((b) =>
|
|
@@ -738,4 +746,101 @@ describe("paperlint's own config", () => {
|
|
|
738
746
|
"tex/venue-leftover": "warn",
|
|
739
747
|
});
|
|
740
748
|
});
|
|
749
|
+
|
|
750
|
+
it("format/layout-override is an error: the layout it guards is the template's", () => {
|
|
751
|
+
expect(FORMAT_RULE_LEVELS).toEqual({ "format/layout-override": "error" });
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
/** A conforming AIDC paper with `preamble` after its class line, and `body` before its statement. */
|
|
756
|
+
const withPreamble = (preamble: string, body = "Text.") =>
|
|
757
|
+
paper(
|
|
758
|
+
`\\documentclass[conference,compsoc]{IEEEtran}\n${preamble}`,
|
|
759
|
+
`${body}\n\\section*{LLM Usage Statement}\nNone.`,
|
|
760
|
+
);
|
|
761
|
+
const overrides = (tex: string, settings: object | undefined = AIDC) =>
|
|
762
|
+
lint(tex, settings).filter((f) => f.rule === "format/layout-override");
|
|
763
|
+
|
|
764
|
+
describe("format/layout-override reports", () => {
|
|
765
|
+
it("each command that changes the template's layout, on its own line", () => {
|
|
766
|
+
const tex = withPreamble(
|
|
767
|
+
[
|
|
768
|
+
"\\usepackage[margin=1in]{geometry}",
|
|
769
|
+
"\\usepackage{amsmath,geometry}",
|
|
770
|
+
"\\setlength{\\textheight}{9.5in}",
|
|
771
|
+
"\\addtolength\\textwidth{1in}",
|
|
772
|
+
"\\setlength{\\columnsep}{0.1in}",
|
|
773
|
+
"\\linespread{0.95}",
|
|
774
|
+
"\\renewcommand{\\baselinestretch}{0.9}",
|
|
775
|
+
"\\geometry{left=1cm}",
|
|
776
|
+
].join("\n"),
|
|
777
|
+
"Text.\n\\newgeometry{top=1cm}\nMore text.",
|
|
778
|
+
);
|
|
779
|
+
expect(
|
|
780
|
+
overrides(tex).map((f) => [f.line, f.message.split("`")[1]]),
|
|
781
|
+
).toEqual([
|
|
782
|
+
[3, "\\usepackage{geometry}"],
|
|
783
|
+
[4, "\\usepackage{geometry}"],
|
|
784
|
+
[5, "\\setlength{\\textheight}"],
|
|
785
|
+
[6, "\\addtolength{\\textwidth}"],
|
|
786
|
+
[7, "\\setlength{\\columnsep}"],
|
|
787
|
+
[8, "\\linespread{0.95}"],
|
|
788
|
+
[9, "\\renewcommand{\\baselinestretch}"],
|
|
789
|
+
[10, "\\geometry"],
|
|
790
|
+
[13, "\\newgeometry"],
|
|
791
|
+
]);
|
|
792
|
+
expect(overrides(tex)[0]?.message).toBe(
|
|
793
|
+
"`\\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
|
+
);
|
|
795
|
+
});
|
|
796
|
+
it("a definition's body counts: the length it sets is set wherever it is used", () => {
|
|
797
|
+
const tex = withPreamble(
|
|
798
|
+
"\\newcommand{\\tall}{\\setlength{\\textheight}{10in}}",
|
|
799
|
+
);
|
|
800
|
+
expect(overrides(tex).map((f) => f.line)).toEqual([3]);
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
describe("format/layout-override stays silent", () => {
|
|
805
|
+
it("silent on what leaves the layout alone", () => {
|
|
806
|
+
const tex = withPreamble(
|
|
807
|
+
[
|
|
808
|
+
"\\usepackage{amsmath}",
|
|
809
|
+
"\\setlength{\\parindent}{0pt}",
|
|
810
|
+
"\\renewcommand{\\thesection}{\\arabic{section}}",
|
|
811
|
+
"% \\usepackage{geometry} in a comment",
|
|
812
|
+
"\\begin{comment}\\linespread{0.9}\\end{comment}",
|
|
813
|
+
// A length command with no arguments at all names no length.
|
|
814
|
+
"{\\setlength}",
|
|
815
|
+
].join("\n"),
|
|
816
|
+
"A \\vspace{2mm} B \\vskip 3pt C \\vskip\\baselineskip D.",
|
|
817
|
+
);
|
|
818
|
+
expect(overrides(tex)).toEqual([]);
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
it("silent on local space pulled back: no venue's call names a negative skip", () => {
|
|
822
|
+
const tex = withPreamble(
|
|
823
|
+
"\\newcommand{\\tight}{\\vspace{-3pt}}",
|
|
824
|
+
"A \\vspace{-2mm} B \\vspace*{-1em} C \\vskip -3pt D.",
|
|
825
|
+
);
|
|
826
|
+
expect(lint(tex, AIDC)).toEqual([]);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it("silent without a preset, or with one that names no template", () => {
|
|
830
|
+
const tex = withPreamble("\\usepackage{geometry}");
|
|
831
|
+
expect(
|
|
832
|
+
lint(tex, undefined).filter((f) => f.rule === "format/layout-override"),
|
|
833
|
+
).toEqual([]);
|
|
834
|
+
expect(overrides(tex, { extends: null })).toEqual([]);
|
|
835
|
+
const house = {
|
|
836
|
+
[`${PAPER}/house.jsonc`]: JSON.stringify({
|
|
837
|
+
tex: { packages: { ieeetran: ["IEEEtran.cls"] } },
|
|
838
|
+
}),
|
|
839
|
+
};
|
|
840
|
+
expect(
|
|
841
|
+
lint(tex, { extends: "./house.jsonc" }, { extra: house }).filter(
|
|
842
|
+
(f) => f.rule === "format/layout-override",
|
|
843
|
+
),
|
|
844
|
+
).toEqual([]);
|
|
845
|
+
});
|
|
741
846
|
});
|
package/src/tex-venue-rules.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* tex/venue-leftover warn the text a reader sees names ANOTHER shipped venue — its `name`
|
|
9
9
|
* or one of its `aliases` — than the one the paper extends
|
|
10
10
|
*
|
|
11
|
+
* and, in the `format` plugin (named for the requirement, not the input — paperlint#151):
|
|
12
|
+
*
|
|
13
|
+
* format/layout-override error the source changes the page layout the venue's template sets for
|
|
14
|
+
* the whole document — margins, text block, line spacing
|
|
15
|
+
*
|
|
16
|
+
* The `format` plugin's other rule, `format/page-limit`, judges the built PDF (`venue-rules.ts`).
|
|
17
|
+
*
|
|
11
18
|
* ── WHY THEY LIVE HERE AND NOT IN `eslint-rules/` ────────────────────────────────
|
|
12
19
|
* Like the `pdf/*` venue rules (`venue-rules.ts`), they need the paper's resolved preset —
|
|
13
20
|
* `paperPreset`, the one answer to "which venue is this paper judged against" — and read it through
|
|
@@ -32,6 +39,7 @@ import {
|
|
|
32
39
|
type ClassLine,
|
|
33
40
|
type DocumentClass,
|
|
34
41
|
type Heading,
|
|
42
|
+
type LayoutOverride,
|
|
35
43
|
type Outline,
|
|
36
44
|
type Place,
|
|
37
45
|
type Span,
|
|
@@ -290,7 +298,13 @@ export function judgeLeftover(
|
|
|
290
298
|
|
|
291
299
|
/** Where a rule's page lives: `docs/rules/tex/<name>.md` on the default branch. */
|
|
292
300
|
export const ruleDocsUrl = (
|
|
293
|
-
name:
|
|
301
|
+
name:
|
|
302
|
+
| TexVenueRuleName
|
|
303
|
+
| "claim-provenance"
|
|
304
|
+
| "register"
|
|
305
|
+
| "contrast-frames"
|
|
306
|
+
| "claim-emphasis"
|
|
307
|
+
| "relation-markers",
|
|
294
308
|
): string =>
|
|
295
309
|
`https://github.com/zernie/paperlint/blob/main/docs/rules/tex/${name}.md`;
|
|
296
310
|
|
|
@@ -306,9 +320,26 @@ type Judge = (r: Reading) => readonly Located[];
|
|
|
306
320
|
export type TexVenueRuleName =
|
|
307
321
|
"template" | "required-section" | "venue-leftover";
|
|
308
322
|
|
|
323
|
+
/** Every rule this module builds: the `tex` plugin's three and the `format` plugin's one. */
|
|
324
|
+
type RuleName = TexVenueRuleName | "layout-override";
|
|
325
|
+
|
|
326
|
+
/** Each layout override, where it stands. Silent for a preset that names no template. */
|
|
327
|
+
export function judgeLayout(
|
|
328
|
+
overrides: readonly LayoutOverride[],
|
|
329
|
+
preset: Preset,
|
|
330
|
+
): readonly Located[] {
|
|
331
|
+
const template = preset.template;
|
|
332
|
+
if (template === null) return [];
|
|
333
|
+
return overrides.map((o) => ({
|
|
334
|
+
messageId: "override",
|
|
335
|
+
data: { command: o.command, venue: preset.label, template: template.text },
|
|
336
|
+
at: spanOf(o.place),
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
|
|
309
340
|
const RULES: Readonly<
|
|
310
341
|
Record<
|
|
311
|
-
|
|
342
|
+
RuleName,
|
|
312
343
|
{ readonly judge: Judge; readonly meta: TexRuleModule["meta"] }
|
|
313
344
|
>
|
|
314
345
|
> = {
|
|
@@ -357,6 +388,22 @@ const RULES: Readonly<
|
|
|
357
388
|
},
|
|
358
389
|
},
|
|
359
390
|
},
|
|
391
|
+
"layout-override": {
|
|
392
|
+
judge: (r) => judgeLayout(r.latex.layoutOverrides(r.src), r.preset),
|
|
393
|
+
meta: {
|
|
394
|
+
type: "problem",
|
|
395
|
+
docs: {
|
|
396
|
+
description:
|
|
397
|
+
"the source changes the page layout the venue's template sets for the whole document: margins, text block, line spacing",
|
|
398
|
+
url: "https://github.com/zernie/paperlint/blob/main/docs/rules/format/layout-override.md",
|
|
399
|
+
},
|
|
400
|
+
schema: [],
|
|
401
|
+
messages: {
|
|
402
|
+
override:
|
|
403
|
+
"`{{command}}` changes the page layout {{venue}}'s template sets (`{{template}}`) — 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",
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
360
407
|
"venue-leftover": {
|
|
361
408
|
judge: (r) =>
|
|
362
409
|
judgeLeftover(r.latex.renderedRuns(r.src), r.preset, r.others()),
|
|
@@ -385,6 +432,13 @@ export const TEX_VENUE_RULE_LEVELS: Readonly<
|
|
|
385
432
|
"tex/venue-leftover": "warn",
|
|
386
433
|
};
|
|
387
434
|
|
|
435
|
+
/** The `format` plugin's rule over the source and its level, on for every `paper.tex`. */
|
|
436
|
+
export const FORMAT_RULE_LEVELS: Readonly<
|
|
437
|
+
Record<"format/layout-override", "error">
|
|
438
|
+
> = {
|
|
439
|
+
"format/layout-override": "error",
|
|
440
|
+
};
|
|
441
|
+
|
|
388
442
|
/**
|
|
389
443
|
* Every shipped venue but the ones on this paper's own chain, each resolved the way a paper
|
|
390
444
|
* extending it would be. A preset that does not resolve is skipped: `pdf/profile` owns that.
|
|
@@ -430,7 +484,7 @@ function readingOf(
|
|
|
430
484
|
};
|
|
431
485
|
}
|
|
432
486
|
|
|
433
|
-
function rule(name:
|
|
487
|
+
function rule(name: RuleName, deps: TexVenueRuleDeps): TexRuleModule {
|
|
434
488
|
const { judge, meta } = RULES[name];
|
|
435
489
|
return {
|
|
436
490
|
meta,
|
|
@@ -459,3 +513,10 @@ export function texVenueRules(
|
|
|
459
513
|
"venue-leftover": rule("venue-leftover", deps),
|
|
460
514
|
};
|
|
461
515
|
}
|
|
516
|
+
|
|
517
|
+
/** The `format` plugin's rule over the source (`page-limit`, over the PDF, is in `venue-rules.ts`). */
|
|
518
|
+
export function formatRules(
|
|
519
|
+
deps: TexVenueRuleDeps,
|
|
520
|
+
): Readonly<Record<"layout-override", TexRuleModule>> {
|
|
521
|
+
return { "layout-override": rule("layout-override", deps) };
|
|
522
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface PaperlintConfig {
|
|
|
26
26
|
kind?: string;
|
|
27
27
|
/** The default path of the built PDF, relative to each paper. */
|
|
28
28
|
pdf?: string;
|
|
29
|
+
/** What identifies the authors, for every paper (joined with a paper's own list). */
|
|
30
|
+
identity?: readonly string[];
|
|
29
31
|
/** A note for humans; ignored. */
|
|
30
32
|
$comment?: unknown;
|
|
31
33
|
}
|