paperlint 3.0.0 → 3.1.1
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/.github/workflows/ci.yml +28 -41
- package/.github/workflows/dependabot-automerge.yml +1 -1
- package/CLAUDE.md +51 -56
- package/CONTRIBUTING.md +30 -18
- package/README.md +179 -180
- package/bin/paperlint.test.mjs +45 -0
- package/dist/adapters/node/files.io.d.ts +2 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +3 -2
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts +15 -4
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +31 -21
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +14 -5
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts +8 -0
- package/dist/adapters/references/pool.d.ts.map +1 -0
- package/dist/adapters/references/pool.js +19 -0
- package/dist/adapters/references/pool.js.map +1 -0
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +6 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +10 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +29 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +93 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +296 -80
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +27 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +4 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/result.d.ts +0 -2
- package/dist/domain/result.d.ts.map +1 -1
- package/dist/domain/result.js +0 -4
- package/dist/domain/result.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -1
- package/dist/hooks-settings.d.ts +8 -5
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +32 -32
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +4 -4
- package/dist/init.js.map +1 -1
- package/dist/latex-log.d.ts +0 -2
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +7 -13
- package/dist/latex-log.js.map +1 -1
- package/dist/link-skills.d.ts +15 -2
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +36 -20
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts +8 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +50 -15
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +30 -8
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +59 -30
- package/dist/paper-files.js.map +1 -1
- package/dist/pdf-facts.d.ts +11 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +16 -18
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -1
- package/dist/presets.js.map +1 -1
- package/dist/references.js +2 -2
- package/dist/references.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +8 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +6 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +8 -3
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +26 -10
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +12 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +48 -4
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/test-tooling.md +7 -30
- package/docs/rules.md +29 -25
- package/docs/skills.md +80 -0
- package/eslint-rules/install-path-literals.harness.mjs +17 -1
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.mjs +12 -9
- package/eslint-rules/latex-language.test.mjs +200 -0
- package/eslint-rules/paper-research-question.harness.mjs +73 -7
- package/eslint-rules/paper-stages.harness.mjs +9 -14
- package/eslint-rules/paper-stages.test.mjs +135 -0
- package/eslint-rules/paper-typography.mjs +56 -9
- package/eslint-rules/paper-typography.test.mjs +186 -1
- package/eslint-rules/pdf-last-page-balance.harness.mjs +63 -7
- package/eslint-rules/review-frontmatter.mjs +2 -1
- package/eslint-rules/review-frontmatter.schema.json +1 -1
- package/eslint-rules/review-frontmatter.test.mjs +37 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +49 -0
- package/eslint-rules/tex-build.mjs +13 -13
- package/eslint.config.mjs +6 -2
- package/fixtures/toolchain-mirror/catalog.txt +1 -0
- package/fixtures/toolchain-mirror/install-tl +4 -1
- package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
- package/hooks/hooks.harness.mjs +6 -11
- package/hooks/paper-edit-guard.hook.mjs +2 -2
- package/hooks/paper-edit-guard.test.mjs +61 -0
- package/hooks/paper-skills-nudge.hook.mjs +2 -2
- package/hooks/paper-skills-nudge.test.mjs +38 -0
- package/hooks/paper-status-gates.test.mjs +42 -0
- package/lib/agent-cli-version.harness.mjs +7 -0
- package/lib/check.d.mts +17 -0
- package/lib/check.mjs +50 -0
- package/lib/check.test.ts +67 -0
- package/lib/markdown.mjs +16 -69
- package/lib/markdown.test.mjs +246 -0
- package/lib/paper-config.harness.mjs +28 -9
- package/lib/paper-config.mjs +7 -2
- package/lib/skill-checks.mjs +18 -46
- package/lib/skill-checks.test.mjs +297 -0
- package/lib/skill-corpus.mjs +11 -13
- package/lib/skill-corpus.test.mjs +54 -0
- package/lib/skill-trigger-cases.harness.mjs +57 -4
- package/package.json +6 -5
- package/scripts/check.harness.mjs +4 -10
- package/scripts/check.mjs +45 -42
- package/scripts/check.test.mjs +104 -0
- package/scripts/coverage-config.test.ts +406 -0
- package/scripts/eslint-report-guard.test.mjs +48 -0
- package/scripts/harness-api.frozen.json +0 -2
- package/scripts/layer-legacy-frozen.d.mts +9 -1
- package/scripts/layer-legacy-frozen.mjs +28 -11
- package/scripts/layer-legacy-frozen.test.ts +143 -1
- package/scripts/rules-are-content-only.harness.mjs +3 -11
- package/scripts/rules-are-content-only.mjs +29 -19
- package/scripts/rules-are-content-only.test.mjs +61 -0
- package/scripts/rules-see-files.mjs +11 -9
- package/scripts/rules-see-files.test.mjs +71 -0
- package/scripts/test.mjs +44 -0
- package/scripts/test.test.mjs +52 -0
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.mjs.snap +57 -0
- package/skills/grade-paper-writing/prose-lint.mjs +31 -12
- package/skills/grade-paper-writing/prose-lint.test.mjs +153 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +3 -3
- package/skills/paper-pipeline/scripts/README.md +13 -132
- package/skills/paper-pipeline/scripts/announce.test.mjs +102 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.test.mjs +86 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +8 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/consumer.mjs +13 -25
- package/skills/paper-pipeline/scripts/consumer.test.mjs +113 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +9 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +380 -0
- package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +71 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +4 -4
- package/skills/paper-pipeline/scripts/generated-code.mjs +3 -2
- package/skills/paper-pipeline/scripts/generated-code.test.mjs +62 -0
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +302 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +1 -1
- package/skills/paper-pipeline/scripts/{ledger.selftest.mjs → ledger.test.mjs} +44 -76
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +474 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -63
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +37 -21
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +73 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +4 -47
- package/skills/paper-pipeline/scripts/population-map.mjs +12 -2
- package/skills/paper-pipeline/scripts/population-map.test.mjs +154 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +26 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +1 -2
- package/skills/paper-pipeline/scripts/round-diff.mjs +13 -6
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +159 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +30 -6
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +468 -0
- package/skills/paper-pipeline/scripts/status.mjs +2 -2
- package/skills/paper-pipeline/scripts/status.test.mjs +232 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +2 -2
- package/skills/render-paper/extract-pdf-facts.harness.mjs +56 -7
- package/skills/tighten-paper/__snapshots__/structure.test.mjs.snap +133 -0
- package/skills/tighten-paper/structure.test.mjs +116 -0
- package/skills/verify-citations/SKILL.md +5 -5
- package/skills/verify-citations/SKILL.md.spec.ts +4 -4
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +235 -0
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +274 -0
- package/skills/verify-citations/scripts/bib-authors.cli.test.mjs +178 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +2 -1
- package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +96 -96
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +287 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +149 -5
- package/src/adapters/banal/failure.test.ts +70 -1
- package/src/adapters/banal/index.test.ts +16 -0
- package/src/adapters/banal/output.test.ts +5 -0
- package/src/adapters/curl/download.test.ts +43 -1
- package/src/adapters/memory/index.test.ts +25 -0
- package/src/adapters/node/files.io.ts +3 -2
- package/src/adapters/node/files.test.ts +13 -1
- package/src/adapters/node/process.test.ts +38 -0
- package/src/adapters/pdfjs/fill.test.ts +34 -0
- package/src/adapters/pdfjs/fill.ts +44 -25
- package/src/adapters/references/index.test.ts +228 -0
- package/src/adapters/references/index.ts +21 -6
- package/src/adapters/references/pool.test.ts +31 -0
- package/src/adapters/references/pool.ts +24 -0
- package/src/build-engine.harness.mjs +3 -7
- package/src/build-engine.test.ts +137 -0
- package/src/build-engine.ts +6 -1
- package/src/build.harness.mjs +51 -11
- package/src/build.test.ts +362 -0
- package/src/build.ts +40 -14
- package/src/cli.harness.mjs +12 -19
- package/src/cli.test.ts +584 -0
- package/src/cli.ts +409 -94
- package/src/doctor.harness.mjs +3 -8
- package/src/doctor.test.ts +64 -0
- package/src/doctor.ts +12 -4
- package/src/domain/result.test.ts +4 -0
- package/src/domain/result.ts +0 -5
- package/src/engine.harness.mjs +77 -7
- package/src/engine.ts +35 -0
- package/src/facts-file.test.ts +110 -0
- package/src/hooks-settings.harness.mjs +43 -13
- package/src/hooks-settings.ts +41 -35
- package/src/init.test.ts +308 -5
- package/src/init.ts +4 -4
- package/src/latex-log.harness.mjs +32 -7
- package/src/latex-log.ts +6 -13
- package/src/latex-loop.harness.mjs +8 -7
- package/src/link-skills.harness.mjs +3 -8
- package/src/link-skills.test.ts +105 -0
- package/src/link-skills.ts +75 -21
- package/src/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +5 -7
- package/src/new-paper.test.ts +203 -2
- package/src/new-paper.ts +84 -17
- package/src/paper-files.test.ts +122 -5
- package/src/paper-files.ts +99 -39
- package/src/paper-settings-commands.test.ts +41 -5
- package/src/paper-settings.test.ts +20 -0
- package/src/parse-args.test.ts +48 -0
- package/src/pdf-facts.harness.mjs +3 -6
- package/src/pdf-facts.test.ts +144 -0
- package/src/pdf-facts.ts +17 -19
- package/src/pdf-geometry.harness.mjs +3 -7
- package/src/pdf-geometry.ts +1 -1
- package/src/presets.test.ts +2 -0
- package/src/presets.ts +7 -1
- package/src/references.test.ts +97 -0
- package/src/references.ts +2 -2
- package/src/rules-config.harness.mjs +10 -8
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +5 -9
- package/src/structure.ts +1 -1
- package/src/tex-requirements.harness.mjs +34 -7
- package/src/tex-requirements.ts +12 -6
- package/src/toolchain.harness.mjs +44 -13
- package/src/toolchain.test.ts +280 -0
- package/src/toolchain.ts +37 -12
- package/src/types.ts +6 -0
- package/src/venue-rules.test.ts +63 -0
- package/src/venue-rules.ts +14 -12
- package/templates/paper/paperlint.json +1 -1
- package/action.mutations.mjs +0 -162
- package/eslint-rules/install-path-literals.mutations.mjs +0 -62
- package/eslint-rules/latex-language.mutations.mjs +0 -196
- package/eslint-rules/paper-research-question.mutations.mjs +0 -127
- package/eslint-rules/paper-stages.mutations.mjs +0 -81
- package/eslint-rules/papers.mutations.mjs +0 -186
- package/eslint-rules/temp-root-realpath.mutations.mjs +0 -99
- package/eslint-rules/tex-build.mutations.mjs +0 -258
- package/hooks/hooks.mutations.mjs +0 -337
- package/lib/agent-cli-version.mutations.mjs +0 -109
- package/lib/mutation-driver.harness.mjs +0 -227
- package/lib/mutation-driver.mjs +0 -397
- package/lib/mutation-driver.mutations.mjs +0 -68
- package/lib/paper-config.mutations.mjs +0 -143
- package/lib/skill-trigger-cases.mutations.mjs +0 -65
- package/scripts/check.mutations.mjs +0 -110
- package/scripts/exclusive.mjs +0 -138
- package/scripts/mutation-batteries-frozen.harness.mjs +0 -204
- package/scripts/mutation-batteries-frozen.mjs +0 -238
- package/scripts/mutation-batteries.frozen.json +0 -108
- package/scripts/rules-are-content-only.mutations.mjs +0 -81
- package/scripts/rules-see-files.mutations.mjs +0 -131
- package/scripts/run-mutations.mjs +0 -100
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +0 -218
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +0 -190
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +0 -65
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +0 -122
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +0 -59
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +0 -276
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +0 -65
- package/skills/skill-contract.mutations.mjs +0 -179
- package/src/build.mutations.mjs +0 -196
- package/src/cli.mutations.mjs +0 -414
- package/src/doctor.mutations.mjs +0 -175
- package/src/hooks-settings.mutations.mjs +0 -99
- package/src/link-skills.mutations.mjs +0 -140
- package/src/new-paper.mutations.mjs +0 -79
- package/src/structure.mutations.mjs +0 -83
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pdf-facts.ts on document shapes real test PDFs do not carry, through a fake pdf.js document
|
|
3
|
+
* (the real pdf.js library for its transforms and operator codes): a font object that is not one,
|
|
4
|
+
* a font without a name or metrics, an item whose font never resolved, zero-scale transforms, a
|
|
5
|
+
* document with no pages, text with no font, and a failure that is not an Error.
|
|
6
|
+
*/
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { getResolvedPDFJS } from "unpdf";
|
|
9
|
+
import { test } from "vitest";
|
|
10
|
+
import { factsOf, failureOf, rawFontOf } from "./pdf-facts.ts";
|
|
11
|
+
|
|
12
|
+
const lib = await getResolvedPDFJS();
|
|
13
|
+
const H = 792;
|
|
14
|
+
|
|
15
|
+
interface FakeItem {
|
|
16
|
+
str: string;
|
|
17
|
+
fontName: string;
|
|
18
|
+
transform: number[];
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}
|
|
22
|
+
/** A page whose text items and loaded fonts are given; its viewport flips y like pdf.js's. */
|
|
23
|
+
function page(items: FakeItem[], fonts: Record<string, unknown>) {
|
|
24
|
+
const objs = new Map(Object.entries(fonts));
|
|
25
|
+
return {
|
|
26
|
+
getOperatorList: async () => ({ fnArray: [], argsArray: [] }),
|
|
27
|
+
getTextContent: async () => ({ items }),
|
|
28
|
+
// Iterable like pdf.js's `commonObjs`, with its `has`/`get`.
|
|
29
|
+
commonObjs: {
|
|
30
|
+
has: (id: string) => objs.has(id),
|
|
31
|
+
get: (id: string) => objs.get(id),
|
|
32
|
+
[Symbol.iterator]: () => objs[Symbol.iterator](),
|
|
33
|
+
},
|
|
34
|
+
getViewport: () => ({
|
|
35
|
+
width: 612,
|
|
36
|
+
height: H,
|
|
37
|
+
transform: [1, 0, 0, -1, 0, H],
|
|
38
|
+
convertToViewportPoint: (x: number, y: number) => [x, H - y],
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const doc = (pages: ReturnType<typeof page>[]) =>
|
|
43
|
+
({
|
|
44
|
+
numPages: pages.length,
|
|
45
|
+
getPage: async (i: number) => pages[i - 1],
|
|
46
|
+
}) as never;
|
|
47
|
+
const item = (
|
|
48
|
+
fontName: string,
|
|
49
|
+
transform: number[],
|
|
50
|
+
height = 10,
|
|
51
|
+
): FakeItem => ({
|
|
52
|
+
str: "word",
|
|
53
|
+
fontName,
|
|
54
|
+
transform,
|
|
55
|
+
width: 20,
|
|
56
|
+
height,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("rawFontOf: not an object, not a font, and a font without a name", () => {
|
|
60
|
+
assert.deepEqual(
|
|
61
|
+
[
|
|
62
|
+
rawFontOf("a", null),
|
|
63
|
+
rawFontOf("b", { x: 1 }),
|
|
64
|
+
rawFontOf("c", { loadedName: "L" }),
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
null,
|
|
68
|
+
null,
|
|
69
|
+
{
|
|
70
|
+
id: "c",
|
|
71
|
+
name: "",
|
|
72
|
+
type: undefined,
|
|
73
|
+
subtype: undefined,
|
|
74
|
+
isType3Font: false,
|
|
75
|
+
missingFile: false,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("a document with no pages is `empty`; text drawn with no font is refused", async () => {
|
|
82
|
+
assert.deepEqual(await factsOf(doc([]), lib), {
|
|
83
|
+
ok: false,
|
|
84
|
+
reason: "empty",
|
|
85
|
+
detail: "the PDF has no pages",
|
|
86
|
+
});
|
|
87
|
+
const r = await factsOf(
|
|
88
|
+
doc([page([item("f0", [10, 0, 0, 10, 50, 700])], {})]),
|
|
89
|
+
lib,
|
|
90
|
+
);
|
|
91
|
+
assert.equal(r.ok, false);
|
|
92
|
+
assert.equal(!r.ok && r.reason, "zero-fonts-on-text");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
96
|
+
const fonts = {
|
|
97
|
+
// a font with a name and an ascent but no descent
|
|
98
|
+
f1: { loadedName: "f1", name: "Named", ascent: 0.8 },
|
|
99
|
+
// a font with no name and no metrics at all
|
|
100
|
+
f2: { loadedName: "f2" },
|
|
101
|
+
};
|
|
102
|
+
const r = await factsOf(
|
|
103
|
+
doc([
|
|
104
|
+
page(
|
|
105
|
+
[
|
|
106
|
+
item("f1", [10, 0, 0, 10, 50, 700]),
|
|
107
|
+
// zero horizontal scale: the size comes from the item's height…
|
|
108
|
+
item("f2", [0, 0, 0, 0, 100, 700], 12),
|
|
109
|
+
// …or, with no height either, from the vertical scale
|
|
110
|
+
item("f2", [0, 0, 0, 9, 150, 700], 0),
|
|
111
|
+
// an item whose font pdf.js never resolved
|
|
112
|
+
item("gone", [10, 0, 0, 10, 200, 700]),
|
|
113
|
+
],
|
|
114
|
+
fonts,
|
|
115
|
+
),
|
|
116
|
+
]),
|
|
117
|
+
lib,
|
|
118
|
+
);
|
|
119
|
+
assert.equal(r.ok, true);
|
|
120
|
+
const boxes = r.ok ? r.facts.layout[0]!.boxes : [];
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
boxes.map((b) => [b.font, b.size, b.fill]),
|
|
123
|
+
[
|
|
124
|
+
["Named", 10, { kind: "unknown" }],
|
|
125
|
+
["f2", 12, { kind: "unknown" }],
|
|
126
|
+
["f2", 9, { kind: "unknown" }],
|
|
127
|
+
["gone", 10, { kind: "unknown" }],
|
|
128
|
+
],
|
|
129
|
+
);
|
|
130
|
+
assert.deepEqual(r.ok ? r.facts.last.words.map((w) => w.text) : [], [
|
|
131
|
+
"word",
|
|
132
|
+
"word",
|
|
133
|
+
"word",
|
|
134
|
+
"word",
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("a failure that is not an Error is named `Error` with its text", () => {
|
|
139
|
+
assert.deepEqual(failureOf("boom"), {
|
|
140
|
+
ok: false,
|
|
141
|
+
reason: "unreadable",
|
|
142
|
+
detail: "Error: boom",
|
|
143
|
+
});
|
|
144
|
+
});
|
package/src/pdf-facts.ts
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from "./pdf-geometry.ts";
|
|
34
34
|
import type { PageLayout, TextBox } from "./domain/page-layout.ts";
|
|
35
35
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
36
|
-
import {
|
|
36
|
+
import { fillsFor, isUpright } from "./adapters/pdfjs/fill.ts";
|
|
37
37
|
|
|
38
38
|
type PdfJs = Awaited<ReturnType<typeof import("unpdf").getResolvedPDFJS>>;
|
|
39
39
|
type Doc = Awaited<ReturnType<PdfJs["getDocument"]>["promise"]>;
|
|
@@ -241,20 +241,18 @@ function layoutOf(
|
|
|
241
241
|
lib: PdfJs,
|
|
242
242
|
): PageLayout {
|
|
243
243
|
const vp = page.getViewport({ scale: 1 });
|
|
244
|
-
const
|
|
245
|
-
lib
|
|
246
|
-
ops,
|
|
247
|
-
items.map((it) => it.str),
|
|
244
|
+
const boxes = fillsFor(lib.OPS, ops, items, (it) => it.str).map(
|
|
245
|
+
({ item, fill }): TextBox => ({ ...boxOf(page, vp, item, lib), fill }),
|
|
248
246
|
);
|
|
249
|
-
const boxes = items.map((it, k): TextBox => ({
|
|
250
|
-
...boxOf(page, vp, it, lib),
|
|
251
|
-
fill: fills[k] ?? { kind: "unknown" },
|
|
252
|
-
}));
|
|
253
247
|
return { widthPt: vp.width, heightPt: vp.height, boxes };
|
|
254
248
|
}
|
|
255
249
|
|
|
256
|
-
/**
|
|
257
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Every page's fonts, and the last page's words. Exported with `failureOf` so a test can hand it a
|
|
252
|
+
* document whose objects pdf.js shapes only in rare files (a font without metrics, a zero-scale
|
|
253
|
+
* transform, no pages) — `readPdf` below is the one production caller.
|
|
254
|
+
*/
|
|
255
|
+
export async function factsOf(doc: Doc, lib: PdfJs): Promise<PdfRead> {
|
|
258
256
|
const read: {
|
|
259
257
|
page: Page;
|
|
260
258
|
raw: RawPage;
|
|
@@ -290,7 +288,7 @@ async function factsOf(doc: Doc, lib: PdfJs): Promise<PdfRead> {
|
|
|
290
288
|
}
|
|
291
289
|
|
|
292
290
|
/** pdf.js's own exception names for the two conditions a caller can act on. */
|
|
293
|
-
function failureOf(e: unknown): PdfRead {
|
|
291
|
+
export function failureOf(e: unknown): PdfRead {
|
|
294
292
|
const err = e as { name?: string; message?: string };
|
|
295
293
|
const detail = `${err.name ?? "Error"}: ${err.message ?? String(e)}`;
|
|
296
294
|
return fail(
|
|
@@ -309,13 +307,13 @@ export const readPdf: PdfReader = async (path) => {
|
|
|
309
307
|
}
|
|
310
308
|
const lib = await loadPdfJs();
|
|
311
309
|
const task = lib.getDocument({ data, ...PDFJS_OPTIONS });
|
|
312
|
-
try
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
310
|
+
// A promise chain rather than try/catch/finally: every path returns a value here, and the code
|
|
311
|
+
// after such a statement is a block that can never run — which coverage reports as unrun.
|
|
312
|
+
const read = await task.promise
|
|
313
|
+
.then((doc) => factsOf(doc, lib))
|
|
314
|
+
.catch(failureOf);
|
|
315
|
+
await task.destroy();
|
|
316
|
+
return read;
|
|
319
317
|
};
|
|
320
318
|
|
|
321
319
|
/** The one wording of a failed read, as a build step or a CLI prints it. */
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* on 2026-08-29 (the midpoint cut, the review build, years mistaken for line numbers), ported from
|
|
7
7
|
* the harness of the function this replaces.
|
|
8
8
|
*/
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
9
|
import { dirname, join } from "node:path";
|
|
11
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { createChecker } from "../lib/check.mjs";
|
|
12
12
|
|
|
13
13
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
14
|
const {
|
|
@@ -21,11 +21,7 @@ const {
|
|
|
21
21
|
STUB_WORDS,
|
|
22
22
|
} = await import(join(HERE, "pdf-geometry.ts"));
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
const check = (label, cond) => {
|
|
26
|
-
assert.ok(cond, label);
|
|
27
|
-
n++;
|
|
28
|
-
};
|
|
24
|
+
const check = createChecker();
|
|
29
25
|
|
|
30
26
|
const W = 612; // US letter, points
|
|
31
27
|
|
|
@@ -250,5 +246,5 @@ check(
|
|
|
250
246
|
);
|
|
251
247
|
|
|
252
248
|
console.log(
|
|
253
|
-
`✓ ${String(
|
|
249
|
+
`✓ ${String(check.count)} assertions passed — pdf-geometry: columns, stub/review, words from runs, fonts`,
|
|
254
250
|
);
|
package/src/pdf-geometry.ts
CHANGED
|
@@ -136,7 +136,7 @@ export function wordsOf(run: TextRun): Word[] {
|
|
|
136
136
|
const y0 = run.baseline - run.ascent * run.size;
|
|
137
137
|
const y1 = run.baseline - run.descent * run.size;
|
|
138
138
|
return [...run.text.matchAll(/\S+/g)].map((m) => {
|
|
139
|
-
const x0 = run.x +
|
|
139
|
+
const x0 = run.x + m.index * perChar;
|
|
140
140
|
return { x0, y0, x1: x0 + m[0].length * perChar, y1, text: m[0] };
|
|
141
141
|
});
|
|
142
142
|
}
|
package/src/presets.test.ts
CHANGED
|
@@ -230,6 +230,8 @@ describe("labelOf — display only, never used to resolve", () => {
|
|
|
230
230
|
["paperlint:agenticdev", "agenticdev"],
|
|
231
231
|
["./venues/usenix-sec.jsonc", "usenix-sec"],
|
|
232
232
|
["../shared/acl.json", "acl"],
|
|
233
|
+
["./.hidden", ".hidden"],
|
|
234
|
+
["paperlint:", ""],
|
|
233
235
|
])("%s → %s", (spec, label) => {
|
|
234
236
|
expect(labelOf(spec)).toBe(label);
|
|
235
237
|
});
|
package/src/presets.ts
CHANGED
|
@@ -293,5 +293,11 @@ export function paperPresetProblem(
|
|
|
293
293
|
if (p.kind === "preset-problem")
|
|
294
294
|
return `${join(paperDir, CONFIG_FILE)}: ${presetProblemText(p.problem)}`;
|
|
295
295
|
if (p.kind !== "settings-problem") return null;
|
|
296
|
-
return
|
|
296
|
+
return settingsProblemLine(paperDir, p.problem);
|
|
297
297
|
}
|
|
298
|
+
|
|
299
|
+
/** The one line naming a paper's `paperlint.json` that does not parse, and why. */
|
|
300
|
+
export const settingsProblemLine = (
|
|
301
|
+
paperDir: string,
|
|
302
|
+
problem: SettingsProblem,
|
|
303
|
+
): string => `${join(paperDir, CONFIG_FILE)}: ${problem.why}`;
|
package/src/references.test.ts
CHANGED
|
@@ -207,3 +207,100 @@ describe("the reference rules", () => {
|
|
|
207
207
|
expect(referencesPath(PAPER)).toBe(`${PAPER}/_build/references.json`);
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
|
|
211
|
+
describe("reading what is on disk", () => {
|
|
212
|
+
it("no paper.tex: the bibliography is refs.bib, from its first byte", () => {
|
|
213
|
+
const files = memoryFiles({ [`${PAPER}/refs.bib`]: ENTRIES });
|
|
214
|
+
expect(bibliographyOf(files, PAPER)).toEqual({
|
|
215
|
+
source: "refs.bib",
|
|
216
|
+
text: ENTRIES,
|
|
217
|
+
offset: 0,
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("a references.json of another schema, without entries, or not JSON reads as none", () => {
|
|
222
|
+
for (const body of [
|
|
223
|
+
JSON.stringify({ schema: 999, entries: [] }),
|
|
224
|
+
JSON.stringify({ schema: 1 }),
|
|
225
|
+
"null",
|
|
226
|
+
"{",
|
|
227
|
+
]) {
|
|
228
|
+
const files = memoryFiles({ [referencesPath(PAPER)]: body });
|
|
229
|
+
expect(readReferences(files, PAPER)).toBeNull();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
/** A record for `bib`, as the build would write it, with `patch` applied to the document. */
|
|
235
|
+
const record = (
|
|
236
|
+
files: ReturnType<typeof memoryFiles>,
|
|
237
|
+
body: Record<string, unknown>,
|
|
238
|
+
) => {
|
|
239
|
+
const bib = bibliographyOf(files, PAPER)!;
|
|
240
|
+
files.writeAtomic(
|
|
241
|
+
referencesPath(PAPER) as AbsolutePath,
|
|
242
|
+
new TextEncoder().encode(
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
schema: 1,
|
|
245
|
+
bib: { source: bib.source, sha256: bibHash(bib) },
|
|
246
|
+
...body,
|
|
247
|
+
}),
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
describe("the reference rules over a record written by hand", () => {
|
|
253
|
+
it("not-checked with no reason recorded says so", async () => {
|
|
254
|
+
const tex = TEX(ENTRIES);
|
|
255
|
+
const files = memoryFiles({ [`${PAPER}/paper.tex`]: tex });
|
|
256
|
+
record(files, { status: "not-checked", entries: [] });
|
|
257
|
+
const msgs = await lint(files, tex);
|
|
258
|
+
expect(msgs.map((m) => m.ruleId)).toEqual(["paper/refs-checked"]);
|
|
259
|
+
expect(msgs[0]!.message).toMatch(/no reason recorded/);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("a verdict without a reason, and one for a key the bibliography lacks, still report", async () => {
|
|
263
|
+
const tex = TEX(ENTRIES);
|
|
264
|
+
const files = memoryFiles({ [`${PAPER}/paper.tex`]: tex });
|
|
265
|
+
record(files, {
|
|
266
|
+
status: "checked",
|
|
267
|
+
entries: [
|
|
268
|
+
{ key: "schick2023", exists: "true", authors: "mismatch" },
|
|
269
|
+
{ key: "ghost", exists: "false", authors: "match" },
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
const msgs = await lint(files, tex);
|
|
273
|
+
expect(msgs.map((m) => [m.ruleId, m.line])).toEqual([
|
|
274
|
+
["paper/cite-exists", 1],
|
|
275
|
+
["paper/author-list", 3],
|
|
276
|
+
]);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("the reference rules over refs.bib, and on other files", () => {
|
|
281
|
+
it("an external refs.bib: findings sit at the start of paper.tex, naming the key", async () => {
|
|
282
|
+
const tex = "\\documentclass{acmart}\n\\begin{document}x\\end{document}\n";
|
|
283
|
+
const files = memoryFiles({
|
|
284
|
+
[`${PAPER}/paper.tex`]: tex,
|
|
285
|
+
[`${PAPER}/refs.bib`]: ENTRIES,
|
|
286
|
+
});
|
|
287
|
+
record(files, {
|
|
288
|
+
status: "checked",
|
|
289
|
+
entries: [verdict("schick2023", "mismatch")],
|
|
290
|
+
});
|
|
291
|
+
const msgs = await lint(files, tex);
|
|
292
|
+
expect(msgs.map((m) => [m.ruleId, m.line])).toEqual([
|
|
293
|
+
["paper/author-list", 1],
|
|
294
|
+
]);
|
|
295
|
+
expect(msgs[0]!.message).toMatch(/schick2023/);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("only paper.tex is judged", () => {
|
|
299
|
+
const files = memoryFiles({});
|
|
300
|
+
const rules = referenceRules({ files });
|
|
301
|
+
for (const rule of Object.values(rules))
|
|
302
|
+
expect(rule.create({ filename: `${PAPER}/notes.tex` } as never)).toEqual(
|
|
303
|
+
{},
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
});
|
package/src/references.ts
CHANGED
|
@@ -129,8 +129,8 @@ export async function recordReferences(
|
|
|
129
129
|
at(referencesPath(paperDir)),
|
|
130
130
|
new TextEncoder().encode(`${JSON.stringify(doc, null, 2)}\n`),
|
|
131
131
|
);
|
|
132
|
-
if (
|
|
133
|
-
return `references NOT checked — ${
|
|
132
|
+
if (result.kind === "not-checked")
|
|
133
|
+
return `references NOT checked — ${result.why}; lint will say so`;
|
|
134
134
|
const bad = doc.entries.filter(
|
|
135
135
|
(e) => e.exists === "false" || e.authors === "mismatch",
|
|
136
136
|
).length;
|
|
@@ -3,20 +3,16 @@
|
|
|
3
3
|
* paperlint ships. The end-to-end path (a project's paperlint.json → `paperlint lint`) is in `cli.harness.mjs`;
|
|
4
4
|
* this pins the parser's output shape and each refusal.
|
|
5
5
|
*/
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
6
|
import { dirname, join } from "node:path";
|
|
8
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { createChecker } from "../lib/check.mjs";
|
|
9
9
|
|
|
10
10
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const { parseRuleBlocks, shippedRuleIds, unknownKeys } = await import(
|
|
12
12
|
join(HERE, "rules-config.ts")
|
|
13
13
|
);
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const check = (label, cond, detail = "") => {
|
|
17
|
-
assert.ok(cond, detail ? `${label} — ${detail}` : label);
|
|
18
|
-
n++;
|
|
19
|
-
};
|
|
15
|
+
const check = createChecker();
|
|
20
16
|
|
|
21
17
|
const SHIPPED = new Set(["pdf/last-page-balance", "paper/typography"]);
|
|
22
18
|
const parse = (raw) => parseRuleBlocks(raw, "S", SHIPPED, "/consumer");
|
|
@@ -112,7 +108,13 @@ check(
|
|
|
112
108
|
markdown: foreign,
|
|
113
109
|
},
|
|
114
110
|
},
|
|
115
|
-
|
|
111
|
+
// A plugin that carries only a language (the `tex` plugin does) has no rules to ship.
|
|
112
|
+
{
|
|
113
|
+
plugins: {
|
|
114
|
+
paper: { rules: { typography: {} } },
|
|
115
|
+
tex: { languages: {} },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
116
118
|
{ files: ["x"] },
|
|
117
119
|
],
|
|
118
120
|
[foreign],
|
|
@@ -126,5 +128,5 @@ check(
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
console.log(
|
|
129
|
-
`✓ ${String(
|
|
131
|
+
`✓ ${String(check.count)} assertions passed — rules-config: blocks parsed with basePath, every refusal names its key`,
|
|
130
132
|
);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A consumer that ALSO lints paper files with its own ESLint config (#104).
|
|
3
|
+
*
|
|
4
|
+
* 3.0.0 made `% eslint-disable-next-line <rule> -- <why>` the way to keep a deliberate exception.
|
|
5
|
+
* A project that runs its own `eslint` over the same `paper.tex` — for its own rules, with
|
|
6
|
+
* paperlint's LaTeX language — did not register paperlint's rule plugins, so that run failed on the
|
|
7
|
+
* directive: "Definition for rule 'paper/leading-zero' was not found". `rulesOff` is the fragment
|
|
8
|
+
* such a config spreads in: every rule paperlint ships, registered and turned off.
|
|
9
|
+
*/
|
|
10
|
+
import { ESLint, type Linter } from "eslint";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
// @ts-expect-error — the module is .mjs and has no types.
|
|
13
|
+
import { texLanguage } from "../eslint-rules/latex-language.mjs";
|
|
14
|
+
import { rulePlugins, rulesOff, SHIPPED_RULES } from "./cli.ts";
|
|
15
|
+
|
|
16
|
+
const PAPER = [
|
|
17
|
+
"\\documentclass{article}",
|
|
18
|
+
"\\begin{document}",
|
|
19
|
+
"% eslint-disable-next-line paper/leading-zero -- quoted from the reviewer",
|
|
20
|
+
"We use a threshold of .05 throughout.",
|
|
21
|
+
"% eslint-disable-next-line bib/reachable-entry -- kept for the artifact",
|
|
22
|
+
"Nothing else.",
|
|
23
|
+
"\\end{document}",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n");
|
|
26
|
+
|
|
27
|
+
/** The consumer's own config: its LaTeX files, with paperlint's language, and its own rules. */
|
|
28
|
+
async function lintAsConsumer(
|
|
29
|
+
fragment: Linter.Config[],
|
|
30
|
+
): Promise<Linter.LintMessage[]> {
|
|
31
|
+
const eslint = new ESLint({
|
|
32
|
+
overrideConfigFile: true,
|
|
33
|
+
overrideConfig: [
|
|
34
|
+
...fragment,
|
|
35
|
+
{
|
|
36
|
+
files: ["**/*.tex"],
|
|
37
|
+
plugins:
|
|
38
|
+
fragment.length > 0
|
|
39
|
+
? {}
|
|
40
|
+
: { tex: { languages: { latex: texLanguage } } },
|
|
41
|
+
language: "tex/latex",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
const [res] = await eslint.lintText(PAPER, {
|
|
46
|
+
filePath: "papers/a/paper.tex",
|
|
47
|
+
});
|
|
48
|
+
return res!.messages;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("rulesOff — paperlint's rules, registered and off, for a consumer's own ESLint run", () => {
|
|
52
|
+
it("without it, the documented escape hatch breaks the consumer's run (the defect)", async () => {
|
|
53
|
+
const messages = await lintAsConsumer([]);
|
|
54
|
+
expect(messages.map((m) => m.message)).toContain(
|
|
55
|
+
"Definition for rule 'paper/leading-zero' was not found.",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("🔴 spreading it: no 'Definition … not found', and no paperlint finding runs", async () => {
|
|
60
|
+
const messages = await lintAsConsumer(rulesOff(texLanguage));
|
|
61
|
+
// Guards: the defect — every directive names a rule ESLint now knows.
|
|
62
|
+
expect(messages.map((m) => m.message).join("\n")).not.toMatch(
|
|
63
|
+
/Definition for rule/,
|
|
64
|
+
);
|
|
65
|
+
// Guards: off means off — paperlint's own run judges these files, not the consumer's.
|
|
66
|
+
expect(messages.filter((m) => m.ruleId?.startsWith("paper/"))).toEqual([]);
|
|
67
|
+
// Guards: a directive naming an OFF rule suppresses nothing, and ESLint would call it unused
|
|
68
|
+
// ("Unused eslint-disable directive (no problems were reported from 'paper/leading-zero')").
|
|
69
|
+
expect(messages).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("registers every rule paperlint ships, each off, and carries the LaTeX language", () => {
|
|
73
|
+
const [cfg] = rulesOff(texLanguage);
|
|
74
|
+
expect(new Set(Object.keys(cfg?.rules ?? {}))).toEqual(SHIPPED_RULES);
|
|
75
|
+
expect(Object.values(cfg?.rules ?? {}).every((e) => e === "off")).toBe(
|
|
76
|
+
true,
|
|
77
|
+
);
|
|
78
|
+
const plugins = rulePlugins(texLanguage);
|
|
79
|
+
const defined = new Set<string>();
|
|
80
|
+
for (const [name, p] of Object.entries(plugins))
|
|
81
|
+
for (const r of Object.keys(p.rules ?? {})) defined.add(`${name}/${r}`);
|
|
82
|
+
expect(defined).toEqual(SHIPPED_RULES);
|
|
83
|
+
expect(plugins["tex"]?.languages?.["latex"]).toBe(texLanguage);
|
|
84
|
+
// `@eslint/markdown` is a dependency's plugin: the consumer registers its own.
|
|
85
|
+
expect(plugins["markdown"]).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("the unused-directive report is off on paper files only, not on the consumer's code", async () => {
|
|
89
|
+
const eslint = new ESLint({
|
|
90
|
+
overrideConfigFile: true,
|
|
91
|
+
overrideConfig: [
|
|
92
|
+
...rulesOff(texLanguage),
|
|
93
|
+
{
|
|
94
|
+
files: ["**/*.js"],
|
|
95
|
+
linterOptions: { reportUnusedDisableDirectives: "warn" },
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
const [res] = await eslint.lintText(
|
|
100
|
+
"// eslint-disable-next-line paper/leading-zero -- nothing to silence\nconst x = 1;\n",
|
|
101
|
+
{ filePath: "src/x.js" },
|
|
102
|
+
);
|
|
103
|
+
expect(res!.messages.map((m) => m.message)).toEqual([
|
|
104
|
+
"Unused eslint-disable directive (no problems were reported from 'paper/leading-zero').",
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("without a LaTeX language, the `tex` plugin carries rules only", () => {
|
|
109
|
+
expect(rulePlugins()["tex"]?.languages).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* So every "found" here has a paired "not found on the neighboring directory", and the defaults
|
|
7
7
|
* are separately checked against the shape of the live corpus.
|
|
8
8
|
*/
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
9
|
import {
|
|
11
10
|
mkdtempSync,
|
|
12
11
|
mkdirSync,
|
|
@@ -17,16 +16,13 @@ import {
|
|
|
17
16
|
import { tmpdir } from "node:os";
|
|
18
17
|
import { join, dirname } from "node:path";
|
|
19
18
|
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { createChecker } from "../lib/check.mjs";
|
|
20
20
|
|
|
21
21
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
const { checkStructure, formatStructure, asEslintResults, STRUCTURE_DEFAULTS } =
|
|
23
23
|
await import(join(HERE, "structure.ts"));
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
const check = (label, cond) => {
|
|
27
|
-
assert.ok(cond, label);
|
|
28
|
-
n++;
|
|
29
|
-
};
|
|
25
|
+
const check = createChecker();
|
|
30
26
|
|
|
31
27
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-struct-")));
|
|
32
28
|
const papers = join(root, "papers");
|
|
@@ -71,8 +67,8 @@ try {
|
|
|
71
67
|
);
|
|
72
68
|
check(
|
|
73
69
|
"and the message names the CONSEQUENCE, not a restatement of the condition",
|
|
74
|
-
/
|
|
75
|
-
/
|
|
70
|
+
/are skipped/.test(at("no-scorecard")[0].message) &&
|
|
71
|
+
/stage, source and research-question checks/.test(
|
|
76
72
|
at("no-scorecard")[0].message,
|
|
77
73
|
),
|
|
78
74
|
);
|
|
@@ -177,5 +173,5 @@ try {
|
|
|
177
173
|
}
|
|
178
174
|
|
|
179
175
|
console.log(
|
|
180
|
-
`✓ ${String(
|
|
176
|
+
`✓ ${String(check.count)} assertions passed — structure: a missing file cannot complain for itself`,
|
|
181
177
|
);
|
package/src/structure.ts
CHANGED
|
@@ -122,7 +122,7 @@ export function checkStructure(
|
|
|
122
122
|
*/
|
|
123
123
|
function whyMissingMatters(file: string, dirName: string): string {
|
|
124
124
|
if (file === "PIPELINE-STATUS.md")
|
|
125
|
-
return
|
|
125
|
+
return `the stage, source and research-question checks are skipped for \`${dirName}\``;
|
|
126
126
|
return `declared as required by your \`structure\` configuration`;
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* 2. the schema REJECTS what it must — each bad profile names the offending path;
|
|
8
8
|
* 3. what a paper gets: its venue plus the base set, or the base set with the reason said.
|
|
9
9
|
*/
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
10
|
import {
|
|
12
11
|
copyFileSync,
|
|
13
12
|
mkdtempSync,
|
|
@@ -19,6 +18,7 @@ import { tmpdir } from "node:os";
|
|
|
19
18
|
import { dirname, join } from "node:path";
|
|
20
19
|
import { fileURLToPath } from "node:url";
|
|
21
20
|
import ts from "typescript";
|
|
21
|
+
import { createChecker } from "../lib/check.mjs";
|
|
22
22
|
|
|
23
23
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
const VENUES = join(
|
|
@@ -31,11 +31,7 @@ const VENUES = join(
|
|
|
31
31
|
);
|
|
32
32
|
const R = await import(join(HERE, "tex-requirements.ts"));
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
const check = (label, cond, detail = "") => {
|
|
36
|
-
assert.ok(cond, `${label}${detail ? ` — ${detail}` : ""}`);
|
|
37
|
-
n++;
|
|
38
|
-
};
|
|
34
|
+
const check = createChecker();
|
|
39
35
|
const throws = (fn) => {
|
|
40
36
|
try {
|
|
41
37
|
fn();
|
|
@@ -229,4 +225,35 @@ rmSync(tmp, { recursive: true, force: true });
|
|
|
229
225
|
);
|
|
230
226
|
}
|
|
231
227
|
|
|
232
|
-
|
|
228
|
+
// ── 5. the parser's tolerances: an optional block left out, and Ajv's optional fields ───
|
|
229
|
+
{
|
|
230
|
+
const bare = R.parsePreset(
|
|
231
|
+
'{ "extends": "paperlint:aisec" }',
|
|
232
|
+
"bare.jsonc",
|
|
233
|
+
VENUES,
|
|
234
|
+
);
|
|
235
|
+
check(
|
|
236
|
+
"a preset that only extends another: no tex block, `rules` an empty object",
|
|
237
|
+
JSON.stringify([bare.extends, bare.tex, bare.rules]) ===
|
|
238
|
+
'["paperlint:aisec",null,{}]',
|
|
239
|
+
bare,
|
|
240
|
+
);
|
|
241
|
+
// Guards: `packages` is read without a fallback because the schema refuses a tex block
|
|
242
|
+
// without it — this is the refusal the type relies on.
|
|
243
|
+
check(
|
|
244
|
+
"a tex block without `packages` is refused by the schema, before anything reads it",
|
|
245
|
+
/tex\.jsonc: \.tex should have required property 'packages'/.test(
|
|
246
|
+
throws(() => R.parsePreset('{ "tex": {} }', "tex.jsonc", VENUES)),
|
|
247
|
+
),
|
|
248
|
+
throws(() => R.parsePreset('{ "tex": {} }', "tex.jsonc", VENUES)),
|
|
249
|
+
);
|
|
250
|
+
check(
|
|
251
|
+
"violations: Ajv's null `errors` is no violation; a message Ajv left out is an empty one",
|
|
252
|
+
R.violations("f.jsonc", { errors: null }).length === 0 &&
|
|
253
|
+
R.violations("f.jsonc", {
|
|
254
|
+
errors: [{ dataPath: "", message: undefined }],
|
|
255
|
+
})[0] === "f.jsonc: (top level) ",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(`tex-requirements: ${check.count} checks passed`);
|