paperlint 4.2.0 → 6.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/CLAUDE.md +15 -2
- package/CONTRIBUTING.md +3 -0
- package/README.md +23 -12
- package/action.harness.mjs +54 -36
- package/action.yml +12 -27
- package/bin/paperlint.mjs +5 -1
- package/dist/adapters/latex/includes.d.ts +5 -1
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +8 -2
- package/dist/adapters/latex/includes.js.map +1 -1
- 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/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/build.d.ts.map +1 -1
- package/dist/build.js +7 -3
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +2 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +92 -42
- 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/paper-source.js +2 -2
- package/dist/domain/tex-document.d.ts +9 -0
- package/dist/domain/tex-document.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.d.ts +5 -1
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +6 -3
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts +20 -0
- package/dist/eslint-rules/paper-context.d.ts.map +1 -0
- package/dist/eslint-rules/paper-context.js +44 -0
- package/dist/eslint-rules/paper-context.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +1 -1
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +32 -18
- package/dist/eslint-rules/paper-typography.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/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +7 -1
- package/dist/eslint-rules/tex-build.js.map +1 -1
- 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/init.d.ts +4 -19
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +24 -29
- package/dist/init.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/package-dirs.d.ts +2 -0
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +8 -0
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-files.d.ts +2 -2
- package/dist/paper-includes.d.ts +17 -0
- package/dist/paper-includes.d.ts.map +1 -0
- package/dist/paper-includes.js +51 -0
- package/dist/paper-includes.js.map +1 -0
- 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 +3 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -0
- package/dist/presets.js.map +1 -1
- package/dist/rules-config.d.ts +1 -0
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-paper.d.ts +17 -3
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +36 -38
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-requirements.d.ts +4 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +3 -0
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +7 -1
- 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 +23 -20
- package/docs/e2e.md +32 -11
- package/docs/install.md +8 -8
- package/docs/prior-art/paper-folder-scaffolding.md +4 -7
- 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.md +55 -46
- package/eslint-rules/latex-language.ts +10 -4
- package/eslint-rules/paper-context.test.ts +47 -0
- package/eslint-rules/paper-context.ts +57 -0
- package/eslint-rules/paper-typography.ts +67 -23
- package/eslint-rules/pdf-last-page-balance.harness.mjs +6 -1
- package/eslint-rules/pdf-last-page-balance.ts +2 -2
- package/eslint-rules/tex-build.ts +8 -1
- 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/fixtures/real-markdown-paper/baseline.json +1 -2
- package/lib/paper-config.ts +3 -1
- package/package.json +1 -1
- package/presets/acm-sigconf.jsonc +3 -1
- package/presets/aidc.jsonc +16 -17
- package/presets/ieee-conference.jsonc +8 -0
- package/presets/venue-profile.schema.json +9 -0
- package/scripts/check.ts +2 -2
- package/scripts/eslint-report-guard.mjs +5 -2
- package/scripts/harness-api.frozen.json +0 -1
- package/skills/paper-pipeline/references/pipeline-status-template.md +2 -16
- package/skills/render-paper/extract-pdf-facts.harness.ts +3 -3
- package/src/adapters/latex/includes.ts +10 -2
- package/src/adapters/latex/index.test.ts +8 -0
- package/src/adapters/latex/index.ts +3 -0
- 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/build.harness.mjs +6 -4
- package/src/build.test.ts +23 -1
- package/src/build.ts +7 -3
- package/src/cli.harness.mjs +22 -54
- package/src/cli.test.ts +15 -18
- package/src/cli.ts +114 -46
- 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/paper-source.ts +2 -2
- package/src/domain/tex-document.ts +10 -0
- package/src/facts-file.test.ts +100 -4
- package/src/facts-file.ts +91 -9
- package/src/init.test.ts +33 -13
- package/src/init.ts +25 -48
- package/src/linted-set.test.ts +11 -1
- package/src/new-paper.harness.mjs +1 -5
- package/src/new-paper.test.ts +7 -1
- package/src/package-dirs.ts +9 -0
- package/src/paper-files.ts +2 -2
- package/src/paper-includes.test.ts +161 -0
- package/src/paper-includes.ts +77 -0
- 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 +3 -0
- package/src/presets.ts +8 -8
- package/src/rule-cases.test.ts +29 -33
- package/src/rule-docs.test.ts +0 -3
- package/src/rules-config.ts +1 -0
- package/src/structure.harness.ts +1 -3
- package/src/structure.ts +1 -1
- package/src/tex-paper.test.ts +78 -61
- package/src/tex-paper.ts +57 -48
- package/src/tex-requirements.ts +9 -0
- package/src/tex-venue-rules.test.ts +111 -6
- package/src/tex-venue-rules.ts +57 -2
- package/src/types.ts +2 -0
- package/src/venue-rules.test.ts +351 -39
- package/src/venue-rules.ts +247 -47
- package/templates/paper/PIPELINE-STATUS.md +0 -4
- package/dist/action-ref.d.ts +0 -12
- package/dist/action-ref.d.ts.map +0 -1
- package/dist/action-ref.js +0 -16
- package/dist/action-ref.js.map +0 -1
- package/dist/eslint-rules/paper-research-question.d.ts +0 -31
- package/dist/eslint-rules/paper-research-question.d.ts.map +0 -1
- package/dist/eslint-rules/paper-research-question.js +0 -188
- package/dist/eslint-rules/paper-research-question.js.map +0 -1
- package/docs/rules/tex/missing-input.md +0 -58
- package/eslint-rules/paper-research-question.harness.mjs +0 -212
- package/eslint-rules/paper-research-question.ts +0 -199
- package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +0 -9
- package/fixtures/paper-research-question/comment-only/paper.tex +0 -7
- package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +0 -10
- package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +0 -6
- package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +0 -6
- package/fixtures/paper-research-question/draft/paper.tex +0 -2
- package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +0 -9
- package/fixtures/paper-research-question/markdown-no-rq/paper.md +0 -4
- package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +0 -12
- package/fixtures/paper-research-question/shipped-no-rq/paper.tex +0 -3
- package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +0 -10
- package/fixtures/paper-research-question/shipped-with-rq/paper.tex +0 -2
- package/src/action-ref.test.ts +0 -26
- package/src/action-ref.ts +0 -15
package/src/tex-paper.ts
CHANGED
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
* file at the include in `paper.tex` that brought it in — the one place ESLint can point to in this
|
|
8
8
|
* file — with the included file, line and column at the front of the message.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* text they never read, and TeX stops on it too. Warn, not error: a file a build step writes (a
|
|
14
|
-
* table of generated numbers) is legitimately absent before that step has run.
|
|
10
|
+
* An include is looked for where the build tells TeX to look (`texSearchPath`): the paper's own
|
|
11
|
+
* directory, then paperlint's inputs. `bodyFiles` names the files of the paper's BODY the author
|
|
12
|
+
* wrote — what the rules over ESLint's own LaTeX text read one by one, each at its own path.
|
|
15
13
|
*/
|
|
16
14
|
import { basename, dirname, join } from "node:path";
|
|
17
15
|
import {
|
|
@@ -23,11 +21,9 @@ import type { Span } from "./domain/tex-document.ts";
|
|
|
23
21
|
import type { LatexReader } from "./ports/latex.ts";
|
|
24
22
|
import type { Files } from "./ports/files.ts";
|
|
25
23
|
import { callerPath } from "./caller-path.ts";
|
|
26
|
-
import type {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
TexRuleModule,
|
|
30
|
-
} from "./tex-venue-rules.ts";
|
|
24
|
+
import type { AbsolutePath } from "./domain/paths.ts";
|
|
25
|
+
import { texSearchPath } from "./package-dirs.ts";
|
|
26
|
+
import type { Located, TexRuleContext } from "./tex-venue-rules.ts";
|
|
31
27
|
|
|
32
28
|
/** What reading a paper needs: the disk, and the LaTeX reader. */
|
|
33
29
|
export interface PaperDeps {
|
|
@@ -46,12 +42,62 @@ export function readPaper(
|
|
|
46
42
|
includes: deps.latex.includes,
|
|
47
43
|
documentBody: deps.latex.documentBody,
|
|
48
44
|
read: (rel) => {
|
|
49
|
-
const
|
|
45
|
+
const at = located(dir, rel, deps.files);
|
|
46
|
+
const b = at === null ? null : deps.files.readBytes(at);
|
|
50
47
|
return b === null ? null : new TextDecoder().decode(b);
|
|
51
48
|
},
|
|
52
49
|
});
|
|
53
50
|
}
|
|
54
51
|
|
|
52
|
+
/** `rel` in the first directory of the paper's TeX search path that holds it, or null. */
|
|
53
|
+
const located = (dir: string, rel: string, files: Files): AbsolutePath | null =>
|
|
54
|
+
texSearchPath(dir)
|
|
55
|
+
.map((d) => callerPath(join(d, rel)))
|
|
56
|
+
.find((p) => files.isFile(p)) ?? null;
|
|
57
|
+
|
|
58
|
+
/** An include that resolved nowhere: the file that wrote it, and the path as written. */
|
|
59
|
+
export interface Unread {
|
|
60
|
+
readonly file: string;
|
|
61
|
+
readonly target: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The files of the body of the paper whose main file is `filename`: every file an include inside its
|
|
66
|
+
* `document` environment brings in, nested ones too, found in the paper's own directory. Not a
|
|
67
|
+
* preamble include (macros are not the body), and not a file found only in paperlint's inputs (not
|
|
68
|
+
* the author's text). `missing` is every include, anywhere, that resolved nowhere.
|
|
69
|
+
*/
|
|
70
|
+
export function bodyFiles(
|
|
71
|
+
filename: string,
|
|
72
|
+
src: string,
|
|
73
|
+
deps: PaperDeps,
|
|
74
|
+
): {
|
|
75
|
+
readonly files: readonly AbsolutePath[];
|
|
76
|
+
readonly missing: readonly Unread[];
|
|
77
|
+
} {
|
|
78
|
+
const dir = dirname(filename);
|
|
79
|
+
const paper = readPaper(filename, src, deps);
|
|
80
|
+
const body = deps.latex.documentBody(src);
|
|
81
|
+
const inBody = (via: Span | null): boolean =>
|
|
82
|
+
via !== null &&
|
|
83
|
+
(body === null || (via.start >= body.start && via.end <= body.end));
|
|
84
|
+
const own = (rel: string): AbsolutePath | null => {
|
|
85
|
+
const at = located(dir, rel, deps.files);
|
|
86
|
+
return at === callerPath(join(dir, rel)) ? at : null;
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
files: [
|
|
90
|
+
...new Set(
|
|
91
|
+
paper.segments
|
|
92
|
+
.filter((s) => s.file !== paper.main && inBody(s.via))
|
|
93
|
+
.map((s) => own(s.file))
|
|
94
|
+
.filter((f): f is AbsolutePath => f !== null),
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
missing: paper.missing.map(({ file, target }) => ({ file, target })),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
55
101
|
/** Line and column (1-based) of an offset in a text. */
|
|
56
102
|
function lineColumn(text: string, at: number): string {
|
|
57
103
|
const line = text.slice(0, at).split("\n").length;
|
|
@@ -113,40 +159,3 @@ export function reportInPaper(
|
|
|
113
159
|
});
|
|
114
160
|
});
|
|
115
161
|
}
|
|
116
|
-
|
|
117
|
-
const MISSING_META: TexRuleModule["meta"] = {
|
|
118
|
-
type: "problem",
|
|
119
|
-
docs: {
|
|
120
|
-
description:
|
|
121
|
-
"an \\input, \\include or \\subfile names a file that is not there, so no rule read its text",
|
|
122
|
-
url: "https://github.com/zernie/paperlint/blob/main/docs/rules/tex/missing-input.md",
|
|
123
|
-
},
|
|
124
|
-
schema: [],
|
|
125
|
-
messages: {
|
|
126
|
-
missing:
|
|
127
|
-
"`{{target}}` is not there (tried `{{target}}.tex` and `{{target}}`, from the paper's directory): TeX stops on it, and no rule read what it should hold",
|
|
128
|
-
missingIn:
|
|
129
|
-
"`{{target}}`, which {{file}} includes, is not there (tried `{{target}}.tex` and `{{target}}`, from the paper's directory): TeX stops on it, and no rule read what it should hold",
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
/** The includes that name no file, each reported where it stands in the main file. */
|
|
134
|
-
export const missingInputRule = (deps: PaperDeps): TexRuleModule => ({
|
|
135
|
-
meta: MISSING_META,
|
|
136
|
-
create(context) {
|
|
137
|
-
return {
|
|
138
|
-
root() {
|
|
139
|
-
const sc = context.sourceCode;
|
|
140
|
-
const paper = readPaper(context.filename, sc.raw ?? sc.text, deps);
|
|
141
|
-
const loc = (i: number) => sc.getLocFromIndex(i);
|
|
142
|
-
paper.missing.forEach((m) => {
|
|
143
|
-
context.report({
|
|
144
|
-
loc: { start: loc(m.via.start), end: loc(m.via.end) },
|
|
145
|
-
messageId: m.file === paper.main ? "missing" : "missingIn",
|
|
146
|
-
data: { target: m.target, file: m.file },
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
},
|
|
152
|
-
});
|
package/src/tex-requirements.ts
CHANGED
|
@@ -131,6 +131,8 @@ export interface VenueFormat {
|
|
|
131
131
|
/** The prefix a font name of the body text starts with (`LinLibertine`). */
|
|
132
132
|
readonly fontsText: string | null;
|
|
133
133
|
readonly fontsTitle: string | null;
|
|
134
|
+
/** How the body is counted: null is banal's count; `references`, the pages before the references. */
|
|
135
|
+
readonly bodyEndsAt: "references" | null;
|
|
134
136
|
readonly kinds: ReadonlyMap<string, KindLimits>;
|
|
135
137
|
}
|
|
136
138
|
|
|
@@ -145,6 +147,7 @@ export const NO_FORMAT: VenueFormat = {
|
|
|
145
147
|
refPtMax: null,
|
|
146
148
|
fontsText: null,
|
|
147
149
|
fontsTitle: null,
|
|
150
|
+
bodyEndsAt: null,
|
|
148
151
|
kinds: new Map(),
|
|
149
152
|
};
|
|
150
153
|
|
|
@@ -168,6 +171,8 @@ export interface PresetFile {
|
|
|
168
171
|
readonly template: string | null;
|
|
169
172
|
/** Other names the venue goes by in a paper's text; empty when the file names none. */
|
|
170
173
|
readonly aliases: readonly string[];
|
|
174
|
+
/** Whether the venue reviews double-blind; null when the file does not say. */
|
|
175
|
+
readonly blind: boolean | null;
|
|
171
176
|
/** Sections the venue requires; null when the file names none (a child's list replaces its parent's). */
|
|
172
177
|
readonly requiredSections: readonly RequiredSection[] | null;
|
|
173
178
|
/** Null when the file declares no `tex` block (allowed only with `extends`). */
|
|
@@ -192,6 +197,7 @@ interface FormatJson {
|
|
|
192
197
|
readonly ref_pt_max?: number;
|
|
193
198
|
readonly fonts_text?: string;
|
|
194
199
|
readonly fonts_title?: string;
|
|
200
|
+
readonly body_ends_at?: "references";
|
|
195
201
|
readonly kinds?: KindsJson;
|
|
196
202
|
}
|
|
197
203
|
|
|
@@ -201,6 +207,7 @@ interface PresetJson {
|
|
|
201
207
|
readonly name?: string;
|
|
202
208
|
readonly template?: string;
|
|
203
209
|
readonly aliases?: readonly string[];
|
|
210
|
+
readonly blind?: boolean;
|
|
204
211
|
readonly required_sections?: readonly {
|
|
205
212
|
readonly title: string;
|
|
206
213
|
readonly position?: "last";
|
|
@@ -238,6 +245,7 @@ function formatOf(j: FormatJson = {}): VenueFormat {
|
|
|
238
245
|
refPtMax: orNull(j.ref_pt_max),
|
|
239
246
|
fontsText: orNull(j.fonts_text),
|
|
240
247
|
fontsTitle: orNull(j.fonts_title),
|
|
248
|
+
bodyEndsAt: orNull(j.body_ends_at),
|
|
241
249
|
kinds: kindsOf(j.kinds),
|
|
242
250
|
};
|
|
243
251
|
}
|
|
@@ -281,6 +289,7 @@ export function parsePreset(
|
|
|
281
289
|
name: orNull(j.name),
|
|
282
290
|
template: orNull(j.template),
|
|
283
291
|
aliases: j.aliases ?? [],
|
|
292
|
+
blind: orNull(j.blind),
|
|
284
293
|
requiredSections: sectionsOf(j.required_sections),
|
|
285
294
|
tex: j.tex ? { packages: j.tex.packages, tools: j.tex.tools ?? {} } : null,
|
|
286
295
|
format: formatOf(j.format),
|
|
@@ -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:
|
|
@@ -199,7 +207,7 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
|
|
|
199
207
|
"\\section*{LLM Usage Statement}\nNone.\n",
|
|
200
208
|
};
|
|
201
209
|
expect(lint(tex, AIDC, { extra })).toEqual([]);
|
|
202
|
-
// Without the file, the section is missing
|
|
210
|
+
// Without the file, the section is missing.
|
|
203
211
|
expect(ids(lint(tex, AIDC))).toEqual(["tex/required-section:missing"]);
|
|
204
212
|
});
|
|
205
213
|
|
|
@@ -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,
|
|
@@ -306,9 +314,26 @@ type Judge = (r: Reading) => readonly Located[];
|
|
|
306
314
|
export type TexVenueRuleName =
|
|
307
315
|
"template" | "required-section" | "venue-leftover";
|
|
308
316
|
|
|
317
|
+
/** Every rule this module builds: the `tex` plugin's three and the `format` plugin's one. */
|
|
318
|
+
type RuleName = TexVenueRuleName | "layout-override";
|
|
319
|
+
|
|
320
|
+
/** Each layout override, where it stands. Silent for a preset that names no template. */
|
|
321
|
+
export function judgeLayout(
|
|
322
|
+
overrides: readonly LayoutOverride[],
|
|
323
|
+
preset: Preset,
|
|
324
|
+
): readonly Located[] {
|
|
325
|
+
const template = preset.template;
|
|
326
|
+
if (template === null) return [];
|
|
327
|
+
return overrides.map((o) => ({
|
|
328
|
+
messageId: "override",
|
|
329
|
+
data: { command: o.command, venue: preset.label, template: template.text },
|
|
330
|
+
at: spanOf(o.place),
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
|
|
309
334
|
const RULES: Readonly<
|
|
310
335
|
Record<
|
|
311
|
-
|
|
336
|
+
RuleName,
|
|
312
337
|
{ readonly judge: Judge; readonly meta: TexRuleModule["meta"] }
|
|
313
338
|
>
|
|
314
339
|
> = {
|
|
@@ -357,6 +382,22 @@ const RULES: Readonly<
|
|
|
357
382
|
},
|
|
358
383
|
},
|
|
359
384
|
},
|
|
385
|
+
"layout-override": {
|
|
386
|
+
judge: (r) => judgeLayout(r.latex.layoutOverrides(r.src), r.preset),
|
|
387
|
+
meta: {
|
|
388
|
+
type: "problem",
|
|
389
|
+
docs: {
|
|
390
|
+
description:
|
|
391
|
+
"the source changes the page layout the venue's template sets for the whole document: margins, text block, line spacing",
|
|
392
|
+
url: "https://github.com/zernie/paperlint/blob/main/docs/rules/format/layout-override.md",
|
|
393
|
+
},
|
|
394
|
+
schema: [],
|
|
395
|
+
messages: {
|
|
396
|
+
override:
|
|
397
|
+
"`{{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",
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
360
401
|
"venue-leftover": {
|
|
361
402
|
judge: (r) =>
|
|
362
403
|
judgeLeftover(r.latex.renderedRuns(r.src), r.preset, r.others()),
|
|
@@ -385,6 +426,13 @@ export const TEX_VENUE_RULE_LEVELS: Readonly<
|
|
|
385
426
|
"tex/venue-leftover": "warn",
|
|
386
427
|
};
|
|
387
428
|
|
|
429
|
+
/** The `format` plugin's rule over the source and its level, on for every `paper.tex`. */
|
|
430
|
+
export const FORMAT_RULE_LEVELS: Readonly<
|
|
431
|
+
Record<"format/layout-override", "error">
|
|
432
|
+
> = {
|
|
433
|
+
"format/layout-override": "error",
|
|
434
|
+
};
|
|
435
|
+
|
|
388
436
|
/**
|
|
389
437
|
* Every shipped venue but the ones on this paper's own chain, each resolved the way a paper
|
|
390
438
|
* extending it would be. A preset that does not resolve is skipped: `pdf/profile` owns that.
|
|
@@ -430,7 +478,7 @@ function readingOf(
|
|
|
430
478
|
};
|
|
431
479
|
}
|
|
432
480
|
|
|
433
|
-
function rule(name:
|
|
481
|
+
function rule(name: RuleName, deps: TexVenueRuleDeps): TexRuleModule {
|
|
434
482
|
const { judge, meta } = RULES[name];
|
|
435
483
|
return {
|
|
436
484
|
meta,
|
|
@@ -459,3 +507,10 @@ export function texVenueRules(
|
|
|
459
507
|
"venue-leftover": rule("venue-leftover", deps),
|
|
460
508
|
};
|
|
461
509
|
}
|
|
510
|
+
|
|
511
|
+
/** The `format` plugin's rule over the source (`page-limit`, over the PDF, is in `venue-rules.ts`). */
|
|
512
|
+
export function formatRules(
|
|
513
|
+
deps: TexVenueRuleDeps,
|
|
514
|
+
): Readonly<Record<"layout-override", TexRuleModule>> {
|
|
515
|
+
return { "layout-override": rule("layout-override", deps) };
|
|
516
|
+
}
|
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
|
}
|