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.
@@ -0,0 +1,214 @@
1
+ {
2
+ "study": "demo-review",
3
+ "kind": "labels",
4
+ "title": "Review comments",
5
+ "intro": "Each card shows one guideline, one changed line and one question about that line.",
6
+ "seed": 5,
7
+ "groups": { "all": "Review comments" },
8
+ "labels": {
9
+ "place": "Comment {n} of {total}",
10
+ "body": "Guideline",
11
+ "repeat": "Comment {index} of {of} on this guideline",
12
+ "practice_example": "Another case: "
13
+ },
14
+ "questions": [
15
+ {
16
+ "id": "applies",
17
+ "text": "Does the guideline cover this line?",
18
+ "short": "covered?",
19
+ "options": [
20
+ {
21
+ "value": "yes",
22
+ "label": "Yes",
23
+ "icon": "✅",
24
+ "example": "«No string-built SQL» and an f-string query"
25
+ },
26
+ {
27
+ "value": "no",
28
+ "label": "No",
29
+ "icon": "🚫",
30
+ "example": "«No string-built SQL» and a log message"
31
+ },
32
+ {
33
+ "value": "unclear",
34
+ "label": "Unclear",
35
+ "icon": "↔️",
36
+ "example": "«Short functions» and a 40-line function",
37
+ "ask_why": "What makes it unclear?"
38
+ }
39
+ ]
40
+ }
41
+ ],
42
+ "practice": [
43
+ {
44
+ "body": "Every query is parameterised",
45
+ "situation": {
46
+ "summary": "One reviewer asked for a change at {line}; the other approved it.",
47
+ "question": "Does «Every query is parameterised» cover {line}?"
48
+ },
49
+ "excerpt": {
50
+ "file": "app/orders.py",
51
+ "lines": [
52
+ { "n": 41, "text": "def total(order_id):", "mark": false },
53
+ {
54
+ "n": 42,
55
+ "text": " sql = f\"SELECT * FROM o WHERE id={order_id}\"",
56
+ "mark": true
57
+ }
58
+ ],
59
+ "caption": "Reviewer A wrote: build this query with parameters."
60
+ },
61
+ "expected": { "applies": "yes" },
62
+ "why": "The value goes into the query text."
63
+ },
64
+ {
65
+ "body": "Every query is parameterised",
66
+ "situation": {
67
+ "summary": "One reviewer flagged {line}; the other did not.",
68
+ "question": "Does «Every query is parameterised» cover {line}?"
69
+ },
70
+ "excerpt": {
71
+ "file": "app/log.py",
72
+ "lines": [
73
+ {
74
+ "n": 7,
75
+ "text": "log.info(f\"order {order_id} paid\")",
76
+ "mark": true
77
+ }
78
+ ]
79
+ },
80
+ "expected": { "applies": "no" },
81
+ "why": "A log line is not a query."
82
+ },
83
+ {
84
+ "body": "Functions stay short",
85
+ "situation": {
86
+ "summary": "One reviewer asked to split the function; the other did not.",
87
+ "question": "Does «Functions stay short» cover this function?"
88
+ },
89
+ "excerpt": {
90
+ "file": "app/cart.py",
91
+ "lines": [
92
+ { "n": 1, "text": "def checkout(cart):", "mark": true },
93
+ { "n": 2, "text": " ...", "mark": false }
94
+ ],
95
+ "caption": "The function is 40 lines long."
96
+ },
97
+ "expected": { "applies": "unclear" },
98
+ "why": "«Short» has no number."
99
+ }
100
+ ],
101
+ "items": [
102
+ {
103
+ "source_id": "review-sql-1",
104
+ "group": "all",
105
+ "body": "Every query is parameterised",
106
+ "repeat": { "group": "guide-sql", "index": 1, "of": 2 },
107
+ "situation": {
108
+ "summary": "Reviewer A asked for a change at {line}; reviewer B approved it.",
109
+ "question": "Does «Every query is parameterised» cover {line}?"
110
+ },
111
+ "excerpt": {
112
+ "file": "app/report.py",
113
+ "url": "https://example.com/shop/blob/0000000/app/report.py#L12",
114
+ "lines": [
115
+ { "n": 11, "text": "def monthly(month):", "mark": false },
116
+ {
117
+ "n": 12,
118
+ "text": " rows = db.raw(\"SELECT * FROM sales WHERE m=\" + month)",
119
+ "mark": true
120
+ },
121
+ { "n": 13, "text": "", "mark": false },
122
+ { "n": 14, "text": " return rows", "mark": false }
123
+ ],
124
+ "caption": "Reviewer A wrote: concatenated SQL.",
125
+ "mark_label": "◀ the line in question"
126
+ },
127
+ "references": {
128
+ "summary": "What the guideline says",
129
+ "entries": [
130
+ {
131
+ "label": "Guideline",
132
+ "title": "Security guide `SQL-1`",
133
+ "fields": [
134
+ { "text": "Build every query with parameters, never by joining strings." },
135
+ {
136
+ "label": "Why",
137
+ "text": "A query built from strings lets input become part of the query, and a value a user typed can then change what the query does: read other rows, change data, or drop a table. Parameters keep data and query apart in every driver the shop uses."
138
+ },
139
+ { "style": "note", "text": "The examples below are the guide's own." },
140
+ {
141
+ "label": "Not like this",
142
+ "style": "code",
143
+ "text": "db.raw(\"SELECT * FROM t WHERE id=\" + id)"
144
+ },
145
+ {
146
+ "label": "Like this",
147
+ "style": "code",
148
+ "text": "db.raw(\"SELECT * FROM t WHERE id=%s\", [id])"
149
+ }
150
+ ],
151
+ "url": "https://example.com/guide#sql-1",
152
+ "url_label": "Security guide"
153
+ }
154
+ ]
155
+ },
156
+ "more": {
157
+ "summary": "3 more lines like this",
158
+ "items": ["`app/export.py`", "`app/admin.py`"],
159
+ "rest": "and 1 more"
160
+ },
161
+ "table": {
162
+ "summary": "What each reviewer wrote",
163
+ "note": "Reviewer A asked for the change; reviewer B approved.",
164
+ "rows": [
165
+ {
166
+ "label": "A only · Comment",
167
+ "value": "use parameters",
168
+ "plain": "asks for a parameterised query",
169
+ "mark": { "tone": "yes", "text": "✓ about this line" }
170
+ },
171
+ {
172
+ "label": "B only · Approval",
173
+ "value": "LGTM",
174
+ "mark": { "tone": "no", "text": "✗ says nothing about this line" }
175
+ }
176
+ ]
177
+ }
178
+ },
179
+ {
180
+ "source_id": "review-sql-2",
181
+ "group": "all",
182
+ "body": "Every query is parameterised",
183
+ "repeat": { "group": "guide-sql", "index": 2, "of": 2 },
184
+ "situation": {
185
+ "summary": "Only one reviewer commented on {line}.",
186
+ "question": "Does «Every query is parameterised» cover {line}?"
187
+ },
188
+ "excerpt": {
189
+ "file": "app/search.py",
190
+ "lines": [
191
+ {
192
+ "n": 30,
193
+ "text": " q = `SELECT * FROM items WHERE name LIKE '${term}'`",
194
+ "mark": true
195
+ }
196
+ ]
197
+ }
198
+ },
199
+ {
200
+ "source_id": "review-short-1",
201
+ "group": "all",
202
+ "body": "Functions stay short",
203
+ "situation": {
204
+ "summary": "One reviewer asked to split the function, the other did not.",
205
+ "question": "Does «Functions stay short» cover this function?"
206
+ },
207
+ "excerpt": {
208
+ "lines": [
209
+ { "n": 1, "text": "def import_orders(path):", "mark": true }
210
+ ]
211
+ }
212
+ }
213
+ ]
214
+ }
@@ -0,0 +1,448 @@
1
+ {
2
+ "study": "demo-witness",
3
+ "preset": "linter-config-witness",
4
+ "kind": "labels",
5
+ "title": "Witness cards",
6
+ "intro": "Each card shows one requirement, one piece of real code and one question about that code. The linter settings behind the card are under «Settings behind this card»; you do not need them to answer.",
7
+ "seed": 11,
8
+ "groups": {
9
+ "all": "Witness cards"
10
+ },
11
+ "questions": [
12
+ {
13
+ "id": "witness",
14
+ "text": "Does the requirement forbid this line, or apply to this file?",
15
+ "short": "answer",
16
+ "options": [
17
+ {
18
+ "value": "covers",
19
+ "label": "Yes",
20
+ "icon": "✅",
21
+ "example": "«Use `===`» and the line `a == b`: yes, the requirement forbids it"
22
+ },
23
+ {
24
+ "value": "excludes",
25
+ "label": "No",
26
+ "icon": "🚫",
27
+ "example": "«No `console` in shipped code» and a test file: no, tests are not shipped"
28
+ },
29
+ {
30
+ "value": "open",
31
+ "label": "Unclear: the wording could be read either way",
32
+ "icon": "↔️",
33
+ "example": "«Use `===`» and the line `x == null`: unclear, a common exception the wording neither allows nor forbids"
34
+ },
35
+ {
36
+ "value": "unsure",
37
+ "label": "I can't judge (tell me why)",
38
+ "icon": "❔",
39
+ "example": "the code is in a language you cannot read: say so in the box",
40
+ "ask_why": "Why can't you judge it?"
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ "practice": [
46
+ {
47
+ "body": "Functions take at most 3 parameters",
48
+ "situation": {
49
+ "summary": "One setting allows up to 5 parameters, the other up to 3.",
50
+ "question": "Does «Functions take at most 3 parameters» forbid {line}?"
51
+ },
52
+ "example": {
53
+ "file": "src/orders/create.ts",
54
+ "lines": [
55
+ {
56
+ "n": 11,
57
+ "text": "",
58
+ "mark": false
59
+ },
60
+ {
61
+ "n": 12,
62
+ "text": "export function createOrder(id, items, user, coupon) {",
63
+ "mark": true
64
+ },
65
+ {
66
+ "n": 13,
67
+ "text": " return { id, items, user, coupon };",
68
+ "mark": false
69
+ }
70
+ ],
71
+ "message": "Function 'createOrder' has too many parameters (4). Maximum allowed is 3.",
72
+ "caught_by": "a"
73
+ },
74
+ "details": {
75
+ "a": [
76
+ {
77
+ "key": "option:max",
78
+ "raw": "3",
79
+ "plain": "at most 3 parameters",
80
+ "matches_example": true
81
+ }
82
+ ],
83
+ "b": [
84
+ {
85
+ "key": "option:max",
86
+ "raw": "5",
87
+ "plain": "at most 5 parameters",
88
+ "matches_example": false
89
+ }
90
+ ]
91
+ },
92
+ "rules": [
93
+ {
94
+ "linter": "eslint",
95
+ "id": "max-params",
96
+ "explanation": {
97
+ "source": "eslint docs",
98
+ "what": "Enforce a maximum number of parameters in function definitions.",
99
+ "example_bad": "function foo (bar, baz, qux, qxx) {}",
100
+ "example_good": "function foo (bar, baz, qux) {}",
101
+ "url": "https://eslint.org/docs/latest/rules/max-params"
102
+ }
103
+ }
104
+ ],
105
+ "expected": {
106
+ "witness": "covers"
107
+ },
108
+ "why": "The words fix the number 3, and the marked line has 4 parameters."
109
+ },
110
+ {
111
+ "body": "Use `===`, never `==`",
112
+ "situation": {
113
+ "summary": "One setting allows `== null`, the other forbids every `==`.",
114
+ "question": "Does «Use `===`, never `==`» forbid {line}?"
115
+ },
116
+ "example": {
117
+ "file": "src/cache/lookup.ts",
118
+ "lines": [
119
+ {
120
+ "n": 41,
121
+ "text": " const entry = store.get(key);",
122
+ "mark": false
123
+ },
124
+ {
125
+ "n": 42,
126
+ "text": " if (entry == null) return undefined;",
127
+ "mark": true
128
+ }
129
+ ],
130
+ "message": "Expected '===' and instead saw '=='.",
131
+ "caught_by": "b"
132
+ },
133
+ "details": {
134
+ "a": [
135
+ {
136
+ "key": "option:null",
137
+ "raw": "\"ignore\"",
138
+ "plain": "allows `== null` and `!= null`",
139
+ "matches_example": false
140
+ }
141
+ ],
142
+ "b": [
143
+ {
144
+ "key": "rule",
145
+ "raw": "\"always\"",
146
+ "plain": "every `==` and `!=`",
147
+ "matches_example": true
148
+ }
149
+ ]
150
+ },
151
+ "rules": [
152
+ {
153
+ "linter": "eslint",
154
+ "id": "eqeqeq",
155
+ "explanation": {
156
+ "source": "none"
157
+ }
158
+ }
159
+ ],
160
+ "expected": {
161
+ "witness": "covers"
162
+ },
163
+ "why": "«never `==`» names no exception, so `== null` is forbidden too."
164
+ },
165
+ {
166
+ "body": "No `console` calls in shipped code",
167
+ "situation": {
168
+ "summary": "One setting checks test files, the other leaves them out.",
169
+ "question": "Should this requirement apply to test files?"
170
+ },
171
+ "example": {
172
+ "file": "test/report.test.ts",
173
+ "lines": [
174
+ {
175
+ "n": 7,
176
+ "text": " console.log(result);",
177
+ "mark": true
178
+ }
179
+ ],
180
+ "message": "Unexpected console statement.",
181
+ "caught_by": "a"
182
+ },
183
+ "details": {
184
+ "a": [
185
+ {
186
+ "key": "files",
187
+ "raw": "**/*.ts",
188
+ "plain": "every TypeScript file",
189
+ "matches_example": true
190
+ }
191
+ ],
192
+ "b": [
193
+ {
194
+ "key": "files",
195
+ "raw": "**/*.ts",
196
+ "plain": "every TypeScript file",
197
+ "matches_example": true
198
+ },
199
+ {
200
+ "key": "ignores",
201
+ "raw": "test/**",
202
+ "plain": "except the files under test/",
203
+ "matches_example": true
204
+ }
205
+ ]
206
+ },
207
+ "rules": [
208
+ {
209
+ "linter": "eslint",
210
+ "id": "no-console",
211
+ "explanation": {
212
+ "source": "none"
213
+ }
214
+ }
215
+ ],
216
+ "expected": {
217
+ "witness": "excludes"
218
+ },
219
+ "why": "Test files are not shipped, so the words do not reach them."
220
+ }
221
+ ],
222
+ "items": [
223
+ {
224
+ "source_id": "req-logging:tests",
225
+ "group": "all",
226
+ "body": "Never log with `print`; use the `logging` module",
227
+ "situation": {
228
+ "summary": "One setting checks the scripts/ folder, the other leaves it out.",
229
+ "question": "Should this requirement apply to the scripts/ folder?"
230
+ },
231
+ "example": {
232
+ "file": "scripts/seed_db.py",
233
+ "url": "https://example.com/demo-shop/blob/0000000/scripts/seed_db.py#L18",
234
+ "lines": [
235
+ {
236
+ "n": 16,
237
+ "text": "for row in rows:",
238
+ "mark": false
239
+ },
240
+ {
241
+ "n": 17,
242
+ "text": " db.insert(row)",
243
+ "mark": false
244
+ },
245
+ {
246
+ "n": 18,
247
+ "text": "print(f\"seeded {len(rows)} rows\")",
248
+ "mark": true
249
+ }
250
+ ],
251
+ "message": "`print` found",
252
+ "caught_by": "b"
253
+ },
254
+ "more_like_this": {
255
+ "count": 3,
256
+ "files": [
257
+ "scripts/migrate.py",
258
+ "scripts/backfill_prices.py"
259
+ ]
260
+ },
261
+ "details": {
262
+ "a": [
263
+ {
264
+ "key": "rule",
265
+ "raw": "T201",
266
+ "plain": "flags every call to `print`",
267
+ "matches_example": null
268
+ },
269
+ {
270
+ "key": "ignores",
271
+ "raw": "scripts/**",
272
+ "plain": "except the files under scripts/",
273
+ "matches_example": true
274
+ }
275
+ ],
276
+ "b": [
277
+ {
278
+ "key": "rule",
279
+ "raw": "T201",
280
+ "plain": "flags every call to `print`",
281
+ "matches_example": null
282
+ }
283
+ ]
284
+ },
285
+ "rules": [
286
+ {
287
+ "linter": "ruff",
288
+ "id": "T201",
289
+ "explanation": {
290
+ "source": "ruff rule",
291
+ "what": "Checks for `print` statements.",
292
+ "why": "`print` statements used for debugging should be omitted from production code. They can lead to the accidental inclusion of sensitive information in logs, and are not configurable by clients, unlike `logging` statements.",
293
+ "example_bad": "def sum_less_than_four(a, b):\n print(f\"Calling with {a} and {b}\")\n return a + b < 4",
294
+ "example_good": "import logging\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\n\ndef sum_less_than_four(a, b):\n logger.debug(\"Calling with %s and %s\", a, b)\n return a + b < 4",
295
+ "url": "https://docs.astral.sh/ruff/rules/print/"
296
+ }
297
+ }
298
+ ],
299
+ "repeat": {
300
+ "group": "req-logging",
301
+ "index": 1,
302
+ "of": 2
303
+ }
304
+ },
305
+ {
306
+ "source_id": "req-logging:debug-flag",
307
+ "group": "all",
308
+ "body": "Never log with `print`; use the `logging` module",
309
+ "situation": {
310
+ "summary": "One setting allows `print` behind a debug flag, the other forbids every `print`.",
311
+ "question": "Does «Never log with `print`; use the `logging` module» forbid {line}?"
312
+ },
313
+ "example": {
314
+ "file": "shop/cart.py",
315
+ "lines": [
316
+ {
317
+ "n": 30,
318
+ "text": " if DEBUG:",
319
+ "mark": false
320
+ },
321
+ {
322
+ "n": 31,
323
+ "text": " print(\"cart\", cart.id, cart.total)",
324
+ "mark": true
325
+ }
326
+ ],
327
+ "message": "`print` found",
328
+ "caught_by": "a"
329
+ },
330
+ "details": {
331
+ "a": [
332
+ {
333
+ "key": "rule",
334
+ "raw": "T201",
335
+ "plain": "flags every call to `print`",
336
+ "matches_example": null
337
+ }
338
+ ],
339
+ "b": [
340
+ {
341
+ "key": "rule",
342
+ "raw": "T201",
343
+ "plain": "flags every call to `print`",
344
+ "matches_example": null
345
+ },
346
+ {
347
+ "key": "option:allow-debug",
348
+ "raw": "true",
349
+ "plain": "allows `print` inside `if DEBUG:`",
350
+ "matches_example": true
351
+ }
352
+ ]
353
+ },
354
+ "rules": [
355
+ {
356
+ "linter": "ruff",
357
+ "id": "T201",
358
+ "explanation": {
359
+ "source": "none"
360
+ }
361
+ }
362
+ ],
363
+ "repeat": {
364
+ "group": "req-logging",
365
+ "index": 2,
366
+ "of": 2
367
+ }
368
+ },
369
+ {
370
+ "source_id": "req-imports:barrel",
371
+ "group": "all",
372
+ "body": "Import from a package's public entry point, never from its internals",
373
+ "situation": {
374
+ "summary": "One setting forbids any path below a package's folder, the other only paths into src/internal/.",
375
+ "question": "Does «Import from a package's public entry point, never from its internals» forbid {line}?"
376
+ },
377
+ "example": {
378
+ "file": "apps/web/src/checkout/total.ts",
379
+ "lines": [
380
+ {
381
+ "n": 1,
382
+ "text": "import { money } from \"@demo/format\";",
383
+ "mark": false
384
+ },
385
+ {
386
+ "n": 2,
387
+ "text": "import { round } from \"@demo/format/src/round\";",
388
+ "mark": true
389
+ },
390
+ {
391
+ "n": 3,
392
+ "text": "",
393
+ "mark": false
394
+ }
395
+ ],
396
+ "message": "'@demo/format/src/round' import is restricted from being used by a pattern. Import from the package's entry point.",
397
+ "caught_by": "a"
398
+ },
399
+ "more_like_this": {
400
+ "count": 1,
401
+ "files": [
402
+ "apps/admin/src/report/sum.ts"
403
+ ]
404
+ },
405
+ "details": {
406
+ "a": [
407
+ {
408
+ "key": "option:patterns",
409
+ "raw": "[{\"group\": [\"@demo/*/*\"], \"message\": \"Import from the package's entry point.\"}]",
410
+ "plain": "forbids any import path one or more folders below a `@demo/` package",
411
+ "matches_example": true
412
+ },
413
+ {
414
+ "key": "files",
415
+ "raw": "apps/**/*.ts",
416
+ "plain": "the TypeScript files under apps/",
417
+ "matches_example": true
418
+ }
419
+ ],
420
+ "b": [
421
+ {
422
+ "key": "option:patterns",
423
+ "raw": "[{\"group\": [\"@demo/*/src/internal/**\"]}]",
424
+ "plain": "forbids only import paths into a package's src/internal/ folder",
425
+ "matches_example": false
426
+ },
427
+ {
428
+ "key": "files",
429
+ "raw": "apps/**/*.ts",
430
+ "plain": "the TypeScript files under apps/",
431
+ "matches_example": true
432
+ }
433
+ ]
434
+ },
435
+ "rules": [
436
+ {
437
+ "linter": "eslint",
438
+ "id": "no-restricted-imports",
439
+ "explanation": {
440
+ "source": "config message",
441
+ "what": "Import from the package's entry point.",
442
+ "url": "https://eslint.org/docs/latest/rules/no-restricted-imports"
443
+ }
444
+ }
445
+ ]
446
+ }
447
+ ]
448
+ }