oxlint-plugin-effect 0.7.0 → 0.8.1
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/README.md +25 -0
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/presets/recommended.d.ts +16 -1
- package/dist/presets/recommended.js +17 -2
- package/dist/rules/_anti-slop-dictionary-types.js +243 -0
- package/dist/rules/index.d.ts +13 -2
- package/dist/rules/index.js +24 -2
- package/dist/rules/no-chained-type-assertions.d.ts +7 -0
- package/dist/rules/no-chained-type-assertions.js +60 -0
- package/dist/rules/no-conditional-empty-object-spread.d.ts +7 -0
- package/dist/rules/no-conditional-empty-object-spread.js +79 -0
- package/dist/rules/no-known-value-widening.d.ts +7 -0
- package/dist/rules/no-known-value-widening.js +144 -0
- package/dist/rules/no-object-parameters.d.ts +7 -0
- package/dist/rules/no-object-parameters.js +70 -0
- package/dist/rules/no-runtime-typeof.d.ts +7 -0
- package/dist/rules/no-runtime-typeof.js +24 -0
- package/dist/rules/no-shape-in-symbol-names.d.ts +7 -0
- package/dist/rules/no-shape-in-symbol-names.js +34 -0
- package/dist/rules/no-unknown-parameters.d.ts +7 -0
- package/dist/rules/no-unknown-parameters.js +55 -0
- package/dist/rules/no-unknown-type-aliases.d.ts +7 -0
- package/dist/rules/no-unknown-type-aliases.js +48 -0
- package/dist/rules/no-unsafe-dictionary-type.d.ts +7 -0
- package/dist/rules/no-unsafe-dictionary-type.js +75 -0
- package/dist/rules/no-widen-then-assert.d.ts +7 -0
- package/dist/rules/no-widen-then-assert.js +199 -0
- package/dist/rules/prefer-effect-fn.d.ts +6 -0
- package/dist/rules/prefer-effect-fn.js +31 -0
- package/dist/vendor/effect-oxlint/AST.d.ts +1 -1
- package/dist/vendor/effect-oxlint/AST.js +1 -1
- package/dist/vendor/effect-oxlint/Comment.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Comment.js +1 -1
- package/dist/vendor/effect-oxlint/Diagnostic.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Diagnostic.js +1 -1
- package/dist/vendor/effect-oxlint/Plugin.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Plugin.js +1 -1
- package/dist/vendor/effect-oxlint/Rule.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Rule.js +1 -1
- package/dist/vendor/effect-oxlint/RuleContext.d.ts +1 -7
- package/dist/vendor/effect-oxlint/Scope.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Scope.js +1 -1
- package/dist/vendor/effect-oxlint/SourceCode.d.ts +1 -1
- package/dist/vendor/effect-oxlint/SourceCode.js +1 -1
- package/dist/vendor/effect-oxlint/Testing.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Testing.js +1 -1
- package/dist/vendor/effect-oxlint/Token.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Token.js +1 -1
- package/dist/vendor/effect-oxlint/Visitor.d.ts +1 -1
- package/dist/vendor/effect-oxlint/Visitor.js +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -39,9 +39,30 @@ If the project uses JSON configuration, copy the exported rule map into `rules`;
|
|
|
39
39
|
| `effect/noDynamicImports` | Allows import() only behind a named lazy-loading boundary; bans require() |
|
|
40
40
|
| `effect/noEffectDo` | Bans Effect.Do |
|
|
41
41
|
| `effect/noEffectBind` | Bans Effect.bind |
|
|
42
|
+
| `effect/preferEffectFn` | Requires `Effect.fn` for a generator operation that adds a span |
|
|
42
43
|
| `effect/noGlobals` | Bans ambient capabilities with direct Effect replacements; allows `process.std*.isTTY` |
|
|
43
44
|
| `effect/noNodeBuiltinImport` | Bans fully replaced Node modules and replaced operations from partial modules |
|
|
44
45
|
|
|
46
|
+
## Anti-Slop Rules
|
|
47
|
+
|
|
48
|
+
The recommended preset also includes these rules from
|
|
49
|
+
[`dmmulroy/anti-slop`](https://github.com/dmmulroy/anti-slop):
|
|
50
|
+
|
|
51
|
+
| Rule | Contract |
|
|
52
|
+
| --------------------------------------- | ------------------------------------------------------------------ |
|
|
53
|
+
| `effect/noChainedTypeAssertions` | Bans nested type assertions that invent type evidence |
|
|
54
|
+
| `effect/noConditionalEmptyObjectSpread` | Bans conditional spreads that use an empty object to omit fields |
|
|
55
|
+
| `effect/noKnownValueWidening` | Bans broad target types that discard known value evidence |
|
|
56
|
+
| `effect/noObjectParameters` | Bans the broad `object` type on function inputs |
|
|
57
|
+
| `effect/noRuntimeTypeof` | Requires boundary parsing instead of runtime `typeof` narrowing |
|
|
58
|
+
| `effect/noShapeInSymbolNames` | Bans `shape` in symbol names |
|
|
59
|
+
| `effect/noUnknownParameters` | Bans `unknown` inputs except an input named `cause` |
|
|
60
|
+
| `effect/noUnknownTypeAliases` | Bans aliases that only hide `unknown` |
|
|
61
|
+
| `effect/noUnsafeDictionaryType` | Bans dictionaries with unsafe broad value types |
|
|
62
|
+
| `effect/noWidenThenAssert` | Bans local flows that widen known values and then assert them back |
|
|
63
|
+
|
|
64
|
+
See `THIRD_PARTY_NOTICES.md` for the source revision and license.
|
|
65
|
+
|
|
45
66
|
The preset intentionally allows `Effect.as`, `Option.as`, `Effect.never`, `Effect.async`, ordinary `if` and `switch` statements, and runtime runners at explicit application boundaries.
|
|
46
67
|
|
|
47
68
|
## Platform Boundaries
|
|
@@ -128,8 +149,12 @@ export const noThing = Rule.define({
|
|
|
128
149
|
bun install
|
|
129
150
|
bun run gate
|
|
130
151
|
bun run codegen
|
|
152
|
+
bun run add-rule -- no-example --dry-run
|
|
131
153
|
```
|
|
132
154
|
|
|
155
|
+
`bun run codegen` owns both the rule export barrel and the recommended preset.
|
|
156
|
+
`bun run gate` fails when either generated file is stale.
|
|
157
|
+
|
|
133
158
|
## License
|
|
134
159
|
|
|
135
160
|
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
The anti-slop rules include code derived from
|
|
4
|
+
[`dmmulroy/anti-slop`](https://github.com/dmmulroy/anti-slop) at commit
|
|
5
|
+
`b5d2288db1f00469a1d5f2e3b0e265e5a5676fd0`.
|
|
6
|
+
|
|
7
|
+
MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Dillon Mulroy
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
//#region src/presets/recommended.d.ts
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Strict, non-type-aware policy for Effect-native application code.
|
|
4
|
+
*
|
|
5
|
+
* AUTO-GENERATED by scripts/codegen.ts — do not edit manually.
|
|
6
|
+
*/
|
|
3
7
|
declare const recommended: {
|
|
4
8
|
readonly "effect/noAs": "error";
|
|
5
9
|
readonly "effect/noAsyncFunction": "error";
|
|
10
|
+
readonly "effect/noChainedTypeAssertions": "error";
|
|
11
|
+
readonly "effect/noConditionalEmptyObjectSpread": "error";
|
|
6
12
|
readonly "effect/noDynamicImports": "error";
|
|
7
13
|
readonly "effect/noEffectBind": "error";
|
|
8
14
|
readonly "effect/noEffectDo": "error";
|
|
9
15
|
readonly "effect/noGlobals": "error";
|
|
16
|
+
readonly "effect/noKnownValueWidening": "error";
|
|
10
17
|
readonly "effect/noNewError": "error";
|
|
11
18
|
readonly "effect/noNewPromise": "error";
|
|
12
19
|
readonly "effect/noNodeBuiltinImport": "error";
|
|
13
20
|
readonly "effect/noNullish": "error";
|
|
21
|
+
readonly "effect/noObjectParameters": "error";
|
|
22
|
+
readonly "effect/noRuntimeTypeof": "error";
|
|
23
|
+
readonly "effect/noShapeInSymbolNames": "error";
|
|
14
24
|
readonly "effect/noTernary": "error";
|
|
15
25
|
readonly "effect/noTestLifecycleHooks": "error";
|
|
16
26
|
readonly "effect/noThrowStatement": "error";
|
|
17
27
|
readonly "effect/noTryCatch": "error";
|
|
28
|
+
readonly "effect/noUnknownParameters": "error";
|
|
29
|
+
readonly "effect/noUnknownTypeAliases": "error";
|
|
30
|
+
readonly "effect/noUnsafeDictionaryType": "error";
|
|
31
|
+
readonly "effect/noWidenThenAssert": "error";
|
|
32
|
+
readonly "effect/preferEffectFn": "error";
|
|
18
33
|
};
|
|
19
34
|
//#endregion
|
|
20
35
|
export { recommended };
|
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
//#region src/presets/recommended.ts
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Strict, non-type-aware policy for Effect-native application code.
|
|
4
|
+
*
|
|
5
|
+
* AUTO-GENERATED by scripts/codegen.ts — do not edit manually.
|
|
6
|
+
*/
|
|
3
7
|
const recommended = {
|
|
4
8
|
"effect/noAs": "error",
|
|
5
9
|
"effect/noAsyncFunction": "error",
|
|
10
|
+
"effect/noChainedTypeAssertions": "error",
|
|
11
|
+
"effect/noConditionalEmptyObjectSpread": "error",
|
|
6
12
|
"effect/noDynamicImports": "error",
|
|
7
13
|
"effect/noEffectBind": "error",
|
|
8
14
|
"effect/noEffectDo": "error",
|
|
9
15
|
"effect/noGlobals": "error",
|
|
16
|
+
"effect/noKnownValueWidening": "error",
|
|
10
17
|
"effect/noNewError": "error",
|
|
11
18
|
"effect/noNewPromise": "error",
|
|
12
19
|
"effect/noNodeBuiltinImport": "error",
|
|
13
20
|
"effect/noNullish": "error",
|
|
21
|
+
"effect/noObjectParameters": "error",
|
|
22
|
+
"effect/noRuntimeTypeof": "error",
|
|
23
|
+
"effect/noShapeInSymbolNames": "error",
|
|
14
24
|
"effect/noTernary": "error",
|
|
15
25
|
"effect/noTestLifecycleHooks": "error",
|
|
16
26
|
"effect/noThrowStatement": "error",
|
|
17
|
-
"effect/noTryCatch": "error"
|
|
27
|
+
"effect/noTryCatch": "error",
|
|
28
|
+
"effect/noUnknownParameters": "error",
|
|
29
|
+
"effect/noUnknownTypeAliases": "error",
|
|
30
|
+
"effect/noUnsafeDictionaryType": "error",
|
|
31
|
+
"effect/noWidenThenAssert": "error",
|
|
32
|
+
"effect/preferEffectFn": "error"
|
|
18
33
|
};
|
|
19
34
|
//#endregion
|
|
20
35
|
export { recommended };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
//#region src/rules/_anti-slop-dictionary-types.ts
|
|
2
|
+
const BUILT_INS = new Set([
|
|
3
|
+
"Record",
|
|
4
|
+
"Readonly",
|
|
5
|
+
"Partial",
|
|
6
|
+
"Required",
|
|
7
|
+
"Pick",
|
|
8
|
+
"Omit",
|
|
9
|
+
"PropertyKey",
|
|
10
|
+
"NonNullable"
|
|
11
|
+
]);
|
|
12
|
+
const TRANSPARENT_WRAPPERS = new Set([
|
|
13
|
+
"Readonly",
|
|
14
|
+
"Partial",
|
|
15
|
+
"Required",
|
|
16
|
+
"NonNullable"
|
|
17
|
+
]);
|
|
18
|
+
function declaredStatement(statement) {
|
|
19
|
+
return statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration ?? null : statement;
|
|
20
|
+
}
|
|
21
|
+
function createTypeEnvironment(program) {
|
|
22
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
23
|
+
const interfaces = /* @__PURE__ */ new Map();
|
|
24
|
+
const shadowedBuiltIns = /* @__PURE__ */ new Set();
|
|
25
|
+
for (const statement of program.body) {
|
|
26
|
+
const declaration = declaredStatement(statement);
|
|
27
|
+
if (declaration?.type === "ImportDeclaration") {
|
|
28
|
+
for (const specifier of declaration.specifiers) if (BUILT_INS.has(specifier.local.name)) shadowedBuiltIns.add(specifier.local.name);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (declaration?.type === "TSTypeAliasDeclaration") {
|
|
32
|
+
if (aliases.get(declaration.id.name) === void 0) aliases.set(declaration.id.name, declaration);
|
|
33
|
+
else shadowedBuiltIns.add(declaration.id.name);
|
|
34
|
+
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (declaration?.type === "TSInterfaceDeclaration") {
|
|
38
|
+
const declarations = interfaces.get(declaration.id.name) ?? [];
|
|
39
|
+
declarations.push(declaration);
|
|
40
|
+
interfaces.set(declaration.id.name, declarations);
|
|
41
|
+
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (declaration?.type === "TSEnumDeclaration") {
|
|
45
|
+
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if ((declaration?.type === "ClassDeclaration" || declaration?.type === "FunctionDeclaration") && declaration.id !== null) {
|
|
49
|
+
if (BUILT_INS.has(declaration.id.name)) shadowedBuiltIns.add(declaration.id.name);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
aliases,
|
|
54
|
+
interfaces,
|
|
55
|
+
shadowedBuiltIns
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function typeReferenceName(type) {
|
|
59
|
+
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
60
|
+
}
|
|
61
|
+
function isBuiltIn(name, environment) {
|
|
62
|
+
return BUILT_INS.has(name) && !environment.shadowedBuiltIns.has(name);
|
|
63
|
+
}
|
|
64
|
+
function isUnappliedReferenceTo(type, name) {
|
|
65
|
+
const unwrapped = unwrapTransparentType(type);
|
|
66
|
+
return unwrapped.type === "TSTypeReference" && typeReferenceName(unwrapped) === name && (unwrapped.typeArguments === null || unwrapped.typeArguments === void 0 || unwrapped.typeArguments.params.length === 0);
|
|
67
|
+
}
|
|
68
|
+
function unwrapTransparentType(type) {
|
|
69
|
+
let current = type;
|
|
70
|
+
while (current.type === "TSParenthesizedType" || current.type === "TSTypeOperator" && current.operator === "readonly") current = current.typeAnnotation;
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
function isNeverType(type) {
|
|
74
|
+
return unwrapTransparentType(type).type === "TSNeverKeyword";
|
|
75
|
+
}
|
|
76
|
+
function isEffectivelyEmptyMember(member) {
|
|
77
|
+
return member.type === "TSPropertySignature" && member.optional === true && member.typeAnnotation !== null && member.typeAnnotation !== void 0 && isNeverType(member.typeAnnotation.typeAnnotation);
|
|
78
|
+
}
|
|
79
|
+
function isEffectivelyEmptyTypeLiteral(type) {
|
|
80
|
+
return type.members.length === 0 || type.members.every(isEffectivelyEmptyMember);
|
|
81
|
+
}
|
|
82
|
+
function isEffectivelyEmptyInterface(declarations) {
|
|
83
|
+
if (declarations.length !== 1) return false;
|
|
84
|
+
const [type] = declarations;
|
|
85
|
+
return type !== void 0 && type.extends.length === 0 && (type.body.body.length === 0 || type.body.body.every(isEffectivelyEmptyMember));
|
|
86
|
+
}
|
|
87
|
+
function resolvedSubstitutionArgument(type, base) {
|
|
88
|
+
const unwrapped = unwrapTransparentType(type);
|
|
89
|
+
if (unwrapped.type !== "TSTypeReference") return type;
|
|
90
|
+
const name = typeReferenceName(unwrapped);
|
|
91
|
+
if (name === null) return type;
|
|
92
|
+
const substitution = base.get(name);
|
|
93
|
+
return substitution === void 0 ? type : resolvedSubstitutionArgument(substitution, base);
|
|
94
|
+
}
|
|
95
|
+
function aliasSubstitution(alias, type, base) {
|
|
96
|
+
const parameters = alias.typeParameters?.params ?? [];
|
|
97
|
+
const typeArguments = type.typeArguments?.params ?? [];
|
|
98
|
+
const next = new Map(base);
|
|
99
|
+
for (const [index, parameter] of parameters.entries()) {
|
|
100
|
+
const argument = typeArguments[index] ?? parameter.default;
|
|
101
|
+
if (argument === null || argument === void 0) return null;
|
|
102
|
+
next.set(parameter.name.name, resolvedSubstitutionArgument(argument, next));
|
|
103
|
+
}
|
|
104
|
+
return next;
|
|
105
|
+
}
|
|
106
|
+
function unsafeDirectValue(type, environment, substitutions, resolvingAliases) {
|
|
107
|
+
const unwrapped = unwrapTransparentType(type);
|
|
108
|
+
if (unwrapped.type === "TSUnknownKeyword") return "unknown";
|
|
109
|
+
if (unwrapped.type === "TSAnyKeyword") return "any";
|
|
110
|
+
if (unwrapped.type === "TSObjectKeyword") return "object";
|
|
111
|
+
if (unwrapped.type === "TSTypeLiteral" && isEffectivelyEmptyTypeLiteral(unwrapped)) return "empty-object";
|
|
112
|
+
if (unwrapped.type === "TSUnionType") return unwrapped.types.some((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases) !== null) ? "union" : null;
|
|
113
|
+
if (unwrapped.type === "TSIntersectionType") {
|
|
114
|
+
const unsafeMembers = unwrapped.types.map((member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases));
|
|
115
|
+
if (unsafeMembers.includes("any")) return "any";
|
|
116
|
+
return unsafeMembers.length > 0 && unsafeMembers.every((member) => member !== null) ? unsafeMembers[0] ?? null : null;
|
|
117
|
+
}
|
|
118
|
+
if (unwrapped.type !== "TSTypeReference") return null;
|
|
119
|
+
const name = typeReferenceName(unwrapped);
|
|
120
|
+
if (name === null) return null;
|
|
121
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
122
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
123
|
+
return wrapped === void 0 ? null : unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
|
|
124
|
+
}
|
|
125
|
+
const substitution = substitutions.get(name);
|
|
126
|
+
if (substitution !== void 0) return isUnappliedReferenceTo(substitution, name) ? null : unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
|
|
127
|
+
const interfaceDeclarations = environment.interfaces.get(name);
|
|
128
|
+
if (interfaceDeclarations !== void 0) return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
|
|
129
|
+
const alias = environment.aliases.get(name);
|
|
130
|
+
if (alias === void 0 || resolvingAliases.has(name)) return null;
|
|
131
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
132
|
+
if (nextSubstitutions === null) return null;
|
|
133
|
+
const nextResolving = new Set(resolvingAliases);
|
|
134
|
+
nextResolving.add(name);
|
|
135
|
+
return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
136
|
+
}
|
|
137
|
+
function dictionaryValueTypes(type, environment, substitutions, resolvingAliases) {
|
|
138
|
+
const unwrapped = unwrapTransparentType(type);
|
|
139
|
+
if (unwrapped.type === "TSTypeLiteral") return unwrapped.members.flatMap((member) => member.type === "TSIndexSignature" && member.typeAnnotation !== null ? [{
|
|
140
|
+
type: member.typeAnnotation.typeAnnotation,
|
|
141
|
+
substitutions
|
|
142
|
+
}] : []);
|
|
143
|
+
if (unwrapped.type === "TSMappedType") return unwrapped.typeAnnotation === null ? [] : [{
|
|
144
|
+
type: unwrapped.typeAnnotation,
|
|
145
|
+
substitutions
|
|
146
|
+
}];
|
|
147
|
+
if (unwrapped.type !== "TSTypeReference") return [];
|
|
148
|
+
const name = typeReferenceName(unwrapped);
|
|
149
|
+
if (name === null) return [];
|
|
150
|
+
const substitution = substitutions.get(name);
|
|
151
|
+
if (substitution !== void 0) return isUnappliedReferenceTo(substitution, name) ? [] : dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
|
|
152
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
153
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
154
|
+
return wrapped === void 0 ? [] : dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
|
|
155
|
+
}
|
|
156
|
+
if (name === "Record" && isBuiltIn(name, environment)) {
|
|
157
|
+
const value = unwrapped.typeArguments?.params[1] ?? null;
|
|
158
|
+
return value === null ? [] : [{
|
|
159
|
+
type: value,
|
|
160
|
+
substitutions
|
|
161
|
+
}];
|
|
162
|
+
}
|
|
163
|
+
if ((name === "Pick" || name === "Omit") && isBuiltIn(name, environment)) {
|
|
164
|
+
const source = unwrapped.typeArguments?.params[0];
|
|
165
|
+
return source === void 0 ? [] : dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
|
|
166
|
+
}
|
|
167
|
+
const alias = environment.aliases.get(name);
|
|
168
|
+
if (alias === void 0 || resolvingAliases.has(name)) return [];
|
|
169
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
170
|
+
if (nextSubstitutions === null) return [];
|
|
171
|
+
const nextResolving = new Set(resolvingAliases);
|
|
172
|
+
nextResolving.add(name);
|
|
173
|
+
return dictionaryValueTypes(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
174
|
+
}
|
|
175
|
+
function classifyUnsafeDictionaryValue(valueType, environment) {
|
|
176
|
+
const unsafeValue = unsafeDirectValue(valueType, environment, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set());
|
|
177
|
+
return unsafeValue === null ? null : {
|
|
178
|
+
kind: "unsafe-dictionary",
|
|
179
|
+
unsafeValue
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function classifyUnsafeDictionary(type, environment) {
|
|
183
|
+
for (const valueType of dictionaryValueTypes(type, environment, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set())) {
|
|
184
|
+
const unsafeValue = unsafeDirectValue(valueType.type, environment, valueType.substitutions, /* @__PURE__ */ new Set());
|
|
185
|
+
if (unsafeValue !== null) return {
|
|
186
|
+
kind: "unsafe-dictionary",
|
|
187
|
+
unsafeValue
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
function resolvesToDictionary(type, environment, substitutions, resolvingAliases) {
|
|
193
|
+
return dictionaryValueTypes(type, environment, substitutions, resolvingAliases).length > 0;
|
|
194
|
+
}
|
|
195
|
+
function classifyWideningTarget(type, environment) {
|
|
196
|
+
const unwrapped = unwrapTransparentType(type);
|
|
197
|
+
if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
|
|
198
|
+
if (unwrapped.type === "TSObjectKeyword") return { kind: "object" };
|
|
199
|
+
if (unwrapped.type === "TSTypeLiteral") return unwrapped.members.some((member) => member.type === "TSIndexSignature") ? { kind: "open dictionary" } : unwrapped.members.length > 0 ? { kind: "anonymous object" } : null;
|
|
200
|
+
if (unwrapped.type === "TSMappedType") return { kind: "open dictionary" };
|
|
201
|
+
if (unwrapped.type !== "TSTypeReference") return null;
|
|
202
|
+
const name = typeReferenceName(unwrapped);
|
|
203
|
+
if (name === null) return null;
|
|
204
|
+
if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, environment)) {
|
|
205
|
+
const wrapped = unwrapped.typeArguments?.params[0];
|
|
206
|
+
return wrapped === void 0 ? null : classifyWideningTarget(wrapped, environment);
|
|
207
|
+
}
|
|
208
|
+
if (name === "Record" && isBuiltIn(name, environment)) return { kind: "open dictionary" };
|
|
209
|
+
const alias = environment.aliases.get(name);
|
|
210
|
+
if (alias === void 0) return null;
|
|
211
|
+
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
212
|
+
const substitutions = aliasSubstitution(alias, unwrapped, /* @__PURE__ */ new Map());
|
|
213
|
+
return substitutions !== null && resolvesToDictionary(alias.typeAnnotation, environment, substitutions, new Set([name])) ? { kind: "generic container" } : null;
|
|
214
|
+
}
|
|
215
|
+
const substitutions = aliasSubstitution(alias, unwrapped, /* @__PURE__ */ new Map());
|
|
216
|
+
if (substitutions === null) return null;
|
|
217
|
+
return classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, new Set([name]));
|
|
218
|
+
}
|
|
219
|
+
function classifyAliasBroadTarget(type, environment, substitutions, resolvingAliases) {
|
|
220
|
+
const unwrapped = unwrapTransparentType(type);
|
|
221
|
+
if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
|
|
222
|
+
if (unwrapped.type === "TSObjectKeyword") return { kind: "object" };
|
|
223
|
+
if (unwrapped.type !== "TSTypeReference") return null;
|
|
224
|
+
const name = typeReferenceName(unwrapped);
|
|
225
|
+
if (name === null) return null;
|
|
226
|
+
const substitution = substitutions.get(name);
|
|
227
|
+
if (substitution !== void 0) return classifyAliasBroadTarget(substitution, environment, substitutions, resolvingAliases);
|
|
228
|
+
const alias = environment.aliases.get(name);
|
|
229
|
+
if (alias === void 0 || resolvingAliases.has(name)) return null;
|
|
230
|
+
const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
|
|
231
|
+
if (nextSubstitutions === null) return null;
|
|
232
|
+
const nextResolving = new Set(resolvingAliases);
|
|
233
|
+
nextResolving.add(name);
|
|
234
|
+
return classifyAliasBroadTarget(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
|
|
235
|
+
}
|
|
236
|
+
function isKnownEvidenceExpression(expression) {
|
|
237
|
+
let current = expression;
|
|
238
|
+
while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression") current = current.expression;
|
|
239
|
+
if (current.type === "ObjectExpression") return true;
|
|
240
|
+
return current.type === "ArrayExpression" || current.type === "ArrowFunctionExpression" || current.type === "ClassExpression" || current.type === "FunctionExpression" || current.type === "NewExpression" || current.type === "Literal" || current.type === "TemplateLiteral" || current.type === "UnaryExpression";
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
export { classifyUnsafeDictionary, classifyUnsafeDictionaryValue, classifyWideningTarget, createTypeEnvironment, isKnownEvidenceExpression };
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -2,20 +2,31 @@ import { noEffectBind } from "./no-effect-bind.js";
|
|
|
2
2
|
import { noEffectDo } from "./no-effect-do.js";
|
|
3
3
|
import { noAs } from "./no-as.js";
|
|
4
4
|
import { noAsyncFunction } from "./no-async-function.js";
|
|
5
|
+
import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
|
|
6
|
+
import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
|
|
5
7
|
import { noDynamicImports } from "./no-dynamic-imports.js";
|
|
6
8
|
import { noGlobals } from "./no-globals.js";
|
|
9
|
+
import { noKnownValueWidening } from "./no-known-value-widening.js";
|
|
7
10
|
import { noNewError } from "./no-new-error.js";
|
|
8
11
|
import { noNewPromise } from "./no-new-promise.js";
|
|
9
12
|
import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
|
|
10
13
|
import { noNullish } from "./no-nullish.js";
|
|
14
|
+
import { noObjectParameters } from "./no-object-parameters.js";
|
|
15
|
+
import { noRuntimeTypeof } from "./no-runtime-typeof.js";
|
|
16
|
+
import { noShapeInSymbolNames } from "./no-shape-in-symbol-names.js";
|
|
11
17
|
import { noTernary } from "./no-ternary.js";
|
|
12
18
|
import { noTestLifecycleHooks } from "./no-test-lifecycle-hooks.js";
|
|
13
19
|
import { noThrowStatement } from "./no-throw-statement.js";
|
|
14
20
|
import { noTryCatch } from "./no-try-catch.js";
|
|
21
|
+
import { noUnknownParameters } from "./no-unknown-parameters.js";
|
|
22
|
+
import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
|
|
23
|
+
import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
|
|
24
|
+
import { noWidenThenAssert } from "./no-widen-then-assert.js";
|
|
25
|
+
import { preferEffectFn } from "./prefer-effect-fn.js";
|
|
15
26
|
|
|
16
27
|
//#region src/rules/index.d.ts
|
|
17
28
|
declare namespace index_d_exports {
|
|
18
|
-
export { noAs, noAsyncFunction, noDynamicImports, noEffectBind, noEffectDo, noGlobals, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch };
|
|
29
|
+
export { noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEffectBind, noEffectDo, noGlobals, noKnownValueWidening, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noRuntimeTypeof, noShapeInSymbolNames, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnknownParameters, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferEffectFn };
|
|
19
30
|
}
|
|
20
31
|
//#endregion
|
|
21
|
-
export { index_d_exports
|
|
32
|
+
export { index_d_exports };
|
package/dist/rules/index.js
CHANGED
|
@@ -3,32 +3,54 @@ import { noEffectBind } from "./no-effect-bind.js";
|
|
|
3
3
|
import { noEffectDo } from "./no-effect-do.js";
|
|
4
4
|
import { noAs } from "./no-as.js";
|
|
5
5
|
import { noAsyncFunction } from "./no-async-function.js";
|
|
6
|
+
import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
|
|
7
|
+
import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
|
|
6
8
|
import { noDynamicImports } from "./no-dynamic-imports.js";
|
|
7
9
|
import { noGlobals } from "./no-globals.js";
|
|
10
|
+
import { noKnownValueWidening } from "./no-known-value-widening.js";
|
|
8
11
|
import { noNewError } from "./no-new-error.js";
|
|
9
12
|
import { noNewPromise } from "./no-new-promise.js";
|
|
10
13
|
import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
|
|
11
14
|
import { noNullish } from "./no-nullish.js";
|
|
15
|
+
import { noObjectParameters } from "./no-object-parameters.js";
|
|
16
|
+
import { noRuntimeTypeof } from "./no-runtime-typeof.js";
|
|
17
|
+
import { noShapeInSymbolNames } from "./no-shape-in-symbol-names.js";
|
|
12
18
|
import { noTernary } from "./no-ternary.js";
|
|
13
19
|
import { noTestLifecycleHooks } from "./no-test-lifecycle-hooks.js";
|
|
14
20
|
import { noThrowStatement } from "./no-throw-statement.js";
|
|
15
21
|
import { noTryCatch } from "./no-try-catch.js";
|
|
22
|
+
import { noUnknownParameters } from "./no-unknown-parameters.js";
|
|
23
|
+
import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
|
|
24
|
+
import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
|
|
25
|
+
import { noWidenThenAssert } from "./no-widen-then-assert.js";
|
|
26
|
+
import { preferEffectFn } from "./prefer-effect-fn.js";
|
|
16
27
|
//#region src/rules/index.ts
|
|
17
28
|
var rules_exports = /* @__PURE__ */ __exportAll({
|
|
18
29
|
noAs: () => noAs,
|
|
19
30
|
noAsyncFunction: () => noAsyncFunction,
|
|
31
|
+
noChainedTypeAssertions: () => noChainedTypeAssertions,
|
|
32
|
+
noConditionalEmptyObjectSpread: () => noConditionalEmptyObjectSpread,
|
|
20
33
|
noDynamicImports: () => noDynamicImports,
|
|
21
34
|
noEffectBind: () => noEffectBind,
|
|
22
35
|
noEffectDo: () => noEffectDo,
|
|
23
36
|
noGlobals: () => noGlobals,
|
|
37
|
+
noKnownValueWidening: () => noKnownValueWidening,
|
|
24
38
|
noNewError: () => noNewError,
|
|
25
39
|
noNewPromise: () => noNewPromise,
|
|
26
40
|
noNodeBuiltinImport: () => noNodeBuiltinImport,
|
|
27
41
|
noNullish: () => noNullish,
|
|
42
|
+
noObjectParameters: () => noObjectParameters,
|
|
43
|
+
noRuntimeTypeof: () => noRuntimeTypeof,
|
|
44
|
+
noShapeInSymbolNames: () => noShapeInSymbolNames,
|
|
28
45
|
noTernary: () => noTernary,
|
|
29
46
|
noTestLifecycleHooks: () => noTestLifecycleHooks,
|
|
30
47
|
noThrowStatement: () => noThrowStatement,
|
|
31
|
-
noTryCatch: () => noTryCatch
|
|
48
|
+
noTryCatch: () => noTryCatch,
|
|
49
|
+
noUnknownParameters: () => noUnknownParameters,
|
|
50
|
+
noUnknownTypeAliases: () => noUnknownTypeAliases,
|
|
51
|
+
noUnsafeDictionaryType: () => noUnsafeDictionaryType,
|
|
52
|
+
noWidenThenAssert: () => noWidenThenAssert,
|
|
53
|
+
preferEffectFn: () => preferEffectFn
|
|
32
54
|
});
|
|
33
55
|
//#endregion
|
|
34
|
-
export {
|
|
56
|
+
export { rules_exports };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as _$_oxlint_plugins0 from "@oxlint/plugins";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-chained-type-assertions.d.ts
|
|
4
|
+
/** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
|
|
5
|
+
declare const noChainedTypeAssertions: _$_oxlint_plugins0.CreateRule;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { noChainedTypeAssertions };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { fromId } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
2
|
+
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
|
+
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
|
+
import "../vendor/effect-oxlint/index.js";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
//#region src/rules/no-chained-type-assertions.ts
|
|
7
|
+
function isTypeAssertionExpression(node) {
|
|
8
|
+
return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
|
|
9
|
+
}
|
|
10
|
+
function unwrapParenthesizedExpression(expression) {
|
|
11
|
+
let current = expression;
|
|
12
|
+
while (current.type === "ParenthesizedExpression") current = current.expression;
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
function isConstAssertion(node) {
|
|
16
|
+
const { typeAnnotation } = node;
|
|
17
|
+
return typeAnnotation.type === "TSTypeReference" && typeAnnotation.typeName.type === "Identifier" && typeAnnotation.typeName.name === "const";
|
|
18
|
+
}
|
|
19
|
+
function isOutermostAssertionInChain(node) {
|
|
20
|
+
let current = node;
|
|
21
|
+
let parent = node.parent;
|
|
22
|
+
while (parent.type === "ParenthesizedExpression" && parent.expression === current) {
|
|
23
|
+
current = parent;
|
|
24
|
+
parent = parent.parent;
|
|
25
|
+
}
|
|
26
|
+
return !isTypeAssertionExpression(parent) || parent.expression !== current;
|
|
27
|
+
}
|
|
28
|
+
function isForbiddenAssertionChain(node) {
|
|
29
|
+
let assertionCount = 0;
|
|
30
|
+
let hasNonConstAssertion = false;
|
|
31
|
+
let current = node;
|
|
32
|
+
while (isTypeAssertionExpression(current)) {
|
|
33
|
+
assertionCount += 1;
|
|
34
|
+
hasNonConstAssertion ||= !isConstAssertion(current);
|
|
35
|
+
current = unwrapParenthesizedExpression(current.expression);
|
|
36
|
+
}
|
|
37
|
+
return assertionCount > 1 && hasNonConstAssertion;
|
|
38
|
+
}
|
|
39
|
+
/** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
|
|
40
|
+
const noChainedTypeAssertions = define({
|
|
41
|
+
name: "no-chained-type-assertions",
|
|
42
|
+
meta: meta({
|
|
43
|
+
type: "problem",
|
|
44
|
+
description: "Disallow chained TypeScript as and angle-bracket assertions, including parenthesized chains.",
|
|
45
|
+
messages: { chained: "Chained type assertions discard existing type evidence and fabricate the target type without parsing. Preserve the value's original precise type, or parse genuinely unknown input at its boundary before using it." }
|
|
46
|
+
}),
|
|
47
|
+
create: function* () {
|
|
48
|
+
const context = yield* RuleContext;
|
|
49
|
+
const checkTypeAssertion = (node) => isOutermostAssertionInChain(node) && isForbiddenAssertionChain(node) ? context.report(fromId({
|
|
50
|
+
node,
|
|
51
|
+
messageId: "chained"
|
|
52
|
+
})) : Effect.void;
|
|
53
|
+
return {
|
|
54
|
+
TSAsExpression: checkTypeAssertion,
|
|
55
|
+
TSTypeAssertion: checkTypeAssertion
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
//#endregion
|
|
60
|
+
export { noChainedTypeAssertions };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as _$_oxlint_plugins0 from "@oxlint/plugins";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-conditional-empty-object-spread.d.ts
|
|
4
|
+
/** Ban conditional empty-object spreads and autofix equivalent direct property declarations. */
|
|
5
|
+
declare const noConditionalEmptyObjectSpread: _$_oxlint_plugins0.CreateRule;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { noConditionalEmptyObjectSpread };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { fromId, replaceText, withFix } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
2
|
+
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
|
+
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
|
+
import "../vendor/effect-oxlint/index.js";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
//#region src/rules/no-conditional-empty-object-spread.ts
|
|
7
|
+
function unwrapParentheses(node) {
|
|
8
|
+
let current = node;
|
|
9
|
+
while (current.type === "ParenthesizedExpression") current = current.expression;
|
|
10
|
+
return current;
|
|
11
|
+
}
|
|
12
|
+
function isEmptyObjectExpression(node) {
|
|
13
|
+
return node.type === "ObjectExpression" && node.properties.length === 0;
|
|
14
|
+
}
|
|
15
|
+
function singleObjectProperty(node) {
|
|
16
|
+
if (node.type !== "ObjectExpression" || node.properties.length !== 1) return null;
|
|
17
|
+
const [property] = node.properties;
|
|
18
|
+
if (property?.type !== "Property" || property.kind !== "init" || property.method || property.computed) return null;
|
|
19
|
+
return property;
|
|
20
|
+
}
|
|
21
|
+
function conditionalEmptyObjectSpread(node) {
|
|
22
|
+
const conditional = unwrapParentheses(node);
|
|
23
|
+
if (conditional.type !== "ConditionalExpression") return null;
|
|
24
|
+
if (isEmptyObjectExpression(conditional.consequent)) return {
|
|
25
|
+
conditional,
|
|
26
|
+
property: singleObjectProperty(conditional.alternate)
|
|
27
|
+
};
|
|
28
|
+
if (isEmptyObjectExpression(conditional.alternate)) return {
|
|
29
|
+
conditional,
|
|
30
|
+
property: singleObjectProperty(conditional.consequent)
|
|
31
|
+
};
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function undefinedCheckedExpression(test) {
|
|
35
|
+
const binary = unwrapParentheses(test);
|
|
36
|
+
if (binary.type !== "BinaryExpression") return null;
|
|
37
|
+
if (binary.operator !== "===" && binary.operator !== "!==") return null;
|
|
38
|
+
const left = unwrapParentheses(binary.left);
|
|
39
|
+
const right = unwrapParentheses(binary.right);
|
|
40
|
+
const leftIsUndefined = left.type === "Identifier" && left.name === "undefined";
|
|
41
|
+
if (leftIsUndefined === (right.type === "Identifier" && right.name === "undefined")) return null;
|
|
42
|
+
return {
|
|
43
|
+
expression: leftIsUndefined ? right : left,
|
|
44
|
+
isDefinedWhenTrue: binary.operator === "!=="
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function canAutofixConditionalEmptyObjectSpread(sourceCode, conditional, property) {
|
|
48
|
+
const checked = undefinedCheckedExpression(conditional.test);
|
|
49
|
+
if (checked === null) return false;
|
|
50
|
+
if (conditional.consequent === property.parent !== checked.isDefinedWhenTrue) return false;
|
|
51
|
+
return sourceCode.getText(unwrapParentheses(checked.expression)) === sourceCode.getText(property.value);
|
|
52
|
+
}
|
|
53
|
+
/** Ban conditional empty-object spreads and autofix equivalent direct property declarations. */
|
|
54
|
+
const noConditionalEmptyObjectSpread = define({
|
|
55
|
+
name: "no-conditional-empty-object-spread",
|
|
56
|
+
meta: meta({
|
|
57
|
+
type: "suggestion",
|
|
58
|
+
fixable: "code",
|
|
59
|
+
description: "Disallow object spreads that conditionally spread an empty object to omit fields.",
|
|
60
|
+
messages: { avoid: "Do not use conditional empty-object spreads. Prefer a direct property or build the object in separate statements." }
|
|
61
|
+
}),
|
|
62
|
+
create: function* () {
|
|
63
|
+
const context = yield* RuleContext;
|
|
64
|
+
return { SpreadElement: (node) => {
|
|
65
|
+
if (node.parent?.type !== "ObjectExpression") return Effect.void;
|
|
66
|
+
const match = conditionalEmptyObjectSpread(node.argument);
|
|
67
|
+
if (match === null) return Effect.void;
|
|
68
|
+
const { conditional, property } = match;
|
|
69
|
+
const diagnostic = fromId({
|
|
70
|
+
node,
|
|
71
|
+
messageId: "avoid"
|
|
72
|
+
});
|
|
73
|
+
if (property === null || !canAutofixConditionalEmptyObjectSpread(context.sourceCode, conditional, property)) return context.report(diagnostic);
|
|
74
|
+
return context.report(withFix(diagnostic, replaceText(node, context.sourceCode.getText(property))));
|
|
75
|
+
} };
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
//#endregion
|
|
79
|
+
export { noConditionalEmptyObjectSpread };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as _$_oxlint_plugins0 from "@oxlint/plugins";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-known-value-widening.d.ts
|
|
4
|
+
/** Detect sound syntactic cases where a known value is explicitly widened and loses evidence. */
|
|
5
|
+
declare const noKnownValueWidening: _$_oxlint_plugins0.CreateRule;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { noKnownValueWidening };
|