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
- If the project uses JSON configuration, copy the exported rule map into `rules`; every recommended rule has `error` severity. The complexity rules carry their limit as an option, `["error", { "max": 21 }]`.
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 both the rule export barrel and the recommended preset.
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
- return { LogicalExpression: (node) => {
23
- if (node.operator !== "||") return Effect.void;
24
- if (node.parent?.type === "LogicalExpression" && node.parent.operator === "||") return Effect.void;
25
- if (isCatchIfPredicate(node)) return Effect.void;
26
- if (!Option.exists(tagComparisonsInOr(node), hasOneTaggedSubject)) return Effect.void;
27
- return context.report(make({
28
- node,
29
- message: "Use a named Predicate refinement composed from Predicate.isTagged for multiple tags."
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.15.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
+ }