paperlint 14.0.0 → 14.2.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.
@@ -6,6 +6,7 @@
6
6
  import assert from "node:assert/strict";
7
7
  import { readFileSync, writeFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
+ import * as R from "remeda";
9
10
  import { test } from "vitest";
10
11
  import { z } from "zod";
11
12
  import {
@@ -29,7 +30,15 @@ import {
29
30
  /** A node of the declared config schema, as `example-generator.py schema` prints it. */
30
31
  const Node: z.ZodType<SchemaNode> = z.lazy(() =>
31
32
  z.object({
32
- kind: z.enum(["string", "integer", "boolean", "map", "array", "object"]),
33
+ kind: z.enum([
34
+ "string",
35
+ "text",
36
+ "integer",
37
+ "boolean",
38
+ "map",
39
+ "array",
40
+ "object",
41
+ ]),
33
42
  required: z.boolean(),
34
43
  pattern: z.string().optional(),
35
44
  key_pattern: z.string().optional(),
@@ -42,7 +51,8 @@ const Node: z.ZodType<SchemaNode> = z.lazy(() =>
42
51
  );
43
52
 
44
53
  interface SchemaNode {
45
- readonly kind: "string" | "integer" | "boolean" | "map" | "array" | "object";
54
+ readonly kind:
55
+ "string" | "text" | "integer" | "boolean" | "map" | "array" | "object";
46
56
  readonly required: boolean;
47
57
  readonly pattern?: string | undefined;
48
58
  readonly key_pattern?: string | undefined;
@@ -73,6 +83,40 @@ const CARD = {
73
83
  rules: [{ linter: "eslint", id: "no-console" }],
74
84
  unit: { id: "L0", text: "One line.", mark: "One", part: 1, of: 1 },
75
85
  readings: { a: "on", b: "off" },
86
+ situation: { summary: "S", question: "Q?" },
87
+ excerpt: {
88
+ file: "src/a.ts",
89
+ url: "https://example.com/a.ts",
90
+ lines: [{ n: 1, text: "", mark: true }],
91
+ caption: "C",
92
+ mark_label: "M",
93
+ },
94
+ references: {
95
+ summary: "S",
96
+ entries: [
97
+ {
98
+ label: "L",
99
+ title: "T",
100
+ fields: [{ label: "F", text: "X", style: "code" }],
101
+ url: "https://example.com/r",
102
+ url_label: "U",
103
+ },
104
+ ],
105
+ },
106
+ more: { summary: "S", items: ["`src/b.ts`"], rest: "R" },
107
+ table: {
108
+ summary: "S",
109
+ note: "N",
110
+ rows: [
111
+ {
112
+ label: "L",
113
+ value: "V",
114
+ plain: "P",
115
+ mark: { tone: "yes", text: "Y" },
116
+ },
117
+ ],
118
+ },
119
+ repeat: { group: "g", index: 1, of: 1 },
76
120
  };
77
121
  /** A config with EVERY optional field present, so every schema path has a value to spoil. */
78
122
  const FULL: DocObject = (() => {
@@ -90,10 +134,22 @@ const FULL: DocObject = (() => {
90
134
  QUESTIONS[1],
91
135
  ],
92
136
  labeller_knows: ["python", "go", "rust"],
137
+ answerers: ["author", "second"],
138
+ min_seconds: 30,
93
139
  familiarity: true,
94
140
  familiarity_question: "How well do you know {group}?",
141
+ labels: {
142
+ place: "C",
143
+ body: "B",
144
+ line: "L",
145
+ line_part: "{line} {part}/{of}",
146
+ repeat: "R",
147
+ practice_example: "P",
148
+ },
95
149
  practice: PRACTICE.map((p, n) => (n === 0 ? { ...p, ...CARD } : p)),
96
- items: ITEMS.map((i, n) => (n === 0 ? { ...i, ...CARD } : i)),
150
+ items: ITEMS.map((i, n) =>
151
+ n === 0 ? { ...i, ...CARD, answerers: ["author"] } : i,
152
+ ),
97
153
  }),
98
154
  );
99
155
  assert.ok(isObject(doc));
@@ -187,6 +243,7 @@ function spoil(path: string, bad: Doc | undefined): DocObject {
187
243
 
188
244
  const WRONG_TYPES: Readonly<Record<SchemaNode["kind"], readonly Doc[]>> = {
189
245
  string: [null, 1, true, ["x"], {}, "", " "],
246
+ text: [null, 1, true, ["x"], {}],
190
247
  integer: [null, "7", true, 7.5, []],
191
248
  boolean: [null, "false", 0, [], {}],
192
249
  map: [null, "x", ["x"], 1],
@@ -223,6 +280,21 @@ function badCases({ node, member }: Leaf): readonly BadCase[] {
223
280
  return [...wrongType, ...extra];
224
281
  }
225
282
 
283
+ /** `doc` with its first item's own answerers taken out. */
284
+ function withoutItemAnswerers(doc: DocObject): DocObject {
285
+ const out = put(doc, ["items", 0, "answerers"], undefined);
286
+ assert.ok(isObject(out));
287
+ return out;
288
+ }
289
+ /**
290
+ * A field whose absence takes another with it: familiarity_question asks nothing unless familiarity
291
+ * is on, and an item's answerers name the page's.
292
+ */
293
+ const RELAX: Partial<Record<string, (doc: DocObject) => DocObject>> = {
294
+ familiarity: (doc) => R.omit(doc, ["familiarity_question"]),
295
+ answerers: withoutItemAnswerers,
296
+ };
297
+
226
298
  /** Spoil `leaf` each way in `badCases`; every one must stop the build and name the path. */
227
299
  function expectRefused(leaf: Leaf): void {
228
300
  badCases(leaf).forEach(([label, value], i) => {
@@ -238,16 +310,9 @@ function expectRefused(leaf: Leaf): void {
238
310
  });
239
311
  if (!leaf.node.required && !leaf.member) {
240
312
  const absent = spoil(leaf.path, undefined);
241
- // familiarity_question asks nothing unless familiarity is on: the two leave together.
242
313
  const r = tryBuild(
243
314
  `schema-${leaf.path.replace(/\W/g, "_")}-absent`,
244
- leaf.path === "familiarity"
245
- ? Object.fromEntries(
246
- Object.entries(absent).filter(
247
- ([k]) => k !== "familiarity_question",
248
- ),
249
- )
250
- : absent,
315
+ (RELAX[leaf.path] ?? R.identity())(absent),
251
316
  );
252
317
  assert.equal(r.status, 0, `${leaf.path} is optional: ${r.out}`);
253
318
  }
@@ -261,6 +326,8 @@ const DOCUMENTED_FIELDS = [
261
326
  "seed",
262
327
  "groups",
263
328
  "labeller_knows",
329
+ "answerers",
330
+ "min_seconds",
264
331
  "familiarity",
265
332
  "familiarity_question",
266
333
  "questions",