@starci/eslint-canon-be 1.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/authorization.mjs +106 -0
- package/cdc.mjs +44 -0
- package/comments.mjs +243 -0
- package/cqrs.mjs +207 -0
- package/data-access.mjs +173 -0
- package/e2e-flow.mjs +213 -0
- package/event-delivery.mjs +40 -0
- package/exception-identity.mjs +200 -0
- package/exceptions.mjs +265 -0
- package/index.mjs +90 -0
- package/module-layering.mjs +171 -0
- package/naming.mjs +143 -0
- package/observability.mjs +141 -0
- package/package.json +35 -0
- package/testing.mjs +353 -0
- package/transport.mjs +103 -0
- package/type-safety.mjs +146 -0
package/type-safety.mjs
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules that hold `type-safety.md`.
|
|
3
|
+
*
|
|
4
|
+
* Three house rules plus two standard ones, and the split is worth knowing. `no-explicit-any` and
|
|
5
|
+
* the array-type spelling already exist in the TypeScript plugin and are named in `recommended`
|
|
6
|
+
* rather than reimplemented -- a second implementation of a rule everybody has is a maintenance
|
|
7
|
+
* cost with no gain.
|
|
8
|
+
*
|
|
9
|
+
* What is house-written is what the standard set does not cover: the double cast (TYPE-2), the
|
|
10
|
+
* inline parameter type (TYPE-3) and the const enum (TYPE-4). Each is a way of switching the
|
|
11
|
+
* compiler off that looks locally reasonable, which is exactly the class a rule is for.
|
|
12
|
+
*
|
|
13
|
+
* TYPE-5 is not enforced. Whether a set of booleans describes ONE situation or several genuinely
|
|
14
|
+
* independent ones needs to know what the code means, and a rule that guessed would fire on every
|
|
15
|
+
* struct with two flags in it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Forward-slash form of a filename, so Windows paths compare like every other path. */
|
|
19
|
+
const normalizePath = (filename) => String(filename || "").replace(/\\/g, "/")
|
|
20
|
+
|
|
21
|
+
/** The spec family and the test tree may build a deliberately wrong value on purpose. */
|
|
22
|
+
const isTestFile = (filename) => {
|
|
23
|
+
const file = normalizePath(filename)
|
|
24
|
+
return /\.(?:spec|test|e2e-spec|int-spec|harness-spec)\.ts$/.test(file) || file.includes("/src/tests/")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A parameter property (`private readonly x: T`) wraps the parameter it declares. */
|
|
28
|
+
const unwrapParam = (param) => (param.type === "TSParameterProperty" ? param.parameter : param)
|
|
29
|
+
|
|
30
|
+
// -- TYPE-2 ----------------------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/** A cast through `unknown` is the compiler being overruled twice. */
|
|
33
|
+
export const noDoubleCast = {
|
|
34
|
+
meta: {
|
|
35
|
+
type: "problem",
|
|
36
|
+
docs: { description: "No `x as unknown as T` outside the test lanes." },
|
|
37
|
+
schema: [],
|
|
38
|
+
messages: {
|
|
39
|
+
doubleCast:
|
|
40
|
+
"`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.",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
create(context) {
|
|
44
|
+
if (isTestFile(context.filename || context.getFilename())) return {}
|
|
45
|
+
return {
|
|
46
|
+
TSAsExpression(node) {
|
|
47
|
+
// the OUTER cast of the pair: its operand is itself a cast, to `unknown`
|
|
48
|
+
const inner = node.expression
|
|
49
|
+
if (!inner || inner.type !== "TSAsExpression") return
|
|
50
|
+
const innerType = inner.typeAnnotation
|
|
51
|
+
if (!innerType || innerType.type !== "TSUnknownKeyword") return
|
|
52
|
+
context.report({ node, messageId: "doubleCast" })
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// -- TYPE-3 ----------------------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/** A destructured parameter's type is named, so a second caller can reference it. */
|
|
61
|
+
export const noInlineParamType = {
|
|
62
|
+
meta: {
|
|
63
|
+
type: "problem",
|
|
64
|
+
docs: { description: "A destructured parameter takes a named type, not an inline literal." },
|
|
65
|
+
schema: [],
|
|
66
|
+
messages: {
|
|
67
|
+
inline:
|
|
68
|
+
"An inline object type on a destructured parameter cannot be referenced, imported or extended - so the second caller writes it again, and when a third field arrives only one of the two copies gets it. Move it to a named interface in the module's `types/`.",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
create(context) {
|
|
72
|
+
const check = (params) => {
|
|
73
|
+
for (const raw of params || []) {
|
|
74
|
+
const param = unwrapParam(raw)
|
|
75
|
+
if (param.type !== "ObjectPattern" || !param.typeAnnotation) continue
|
|
76
|
+
const annotation = param.typeAnnotation.typeAnnotation
|
|
77
|
+
if (annotation && annotation.type === "TSTypeLiteral") {
|
|
78
|
+
context.report({ node: param.typeAnnotation, messageId: "inline" })
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
FunctionDeclaration(node) {
|
|
84
|
+
check(node.params)
|
|
85
|
+
},
|
|
86
|
+
FunctionExpression(node) {
|
|
87
|
+
check(node.params)
|
|
88
|
+
},
|
|
89
|
+
ArrowFunctionExpression(node) {
|
|
90
|
+
check(node.params)
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// -- TYPE-4 ----------------------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/** An enum keeps its runtime object. */
|
|
99
|
+
export const noConstEnum = {
|
|
100
|
+
meta: {
|
|
101
|
+
type: "problem",
|
|
102
|
+
docs: { description: "Enums are declared plain, never `const enum`." },
|
|
103
|
+
schema: [],
|
|
104
|
+
messages: {
|
|
105
|
+
constEnum:
|
|
106
|
+
"`const enum {{name}}` is inlined at compile time and has no runtime object: it cannot be iterated, cannot be reverse-mapped, and cannot cross the isolated-modules boundary this repository compiles under. It saves a few bytes and costs a family of things that simply do not work. Declare a plain `enum`.",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
create(context) {
|
|
110
|
+
return {
|
|
111
|
+
TSEnumDeclaration(node) {
|
|
112
|
+
if (!node.const) return
|
|
113
|
+
context.report({ node, messageId: "constEnum", data: { name: node.id.name } })
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The rules this law contributes to the plugin. */
|
|
120
|
+
export const rules = {
|
|
121
|
+
"no-double-cast": noDoubleCast,
|
|
122
|
+
"no-inline-param-type": noInlineParamType,
|
|
123
|
+
"no-const-enum": noConstEnum,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The level this law asks for, as the plugin's own opinion.
|
|
128
|
+
*
|
|
129
|
+
* The two standard rules are listed beside the house ones so a consuming repository switches the
|
|
130
|
+
* set on together -- `no-explicit-any` in particular is the one everybody already has and the one
|
|
131
|
+
* whose absence makes the rest decorative.
|
|
132
|
+
*
|
|
133
|
+
* The test exit for `no-double-cast` is inside the rule rather than in a config glob, because
|
|
134
|
+
* building a deliberately wrong value is how a spec proves a closed API refuses it, and that is a
|
|
135
|
+
* property of the lane rather than of any repository's file layout.
|
|
136
|
+
*/
|
|
137
|
+
export const recommended = {
|
|
138
|
+
"starci-be/no-double-cast": "error",
|
|
139
|
+
"starci-be/no-inline-param-type": "error",
|
|
140
|
+
"starci-be/no-const-enum": "error",
|
|
141
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
142
|
+
"@typescript-eslint/array-type": ["error", {
|
|
143
|
+
default: "generic",
|
|
144
|
+
readonly: "generic",
|
|
145
|
+
}],
|
|
146
|
+
}
|