@starci/eslint-canon-be 1.8.0 → 2.0.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 +95 -5
- package/README.md +41 -52
- package/async-discipline.mjs +2 -2
- package/authorization.mjs +38 -88
- package/builders.mjs +305 -0
- package/comments.mjs +19 -34
- package/config-owner.mjs +127 -125
- package/connections.mjs +254 -0
- package/cqrs.mjs +426 -135
- package/data-access.mjs +320 -391
- package/default-deny.mjs +14 -10
- package/e2e-flow.mjs +208 -180
- package/error-handling.mjs +254 -128
- package/idempotency.mjs +98 -67
- package/index.mjs +32 -9
- package/infra-owner.mjs +96 -0
- package/injection.mjs +602 -0
- package/input-bounds.mjs +166 -66
- package/lib/config.mjs +70 -33
- package/lib/db-writes.mjs +58 -0
- package/lib/declared.mjs +154 -0
- package/lib/hfs.mjs +61 -0
- package/lib/import-source.mjs +104 -0
- package/lib/persistence.mjs +160 -0
- package/lib/ports.mjs +138 -0
- package/lib/transport-slots.mjs +14 -0
- package/lib/types.mjs +133 -0
- package/lib/unit-spec.mjs +33 -0
- package/log-safety.mjs +94 -26
- package/module-layering.mjs +180 -297
- package/module-shape.mjs +296 -59
- package/naming.mjs +44 -12
- package/observability.mjs +57 -91
- package/operations.mjs +201 -0
- package/package.json +10 -7
- package/query-safety.mjs +189 -223
- package/resilience.mjs +126 -31
- package/runtime/engine/plain-object.mjs +5 -0
- package/runtime/engine/runtime-root.mjs +32 -0
- package/runtime/engine/yaml.mjs +161 -0
- package/runtime/knowledge/hfs/slots.yaml +1111 -0
- package/runtime/scripts/lib/git.mjs +53 -0
- package/runtime/scripts/lib/glob.mjs +23 -0
- package/runtime/scripts/lib/hfs-allows.mjs +57 -0
- package/runtime/scripts/lib/hfs-slots.mjs +729 -0
- package/runtime/scripts/lib/hfs-view.mjs +68 -0
- package/runtime/scripts/lib/language.mjs +107 -0
- package/runtime/scripts/lib/path-key.mjs +17 -0
- package/runtime/scripts/lib/recorded-lines.mjs +27 -0
- package/schema-authority.mjs +273 -91
- package/size-budget.mjs +19 -61
- package/spec-no-skip.mjs +84 -0
- package/spec-quality.mjs +76 -35
- package/suppression.mjs +34 -16
- package/temporal.mjs +101 -36
- package/test-world.mjs +204 -0
- package/testing.mjs +99 -251
- package/transport.mjs +433 -142
- package/type-safety.mjs +50 -278
- package/unit-spec.mjs +452 -0
- package/user-copy.mjs +56 -37
- package/lib/slots.mjs +0 -83
package/type-safety.mjs
CHANGED
|
@@ -1,222 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The rules that hold `type-
|
|
2
|
+
* The rules that hold the type-safety law (catalog R72 `BE_TYPE_ESCAPE` and the type-shape rows).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* this canon
|
|
4
|
+
* WHAT THE FACTORY BORROWS, SO THIS FILE DOES NOT REIMPLEMENT IT. `starciBeConfig` turns on, at `error`, in every file
|
|
5
|
+
* of the repository and with no lane of exemption (a spec, a fixture and an e2e setup file get the same law):
|
|
6
|
+
* `@typescript-eslint/consistent-type-assertions` with `assertionStyle: "never"` (no `x as T`, no `<T>x`, hence no
|
|
7
|
+
* `as never`, no `as unknown as T`, no cast out of an `unknown` value; only `as const` stays),
|
|
8
|
+
* `@typescript-eslint/no-non-null-assertion` (no `x!`) and `@typescript-eslint/no-explicit-any`. One obligation, one
|
|
9
|
+
* rule: this canon writes no second copy of any of them, so the four house rules that once restated them
|
|
10
|
+
* (`no-never-cast`, `no-non-null-assertion`, `no-double-cast`, `no-unguarded-unknown-cast`) are deleted.
|
|
10
11
|
*
|
|
11
|
-
* What is house-written is what the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (TYPE-3, rule 4, extended; see the export below for its `recommended` level and the measurement
|
|
15
|
-
* that set it), the const enum
|
|
16
|
-
* (TYPE-4, rule 5), the unguarded cast off an `unknown` value (TYPE-1's second half, rule 2), and
|
|
17
|
-
* a way of switching the
|
|
18
|
-
* compiler off -- or of leaving it turned further down than the law asks -- that looks locally
|
|
19
|
-
* reasonable, which is exactly the class a rule is for.
|
|
12
|
+
* What is house-written is what the borrowed set does not cover: `Function` as a type and `eval` (R72), the inline
|
|
13
|
+
* parameter type and the inline object type in every other position (TYPE-3), the const enum (TYPE-4) and the
|
|
14
|
+
* handler return type (R75).
|
|
20
15
|
*
|
|
21
|
-
* TYPE-5 (
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* TYPE-5 (the boolean-flag product) is NOT among them, and that absence was tested rather than assumed: a rule was
|
|
17
|
+
* written, measured against the reference backend and withdrawn because both of its real firings were wrong. It stays
|
|
18
|
+
* a reader's job, exactly as the law's own `Layer held` table says.
|
|
24
19
|
*/
|
|
25
20
|
|
|
26
|
-
import { normalizePath } from "./lib/path.mjs"
|
|
27
|
-
|
|
28
|
-
/** The spec family and the test tree may build a deliberately wrong value on purpose. */
|
|
29
|
-
const isTestFile = (filename) => {
|
|
30
|
-
const file = normalizePath(filename)
|
|
31
|
-
return /\.(?:spec|test|e2e-spec)\.ts$/.test(file) || file.includes("/src/tests/")
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
/** A parameter property (`private readonly x: T`) wraps the parameter it declares. */
|
|
35
22
|
const unwrapParam = (param) => (param.type === "TSParameterProperty" ? param.parameter : param)
|
|
36
23
|
|
|
37
|
-
// -- TYPE-2 ----------------------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
/** A cast through `unknown` is the compiler being overruled twice. */
|
|
40
|
-
export const noDoubleCast = {
|
|
41
|
-
meta: {
|
|
42
|
-
type: "problem",
|
|
43
|
-
docs: { description: "No `x as unknown as T` outside the test lanes." },
|
|
44
|
-
schema: [],
|
|
45
|
-
messages: {
|
|
46
|
-
doubleCast:
|
|
47
|
-
"`as unknown as` is the compiler saying these types do not overlap, and being overruled twice. It is worse than `any` in one specific way: the result CLAIMS to be the target type, so everything downstream trusts it completely and the failure surfaces far from this line. Fix the type, or narrow with a guard that actually checks.",
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
create(context) {
|
|
51
|
-
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
52
|
-
return {
|
|
53
|
-
TSAsExpression(node) {
|
|
54
|
-
// the OUTER cast of the pair: its operand is itself a cast, to `unknown`
|
|
55
|
-
const inner = node.expression
|
|
56
|
-
if (!inner || inner.type !== "TSAsExpression") return
|
|
57
|
-
const innerType = inner.typeAnnotation
|
|
58
|
-
if (!innerType || innerType.type !== "TSUnknownKeyword") return
|
|
59
|
-
context.report({ node, messageId: "doubleCast" })
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// -- TYPE-1, second half (rule 2) -------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
/** typeof/instanceof/in/null-equality/Array.isArray are the sanctioned ways to narrow an `unknown`. */
|
|
68
|
-
const isNullOrUndefinedLiteral = (node) =>
|
|
69
|
-
(node.type === "Literal" && node.value === null) || (node.type === "Identifier" && node.name === "undefined")
|
|
70
|
-
|
|
71
|
-
/** The nearest enclosing function, so narrowing found in one function never covers another. */
|
|
72
|
-
const enclosingFunction = (node) => {
|
|
73
|
-
let current = node.parent
|
|
74
|
-
while (current) {
|
|
75
|
-
if (
|
|
76
|
-
current.type === "FunctionDeclaration"
|
|
77
|
-
|| current.type === "FunctionExpression"
|
|
78
|
-
|| current.type === "ArrowFunctionExpression"
|
|
79
|
-
) {
|
|
80
|
-
return current
|
|
81
|
-
}
|
|
82
|
-
current = current.parent
|
|
83
|
-
}
|
|
84
|
-
return null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Whether a parameter or variable is explicitly declared `: unknown` at the point it enters. */
|
|
88
|
-
const isUnknownAnnotation = (typeAnnotation) =>
|
|
89
|
-
!!typeAnnotation && !!typeAnnotation.typeAnnotation && typeAnnotation.typeAnnotation.type === "TSUnknownKeyword"
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* A value declared `unknown` is cast straight to a concrete type with no visible check first.
|
|
93
|
-
*
|
|
94
|
-
* `unknown` refuses every operation until narrowed -- except a cast, which TypeScript allows
|
|
95
|
-
* FREELY from `unknown` to anything, no guard required. `no-double-cast` catches the pair that
|
|
96
|
-
* launders a wrong type through `unknown`; it does not catch a single cast that starts already
|
|
97
|
-
* `unknown` and asserts straight past the type system with nothing behind it. That gap is TYPE-1's
|
|
98
|
-
* second half: the law asks for "unknown, narrowed exactly once, visibly", and a bare cast narrows
|
|
99
|
-
* nothing -- it only claims to.
|
|
100
|
-
*
|
|
101
|
-
* Scoped to identifiers explicitly annotated `unknown` (a parameter or a `const x: unknown`), and
|
|
102
|
-
* to narrowing found anywhere earlier in the SAME enclosing function -- not a full control-flow
|
|
103
|
-
* proof, because this plugin carries no type information to build one. The forms it accepts as
|
|
104
|
-
* narrowing are `typeof`, `instanceof`, `in`, a null/undefined equality check, and -- deliberately
|
|
105
|
-
* loose -- passing the value into ANY function call, because the law names "a predicate" beside
|
|
106
|
-
* `typeof`/`instanceof` and a custom guard is exactly that predicate; this rule cannot read what
|
|
107
|
-
* is inside the call, so it trusts that a call taking the value is a check on it. That looseness
|
|
108
|
-
* trades missed gaps for avoided false positives on purpose. The test lanes are exempt for the
|
|
109
|
-
* same reason `no-double-cast` exempts them: a harness helper that hands a caller-supplied
|
|
110
|
-
* generic type back unchecked (`nextMessage<TPayload>`) is deliberate, not a gap. Measured against
|
|
111
|
-
* the reference backend, this rule still stays at `warn` -- the debt is real and small enough to
|
|
112
|
-
* burn down, not yet zero.
|
|
113
|
-
*/
|
|
114
|
-
export const noUnguardedUnknownCast = {
|
|
115
|
-
meta: {
|
|
116
|
-
type: "problem",
|
|
117
|
-
docs: {
|
|
118
|
-
description:
|
|
119
|
-
"A value declared `unknown` is narrowed by a visible check before it is cast to a concrete type (TYPE-1, law 2).",
|
|
120
|
-
},
|
|
121
|
-
schema: [],
|
|
122
|
-
messages: {
|
|
123
|
-
unguarded:
|
|
124
|
-
"`{{name}}` entered as `unknown` and is cast straight to `{{target}}` with no `typeof`, `instanceof`, `in` or null check on `{{name}}` anywhere earlier in this function. A cast out of `unknown` is unrestricted by design -- TypeScript will not stop you -- so the check has to be the thing that stops you. Add one before the cast: `typeof {{name}} === \"...\"`, `{{name}} instanceof ...`, or `{{name}} !== null`.",
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
create(context) {
|
|
128
|
-
// the harness lane builds generic pass-through helpers on purpose (`nextMessage<TPayload>`
|
|
129
|
-
// handing back whatever the event bus produced as the caller's declared type) -- the same
|
|
130
|
-
// "deliberately unchecked on purpose" property TYPE-2 already carves the test lanes out for
|
|
131
|
-
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
132
|
-
const sourceCode = context.sourceCode || context.getSourceCode()
|
|
133
|
-
const trackedByScope = new Map()
|
|
134
|
-
const narrowedByScope = new Map()
|
|
135
|
-
const scopeKey = (node) => enclosingFunction(node) || "module"
|
|
136
|
-
|
|
137
|
-
const track = (name, scope) => {
|
|
138
|
-
if (!trackedByScope.has(scope)) trackedByScope.set(scope, new Set())
|
|
139
|
-
trackedByScope.get(scope).add(name)
|
|
140
|
-
}
|
|
141
|
-
const markNarrowed = (name, scope) => {
|
|
142
|
-
if (!narrowedByScope.has(scope)) narrowedByScope.set(scope, new Set())
|
|
143
|
-
narrowedByScope.get(scope).add(name)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const checkParams = (fnNode) => {
|
|
147
|
-
for (const raw of fnNode.params) {
|
|
148
|
-
const param = unwrapParam(raw)
|
|
149
|
-
if (param.type === "Identifier" && isUnknownAnnotation(param.typeAnnotation)) {
|
|
150
|
-
track(param.name, fnNode)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
FunctionDeclaration(node) {
|
|
157
|
-
checkParams(node)
|
|
158
|
-
},
|
|
159
|
-
FunctionExpression(node) {
|
|
160
|
-
checkParams(node)
|
|
161
|
-
},
|
|
162
|
-
ArrowFunctionExpression(node) {
|
|
163
|
-
checkParams(node)
|
|
164
|
-
},
|
|
165
|
-
VariableDeclarator(node) {
|
|
166
|
-
if (node.id.type === "Identifier" && isUnknownAnnotation(node.id.typeAnnotation)) {
|
|
167
|
-
track(node.id.name, scopeKey(node))
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
UnaryExpression(node) {
|
|
171
|
-
if (node.operator !== "typeof") return
|
|
172
|
-
if (node.argument.type !== "Identifier") return
|
|
173
|
-
markNarrowed(node.argument.name, scopeKey(node))
|
|
174
|
-
},
|
|
175
|
-
BinaryExpression(node) {
|
|
176
|
-
if (node.operator === "instanceof") {
|
|
177
|
-
if (node.left.type === "Identifier") markNarrowed(node.left.name, scopeKey(node))
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
if (node.operator === "in") {
|
|
181
|
-
if (node.right.type === "Identifier") markNarrowed(node.right.name, scopeKey(node))
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
if (!["===", "!==", "==", "!="].includes(node.operator)) return
|
|
185
|
-
if (node.left.type === "Identifier" && isNullOrUndefinedLiteral(node.right)) {
|
|
186
|
-
markNarrowed(node.left.name, scopeKey(node))
|
|
187
|
-
}
|
|
188
|
-
if (node.right.type === "Identifier" && isNullOrUndefinedLiteral(node.left)) {
|
|
189
|
-
markNarrowed(node.right.name, scopeKey(node))
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
CallExpression(node) {
|
|
193
|
-
// any predicate call that TAKES the value narrows it, the same way `typeof` does -- the
|
|
194
|
-
// law names "a predicate" beside `typeof`/`instanceof` on purpose, and a custom guard
|
|
195
|
-
// (`this.isPlainObject(current)`, `Array.isArray(value)`) is exactly that predicate. This
|
|
196
|
-
// side is deliberately loose: the cost of missing a real gap here is smaller than the cost
|
|
197
|
-
// of flagging a guard this rule cannot read the inside of.
|
|
198
|
-
for (const arg of node.arguments) {
|
|
199
|
-
if (arg.type === "Identifier") markNarrowed(arg.name, scopeKey(node))
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
TSAsExpression(node) {
|
|
203
|
-
if (node.expression.type !== "Identifier") return
|
|
204
|
-
const name = node.expression.name
|
|
205
|
-
const scope = scopeKey(node)
|
|
206
|
-
if (!trackedByScope.get(scope) || !trackedByScope.get(scope).has(name)) return
|
|
207
|
-
const target = node.typeAnnotation
|
|
208
|
-
if (target && target.type === "TSUnknownKeyword") return
|
|
209
|
-
if (narrowedByScope.get(scope) && narrowedByScope.get(scope).has(name)) return
|
|
210
|
-
context.report({
|
|
211
|
-
node,
|
|
212
|
-
messageId: "unguarded",
|
|
213
|
-
data: { name, target: sourceCode.getText(target) },
|
|
214
|
-
})
|
|
215
|
-
},
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
}
|
|
219
|
-
|
|
220
24
|
// -- TYPE-3 ----------------------------------------------------------------------------------------
|
|
221
25
|
|
|
222
26
|
/** A destructured parameter's type is named, so a second caller can reference it. */
|
|
@@ -271,7 +75,7 @@ export const noInlineParamType = {
|
|
|
271
75
|
* fix is a stricter type, not a name) and this rule does not touch it.
|
|
272
76
|
* - A type literal inside a `declare module` / module augmentation describes a vendor's own shape.
|
|
273
77
|
* It cannot be pulled into a named type this repository owns without the two drifting the moment
|
|
274
|
-
* the vendor changes theirs, so the ambient lane is out of scope
|
|
78
|
+
* the vendor changes theirs, so the ambient lane is out of scope.
|
|
275
79
|
* - A type literal that is already the right-hand side of a named type alias (`type Foo = { ... }`)
|
|
276
80
|
* IS the fix this rule asks for. Flagging it would make the rule unsatisfiable -- there would be no
|
|
277
81
|
* way to hold an object type by name that this rule accepted. A type literal declared elsewhere is
|
|
@@ -315,8 +119,6 @@ export const noInlineObjectType = {
|
|
|
315
119
|
},
|
|
316
120
|
},
|
|
317
121
|
create(context) {
|
|
318
|
-
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
319
|
-
|
|
320
122
|
const reportLiteral = (node, position) => {
|
|
321
123
|
context.report({ node, messageId: "inlineObjectType", data: { position } })
|
|
322
124
|
}
|
|
@@ -389,43 +191,46 @@ export const noConstEnum = {
|
|
|
389
191
|
|
|
390
192
|
// -- R72 -------------------------------------------------------------------------------------------
|
|
391
193
|
|
|
392
|
-
/** `
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
create(context) {
|
|
404
|
-
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
405
|
-
return {
|
|
406
|
-
TSAsExpression(node) {
|
|
407
|
-
if (node.typeAnnotation?.type === "TSNeverKeyword") context.report({ node, messageId: "neverCast" })
|
|
408
|
-
},
|
|
409
|
-
}
|
|
410
|
-
},
|
|
194
|
+
/** A global name (`Function`, `eval`) that no declaration of this file shadows. */
|
|
195
|
+
const isGlobalName = (context, node, name) => {
|
|
196
|
+
let scope = (context.sourceCode || context.getSourceCode()).getScope(node)
|
|
197
|
+
while (scope) {
|
|
198
|
+
const variable = scope.set.get(name)
|
|
199
|
+
if (variable) return variable.defs.length === 0
|
|
200
|
+
scope = scope.upper
|
|
201
|
+
}
|
|
202
|
+
return true
|
|
411
203
|
}
|
|
412
204
|
|
|
413
|
-
/** `
|
|
414
|
-
export const
|
|
205
|
+
/** `Function` as a type, `eval` and `new Function` run or accept code the compiler cannot see. */
|
|
206
|
+
export const noFunctionOrEval = {
|
|
415
207
|
meta: {
|
|
416
208
|
type: "problem",
|
|
417
|
-
docs: { description: "No
|
|
209
|
+
docs: { description: "No `Function` type, no `eval`, no `new Function` (the type escapes the borrowed assertion rules do not cover)." },
|
|
418
210
|
schema: [],
|
|
419
211
|
messages: {
|
|
420
|
-
|
|
421
|
-
"
|
|
212
|
+
functionType:
|
|
213
|
+
"The `Function` type accepts any callable and returns `any`, so the compiler stops checking every call through it. Write the signature: `(input: Input) => Result`.",
|
|
214
|
+
evaluated:
|
|
215
|
+
"`{{name}}` builds code from a string, which the compiler cannot type and a reader cannot follow. Call a real function, or map a key to a function in a typed record.",
|
|
422
216
|
},
|
|
423
217
|
},
|
|
424
218
|
create(context) {
|
|
425
|
-
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
426
219
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
220
|
+
TSTypeReference(node) {
|
|
221
|
+
if (node.typeName.type === "Identifier" && node.typeName.name === "Function" && isGlobalName(context, node, "Function")) {
|
|
222
|
+
context.report({ node, messageId: "functionType" })
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
CallExpression(node) {
|
|
226
|
+
const callee = node.callee
|
|
227
|
+
if (callee.type !== "Identifier" || (callee.name !== "eval" && callee.name !== "Function")) return
|
|
228
|
+
if (isGlobalName(context, node, callee.name)) context.report({ node, messageId: "evaluated", data: { name: callee.name } })
|
|
229
|
+
},
|
|
230
|
+
NewExpression(node) {
|
|
231
|
+
if (node.callee.type === "Identifier" && node.callee.name === "Function" && isGlobalName(context, node, "Function")) {
|
|
232
|
+
context.report({ node, messageId: "evaluated", data: { name: "new Function" } })
|
|
233
|
+
}
|
|
429
234
|
},
|
|
430
235
|
}
|
|
431
236
|
},
|
|
@@ -458,7 +263,6 @@ export const explicitHandlerReturnType = {
|
|
|
458
263
|
},
|
|
459
264
|
},
|
|
460
265
|
create(context) {
|
|
461
|
-
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
462
266
|
return {
|
|
463
267
|
MethodDefinition(node) {
|
|
464
268
|
if (node.kind !== "method" || node.static || !node.value.body || node.value.returnType) return
|
|
@@ -497,55 +301,23 @@ export const explicitHandlerReturnType = {
|
|
|
497
301
|
|
|
498
302
|
/** The rules this law contributes to the plugin. */
|
|
499
303
|
export const rules = {
|
|
500
|
-
"no-
|
|
304
|
+
"no-function-or-eval": noFunctionOrEval,
|
|
501
305
|
"no-inline-param-type": noInlineParamType,
|
|
502
306
|
"no-inline-object-type": noInlineObjectType,
|
|
503
307
|
"no-const-enum": noConstEnum,
|
|
504
|
-
"no-unguarded-unknown-cast": noUnguardedUnknownCast,
|
|
505
|
-
"no-never-cast": noNeverCast,
|
|
506
|
-
"no-non-null-assertion": noNonNullAssertion,
|
|
507
308
|
"explicit-handler-return-type": explicitHandlerReturnType,
|
|
508
309
|
}
|
|
509
310
|
|
|
510
311
|
/**
|
|
511
|
-
* The level this law asks for
|
|
512
|
-
*
|
|
513
|
-
* The two standard rules are listed beside the house ones so a consuming repository switches the
|
|
514
|
-
* set on together -- `no-explicit-any` in particular is the one everybody already has and the one
|
|
515
|
-
* whose absence makes the rest decorative.
|
|
516
|
-
*
|
|
517
|
-
* The test exit for `no-double-cast` is inside the rule rather than in a config glob, because
|
|
518
|
-
* building a deliberately wrong value is how a spec proves a closed API refuses it, and that is a
|
|
519
|
-
* property of the lane rather than of any repository's file layout.
|
|
520
|
-
*
|
|
521
|
-
* `no-inline-object-type` was published at `"off"`, not `"error"` or `"warn"` like its siblings, until
|
|
522
|
-
* it was measured against the reference backend. The gate this package ships (`index.test.mjs`)
|
|
523
|
-
* requires every published rule to carry a level, so `"off"` -- not absence -- was how the rule stayed
|
|
524
|
-
* registered and switched-off at once: present for a consuming repository to opt into deliberately,
|
|
525
|
-
* silent until the owner had the debt count in hand.
|
|
312
|
+
* The level this law asks for: every rule at `error`, in every file, specs included.
|
|
526
313
|
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* 4,738 files at measurement time) found zero remaining product-code fires once the three real
|
|
530
|
-
* ones (two in `apps/mock/src/main.ts`, one in `apps/playground-rag-agent/src/rag-agent.service.ts`,
|
|
531
|
-
* plus a fourth the same sweep turned up in `src/modules/ai/utils/extract-json-block.ts` that an
|
|
532
|
-
* earlier pass had missed) were each given a named type. The rule's own test-file exemption held
|
|
533
|
-
* throughout: every other hit was inside `*.spec.ts`. Zero debt is exactly the condition this
|
|
534
|
-
* canon's own convention requires before a rule moves from `off` to `error` -- the same standard
|
|
535
|
-
* `no-unguarded-unknown-cast` is still waiting to clear before its own `warn` becomes `error`.
|
|
314
|
+
* The borrowed type-escape rules (`consistent-type-assertions` never, `no-non-null-assertion`, `no-explicit-any`)
|
|
315
|
+
* are added by the factory (`lib/config.mjs`), not listed here. The array spelling rule stays with this law.
|
|
536
316
|
*/
|
|
537
317
|
export const recommended = {
|
|
538
|
-
"starci-be/no-
|
|
318
|
+
"starci-be/no-function-or-eval": "error",
|
|
539
319
|
"starci-be/no-inline-param-type": "error",
|
|
540
320
|
"starci-be/no-inline-object-type": "error",
|
|
541
321
|
"starci-be/no-const-enum": "error",
|
|
542
|
-
"starci-be/no-unguarded-unknown-cast": "warn", // no=3 -- see type-safety.mjs's doc comment
|
|
543
|
-
"starci-be/no-never-cast": "error",
|
|
544
|
-
"starci-be/no-non-null-assertion": "error",
|
|
545
322
|
"starci-be/explicit-handler-return-type": "error",
|
|
546
|
-
"@typescript-eslint/no-explicit-any": "error",
|
|
547
|
-
"@typescript-eslint/array-type": ["error", {
|
|
548
|
-
default: "generic",
|
|
549
|
-
readonly: "generic",
|
|
550
|
-
}],
|
|
551
323
|
}
|