paperlint 6.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/dist/adapters/latex/index.d.ts +1 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +3 -2
- package/dist/adapters/latex/index.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 +15 -18
- package/dist/cli.js.map +1 -1
- 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 +19 -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/ports/latex.d.ts +6 -1
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/presets.d.ts +3 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -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 +6 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +26 -6
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +1 -1
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js.map +1 -1
- 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 +46 -42
- package/package.json +1 -1
- package/presets/aidc.jsonc +50 -0
- package/presets/venue-profile.schema.json +48 -1
- package/skills/grade-paper-writing/SKILL.md +59 -27
- package/skills/grade-paper-writing/SKILL.md.spec.ts +58 -26
- package/src/adapters/latex/index.test.ts +91 -0
- package/src/adapters/latex/index.ts +3 -2
- package/src/adapters/latex/prose.ts +188 -58
- package/src/cli.ts +19 -18
- package/src/domain/register.test.ts +153 -0
- package/src/domain/register.ts +261 -0
- package/src/domain/tex-document.ts +37 -3
- package/src/ports/latex.ts +6 -0
- package/src/presets.ts +7 -1
- 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 +67 -17
- package/src/tex-requirements.ts +47 -8
- package/src/tex-venue-rules.ts +7 -1
package/src/rule-cases.test.ts
CHANGED
|
@@ -168,6 +168,14 @@ const FORMAL = Array.from(
|
|
|
168
168
|
() =>
|
|
169
169
|
"The rule reads the body of the paper and counts every sentence it holds.",
|
|
170
170
|
).join(" ");
|
|
171
|
+
/** FORMAL with `n` more sentences `s`: a body whose rate of one register measure is set. */
|
|
172
|
+
const formalWith = (n: number, s: string): string =>
|
|
173
|
+
`${FORMAL} ${Array.from({ length: n }, () => s).join(" ")}`;
|
|
174
|
+
/** FORMAL with enough relation markers for AIDC's band (above 25.4 per 10,000 words). */
|
|
175
|
+
const LINKED = formalWith(
|
|
176
|
+
10,
|
|
177
|
+
"Therefore, the rule counts the sentence because the body holds it.",
|
|
178
|
+
);
|
|
171
179
|
const STATUS_FILE = `${P}/PIPELINE-STATUS.md`;
|
|
172
180
|
const SHIPPED_STATUS =
|
|
173
181
|
"---\nstages:\n - stage: submitted\n date: 2026-07-22\n pdf: versions/a.pdf\n bytes: 1\n---\n# PIPELINE-STATUS\n";
|
|
@@ -357,6 +365,32 @@ const CASES: Readonly<Record<string, RuleCases>> = {
|
|
|
357
365
|
},
|
|
358
366
|
silent: paper(tex(FORMAL)),
|
|
359
367
|
},
|
|
368
|
+
// The three register bands judge a body against the anchors of AIDC's preset; the body opens on
|
|
369
|
+
// line 3, right after the \\section, where the finding about the whole body stands.
|
|
370
|
+
"tex/contrast-frames": {
|
|
371
|
+
reports: {
|
|
372
|
+
tree: aidc(formalWith(5, "It reads the word, not the operation.")),
|
|
373
|
+
file: TEX_FILE,
|
|
374
|
+
severity: 1,
|
|
375
|
+
line: 3,
|
|
376
|
+
},
|
|
377
|
+
silent: aidc(LINKED),
|
|
378
|
+
},
|
|
379
|
+
"tex/claim-emphasis": {
|
|
380
|
+
reports: {
|
|
381
|
+
tree: aidc(
|
|
382
|
+
formalWith(6, "We find that \\textbf{the rule counts 46 sentences}."),
|
|
383
|
+
),
|
|
384
|
+
file: TEX_FILE,
|
|
385
|
+
severity: 1,
|
|
386
|
+
line: 3,
|
|
387
|
+
},
|
|
388
|
+
silent: aidc(LINKED),
|
|
389
|
+
},
|
|
390
|
+
"tex/relation-markers": {
|
|
391
|
+
reports: { tree: aidc(FORMAL), file: TEX_FILE, severity: 1, line: 3 },
|
|
392
|
+
silent: aidc(LINKED),
|
|
393
|
+
},
|
|
360
394
|
"bib/reachable-entry": {
|
|
361
395
|
reports: {
|
|
362
396
|
tree: paper(inlineBib("@book{a,\n title = {A},\n year = {2026}\n}")),
|
|
@@ -498,23 +532,39 @@ describe("every registered rule has cases, and every case names a registered rul
|
|
|
498
532
|
});
|
|
499
533
|
});
|
|
500
534
|
|
|
535
|
+
/**
|
|
536
|
+
* A case lints its tree through the whole CLI: a body long enough for a rate to be judged (2 000
|
|
537
|
+
* words and more) takes seconds under coverage, on a machine running every suite at once.
|
|
538
|
+
*/
|
|
539
|
+
const CASE_TIMEOUT_MS = 30_000;
|
|
540
|
+
|
|
501
541
|
describe.each(Object.entries(CASES))("%s, through real ESLint", (id, c) => {
|
|
502
|
-
it(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
542
|
+
it(
|
|
543
|
+
`reports on ${c.reports.file}:${String(c.reports.line)} at severity ${String(c.reports.severity)}`,
|
|
544
|
+
{ timeout: CASE_TIMEOUT_MS },
|
|
545
|
+
async () => {
|
|
546
|
+
const got = (await lintTree(c.reports.tree)).filter(
|
|
547
|
+
(m) => m.ruleId === id,
|
|
548
|
+
);
|
|
549
|
+
expect(
|
|
550
|
+
got.some(
|
|
551
|
+
(m) =>
|
|
552
|
+
m.file === c.reports.file &&
|
|
553
|
+
m.line === c.reports.line &&
|
|
554
|
+
m.severity === c.reports.severity,
|
|
555
|
+
),
|
|
556
|
+
JSON.stringify(got),
|
|
557
|
+
).toBe(true);
|
|
558
|
+
},
|
|
559
|
+
);
|
|
514
560
|
|
|
515
|
-
it(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
)
|
|
519
|
-
|
|
561
|
+
it(
|
|
562
|
+
"stays silent on a conforming paper",
|
|
563
|
+
{ timeout: CASE_TIMEOUT_MS },
|
|
564
|
+
async () => {
|
|
565
|
+
expect((await lintTree(c.silent)).filter((m) => m.ruleId === id)).toEqual(
|
|
566
|
+
[],
|
|
567
|
+
);
|
|
568
|
+
},
|
|
569
|
+
);
|
|
520
570
|
});
|
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[]>>;
|
|
@@ -180,6 +181,11 @@ export interface PresetFile {
|
|
|
180
181
|
readonly format: VenueFormat;
|
|
181
182
|
/** rule id → ESLint entry, checked against the shipped rules where a config is built. */
|
|
182
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;
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
type KindsJson = Readonly<
|
|
@@ -217,6 +223,12 @@ interface PresetJson {
|
|
|
217
223
|
Partial<Pick<TexRequirements, "tools">>;
|
|
218
224
|
readonly format?: FormatJson;
|
|
219
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
|
+
};
|
|
220
232
|
}
|
|
221
233
|
|
|
222
234
|
/** An optional field as the typed preset holds it: absent is null. */
|
|
@@ -258,6 +270,38 @@ const sectionsOf = (
|
|
|
258
270
|
? null
|
|
259
271
|
: r.map((x) => ({ title: x.title, position: orNull(x.position) }));
|
|
260
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
|
+
|
|
261
305
|
/**
|
|
262
306
|
* The text of one preset file → the typed file, or an Error naming every problem. The ONE parser
|
|
263
307
|
* of a preset: the toolchain reads its `tex`, the venue rules its `format`, the config its `rules`.
|
|
@@ -271,13 +315,7 @@ export function parsePreset(
|
|
|
271
315
|
file: string,
|
|
272
316
|
dir: string = presetsDir(),
|
|
273
317
|
): PresetFile {
|
|
274
|
-
const
|
|
275
|
-
const config: unknown = parsed.config;
|
|
276
|
-
const error = parsed.error;
|
|
277
|
-
if (error)
|
|
278
|
-
throw new Error(
|
|
279
|
-
`${file}: not valid JSONC — ${typescript().flattenDiagnosticMessageText(error.messageText, " ")}`,
|
|
280
|
-
);
|
|
318
|
+
const config = jsoncOf(text, file);
|
|
281
319
|
const validate = validatorFor(dir);
|
|
282
320
|
if (!matchesSchema(config, validate))
|
|
283
321
|
throw new Error(
|
|
@@ -291,9 +329,10 @@ export function parsePreset(
|
|
|
291
329
|
aliases: j.aliases ?? [],
|
|
292
330
|
blind: orNull(j.blind),
|
|
293
331
|
requiredSections: sectionsOf(j.required_sections),
|
|
294
|
-
tex: j.tex
|
|
332
|
+
tex: texOf(j.tex),
|
|
295
333
|
format: formatOf(j.format),
|
|
296
334
|
rules: j.rules ?? {},
|
|
335
|
+
registerAnchors: anchorsOf(j.register),
|
|
297
336
|
};
|
|
298
337
|
}
|
|
299
338
|
|
package/src/tex-venue-rules.ts
CHANGED
|
@@ -298,7 +298,13 @@ export function judgeLeftover(
|
|
|
298
298
|
|
|
299
299
|
/** Where a rule's page lives: `docs/rules/tex/<name>.md` on the default branch. */
|
|
300
300
|
export const ruleDocsUrl = (
|
|
301
|
-
name:
|
|
301
|
+
name:
|
|
302
|
+
| TexVenueRuleName
|
|
303
|
+
| "claim-provenance"
|
|
304
|
+
| "register"
|
|
305
|
+
| "contrast-frames"
|
|
306
|
+
| "claim-emphasis"
|
|
307
|
+
| "relation-markers",
|
|
302
308
|
): string =>
|
|
303
309
|
`https://github.com/zernie/paperlint/blob/main/docs/rules/tex/${name}.md`;
|
|
304
310
|
|