@avi2dg/checks 0.21.0 → 0.23.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 +66 -40
- package/CONTRIBUTING.md +14 -10
- package/README.md +15 -24
- package/bunfig.toml +1 -1
- package/dist/effect-channel/index.js +122 -0
- package/dist/{index.js → readability/index.js} +8 -120
- package/docs/configs/commit-messages.md +5 -1
- package/docs/configs/dependency-rules.md +5 -2
- package/docs/configs/effect-rules.md +32 -33
- package/docs/configs/native-settings.md +74 -0
- package/docs/configs/typescript-rules.md +4 -0
- package/docs/design.md +26 -27
- package/docs/gates/checks-backtest.md +4 -0
- package/docs/gates/checks-ci-wiring.md +30 -93
- package/docs/gates/checks-comment-gate.md +4 -0
- package/docs/gates/checks-commit-identity.md +23 -27
- package/docs/gates/checks-docs.md +23 -21
- package/docs/gates/checks-flake.md +4 -10
- package/docs/gates/checks-lint-coverage.md +5 -1
- package/docs/gates/checks-lint.md +22 -104
- package/docs/gates/checks-mutation-compare.md +4 -0
- package/docs/gates/checks-quarantine-clock.md +4 -0
- package/docs/gates/checks-repetition.md +27 -41
- package/docs/gates/checks-subsumed-tests.md +4 -0
- package/docs/gates/checks-suppressions-ratchet.md +4 -0
- package/docs/gates/checks-test-layout.md +16 -8
- package/docs/gates/checks-test.md +68 -36
- package/docs/gates/checks-vendor.md +20 -13
- package/oxlintrc.json +1 -1
- package/package.json +10 -21
- package/scripts/ci-wiring.ts +28 -97
- package/scripts/commit-identity.ts +32 -4
- package/scripts/doc-rules.ts +26 -11
- package/scripts/doc-templates.ts +2 -1
- package/scripts/docs.ts +4 -7
- package/scripts/gates.ts +0 -29
- package/scripts/git.ts +24 -1
- package/scripts/lint.ts +15 -34
- package/scripts/range-gate.ts +1 -2
- package/scripts/repetition.ts +51 -40
- package/scripts/shell-command.ts +7 -1
- package/scripts/swc.ts +46 -0
- package/scripts/test-layout.ts +40 -56
- package/scripts/test-skips.ts +180 -0
- package/scripts/test.ts +33 -38
- package/scripts/vendor.ts +55 -11
- package/dist/feature-rules.js +0 -354
- package/docs/configs/quality-file.md +0 -103
- package/docs/gates/checks-feature-owners.md +0 -113
- package/docs/gates/checks-quality.md +0 -111
- package/docs/gates/checks-size-budget.md +0 -107
- package/quality.schema.json +0 -514
- package/scripts/feature-owners.ts +0 -139
- package/scripts/quality-file.ts +0 -353
- package/scripts/quality.ts +0 -363
- package/scripts/size-budget.ts +0 -285
- package/scripts/size-rules.ts +0 -126
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// readability/cognitive-nodes.ts
|
|
2
2
|
var CONTROL_TYPES = ["IfStatement", "ConditionalExpression", "SwitchStatement", "SwitchCase", "TryStatement", "CatchClause"];
|
|
3
3
|
var LOOP_TYPES = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement", "LabeledStatement"];
|
|
4
4
|
var CALL_TYPES = ["LogicalExpression", "BreakStatement", "ContinueStatement", "CallExpression", "NewExpression", "ImportExpression"];
|
|
@@ -7,7 +7,7 @@ var PLAIN_A_TYPES = ["BlockStatement", "ExpressionStatement", "ReturnStatement",
|
|
|
7
7
|
var PLAIN_B_TYPES = ["ClassDeclaration", "ClassExpression", "ClassBody", "MethodDefinition", "TSAbstractMethodDefinition", "PropertyDefinition", "TSAbstractPropertyDefinition", "AccessorProperty", "TSAbstractAccessorProperty", "ObjectExpression", "ArrayExpression"];
|
|
8
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
9
|
|
|
10
|
-
//
|
|
10
|
+
// readability/cognitive-plain.ts
|
|
11
11
|
function isControl(node) {
|
|
12
12
|
return CONTROL_TYPES.includes(node.type);
|
|
13
13
|
}
|
|
@@ -142,7 +142,7 @@ function plainChildrenC(node) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
//
|
|
145
|
+
// readability/cognitive.ts
|
|
146
146
|
function unreachable2(_value) {}
|
|
147
147
|
function scoreList(state, nodes, nesting, parent) {
|
|
148
148
|
for (const node of nodes) {
|
|
@@ -334,7 +334,7 @@ function cognitiveComplexity(root, names) {
|
|
|
334
334
|
return state.recursive ? state.total + 1 : state.total;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
//
|
|
337
|
+
// readability/cognitive-complexity.ts
|
|
338
338
|
var DEFAULT_MAX = 15;
|
|
339
339
|
function maxOf(options) {
|
|
340
340
|
const [first] = options;
|
|
@@ -416,126 +416,14 @@ var rule = {
|
|
|
416
416
|
};
|
|
417
417
|
var cognitive_complexity_default = rule;
|
|
418
418
|
|
|
419
|
-
//
|
|
420
|
-
var EFFECT_SOURCES = new Set(["effect", "effect/Effect"]);
|
|
421
|
-
var INSTEAD = {
|
|
422
|
-
drops: "it drops the failure and the success together, so no caller can tell one from the other. Handle the error by tag, or keep it as a value with Effect.result or Effect.exit",
|
|
423
|
-
swallows: "a Cause is the typed error plus defects plus interruption, so this swallows the bugs and the cancellations along with it. Catch the errors you named, with Effect.catchTag or Effect.catchTags",
|
|
424
|
-
blind: "the handler cannot see what it is recovering from, so every error in the channel collapses into one fallback. Name the errors with Effect.catchTag, or take the error and use it"
|
|
425
|
-
};
|
|
426
|
-
var REFUSED = new Map([
|
|
427
|
-
["ignore", INSTEAD.drops],
|
|
428
|
-
["ignoreCause", INSTEAD.drops],
|
|
429
|
-
["catchCause", INSTEAD.swallows],
|
|
430
|
-
["catchCauseIf", INSTEAD.swallows],
|
|
431
|
-
["catchCauseFilter", INSTEAD.swallows]
|
|
432
|
-
]);
|
|
433
|
-
var blindToTheError = (handler) => {
|
|
434
|
-
if (!handler)
|
|
435
|
-
return false;
|
|
436
|
-
if (handler.type !== "ArrowFunctionExpression" && handler.type !== "FunctionExpression")
|
|
437
|
-
return false;
|
|
438
|
-
return handler.params.every((param) => param.type === "Identifier" && /^_+$/.test(param.name));
|
|
439
|
-
};
|
|
440
|
-
var rule2 = {
|
|
441
|
-
meta: {
|
|
442
|
-
type: "problem",
|
|
443
|
-
docs: { description: "Disallow the combinators that erase Effect's error channel" }
|
|
444
|
-
},
|
|
445
|
-
create(context) {
|
|
446
|
-
const effect = new Set;
|
|
447
|
-
const named = (node) => {
|
|
448
|
-
if (!node || node.type !== "MemberExpression" || node.computed)
|
|
449
|
-
return null;
|
|
450
|
-
if (node.object.type !== "Identifier" || !effect.has(node.object.name))
|
|
451
|
-
return null;
|
|
452
|
-
return node.property.type === "Identifier" ? node.property.name : null;
|
|
453
|
-
};
|
|
454
|
-
const refuse = (node, combinator, instead) => {
|
|
455
|
-
context.report({ node, message: `Effect.${combinator} erases the error channel: ${instead}` });
|
|
456
|
-
};
|
|
457
|
-
return {
|
|
458
|
-
ImportDeclaration(node) {
|
|
459
|
-
if (!EFFECT_SOURCES.has(node.source.value))
|
|
460
|
-
return;
|
|
461
|
-
const module = node.source.value === "effect/Effect";
|
|
462
|
-
for (const specifier of node.specifiers) {
|
|
463
|
-
if (specifier.type === "ImportSpecifier") {
|
|
464
|
-
if (specifier.imported.type === "Identifier" && specifier.imported.name === "Effect")
|
|
465
|
-
effect.add(specifier.local.name);
|
|
466
|
-
} else if (module)
|
|
467
|
-
effect.add(specifier.local.name);
|
|
468
|
-
}
|
|
469
|
-
},
|
|
470
|
-
MemberExpression(node) {
|
|
471
|
-
const combinator = named(node);
|
|
472
|
-
if (combinator === null)
|
|
473
|
-
return;
|
|
474
|
-
const instead = REFUSED.get(combinator);
|
|
475
|
-
if (instead !== undefined)
|
|
476
|
-
refuse(node, combinator, instead);
|
|
477
|
-
},
|
|
478
|
-
CallExpression(node) {
|
|
479
|
-
if (named(node.callee) !== "catch")
|
|
480
|
-
return;
|
|
481
|
-
if (!blindToTheError(node.arguments[node.arguments.length - 1]))
|
|
482
|
-
return;
|
|
483
|
-
refuse(node, "catch", INSTEAD.blind);
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
var no_error_channel_escape_default = rule2;
|
|
489
|
-
|
|
490
|
-
// effect-channel/no-throw.ts
|
|
491
|
-
var rule3 = {
|
|
492
|
-
meta: {
|
|
493
|
-
type: "problem",
|
|
494
|
-
docs: { description: "Disallow throw, which fails outside Effect's error channel" }
|
|
495
|
-
},
|
|
496
|
-
create(context) {
|
|
497
|
-
return {
|
|
498
|
-
ThrowStatement(node) {
|
|
499
|
-
context.report({
|
|
500
|
-
node,
|
|
501
|
-
message: "throw escapes the error channel: no type records the failure, so no caller has to answer for it. Define the failure with Schema.TaggedError and fail with it through Effect.fail, so it stays in E for Effect.catchTag to handle"
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
var no_throw_default = rule3;
|
|
508
|
-
|
|
509
|
-
// effect-channel/no-try-catch.ts
|
|
510
|
-
var rule4 = {
|
|
511
|
-
meta: {
|
|
512
|
-
type: "problem",
|
|
513
|
-
docs: { description: "Disallow a try statement with a catch clause, which recovers outside Effect's error channel" }
|
|
514
|
-
},
|
|
515
|
-
create(context) {
|
|
516
|
-
return {
|
|
517
|
-
CatchClause(node) {
|
|
518
|
-
context.report({
|
|
519
|
-
node,
|
|
520
|
-
message: "catch recovers outside the error channel: it takes whatever was thrown as unknown, bugs included. Wrap the throwing call in Effect.try or Effect.tryPromise, whose catch maps the cause to a Schema.TaggedError, and recover by tag with Effect.catchTag"
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
var no_try_catch_default = rule4;
|
|
527
|
-
|
|
528
|
-
// effect-channel/index.ts
|
|
419
|
+
// readability/index.ts
|
|
529
420
|
var plugin = {
|
|
530
|
-
meta: { name: "
|
|
421
|
+
meta: { name: "readability" },
|
|
531
422
|
rules: {
|
|
532
|
-
"no-error-channel-escape": no_error_channel_escape_default,
|
|
533
|
-
"no-throw": no_throw_default,
|
|
534
|
-
"no-try-catch": no_try_catch_default,
|
|
535
423
|
"cognitive-complexity": cognitive_complexity_default
|
|
536
424
|
}
|
|
537
425
|
};
|
|
538
|
-
var
|
|
426
|
+
var readability_default = plugin;
|
|
539
427
|
export {
|
|
540
|
-
|
|
428
|
+
readability_default as default
|
|
541
429
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: reference
|
|
3
|
+
audience: consumers
|
|
4
|
+
---
|
|
1
5
|
# The commit message lint
|
|
2
6
|
|
|
3
7
|
The shared commitlint config holds each pull request title to conventional commits, and a reader looks it up to wire the lint into a repository's CI.
|
|
@@ -10,7 +14,7 @@ It arrives with the kit, since `@commitlint/cli` and `@commitlint/config-convent
|
|
|
10
14
|
## Workflow
|
|
11
15
|
|
|
12
16
|
The lint runs in CI on pull requests, because `jj` never fires a git hook.
|
|
13
|
-
|
|
17
|
+
Each repository owns `.github/workflows/commitlint.yml` and runs the installed `commitlint` binary in a pull request step.
|
|
14
18
|
The workflow lints with the installed kit's `commitlint.config.js`, so every repository holds titles to the same rules.
|
|
15
19
|
|
|
16
20
|
## What it lints
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: reference
|
|
3
|
+
audience: consumers
|
|
4
|
+
---
|
|
1
5
|
# The dependency rules
|
|
2
6
|
|
|
3
7
|
The shared dependency-cruiser base holds a repository's imports to a set of rules every repository shares, and a reader looks it up to add a boundary of its own.
|
|
@@ -35,7 +39,7 @@ module.exports = {
|
|
|
35
39
|
|
|
36
40
|
A rule that restates a base name overrides it field by field.
|
|
37
41
|
That is how an entry point stops being an orphan: redeclare `no-orphans` with the entry added to its `pathNot`.
|
|
38
|
-
A repository
|
|
42
|
+
A repository with an import boundary writes its rule directly under `forbidden`.
|
|
39
43
|
|
|
40
44
|
## Running it
|
|
41
45
|
|
|
@@ -59,5 +63,4 @@ jobs:
|
|
|
59
63
|
|
|
60
64
|
## Related topics
|
|
61
65
|
|
|
62
|
-
- [checks-feature-owners](../gates/checks-feature-owners.md)
|
|
63
66
|
- [Why it is shaped this way](../design.md)
|
|
@@ -1,49 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: reference
|
|
3
|
+
audience: consumers
|
|
4
|
+
---
|
|
1
5
|
# The Effect rules
|
|
2
6
|
|
|
3
|
-
The oxlint base
|
|
7
|
+
The oxlint base and Effect language service check paths a repository writes with Effect.
|
|
4
8
|
|
|
5
|
-
##
|
|
9
|
+
## Oxlint override
|
|
6
10
|
|
|
7
|
-
The
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Two more rules ship off, because a repository writes only some of its paths in Effect, and code a host loads without `node_modules`, such as a hook bundle or this oxlint plugin, cannot import it:
|
|
11
|
-
|
|
12
|
-
- `effect-channel/no-throw` refuses a `throw` statement.
|
|
13
|
-
- `effect-channel/no-try-catch` refuses a `try` statement with a `catch` clause, and `try`/`finally` stays allowed.
|
|
14
|
-
|
|
15
|
-
Each refusal says what to write instead: a `Schema.TaggedError` failed through `Effect.fail`, a throwing call wrapped in `Effect.try` or `Effect.tryPromise`, and recovery by tag with `Effect.catchTag`.
|
|
16
|
-
|
|
17
|
-
## Effect paths
|
|
18
|
-
|
|
19
|
-
A repository turns the rules on for the paths it writes in Effect by declaring those paths in `quality.json` and extending the fragments [checks-quality](../gates/checks-quality.md) generates:
|
|
11
|
+
The shared `oxlintrc.json` loads `effect-channel/no-error-channel-escape` across the tree.
|
|
12
|
+
The repo's `.oxlintrc.json` owns its Effect paths and exemptions in an override:
|
|
20
13
|
|
|
21
14
|
```json
|
|
22
|
-
|
|
23
|
-
"
|
|
15
|
+
{
|
|
16
|
+
"extends": ["./node_modules/@avi2dg/checks/oxlintrc.json"],
|
|
17
|
+
"plugins": ["typescript", "oxc", "eslint", "import"],
|
|
18
|
+
"overrides": [{
|
|
19
|
+
"files": ["src/**/*.ts"],
|
|
20
|
+
"excludeFiles": ["src/host/*.ts"],
|
|
21
|
+
"plugins": ["typescript", "oxc", "eslint", "import", "node", "promise", "unicorn"],
|
|
22
|
+
"rules": {
|
|
23
|
+
"node/no-sync": "error",
|
|
24
|
+
"oxc/no-async-await": "error",
|
|
25
|
+
"promise/avoid-new": "error",
|
|
26
|
+
"unicorn/no-process-exit": "error",
|
|
27
|
+
"effect-channel/no-throw": "error",
|
|
28
|
+
"effect-channel/no-try-catch": "error"
|
|
29
|
+
}
|
|
30
|
+
}]
|
|
24
31
|
}
|
|
25
32
|
```
|
|
26
33
|
|
|
27
|
-
The
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
oxlint resolves `files` against the directory of the config that holds the override, so a config passed with `-c` from outside the repository matches nothing and reports nothing.
|
|
32
|
-
|
|
33
|
-
`unicorn/no-process-exit` passes over any file that opens with a shebang.
|
|
34
|
-
A repository whose bins open with one bans `process.exit` itself with `no-restricted-properties` in its own `.oxlintrc.json`, as the kit's own repository does.
|
|
35
|
-
The preset leaves that rule out because a repository's own `no-restricted-properties` list for the same files would replace it, or be replaced by it.
|
|
36
|
-
Sites standing when the declaration lands go in oxlint's own baseline, `oxlint --suppress-all`, so their count can only fall, as [checks-suppressions-ratchet](../gates/checks-suppressions-ratchet.md) holds.
|
|
34
|
+
The kit ships the rule block in `presets/effect.oxlint.json` for copying into the override.
|
|
35
|
+
Each config in an oxlint `extends` chain sets `plugins` explicitly, because an omitted list enables defaults across the chain.
|
|
36
|
+
`unicorn/no-process-exit` does not check a shebang script, so a bin can use `eslint/no-restricted-properties` for `process.exit`.
|
|
37
37
|
|
|
38
38
|
## Language service
|
|
39
39
|
|
|
40
|
-
The
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
The repository's `tsconfig.json` holds its Effect override under `compilerOptions.plugins`.
|
|
41
|
+
The override includes the same source paths and excludes the same exempt paths as oxlint.
|
|
42
|
+
The kit ships severity values in `presets/effect.language-service.json` for the override's `options`.
|
|
43
|
+
The kit's `tsconfig.effect.json` keeps the shared language service diagnostics.
|
|
43
44
|
|
|
44
45
|
## Related topics
|
|
45
46
|
|
|
46
47
|
- [The TypeScript rules](typescript-rules.md)
|
|
47
|
-
- [
|
|
48
|
-
- [The quality file](quality-file.md)
|
|
49
|
-
- [Why it is shaped this way](../design.md)
|
|
48
|
+
- [Native settings](native-settings.md)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: reference
|
|
3
|
+
audience: consumers
|
|
4
|
+
---
|
|
5
|
+
# Native settings
|
|
6
|
+
|
|
7
|
+
A consuming repository puts each setting in the file its tool reads.
|
|
8
|
+
|
|
9
|
+
## Settings by file
|
|
10
|
+
|
|
11
|
+
| File | Setting | Reader |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `.github/workflows/*.yml` | Pull request commands, scheduled commands, runner labels | GitHub Actions and `checks-ci-wiring` |
|
|
14
|
+
| `.oxlintrc.json` | Effect paths, exemptions, file size, function size, statements, cognitive complexity and depth | oxlint |
|
|
15
|
+
| `oxlint-suppressions.json` | The existing violations oxlint suppresses, per file and rule | oxlint and `checks-suppressions-ratchet` |
|
|
16
|
+
| `.jscpd.json` | The `path` and `ignore` globs of the files repetition is measured in | jscpd and `checks-repetition` |
|
|
17
|
+
| `tsconfig.json` | Effect language service scope and severity | TypeScript and Effect language service |
|
|
18
|
+
| `package.json` | `scripts` with the `checks-vendor` arguments in `prepare`, `author` and `contributors` | Bun, `checks-commit-identity` and `checks-vendor` |
|
|
19
|
+
| `bunfig.toml` | Test discovery and quarantine exclusion | Bun and `checks-test-layout` |
|
|
20
|
+
| `.dependency-cruiser.cjs` | Import rules | dependency-cruiser |
|
|
21
|
+
| `stryker.conf.mjs` | Mutation settings | Stryker |
|
|
22
|
+
| Each page under `docs/` | Diátaxis mode in `kind` front matter, and `audience: consumers` on a page that speaks to a consuming repository | `checks-docs` |
|
|
23
|
+
|
|
24
|
+
Every page under `docs/` names its mode in `kind` front matter, whatever directory holds it.
|
|
25
|
+
A page whose front matter sets `audience: consumers` names commands a consuming repository runs, so `checks-docs` does not hold them to this `package.json`.
|
|
26
|
+
Every other living doc names commands this repository runs, and `checks-docs` holds each one to its `package.json`.
|
|
27
|
+
|
|
28
|
+
## Consumer migration
|
|
29
|
+
|
|
30
|
+
The following table maps the former fields to their owners.
|
|
31
|
+
|
|
32
|
+
| Former field | New owner |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `defaultBranch` | Git's `refs/remotes/origin/HEAD`, or the pull request base or event repository in CI |
|
|
35
|
+
| `runsOn`, `gates.ci`, `gates.scheduled` | `runs-on` and `run` steps in `.github/workflows/*.yml` |
|
|
36
|
+
| `gates.lint` | `checks-lint` runs every kit gate |
|
|
37
|
+
| `commitIdentity.authors` | `author` and `contributors` in `package.json` |
|
|
38
|
+
| `sources.production` | `path` and `ignore` in `.jscpd.json` |
|
|
39
|
+
| `size.production`, `size.tests` | File overrides and native size rules in `.oxlintrc.json` |
|
|
40
|
+
| `sources.effect.paths`, `sources.effect.exempt` | Overrides in `.oxlintrc.json` and `tsconfig.json` |
|
|
41
|
+
| `sources.libraries` | `checks-vendor` arguments in the `prepare` script of `package.json` |
|
|
42
|
+
| `docs.pages` | `kind` front matter on each page |
|
|
43
|
+
| `docs.forConsumers` | `audience: consumers` front matter on each page |
|
|
44
|
+
| `features`, `changeSignal`, `agentRules` | No active declarations used these fields |
|
|
45
|
+
|
|
46
|
+
The kit has no general configuration manifest or generated workflow.
|
|
47
|
+
`checks-ci-wiring` requires title lint on opened and synchronized pull requests.
|
|
48
|
+
It also requires lint, build, typecheck and test for each of those scripts that `package.json` defines, and a clean git diff after a build.
|
|
49
|
+
`checks-repetition` runs jscpd with the repository's own `.jscpd.json`, so its `path` and `ignore` globs decide which files are measured.
|
|
50
|
+
|
|
51
|
+
## Size limits
|
|
52
|
+
|
|
53
|
+
The size rules are plain oxlint rules at `error` in `.oxlintrc.json`, and `bun run lint` enforces them on the whole tree.
|
|
54
|
+
A repository records its existing violations with `oxlint --suppress-all`, which writes them to `oxlint-suppressions.json`.
|
|
55
|
+
`checks-suppressions-ratchet` refuses any count in that file that rises, so the recorded debt only falls.
|
|
56
|
+
The kit's own `.oxlintrc.json` sets these limits for each size override, and a repository may copy them:
|
|
57
|
+
|
|
58
|
+
<!-- generated size-limits: bun run build writes it from .oxlintrc.json, SIZE_RULES in scripts/size-rules.ts and scripts/doc-blocks.ts -->
|
|
59
|
+
|
|
60
|
+
| Limits | oxlint rule | `effect-channel/**/*.ts`, `readability/**/*.ts`, `scripts/**/*.ts` | `tests/**/*.ts` |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| The most lines a file may hold, blank and comment lines counted | `max-lines` | 400 | 600 |
|
|
63
|
+
| The most lines a function may span, blank and comment lines counted | `max-lines-per-function` | 100 | off |
|
|
64
|
+
| The most statements a function may hold | `max-statements` | 30 | 50 |
|
|
65
|
+
| The highest cognitive complexity a function may reach, a switch counted once | `readability/cognitive-complexity` | 15 | 15 |
|
|
66
|
+
| The deepest a block may nest inside a function | `max-depth` | 4 | 4 |
|
|
67
|
+
|
|
68
|
+
<!-- end generated size-limits -->
|
|
69
|
+
|
|
70
|
+
## Related topics
|
|
71
|
+
|
|
72
|
+
- [The Effect rules](effect-rules.md)
|
|
73
|
+
- [The CI wiring check](../gates/checks-ci-wiring.md)
|
|
74
|
+
- [The suppressions ratchet](../gates/checks-suppressions-ratchet.md)
|
package/docs/design.md
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: explanation
|
|
3
|
+
---
|
|
1
4
|
# Why it is shaped this way
|
|
2
5
|
|
|
3
6
|
Each entry below is a choice in the kit's shape and the constraint that forced it.
|
|
4
7
|
|
|
5
8
|
- Every config in an oxlint `extends` chain brings its own `plugins`, and one that sets none brings oxlint's default plugins, whose category rules the base's `categories` then turn on across the tree.
|
|
6
9
|
`rules`, `categories` and `jsPlugins` inherit as expected.
|
|
7
|
-
That is why the consumer
|
|
10
|
+
That is why the consumer config and each override restate `plugins`.
|
|
8
11
|
- `node_modules/` is excluded through the consumer's `.gitignore`, not `ignorePatterns`: oxlint still walks the installed package when only `ignorePatterns` names it.
|
|
9
12
|
- `files` in package.json is the published surface: `tests/`, `AGENTS.md` and the `.ts` plugin source never reach an install.
|
|
10
13
|
npm adds `package.json`, `README` and `LICENSE` to the tarball whatever `files` says.
|
|
11
14
|
`bun pm pack` builds the same tarball the registry serves, which is what the packed-tarball consumer e2e test installs.
|
|
12
|
-
-
|
|
15
|
+
- Each plugin ships compiled under `dist/`, built with `bun build <name>/index.ts --outdir dist/<name> --target node --format esm`.
|
|
13
16
|
Node refuses to type-strip a `.ts` plugin under `node_modules`, so the `.ts` source would fail to load from an installed package.
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- `checks-size-budget` ratchets against the base of the range rather than a committed baseline such as `oxlint-suppressions.json`.
|
|
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.
|
|
21
|
-
The gate sums how far each site runs over its limit instead, which grows with the file.
|
|
22
|
-
The base commit already holds that sum, so nothing is generated, committed or pruned.
|
|
23
|
-
- `checks-repetition` writes the production files of both ends of the range to temporary directories and runs jscpd in each, so the consumer's `.jscpd.json` and ignore files never reach the count.
|
|
17
|
+
- The size rules are plain oxlint rules at `error` in `.oxlintrc.json`, and `bun run lint` enforces them on the whole tree.
|
|
18
|
+
A repository records its existing violations with `oxlint --suppress-all`, and `checks-suppressions-ratchet` refuses any count that rises.
|
|
19
|
+
The kit runs no size script of its own, since restating how oxlint reads its config and compares sites left corners the native rules never had.
|
|
20
|
+
The suppression count lets a file or function already over its limit grow without a new site, which was accepted in exchange for dropping that script, and each repository is to work its counts down to zero.
|
|
21
|
+
- `checks-repetition` writes both ends of the range to temporary directories and runs jscpd in each with the head's `.jscpd.json`, so its `path` and `ignore` globs decide the files at both ends.
|
|
24
22
|
It compares each file's count of repeated lines rather than using jscpd's `--baseline-from-ref`.
|
|
25
23
|
That flag reports a repeated block as new once its text changes, so a change that shortens a grandfathered block would fail.
|
|
26
24
|
- `dist/` is committed.
|
|
27
25
|
No `prepack` or `prepublishOnly` builds it, so a publish ships whatever bundle the publishing worktree holds.
|
|
28
26
|
Rebuild it after pulling with `bun run build`.
|
|
29
|
-
`bun run build` also emits `
|
|
27
|
+
`bun run build` also emits `templates/` and `CHANGELOG.md`, which are committed the same way.
|
|
30
28
|
CI runs `git diff --exit-code` over the whole tree after the build, because a test that compares a generated file with its source passes on the copy the build just rewrote.
|
|
31
29
|
- `CHANGELOG.md` is generated, so a release commit carries it and the tarball ships it.
|
|
32
30
|
The commit that bumps package.json `version` closes its release, and the `v*` tag goes on that commit, so commits merged after it wait for the next release.
|
|
@@ -36,12 +34,13 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
36
34
|
A section keeps the date it was written with, since the squash merge that lands the release commit may fall on another day.
|
|
37
35
|
Entries come from commit subjects, the squash-merged pull request titles commitlint holds to the conventional format.
|
|
38
36
|
The bodies are the branch's own messages, which nothing lints.
|
|
39
|
-
The
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
The changelog still arrives in the release commit's pull request, not from a workflow that pushes.
|
|
38
|
+
The release path writes to the repository only through the `github-release` job's `contents: write`, which creates or updates the GitHub release from the tag's `CHANGELOG.md` section.
|
|
39
|
+
- Workflow YAML owns CI execution, and the kit owns the required commands, but a repository is held only to the commands its own `package.json` can run.
|
|
40
|
+
`checks-ci-wiring` requires `bun run` with each of `lint`, `build`, `typecheck` and `test` that `package.json` defines as a script, `git diff --exit-code` when a `build` script exists, and commitlint always.
|
|
41
|
+
The target branch comes from git's own record in `refs/remotes/origin/HEAD`, or in CI from the pull request base or the default branch GitHub's event names, and never from a guess at the workflow files, and `checks-lint` starts its local range from the same branch.
|
|
42
|
+
Oxlint and TypeScript read their own overrides directly, so the consumer can change a rule where its tool reads it.
|
|
43
|
+
The Effect scopes occur in two native files, and the installed consumer test checks both independently.
|
|
45
44
|
- The base parses with swc because typescript 7, which is tsgo, has no compiler API for dependency-cruiser to use.
|
|
46
45
|
Without `@swc/core` installed the cruise silently skips every `.ts` file, so this repo's test asserts its own TypeScript is cruised.
|
|
47
46
|
- `bunfig.toml` has no `extends` and no include: bun ignores an unknown top-level key in silence, so a preset cannot be inherited and the consumer's copy is compared key by key against the installed one instead.
|
|
@@ -52,11 +51,11 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
52
51
|
`@effect/platform-node-shared` is a direct dependency at the same exact version only to pin it: `@effect/platform-bun` asks for it with a `^` range, and a newer rc peers on a newer `effect` than consumers install, so all three move together.
|
|
53
52
|
- Each runnable script ships a `checks-` bin entry, so consumer `package.json` scripts call the short name, which the package manager puts on `PATH` only there.
|
|
54
53
|
A shell runs it through `bun run`, which never falls back to the registry the way `bunx` does.
|
|
55
|
-
The `.ts` checks keep a `bun` shebang, which needs no build step and no `dist/` entry, unlike the oxlint
|
|
54
|
+
The `.ts` checks keep a `bun` shebang, which needs no build step and no `dist/` entry, unlike the oxlint plugins that node loads.
|
|
56
55
|
- `checks-lint` runs each gate as its own bin in a child process rather than importing it, so a gate behaves the same called alone or through the entry point, and `lint-coverage.sh` stays a shell script.
|
|
57
56
|
The gates run one at a time with their output passed straight through, so each report reads whole and in the table's order.
|
|
58
|
-
-
|
|
59
|
-
|
|
57
|
+
- `checks-lint` determines applicable gates from tracked files instead of accepting a repository selection.
|
|
58
|
+
A TypeScript gate starts running as soon as TypeScript source is tracked.
|
|
60
59
|
- `checks-test` runs bun itself rather than reading a report some other run left: a skip taken only on CI is visible only in CI's own run, and an earlier run's report may be stale or narrowed.
|
|
61
60
|
It reads the JUnit report bun writes to a temporary directory, since bun has no other per-test output meant for a program.
|
|
62
61
|
- `checks-ci-wiring` runs inside `lint`, not in a workflow of its own: deleting the step that runs a check is the violation it catches, so the local `lint` is where it has to fail.
|
|
@@ -75,9 +74,9 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
75
74
|
That is the entry-point and layer recipe under Boundaries in [The dependency rules](configs/dependency-rules.md).
|
|
76
75
|
- The templates in `templates/` are rendered from `scripts/doc-templates.ts`, the spec `checks-docs` reads.
|
|
77
76
|
A template written by hand beside the check agrees with it only until someone edits one of them.
|
|
78
|
-
- A page's Diátaxis mode
|
|
79
|
-
|
|
80
|
-
- `checks-docs` holds a doc file to its template when a change touches it, the way `checks-
|
|
77
|
+
- A page's Diátaxis mode comes from `kind` front matter on the page, whatever directory holds it.
|
|
78
|
+
The repository makes the judgment beside the page, and the check holds it to that template.
|
|
79
|
+
- `checks-docs` holds a doc file to its template when a change touches it, the way `checks-comment-gate` judges the comments a change adds.
|
|
81
80
|
A repository adopts the templates as its files change, and an untouched file is listed as advisory rather than failing a change that never read it.
|
|
82
81
|
- A task heading is verb first, and review holds it there rather than the check.
|
|
83
82
|
No word list tells `Test layout` from `Test the layout`, and a check that passes the noun is worse than none.
|
|
@@ -95,8 +94,8 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
95
94
|
A reference goes stale when the code it names moves far more often than when its own line is edited, so a gate on edited lines alone would miss the usual break.
|
|
96
95
|
- A path under a top directory the repository lacks names a file in another repository, such as a consumer's, and no program tells that from a typo.
|
|
97
96
|
A directory the range deletes still counts as this repository's, so a path under it reads as stale rather than foreign.
|
|
98
|
-
- The command check passes over
|
|
99
|
-
|
|
97
|
+
- The command check passes over a page whose front matter sets `audience: consumers`.
|
|
98
|
+
Such a page speaks to a consuming repository, whose scripts are not this one's.
|
|
100
99
|
|
|
101
100
|
## Related topics
|
|
102
101
|
|