oxlint-plugin-effect 0.15.0 → 0.16.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
CHANGED
|
@@ -21,7 +21,19 @@ export default {
|
|
|
21
21
|
};
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
A JSON configuration extends the generated preset file. It loads the plugin and enables every recommended rule, so new rules arrive with each release:
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
{
|
|
28
|
+
"extends": ["./node_modules/oxlint-plugin-effect/presets/recommended.json"],
|
|
29
|
+
"rules": {
|
|
30
|
+
// Local choices override the preset.
|
|
31
|
+
"effect/noTernary": "off",
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Every recommended rule has `error` severity. The complexity rules carry their limit as an option, `["error", { "max": 21 }]`.
|
|
25
37
|
|
|
26
38
|
## Recommended Rules
|
|
27
39
|
|
|
@@ -55,8 +67,8 @@ If the project uses JSON configuration, copy the exported rule map into `rules`;
|
|
|
55
67
|
| `effect/noEffectBind` | Bans Effect.bind |
|
|
56
68
|
| `effect/preferCatchTag` | Replaces manual `_tag` predicates and `catchAll` dispatch with tagged recovery |
|
|
57
69
|
| `effect/preferEffectFn` | Requires `Effect.fn` for a generator operation that adds a span |
|
|
58
|
-
| `effect/preferMatchTagsExhaustive` | Requires exhaustive `Match` for return-only `_tag` switches and if chains
|
|
59
|
-
| `effect/preferPredicateIsTagged` | Replaces combined `_tag` comparisons with a named `Predicate` refinement
|
|
70
|
+
| `effect/preferMatchTagsExhaustive` | Requires exhaustive `Match` for return-only `_tag` switches and if chains in Effect files |
|
|
71
|
+
| `effect/preferPredicateIsTagged` | Replaces combined `_tag` comparisons with a named `Predicate` refinement in Effect files |
|
|
60
72
|
| `effect/preferServiceOf` | Checks inline Layer implementations through `Service.of` |
|
|
61
73
|
| `effect/requireNamedEffectFn` | Requires stable names for `Effect.fn` operations |
|
|
62
74
|
| `effect/requireSuppressionReason` | Requires lint, Effect, and TS suppressions to name their target and give a `--` reason |
|
|
@@ -204,7 +216,7 @@ bun run codegen
|
|
|
204
216
|
bun run add-rule -- no-example --dry-run
|
|
205
217
|
```
|
|
206
218
|
|
|
207
|
-
`bun run codegen` owns
|
|
219
|
+
`bun run codegen` owns the rule export barrel and both forms of the recommended preset: `src/presets/recommended.ts` and `presets/recommended.json`.
|
|
208
220
|
`bun run gate` fails when either generated file is stale.
|
|
209
221
|
|
|
210
222
|
## License
|
|
@@ -21,5 +21,10 @@ const visibleNamespaces = (ctx, node, namespaces) => new Set([...namespaces].fil
|
|
|
21
21
|
})));
|
|
22
22
|
const isStaticMember = (node, namespaces, property) => node.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && namespaces.has(node.object.name) && node.property.type === "Identifier" && node.property.name === property;
|
|
23
23
|
const isStaticCall = (node, namespaces, property) => node.type === "CallExpression" && node.callee.type !== "Super" && isStaticMember(node.callee, namespaces, property);
|
|
24
|
+
/** Whether an import loads the effect package, one of its subpaths, or an @effect package. */
|
|
25
|
+
const isEffectPackageImport = (node) => {
|
|
26
|
+
const source = node.source.value;
|
|
27
|
+
return source === "effect" || source.startsWith("effect/") || source.startsWith("@effect/");
|
|
28
|
+
};
|
|
24
29
|
//#endregion
|
|
25
|
-
export { importedNamespaces, isStaticCall, isStaticMember, visibleNamespaces };
|
|
30
|
+
export { importedNamespaces, isEffectPackageImport, isStaticCall, isStaticMember, visibleNamespaces };
|
|
@@ -2,6 +2,7 @@ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
|
2
2
|
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
3
|
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
4
|
import "../vendor/effect-oxlint/index.js";
|
|
5
|
+
import { isEffectPackageImport } from "./_effect-namespaces.js";
|
|
5
6
|
import { hasOneTaggedSubject, isInsideCatchAllHandler, tagComparison, taggedSwitchSubject } from "./_tagged-control-flow.js";
|
|
6
7
|
import * as Arr from "effect/Array";
|
|
7
8
|
import * as Option from "effect/Option";
|
|
@@ -66,16 +67,21 @@ const preferMatchTagsExhaustive = define({
|
|
|
66
67
|
}),
|
|
67
68
|
create: function* () {
|
|
68
69
|
const context = yield* RuleContext;
|
|
70
|
+
const effectImports = /* @__PURE__ */ new Set();
|
|
69
71
|
return {
|
|
72
|
+
ImportDeclaration: (node) => {
|
|
73
|
+
if (isEffectPackageImport(node)) effectImports.add(node.source.value);
|
|
74
|
+
return Effect.void;
|
|
75
|
+
},
|
|
70
76
|
SwitchStatement: (node) => {
|
|
71
|
-
if (!isCompleteSwitchTransformation(node) || isInsideCatchAllHandler(node)) return Effect.void;
|
|
77
|
+
if (effectImports.size === 0 || !isCompleteSwitchTransformation(node) || isInsideCatchAllHandler(node)) return Effect.void;
|
|
72
78
|
return context.report(make({
|
|
73
79
|
node,
|
|
74
80
|
message: "Use Match.type with Match.tagsExhaustive so a new tagged variant requires a new case."
|
|
75
81
|
}));
|
|
76
82
|
},
|
|
77
83
|
IfStatement: (node) => {
|
|
78
|
-
if (!isCompleteIfTransformation(node) && !isCompleteSequentialIfTransformation(node) || isInsideCatchAllHandler(node)) return Effect.void;
|
|
84
|
+
if (effectImports.size === 0 || !isCompleteIfTransformation(node) && !isCompleteSequentialIfTransformation(node) || isInsideCatchAllHandler(node)) return Effect.void;
|
|
79
85
|
return context.report(make({
|
|
80
86
|
node,
|
|
81
87
|
message: "Use Match.type with Match.tagsExhaustive so a new tagged variant requires a new branch."
|
|
@@ -2,6 +2,7 @@ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
|
2
2
|
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
3
|
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
4
|
import "../vendor/effect-oxlint/index.js";
|
|
5
|
+
import { isEffectPackageImport } from "./_effect-namespaces.js";
|
|
5
6
|
import { effectCallbackLocation, hasOneTaggedSubject, tagComparisonsInOr } from "./_tagged-control-flow.js";
|
|
6
7
|
import * as Option from "effect/Option";
|
|
7
8
|
import * as Effect from "effect/Effect";
|
|
@@ -19,16 +20,23 @@ const preferPredicateIsTagged = define({
|
|
|
19
20
|
}),
|
|
20
21
|
create: function* () {
|
|
21
22
|
const context = yield* RuleContext;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
const effectImports = /* @__PURE__ */ new Set();
|
|
24
|
+
return {
|
|
25
|
+
ImportDeclaration: (node) => {
|
|
26
|
+
if (isEffectPackageImport(node)) effectImports.add(node.source.value);
|
|
27
|
+
return Effect.void;
|
|
28
|
+
},
|
|
29
|
+
LogicalExpression: (node) => {
|
|
30
|
+
if (effectImports.size === 0 || node.operator !== "||") return Effect.void;
|
|
31
|
+
if (node.parent?.type === "LogicalExpression" && node.parent.operator === "||") return Effect.void;
|
|
32
|
+
if (isCatchIfPredicate(node)) return Effect.void;
|
|
33
|
+
if (!Option.exists(tagComparisonsInOr(node), hasOneTaggedSubject)) return Effect.void;
|
|
34
|
+
return context.report(make({
|
|
35
|
+
node,
|
|
36
|
+
message: "Use a named Predicate refinement composed from Predicate.isTagged for multiple tags."
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
32
40
|
}
|
|
33
41
|
});
|
|
34
42
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-effect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "oxlint plugin with Effect-specific lint rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"presets",
|
|
12
13
|
"THIRD_PARTY_NOTICES.md"
|
|
13
14
|
],
|
|
14
15
|
"type": "module",
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
"default": "./dist/vendor/effect-oxlint/index.js"
|
|
32
33
|
}
|
|
33
34
|
},
|
|
35
|
+
"./presets/recommended.json": "./presets/recommended.json",
|
|
34
36
|
"./presets/*": {
|
|
35
37
|
"import": {
|
|
36
38
|
"types": "./dist/presets/*.d.ts",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jsPlugins": ["oxlint-plugin-effect/plugin"],
|
|
3
|
+
"rules": {
|
|
4
|
+
"complexity": ["error", { "max": 21 }],
|
|
5
|
+
"effect/maxCognitiveComplexity": ["error", { "max": 21 }],
|
|
6
|
+
"effect/maxHalsteadDifficulty": ["error", { "max": 79 }],
|
|
7
|
+
"effect/noArrayFilterMap": "error",
|
|
8
|
+
"effect/noAs": "error",
|
|
9
|
+
"effect/noAsyncFunction": "error",
|
|
10
|
+
"effect/noChainedTypeAssertions": "error",
|
|
11
|
+
"effect/noConditionalEmptyObjectSpread": "error",
|
|
12
|
+
"effect/noDynamicImports": "error",
|
|
13
|
+
"effect/noEagerAcquire": "error",
|
|
14
|
+
"effect/noEffectBind": "error",
|
|
15
|
+
"effect/noEffectDo": "error",
|
|
16
|
+
"effect/noEffectRunInTests": "error",
|
|
17
|
+
"effect/noGlobals": "error",
|
|
18
|
+
"effect/noInlineProvide": "error",
|
|
19
|
+
"effect/noKnownValueWidening": "error",
|
|
20
|
+
"effect/noManagedRuntimeInEffect": "error",
|
|
21
|
+
"effect/noModuleLevelMutableState": "error",
|
|
22
|
+
"effect/noModuleMocks": "error",
|
|
23
|
+
"effect/noNestedEffectGen": "error",
|
|
24
|
+
"effect/noNewError": "error",
|
|
25
|
+
"effect/noNewPromise": "error",
|
|
26
|
+
"effect/noNodeBuiltinImport": "error",
|
|
27
|
+
"effect/noNullish": "error",
|
|
28
|
+
"effect/noObjectParameters": "error",
|
|
29
|
+
"effect/noPerCallCacheConstruction": "error",
|
|
30
|
+
"effect/noReduceAccumulatorCopy": "error",
|
|
31
|
+
"effect/noReflectApply": "error",
|
|
32
|
+
"effect/noReflectGet": "error",
|
|
33
|
+
"effect/noRunCollectOnUnboundedStream": "error",
|
|
34
|
+
"effect/noRuntimeTypeof": "error",
|
|
35
|
+
"effect/noSequentialEffectAll": "error",
|
|
36
|
+
"effect/noShapeInSymbolNames": "error",
|
|
37
|
+
"effect/noSilentCatchAll": "error",
|
|
38
|
+
"effect/noTernary": "error",
|
|
39
|
+
"effect/noTestGlobals": "error",
|
|
40
|
+
"effect/noTestLifecycleHooks": "error",
|
|
41
|
+
"effect/noThrowStatement": "error",
|
|
42
|
+
"effect/noTryCatch": "error",
|
|
43
|
+
"effect/noUnboundedConcurrency": "error",
|
|
44
|
+
"effect/noUnboundedRetry": "error",
|
|
45
|
+
"effect/noUnknownParameters": "error",
|
|
46
|
+
"effect/noUnknownReturns": "error",
|
|
47
|
+
"effect/noUnknownTypeAliases": "error",
|
|
48
|
+
"effect/noUnsafeDictionaryType": "error",
|
|
49
|
+
"effect/noWidenThenAssert": "error",
|
|
50
|
+
"effect/preferCatchTag": "error",
|
|
51
|
+
"effect/preferEffectFn": "error",
|
|
52
|
+
"effect/preferMatchTagsExhaustive": "error",
|
|
53
|
+
"effect/preferPredicateIsTagged": "error",
|
|
54
|
+
"effect/preferServiceOf": "error",
|
|
55
|
+
"effect/requireNamedEffectFn": "error",
|
|
56
|
+
"effect/requireSuppressionReason": "error"
|
|
57
|
+
}
|
|
58
|
+
}
|