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.
- package/dist/skills/build-label-page/scripts/label-page-core.d.ts +105 -25
- package/dist/skills/build-label-page/scripts/label-page-presets.d.ts +10 -0
- package/package.json +1 -1
- package/skills/build-label-page/SKILL.md +20 -2
- package/skills/build-label-page/SKILL.md.spec.ts +19 -1
- package/skills/build-label-page/references/closed-choice-generator.md +171 -5
- package/skills/build-label-page/scripts/fixtures/blocks-items.json +214 -0
- package/skills/build-label-page/scripts/fixtures/linter-config-witness-items.json +448 -0
- package/skills/build-label-page/scripts/label-page-answerers.test.ts +381 -0
- package/skills/build-label-page/scripts/label-page-blocks.test.ts +335 -0
- package/skills/build-label-page/scripts/label-page-browser.test.ts +637 -2
- package/skills/build-label-page/scripts/label-page-core.js +360 -70
- package/skills/build-label-page/scripts/label-page-core.test.ts +78 -11
- package/skills/build-label-page/scripts/label-page-core.ts +585 -107
- package/skills/build-label-page/scripts/label-page-html.test.ts +22 -0
- package/skills/build-label-page/scripts/label-page-presets.js +222 -0
- package/skills/build-label-page/scripts/label-page-presets.test.ts +175 -0
- package/skills/build-label-page/scripts/label-page-presets.ts +257 -0
- package/skills/build-label-page/scripts/label-page.js +13 -2
- package/skills/build-label-page/scripts/label-page.ts +13 -2
- package/skills/build-label-page/scripts/page-template.html +438 -44
|
@@ -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
|
+
}
|