@avi2dg/checks 0.16.0 → 0.18.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/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
4
4
 
5
+ ## 0.18.0
6
+
7
+ Released 2026-09-26.
8
+
9
+ ### Features
10
+
11
+ - **scripts:** generate commitlint and CI workflows with checks-quality (#54)
12
+
13
+ ### Fixes
14
+
15
+ - **scripts:** make checks-quality refuse a config that drops the kit's extends (#55)
16
+
17
+ ## 0.17.0
18
+
19
+ Released 2026-09-25.
20
+
21
+ ### Features
22
+
23
+ - **effect-channel:** add a cognitive complexity rule and make it the size budget's (#50)
24
+ - **scripts:** export the refused directive names from comment-matchers (#49)
25
+ - refuse undeclared package imports and deprecated symbol use (#48)
26
+
5
27
  ## 0.16.0
6
28
 
7
29
  Released 2026-09-25.
package/CONTRIBUTING.md CHANGED
@@ -65,7 +65,7 @@ To place a change:
65
65
  | Path | What it holds |
66
66
  | --- | --- |
67
67
  | `scripts/` | every bin, and the modules they share |
68
- | `effect-channel/` | the Effect error-channel oxlint plugin |
68
+ | `effect-channel/` | the oxlint plugin with the Effect error-channel and cognitive complexity rules |
69
69
  | `dist/` | the committed bundles of the plugin and of `featureRules` |
70
70
  | `presets/` | the Effect presets `checks-quality` builds its fragments from |
71
71
  | `templates/` | one template per kind of doc file, which `bun run build` renders |
package/README.md CHANGED
@@ -170,7 +170,7 @@ Every path is relative to the installed package, `node_modules/@avi2dg/checks/`.
170
170
  | `oxlintrc.json` | the oxlint base config `.oxlintrc.json` extends |
171
171
  | `stryker.preset.js` | the Stryker mutation-testing preset |
172
172
  | `tsconfig.effect.json` | the tsconfig fragment with the Effect language-service block |
173
- | `dist/` | the compiled Effect error-channel plugin and `featureRules` |
173
+ | `dist/` | the compiled oxlint plugin with the Effect error-channel and cognitive complexity rules, and `featureRules` |
174
174
 
175
175
  <!-- end generated shipped -->
176
176
 
@@ -178,6 +178,7 @@ Every path is relative to the installed package, `node_modules/@avi2dg/checks/`.
178
178
 
179
179
  - [The quality file](docs/configs/quality-file.md)
180
180
  - [The Effect rules](docs/configs/effect-rules.md)
181
+ - [The TypeScript rules](docs/configs/typescript-rules.md)
181
182
  - [The dependency rules](docs/configs/dependency-rules.md)
182
183
  - [The commit message lint](docs/configs/commit-messages.md)
183
184
  - [Why it is shaped this way](docs/design.md)
@@ -41,6 +41,14 @@ export default {
41
41
  dependencyTypesNot: ["type-only", "npm-peer"],
42
42
  },
43
43
  },
44
+ {
45
+ name: "no-non-package-json",
46
+ severity: "error",
47
+ comment:
48
+ "The import resolves to an installed package the nearest package.json does not declare, so it holds only while something else keeps it hoisted. Declare it in dependencies, devDependencies or peerDependencies.",
49
+ from: {},
50
+ to: { dependencyTypes: ["npm-no-pkg", "npm-unknown"] },
51
+ },
44
52
  {
45
53
  name: "not-to-unresolvable",
46
54
  severity: "error",
@@ -59,10 +59,11 @@ var STATEMENTS = {
59
59
  };
60
60
  var COMPLEXITY = {
61
61
  key: "complexity",
62
- rule: "complexity",
63
- options: { variant: "modified" },
64
- measured: /has a complexity of (\d+)/,
65
- limits: "The highest cyclomatic complexity a function may reach, a switch counted once"
62
+ rule: "cognitive-complexity",
63
+ plugin: "effect-channel",
64
+ options: {},
65
+ measured: /has a cognitive complexity of (\d+)/,
66
+ limits: "The highest cognitive complexity a function may reach, a switch counted once"
66
67
  };
67
68
  var DEPTH = {
68
69
  key: "depth",
package/dist/index.js CHANGED
@@ -1,3 +1,421 @@
1
+ // effect-channel/cognitive-nodes.ts
2
+ var CONTROL_TYPES = ["IfStatement", "ConditionalExpression", "SwitchStatement", "SwitchCase", "TryStatement", "CatchClause"];
3
+ var LOOP_TYPES = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement", "LabeledStatement"];
4
+ var CALL_TYPES = ["LogicalExpression", "BreakStatement", "ContinueStatement", "CallExpression", "NewExpression", "ImportExpression"];
5
+ var FUNCTION_TYPES = ["FunctionDeclaration", "FunctionExpression", "TSDeclareFunction", "TSEmptyBodyFunctionExpression", "ArrowFunctionExpression", "StaticBlock"];
6
+ var PLAIN_A_TYPES = ["BlockStatement", "ExpressionStatement", "ReturnStatement", "ThrowStatement", "VariableDeclaration", "VariableDeclarator", "AssignmentPattern", "ObjectPattern", "ArrayPattern", "Property", "RestElement", "TSParameterProperty", "WithStatement", "TSEnumDeclaration", "TSEnumBody", "TSEnumMember"];
7
+ var PLAIN_B_TYPES = ["ClassDeclaration", "ClassExpression", "ClassBody", "MethodDefinition", "TSAbstractMethodDefinition", "PropertyDefinition", "TSAbstractPropertyDefinition", "AccessorProperty", "TSAbstractAccessorProperty", "ObjectExpression", "ArrayExpression"];
8
+ var PLAIN_C_TYPES = ["AwaitExpression", "UnaryExpression", "UpdateExpression", "SpreadElement", "YieldExpression", "BinaryExpression", "AssignmentExpression", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression", "ChainExpression", "ParenthesizedExpression", "Decorator", "TSInstantiationExpression", "MemberExpression", "JSXMemberExpression", "TemplateLiteral", "TaggedTemplateExpression", "SequenceExpression", "JSXElement", "JSXFragment", "JSXOpeningElement", "JSXAttribute", "JSXExpressionContainer", "JSXSpreadChild", "JSXSpreadAttribute"];
9
+
10
+ // effect-channel/cognitive-plain.ts
11
+ function isControl(node) {
12
+ return CONTROL_TYPES.includes(node.type);
13
+ }
14
+ function isLoop(node) {
15
+ return LOOP_TYPES.includes(node.type);
16
+ }
17
+ function isCall(node) {
18
+ return CALL_TYPES.includes(node.type);
19
+ }
20
+ function isFunction(node) {
21
+ return FUNCTION_TYPES.includes(node.type);
22
+ }
23
+ function isPlainA(node) {
24
+ return PLAIN_A_TYPES.includes(node.type);
25
+ }
26
+ function isPlainB(node) {
27
+ return PLAIN_B_TYPES.includes(node.type);
28
+ }
29
+ function isPlainC(node) {
30
+ return PLAIN_C_TYPES.includes(node.type);
31
+ }
32
+ function unreachable(_value) {}
33
+ function plainChildrenA(node) {
34
+ switch (node.type) {
35
+ case "BlockStatement":
36
+ return node.body;
37
+ case "ExpressionStatement":
38
+ return [node.expression];
39
+ case "ReturnStatement":
40
+ case "ThrowStatement":
41
+ return [node.argument];
42
+ case "VariableDeclaration":
43
+ return node.declarations;
44
+ case "VariableDeclarator":
45
+ return [node.id, node.init];
46
+ case "AssignmentPattern":
47
+ return [node.left, node.right];
48
+ case "ObjectPattern":
49
+ return node.properties;
50
+ case "ArrayPattern":
51
+ return node.elements;
52
+ case "Property":
53
+ return [node.key, node.value];
54
+ case "RestElement":
55
+ return [node.argument];
56
+ case "TSParameterProperty":
57
+ return [node.parameter];
58
+ case "WithStatement":
59
+ return [node.object, node.body];
60
+ case "TSEnumDeclaration":
61
+ return [node.body];
62
+ case "TSEnumBody":
63
+ return node.members;
64
+ case "TSEnumMember":
65
+ return [node.initializer];
66
+ default:
67
+ unreachable(node);
68
+ return [];
69
+ }
70
+ }
71
+ function plainChildrenB(node) {
72
+ switch (node.type) {
73
+ case "ClassDeclaration":
74
+ case "ClassExpression":
75
+ return [...node.decorators, node.id, node.superClass, node.body];
76
+ case "ClassBody":
77
+ return node.body;
78
+ case "MethodDefinition":
79
+ case "TSAbstractMethodDefinition":
80
+ return [node.key, node.value];
81
+ case "PropertyDefinition":
82
+ case "TSAbstractPropertyDefinition":
83
+ case "AccessorProperty":
84
+ case "TSAbstractAccessorProperty":
85
+ return [node.key, node.value];
86
+ case "ObjectExpression":
87
+ return node.properties;
88
+ case "ArrayExpression":
89
+ return node.elements;
90
+ default:
91
+ unreachable(node);
92
+ return [];
93
+ }
94
+ }
95
+ function plainChildrenC(node) {
96
+ switch (node.type) {
97
+ case "AwaitExpression":
98
+ case "UnaryExpression":
99
+ case "UpdateExpression":
100
+ case "SpreadElement":
101
+ return [node.argument];
102
+ case "YieldExpression":
103
+ return [node.argument];
104
+ case "BinaryExpression":
105
+ return [node.left, node.right];
106
+ case "AssignmentExpression":
107
+ return [node.left, node.right];
108
+ case "TSAsExpression":
109
+ case "TSSatisfiesExpression":
110
+ case "TSTypeAssertion":
111
+ case "TSNonNullExpression":
112
+ case "ChainExpression":
113
+ case "ParenthesizedExpression":
114
+ case "Decorator":
115
+ case "TSInstantiationExpression":
116
+ return [node.expression];
117
+ case "MemberExpression":
118
+ case "JSXMemberExpression":
119
+ return [node.object, node.property];
120
+ case "TemplateLiteral":
121
+ return node.expressions;
122
+ case "TaggedTemplateExpression":
123
+ return [node.tag, node.quasi];
124
+ case "SequenceExpression":
125
+ return node.expressions;
126
+ case "JSXElement":
127
+ return [node.openingElement, ...node.children];
128
+ case "JSXFragment":
129
+ return node.children;
130
+ case "JSXOpeningElement":
131
+ return node.attributes;
132
+ case "JSXAttribute":
133
+ return [node.value];
134
+ case "JSXExpressionContainer":
135
+ case "JSXSpreadChild":
136
+ return [node.expression];
137
+ case "JSXSpreadAttribute":
138
+ return [node.argument];
139
+ default:
140
+ unreachable(node);
141
+ return [];
142
+ }
143
+ }
144
+
145
+ // effect-channel/cognitive.ts
146
+ function unreachable2(_value) {}
147
+ function scoreList(state, nodes, nesting, parent) {
148
+ for (const node of nodes) {
149
+ if (node !== null)
150
+ score(state, node, nesting, parent);
151
+ }
152
+ }
153
+ function score(state, node, nesting, parent) {
154
+ if (isControl(node))
155
+ return scoreControl(state, node, nesting);
156
+ if (isLoop(node))
157
+ return scoreLoop(state, node, nesting);
158
+ if (isCall(node))
159
+ return scoreCall(state, node, nesting, parent);
160
+ if (isFunction(node))
161
+ return;
162
+ if (isPlainA(node))
163
+ return scoreList(state, plainChildrenA(node), nesting, node);
164
+ if (isPlainB(node))
165
+ return scoreList(state, plainChildrenB(node), nesting, node);
166
+ if (isPlainC(node))
167
+ return scoreList(state, plainChildrenC(node), nesting, node);
168
+ }
169
+ function scoreBranch(state, node, nesting) {
170
+ score(state, node.test, nesting, node);
171
+ score(state, node.consequent, nesting + 1, node);
172
+ const alternate = node.alternate;
173
+ if (alternate === null)
174
+ return;
175
+ state.total += 1;
176
+ if (alternate.type === "IfStatement")
177
+ return scoreBranch(state, alternate, nesting);
178
+ score(state, alternate, nesting + 1, node);
179
+ }
180
+ function scoreIf(state, node, nesting) {
181
+ state.total += 1 + nesting;
182
+ scoreBranch(state, node, nesting);
183
+ }
184
+ function scoreControl(state, node, nesting) {
185
+ switch (node.type) {
186
+ case "IfStatement": {
187
+ return scoreIf(state, node, nesting);
188
+ }
189
+ case "ConditionalExpression": {
190
+ state.total += 1 + nesting;
191
+ score(state, node.test, nesting, node);
192
+ score(state, node.consequent, nesting + 1, node);
193
+ score(state, node.alternate, nesting + 1, node);
194
+ return;
195
+ }
196
+ case "SwitchStatement": {
197
+ state.total += 1 + nesting;
198
+ score(state, node.discriminant, nesting, node);
199
+ scoreList(state, node.cases, nesting + 1, node);
200
+ return;
201
+ }
202
+ case "SwitchCase": {
203
+ if (node.test !== null)
204
+ score(state, node.test, nesting, node);
205
+ scoreList(state, node.consequent, nesting, node);
206
+ return;
207
+ }
208
+ case "TryStatement": {
209
+ score(state, node.block, nesting, node);
210
+ if (node.handler !== null)
211
+ score(state, node.handler, nesting, node);
212
+ if (node.finalizer !== null)
213
+ score(state, node.finalizer, nesting, node);
214
+ return;
215
+ }
216
+ case "CatchClause": {
217
+ state.total += 1 + nesting;
218
+ if (node.param !== null)
219
+ score(state, node.param, nesting, node);
220
+ score(state, node.body, nesting + 1, node);
221
+ return;
222
+ }
223
+ default: {
224
+ return unreachable2(node);
225
+ }
226
+ }
227
+ }
228
+ function scoreLoop(state, node, nesting) {
229
+ switch (node.type) {
230
+ case "ForStatement": {
231
+ state.total += 1 + nesting;
232
+ if (node.init !== null)
233
+ score(state, node.init, nesting, node);
234
+ if (node.test !== null)
235
+ score(state, node.test, nesting, node);
236
+ if (node.update !== null)
237
+ score(state, node.update, nesting, node);
238
+ score(state, node.body, nesting + 1, node);
239
+ return;
240
+ }
241
+ case "ForInStatement":
242
+ case "ForOfStatement": {
243
+ state.total += 1 + nesting;
244
+ score(state, node.left, nesting, node);
245
+ score(state, node.right, nesting, node);
246
+ score(state, node.body, nesting + 1, node);
247
+ return;
248
+ }
249
+ case "WhileStatement": {
250
+ state.total += 1 + nesting;
251
+ score(state, node.test, nesting, node);
252
+ score(state, node.body, nesting + 1, node);
253
+ return;
254
+ }
255
+ case "DoWhileStatement": {
256
+ state.total += 1 + nesting;
257
+ score(state, node.body, nesting + 1, node);
258
+ score(state, node.test, nesting, node);
259
+ return;
260
+ }
261
+ case "LabeledStatement": {
262
+ score(state, node.body, nesting, node);
263
+ return;
264
+ }
265
+ default: {
266
+ return unreachable2(node);
267
+ }
268
+ }
269
+ }
270
+ function insideRun(parent) {
271
+ return parent !== null && parent.type === "LogicalExpression" && (parent.operator === "&&" || parent.operator === "||");
272
+ }
273
+ function countRuns(node, parentOperator) {
274
+ if (node.type !== "LogicalExpression")
275
+ return 0;
276
+ if (node.operator !== "&&" && node.operator !== "||")
277
+ return 0;
278
+ const own = node.operator === parentOperator ? 0 : 1;
279
+ return own + countRuns(node.left, node.operator) + countRuns(node.right, node.operator);
280
+ }
281
+ function isSelfCall(state, callee) {
282
+ if (callee.type === "Identifier")
283
+ return state.names.identifiers.includes(callee.name);
284
+ if (callee.type === "MemberExpression" && callee.object.type === "ThisExpression" && callee.property.type === "Identifier") {
285
+ return state.names.members.includes(callee.property.name);
286
+ }
287
+ return false;
288
+ }
289
+ function scoreCall(state, node, nesting, parent) {
290
+ switch (node.type) {
291
+ case "LogicalExpression": {
292
+ if (!insideRun(parent) && (node.operator === "&&" || node.operator === "||"))
293
+ state.total += countRuns(node, null);
294
+ score(state, node.left, nesting, node);
295
+ score(state, node.right, nesting, node);
296
+ return;
297
+ }
298
+ case "BreakStatement":
299
+ case "ContinueStatement": {
300
+ if (node.label !== null)
301
+ state.total += 1;
302
+ return;
303
+ }
304
+ case "CallExpression":
305
+ case "NewExpression": {
306
+ if (isSelfCall(state, node.callee))
307
+ state.recursive = true;
308
+ score(state, node.callee, nesting, node);
309
+ scoreList(state, node.arguments, nesting, node);
310
+ return;
311
+ }
312
+ case "ImportExpression": {
313
+ score(state, node.source, nesting, node);
314
+ if (node.options !== null)
315
+ score(state, node.options, nesting, node);
316
+ return;
317
+ }
318
+ default: {
319
+ return unreachable2(node);
320
+ }
321
+ }
322
+ }
323
+ function cognitiveComplexity(root, names) {
324
+ const state = { total: 0, recursive: false, names };
325
+ if (root.type === "StaticBlock") {
326
+ scoreList(state, root.body, 0, root);
327
+ return state.total;
328
+ }
329
+ scoreList(state, root.params, 0, root);
330
+ if (root.type === "ArrowFunctionExpression")
331
+ score(state, root.body, 0, root);
332
+ else if (root.body !== null)
333
+ score(state, root.body, 0, root);
334
+ return state.recursive ? state.total + 1 : state.total;
335
+ }
336
+
337
+ // effect-channel/cognitive-complexity.ts
338
+ var DEFAULT_MAX = 15;
339
+ function maxOf(options) {
340
+ const [first] = options;
341
+ if (typeof first === "object" && first !== null && "max" in first && typeof first.max === "number" && first.max > 0) {
342
+ return Math.floor(first.max);
343
+ }
344
+ return DEFAULT_MAX;
345
+ }
346
+ function keyName(holder) {
347
+ if (holder.computed)
348
+ return;
349
+ if (holder.key.type === "Identifier")
350
+ return holder.key.name;
351
+ if (holder.key.type === "Literal" && typeof holder.key.value === "string")
352
+ return holder.key.value;
353
+ return;
354
+ }
355
+ function assignedBinding(target) {
356
+ if (target.type === "Identifier")
357
+ return { kind: "identifiers", name: target.name };
358
+ if (target.type === "MemberExpression" && target.object.type === "ThisExpression" && target.property.type === "Identifier") {
359
+ return { kind: "members", name: target.property.name };
360
+ }
361
+ return;
362
+ }
363
+ function memberBinding(holder) {
364
+ const name = keyName(holder);
365
+ return name === undefined ? undefined : { kind: "members", name };
366
+ }
367
+ function binding(node) {
368
+ const parent = node.parent;
369
+ if (parent.type === "VariableDeclarator" && parent.init === node && parent.id.type === "Identifier") {
370
+ return { kind: "identifiers", name: parent.id.name };
371
+ }
372
+ if ((parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition" || parent.type === "AccessorProperty") && parent.value === node) {
373
+ return memberBinding(parent);
374
+ }
375
+ if (parent.type === "AssignmentExpression" && parent.right === node)
376
+ return assignedBinding(parent.left);
377
+ return;
378
+ }
379
+ function displayName(node) {
380
+ if (node.type !== "ArrowFunctionExpression" && node.id !== null)
381
+ return node.id.name;
382
+ return binding(node)?.name ?? "anonymous";
383
+ }
384
+ function selfNames(node) {
385
+ const own = node.type === "ArrowFunctionExpression" || node.id === null ? [] : [node.id.name];
386
+ const bound = binding(node);
387
+ return {
388
+ identifiers: bound?.kind === "identifiers" ? [...own, bound.name] : own,
389
+ members: bound?.kind === "members" ? [bound.name] : []
390
+ };
391
+ }
392
+ var NO_NAMES = { identifiers: [], members: [] };
393
+ var rule = {
394
+ meta: {
395
+ type: "problem",
396
+ docs: { description: "Hold each function to a cognitive complexity of 15" },
397
+ schema: [{ type: "object", properties: { max: { type: "number" } }, additionalProperties: false }],
398
+ defaultOptions: [{ max: DEFAULT_MAX }]
399
+ },
400
+ create(context) {
401
+ const max = maxOf(context.options);
402
+ const check = (node) => {
403
+ const score2 = node.type === "StaticBlock" ? cognitiveComplexity(node, NO_NAMES) : cognitiveComplexity(node, selfNames(node));
404
+ if (score2 <= max)
405
+ return;
406
+ const name = node.type === "StaticBlock" ? "static block" : `function \`${displayName(node)}\``;
407
+ context.report({ node, message: `${name} has a cognitive complexity of ${score2}. Maximum allowed is ${max}.` });
408
+ };
409
+ return {
410
+ FunctionDeclaration: check,
411
+ FunctionExpression: check,
412
+ ArrowFunctionExpression: check,
413
+ StaticBlock: check
414
+ };
415
+ }
416
+ };
417
+ var cognitive_complexity_default = rule;
418
+
1
419
  // effect-channel/no-error-channel-escape.ts
2
420
  var EFFECT_SOURCES = new Set(["effect", "effect/Effect"]);
3
421
  var INSTEAD = {
@@ -19,7 +437,7 @@ var blindToTheError = (handler) => {
19
437
  return false;
20
438
  return handler.params.every((param) => param.type === "Identifier" && /^_+$/.test(param.name));
21
439
  };
22
- var rule = {
440
+ var rule2 = {
23
441
  meta: {
24
442
  type: "problem",
25
443
  docs: { description: "Disallow the combinators that erase Effect's error channel" }
@@ -67,10 +485,10 @@ var rule = {
67
485
  };
68
486
  }
69
487
  };
70
- var no_error_channel_escape_default = rule;
488
+ var no_error_channel_escape_default = rule2;
71
489
 
72
490
  // effect-channel/no-throw.ts
73
- var rule2 = {
491
+ var rule3 = {
74
492
  meta: {
75
493
  type: "problem",
76
494
  docs: { description: "Disallow throw, which fails outside Effect's error channel" }
@@ -86,10 +504,10 @@ var rule2 = {
86
504
  };
87
505
  }
88
506
  };
89
- var no_throw_default = rule2;
507
+ var no_throw_default = rule3;
90
508
 
91
509
  // effect-channel/no-try-catch.ts
92
- var rule3 = {
510
+ var rule4 = {
93
511
  meta: {
94
512
  type: "problem",
95
513
  docs: { description: "Disallow a try statement with a catch clause, which recovers outside Effect's error channel" }
@@ -105,7 +523,7 @@ var rule3 = {
105
523
  };
106
524
  }
107
525
  };
108
- var no_try_catch_default = rule3;
526
+ var no_try_catch_default = rule4;
109
527
 
110
528
  // effect-channel/index.ts
111
529
  var plugin = {
@@ -113,7 +531,8 @@ var plugin = {
113
531
  rules: {
114
532
  "no-error-channel-escape": no_error_channel_escape_default,
115
533
  "no-throw": no_throw_default,
116
- "no-try-catch": no_try_catch_default
534
+ "no-try-catch": no_try_catch_default,
535
+ "cognitive-complexity": cognitive_complexity_default
117
536
  }
118
537
  };
119
538
  var effect_channel_default = plugin;
@@ -10,24 +10,8 @@ It arrives with the kit, since `@commitlint/cli` and `@commitlint/config-convent
10
10
  ## Workflow
11
11
 
12
12
  The lint runs in CI on pull requests, because `jj` never fires a git hook.
13
- A repository adds this workflow:
14
-
15
- ```yaml
16
- on:
17
- pull_request:
18
- types: [opened, edited, synchronize, reopened]
19
- jobs:
20
- commitlint:
21
- runs-on: ubuntu-latest
22
- steps:
23
- - uses: actions/checkout@v5
24
- - uses: oven-sh/setup-bun@v2
25
- - run: bun install --frozen-lockfile
26
- - run: printf '%s' "$PR_TITLE (#0000)" > "$RUNNER_TEMP/pr-title"
27
- env:
28
- PR_TITLE: ${{ github.event.pull_request.title }}
29
- - run: ./node_modules/.bin/commitlint --config ./node_modules/@avi2dg/checks/commitlint.config.js --edit "$RUNNER_TEMP/pr-title"
30
- ```
13
+ `checks-quality generate` writes the workflow whole into `.github/workflows/commitlint.yml`, as [checks-quality](../gates/checks-quality.md) says.
14
+ The workflow lints with the installed kit's `commitlint.config.js`, so every repository holds titles to the same rules.
31
15
 
32
16
  ## What it lints
33
17
 
@@ -9,6 +9,7 @@ The shared dependency-cruiser base holds a repository's imports to a set of rule
9
9
  - `no-circular`
10
10
  - `no-orphans`
11
11
  - `not-to-dev-dep`, which refuses shipped source importing a dev-only package, and a package listed in `peerDependencies` too is not dev-only
12
+ - `no-non-package-json`, which refuses an import of an installed package that the nearest `package.json` does not declare
12
13
  - `not-to-unresolvable`, which refuses a specifier nothing installed answers
13
14
  - `no-deep-imports`, which refuses a subpath the package's exports map does not publish
14
15
 
@@ -43,6 +43,7 @@ effect-tsgo keeps the severities `tsconfig.effect.json` sets when a later config
43
43
 
44
44
  ## Related topics
45
45
 
46
+ - [The TypeScript rules](typescript-rules.md)
46
47
  - [checks-quality](../gates/checks-quality.md)
47
48
  - [The quality file](quality-file.md)
48
49
  - [Why it is shaped this way](../design.md)
@@ -39,8 +39,8 @@ The kit's bins find the file at the git root and read it there:
39
39
 
40
40
  | Key | Read by | Holds |
41
41
  | --- | --- | --- |
42
- | `defaultBranch` | `checks-lint`, `checks-ci-wiring` | the branch pull requests merge into, `main` when absent |
43
- | `gates.ci` | `checks-ci-wiring` | the commands CI runs on every pull request, as [checks-ci-wiring](../gates/checks-ci-wiring.md) says |
42
+ | `defaultBranch` | `checks-lint`, `checks-ci-wiring`, `checks-quality` | the branch pull requests merge into, `main` when absent |
43
+ | `gates.ci` | `checks-ci-wiring`, `checks-quality` | the commands CI runs on every pull request, as [checks-ci-wiring](../gates/checks-ci-wiring.md) says |
44
44
  | `gates.scheduled` | `checks-ci-wiring` | the commands a schedule runs |
45
45
  | `gates.lint` | `checks-lint`, `checks-ci-wiring` | the gates `checks-lint` runs when not all apply, as [Gate selection](../gates/checks-lint.md#gate-selection) says |
46
46
  | `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit, as [checks-commit-identity](../gates/checks-commit-identity.md) says |
@@ -0,0 +1,36 @@
1
+ # The TypeScript rules
2
+
3
+ The oxlint base config holds a repository's TypeScript to a set of rules, and a reader looks it up to learn what each rule refuses and which rules need type information.
4
+
5
+ ## Syntax rules
6
+
7
+ `oxlintrc.json` turns on the `correctness` and `suspicious` categories as errors, and these rules on top of them:
8
+
9
+ - `typescript/no-explicit-any` refuses an `any` type written out.
10
+ - `typescript/ban-ts-comment` refuses `@ts-ignore`, `@ts-expect-error` and `@ts-nocheck`.
11
+ - `typescript/no-inferrable-types` refuses a type annotation on a variable or a parameter default whose literal initializer already gives the type.
12
+ - `typescript/explicit-module-boundary-types` refuses an exported function without a return type, and an exported function parameter typed `any`.
13
+ - `typescript/no-non-null-assertion` refuses the non-null assertion `!`.
14
+ - `eslint/no-unused-vars` refuses a variable, a parameter or an import nothing reads, and passes over a variable or a parameter whose name starts with `_` and the siblings of a rest property.
15
+
16
+ The base turns off `typescript/consistent-return`, which its categories would otherwise turn on.
17
+ It turns on `effect-channel/no-error-channel-escape` as well, and [The Effect rules](effect-rules.md) says what that rule refuses.
18
+
19
+ ## Type-aware rules
20
+
21
+ These rules read the types, so they run only under `oxlint --type-aware` with `oxlint-tsgolint` installed.
22
+ Without the flag, oxlint skips them and reports nothing about them.
23
+
24
+ - `typescript/switch-exhaustiveness-check` refuses a `switch` over a union that leaves a member without a case.
25
+ - `typescript/prefer-readonly` refuses a private member that nothing reassigns and that is not `readonly`.
26
+ - `typescript/no-unnecessary-condition` refuses a condition whose type makes its result always the same, such as `??` on a value that cannot be null, and passes over a constant loop condition.
27
+ - `typescript/no-unnecessary-type-parameters` refuses a type parameter the signature uses only once.
28
+ - `typescript/use-unknown-in-catch-callback-variable` refuses a rejection callback whose parameter is not typed `unknown`.
29
+ - `typescript/no-unsafe-type-assertion` refuses an `as` that narrows a value to a type the compiler cannot prove.
30
+ - `typescript/no-deprecated` refuses a use of a symbol whose declaration carries a `@deprecated` tag, in the repository's own code or in a package's types, and repeats the tag's text.
31
+
32
+ ## Related topics
33
+
34
+ - [The Effect rules](effect-rules.md)
35
+ - [The dependency rules](dependency-rules.md)
36
+ - [Why it is shaped this way](../design.md)
package/docs/design.md CHANGED
@@ -13,7 +13,9 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
13
13
  Node refuses to type-strip a `.ts` plugin under `node_modules`, so the `.ts` source would fail to load from an installed package.
14
14
  - `featureRules` ships compiled as `dist/feature-rules.js` for the same reason, with `effect` left out of the bundle so it resolves the consumer's own copy.
15
15
  dependency-cruiser uses a config's export as it is and never awaits it, so the declaration decodes synchronously, and `quality.json` exempts that one file from the Effect rules.
16
- - `checks-size-budget` writes the head commit's files to a temporary directory and runs oxlint there, with a configuration that sets no plugin and turns every category off, so the consumer's own `.oxlintrc.json`, its ignore files and its other rules never reach the count.
16
+ - `checks-size-budget` writes the head commit's files to a temporary directory and runs oxlint there.
17
+ Its configuration loads the kit's own plugin bundle for the complexity rule and turns every category off.
18
+ The consumer's own `.oxlintrc.json`, its ignore files and its other rules never reach the count.
17
19
  - `checks-size-budget` ratchets against the base of the range rather than a committed baseline such as `oxlint-suppressions.json`.
18
20
  A suppression file stores a count of sites per file and rule, and `max-lines` reports a file once however long it grows, so the count stays at one while the file doubles.
19
21
  The gate sums how far each site runs over its limit instead, which grows with the file.