oxlint-plugin-effect 0.13.0 → 0.14.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.
Files changed (46) hide show
  1. package/README.md +24 -13
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/presets/recommended.d.ts +9 -0
  4. package/dist/presets/recommended.js +10 -1
  5. package/dist/rules/_anti-slop-array-method.js +70 -0
  6. package/dist/rules/_anti-slop-dictionary-types.js +58 -53
  7. package/dist/rules/_anti-slop-function-parameters.js +27 -0
  8. package/dist/rules/_anti-slop-lexical-type-parameters.js +33 -0
  9. package/dist/rules/_anti-slop-reflect-method.js +21 -0
  10. package/dist/rules/_anti-slop-scope.js +13 -0
  11. package/dist/rules/_anti-slop-type-alias-resolution.js +146 -0
  12. package/dist/rules/_function-metrics.js +31 -31
  13. package/dist/rules/_test-files.js +8 -0
  14. package/dist/rules/index.d.ts +10 -1
  15. package/dist/rules/index.js +19 -1
  16. package/dist/rules/no-array-filter-map.d.ts +7 -0
  17. package/dist/rules/no-array-filter-map.js +39 -0
  18. package/dist/rules/no-conditional-empty-object-spread.d.ts +1 -1
  19. package/dist/rules/no-conditional-empty-object-spread.js +8 -51
  20. package/dist/rules/no-eager-acquire.d.ts +6 -0
  21. package/dist/rules/no-eager-acquire.js +92 -0
  22. package/dist/rules/no-effect-run-in-tests.d.ts +6 -0
  23. package/dist/rules/no-effect-run-in-tests.js +57 -0
  24. package/dist/rules/no-known-value-widening.js +6 -14
  25. package/dist/rules/no-module-level-mutable-state.d.ts +6 -0
  26. package/dist/rules/no-module-level-mutable-state.js +51 -0
  27. package/dist/rules/no-object-parameters.js +11 -27
  28. package/dist/rules/no-reduce-accumulator-copy.d.ts +7 -0
  29. package/dist/rules/no-reduce-accumulator-copy.js +94 -0
  30. package/dist/rules/no-reflect-apply.d.ts +7 -0
  31. package/dist/rules/no-reflect-apply.js +25 -0
  32. package/dist/rules/no-reflect-get.d.ts +7 -0
  33. package/dist/rules/no-reflect-get.js +25 -0
  34. package/dist/rules/no-runtime-typeof.js +14 -1
  35. package/dist/rules/no-shape-in-symbol-names.js +7 -1
  36. package/dist/rules/no-unknown-parameters.d.ts +1 -1
  37. package/dist/rules/no-unknown-parameters.js +23 -16
  38. package/dist/rules/no-unknown-returns.d.ts +7 -0
  39. package/dist/rules/no-unknown-returns.js +73 -0
  40. package/dist/rules/no-unknown-type-aliases.js +17 -27
  41. package/dist/rules/no-unsafe-dictionary-type.js +54 -3
  42. package/dist/rules/no-widen-then-assert.js +1 -1
  43. package/dist/rules/require-suppression-reason.d.ts +6 -0
  44. package/dist/rules/require-suppression-reason.js +87 -0
  45. package/dist/vendor/effect-oxlint/Diagnostic.js +1 -1
  46. package/package.json +3 -2
@@ -0,0 +1,146 @@
1
+ import { lexicalTypeParameterNames } from "./_anti-slop-lexical-type-parameters.js";
2
+ //#region src/rules/_anti-slop-type-alias-resolution.ts
3
+ const environmentsByProgram = /* @__PURE__ */ new WeakMap();
4
+ function isNode(value) {
5
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
6
+ }
7
+ function enclosingTypeScope(node) {
8
+ let current = node.parent;
9
+ while (current !== null) {
10
+ if (current.type === "Program" || current.type === "BlockStatement" || current.type === "TSModuleBlock" || current.type === "StaticBlock" || current.type === "SwitchStatement") return current;
11
+ current = current.parent;
12
+ }
13
+ return node;
14
+ }
15
+ function declaredTypeBinding(node) {
16
+ if (node.type === "TSTypeAliasDeclaration") return {
17
+ alias: node,
18
+ name: node.id.name
19
+ };
20
+ if (node.type === "TSInterfaceDeclaration" || node.type === "TSEnumDeclaration" || node.type === "ClassDeclaration" || node.type === "ClassExpression") return node.id === null ? null : {
21
+ alias: null,
22
+ name: node.id.name
23
+ };
24
+ if (node.type === "ImportSpecifier" || node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier") return {
25
+ alias: null,
26
+ name: node.local.name
27
+ };
28
+ return null;
29
+ }
30
+ function collectTypeBindings(node, visitorKeys, bindingsByName, aliases) {
31
+ const declared = declaredTypeBinding(node);
32
+ if (declared !== null) {
33
+ const bindings = bindingsByName.get(declared.name) ?? [];
34
+ bindings.push({
35
+ ...declared,
36
+ scope: enclosingTypeScope(node)
37
+ });
38
+ bindingsByName.set(declared.name, bindings);
39
+ if (declared.alias !== null) aliases.push(declared.alias);
40
+ }
41
+ const fields = node;
42
+ for (const key of visitorKeys[node.type] ?? []) {
43
+ const value = fields[key];
44
+ if (isNode(value)) {
45
+ collectTypeBindings(value, visitorKeys, bindingsByName, aliases);
46
+ continue;
47
+ }
48
+ if (!Array.isArray(value)) continue;
49
+ for (const child of value) if (isNode(child)) collectTypeBindings(child, visitorKeys, bindingsByName, aliases);
50
+ }
51
+ }
52
+ /** Collect every lexical type alias and competing type binding in a program. */
53
+ function createTypeAliasEnvironment(program, visitorKeys) {
54
+ const cached = environmentsByProgram.get(program);
55
+ if (cached !== void 0) return cached;
56
+ const bindingsByName = /* @__PURE__ */ new Map();
57
+ const aliases = [];
58
+ collectTypeBindings(program, visitorKeys, bindingsByName, aliases);
59
+ const environment = {
60
+ aliases,
61
+ bindingsByName,
62
+ visitorKeys
63
+ };
64
+ environmentsByProgram.set(program, environment);
65
+ return environment;
66
+ }
67
+ function ancestorDistance(ancestor, node) {
68
+ let current = node;
69
+ let distance = 0;
70
+ while (current !== null) {
71
+ if (current === ancestor) return distance;
72
+ current = current.parent;
73
+ distance += 1;
74
+ }
75
+ return null;
76
+ }
77
+ function nearestTypeBindings(name, use, environment) {
78
+ const candidates = environment.bindingsByName.get(name) ?? [];
79
+ let nearestDistance = Number.POSITIVE_INFINITY;
80
+ let nearest = [];
81
+ for (const candidate of candidates) {
82
+ const distance = ancestorDistance(candidate.scope, use);
83
+ if (distance === null || distance > nearestDistance) continue;
84
+ if (distance === nearestDistance) {
85
+ nearest.push(candidate);
86
+ continue;
87
+ }
88
+ nearestDistance = distance;
89
+ nearest = [candidate];
90
+ }
91
+ return nearest;
92
+ }
93
+ /** Resolve the nearest visible alias with this name, respecting lexical shadowing. */
94
+ function visibleTypeAlias(name, use, environment) {
95
+ if (lexicalTypeParameterNames(use, environment.visitorKeys).has(name)) return null;
96
+ const bindings = nearestTypeBindings(name, use, environment);
97
+ return bindings.length === 1 ? bindings[0]?.alias ?? null : null;
98
+ }
99
+ /** Return whether a local declaration shadows a built-in type at this use. */
100
+ function hasVisibleTypeBinding(name, use, environment) {
101
+ return lexicalTypeParameterNames(use, environment.visitorKeys).has(name) || nearestTypeBindings(name, use, environment).length > 0;
102
+ }
103
+ function typeReferenceName(type) {
104
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
105
+ }
106
+ function aliasSubstitutions(alias, reference, base) {
107
+ const parameters = alias.typeParameters?.params ?? [];
108
+ const typeArguments = reference.typeArguments?.params ?? [];
109
+ const next = new Map(base);
110
+ for (const [index, parameter] of parameters.entries()) {
111
+ const explicitArgument = typeArguments[index];
112
+ const argument = explicitArgument ?? parameter.default;
113
+ if (argument === null || argument === void 0) return null;
114
+ const argumentSubstitutions = explicitArgument === void 0 ? next : base;
115
+ next.set(parameter.name.name, {
116
+ type: argument,
117
+ substitutions: new Map(argumentSubstitutions)
118
+ });
119
+ }
120
+ return next;
121
+ }
122
+ /** Match a type after resolving visible aliases and substituting their type parameters. */
123
+ function resolvedTypeMatches(type, environment, matcher) {
124
+ const evaluate = (current, substitutions, resolvingAliases) => {
125
+ if (current.type === "TSTypeReference") {
126
+ const name = typeReferenceName(current);
127
+ if (name !== null) {
128
+ const substitution = substitutions.get(name);
129
+ if (substitution !== void 0 && !current.typeArguments?.params.length) return evaluate(substitution.type, substitution.substitutions, resolvingAliases);
130
+ const alias = visibleTypeAlias(name, current, environment);
131
+ if (alias !== null && !resolvingAliases.has(alias)) {
132
+ const nextSubstitutions = aliasSubstitutions(alias, current, substitutions);
133
+ if (nextSubstitutions !== null) {
134
+ const nextResolving = new Set(resolvingAliases);
135
+ nextResolving.add(alias);
136
+ return evaluate(alias.typeAnnotation, nextSubstitutions, nextResolving);
137
+ }
138
+ }
139
+ }
140
+ }
141
+ return matcher(current, (child) => evaluate(child, substitutions, resolvingAliases));
142
+ };
143
+ return evaluate(type, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set());
144
+ }
145
+ //#endregion
146
+ export { createTypeAliasEnvironment, hasVisibleTypeBinding, resolvedTypeMatches, visibleTypeAlias };
@@ -154,36 +154,36 @@ const templateOperand = (node) => {
154
154
  return typeof raw === "string" && raw.length > 0 ? raw : null;
155
155
  };
156
156
  /** Node types that always stand for the same operator tokens. */
157
- const fixedOperators = {
158
- ForStatement: ["for"],
159
- ForInStatement: ["for-in"],
160
- WhileStatement: ["while"],
161
- DoWhileStatement: ["do-while"],
162
- SwitchStatement: ["switch"],
163
- ReturnStatement: ["return"],
164
- ThrowStatement: ["throw"],
165
- CatchClause: ["catch"],
166
- BreakStatement: ["break"],
167
- ContinueStatement: ["continue"],
168
- LabeledStatement: [":"],
169
- DebuggerStatement: ["debugger"],
170
- WithStatement: ["with"],
171
- AssignmentPattern: ["="],
172
- NewExpression: ["new"],
173
- ArrayExpression: ["[]"],
174
- ArrayPattern: ["[]"],
175
- ObjectExpression: ["{}"],
176
- ObjectPattern: ["{}"],
177
- SpreadElement: ["..."],
178
- RestElement: ["..."],
179
- ConditionalExpression: ["?:"],
180
- SequenceExpression: [","],
181
- AwaitExpression: ["await"],
182
- ImportExpression: ["import()"],
183
- TemplateLiteral: ["`"],
184
- JSXElement: ["<>"],
185
- JSXFragment: ["<>"]
186
- };
157
+ const fixedOperators = new Map([
158
+ ["ForStatement", ["for"]],
159
+ ["ForInStatement", ["for-in"]],
160
+ ["WhileStatement", ["while"]],
161
+ ["DoWhileStatement", ["do-while"]],
162
+ ["SwitchStatement", ["switch"]],
163
+ ["ReturnStatement", ["return"]],
164
+ ["ThrowStatement", ["throw"]],
165
+ ["CatchClause", ["catch"]],
166
+ ["BreakStatement", ["break"]],
167
+ ["ContinueStatement", ["continue"]],
168
+ ["LabeledStatement", [":"]],
169
+ ["DebuggerStatement", ["debugger"]],
170
+ ["WithStatement", ["with"]],
171
+ ["AssignmentPattern", ["="]],
172
+ ["NewExpression", ["new"]],
173
+ ["ArrayExpression", ["[]"]],
174
+ ["ArrayPattern", ["[]"]],
175
+ ["ObjectExpression", ["{}"]],
176
+ ["ObjectPattern", ["{}"]],
177
+ ["SpreadElement", ["..."]],
178
+ ["RestElement", ["..."]],
179
+ ["ConditionalExpression", ["?:"]],
180
+ ["SequenceExpression", [","]],
181
+ ["AwaitExpression", ["await"]],
182
+ ["ImportExpression", ["import()"]],
183
+ ["TemplateLiteral", ["`"]],
184
+ ["JSXElement", ["<>"]],
185
+ ["JSXFragment", ["<>"]]
186
+ ]);
187
187
  /** Node types whose operator token carries the operator itself. */
188
188
  const operatorCarriers = new Set([
189
189
  "BinaryExpression",
@@ -229,7 +229,7 @@ const expressionOperators = (node) => {
229
229
  };
230
230
  /** Every operator token a node stands for. */
231
231
  const operatorsOf = (node) => [
232
- ...fixedOperators[node.type] ?? [],
232
+ ...fixedOperators.get(node.type) ?? [],
233
233
  ...statementOperators(node),
234
234
  ...expressionOperators(node)
235
235
  ];
@@ -0,0 +1,8 @@
1
+ //#region src/rules/_test-files.ts
2
+ /** Test and spec modules: `*.test.*` and `*.spec.*` with a JS or TS extension. */
3
+ const testFilePattern = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
4
+ const isTestFile = (filename) => testFilePattern.test(filename);
5
+ /** Visitor for a file a rule does not apply to. */
6
+ const skipFile = {};
7
+ //#endregion
8
+ export { isTestFile, skipFile };
@@ -2,15 +2,19 @@ import { noEffectBind } from "./no-effect-bind.js";
2
2
  import { noEffectDo } from "./no-effect-do.js";
3
3
  import { maxCognitiveComplexity } from "./max-cognitive-complexity.js";
4
4
  import { maxHalsteadDifficulty } from "./max-halstead-difficulty.js";
5
+ import { noArrayFilterMap } from "./no-array-filter-map.js";
5
6
  import { noAs } from "./no-as.js";
6
7
  import { noAsyncFunction } from "./no-async-function.js";
7
8
  import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
8
9
  import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
9
10
  import { noDynamicImports } from "./no-dynamic-imports.js";
11
+ import { noEagerAcquire } from "./no-eager-acquire.js";
12
+ import { noEffectRunInTests } from "./no-effect-run-in-tests.js";
10
13
  import { noGlobals } from "./no-globals.js";
11
14
  import { noInlineProvide } from "./no-inline-provide.js";
12
15
  import { noKnownValueWidening } from "./no-known-value-widening.js";
13
16
  import { noManagedRuntimeInEffect } from "./no-managed-runtime-in-effect.js";
17
+ import { noModuleLevelMutableState } from "./no-module-level-mutable-state.js";
14
18
  import { noModuleMocks } from "./no-module-mocks.js";
15
19
  import { noNestedEffectGen } from "./no-nested-effect-gen.js";
16
20
  import { noNewError } from "./no-new-error.js";
@@ -19,6 +23,9 @@ import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
19
23
  import { noNullish } from "./no-nullish.js";
20
24
  import { noObjectParameters } from "./no-object-parameters.js";
21
25
  import { noPerCallCacheConstruction } from "./no-per-call-cache-construction.js";
26
+ import { noReduceAccumulatorCopy } from "./no-reduce-accumulator-copy.js";
27
+ import { noReflectApply } from "./no-reflect-apply.js";
28
+ import { noReflectGet } from "./no-reflect-get.js";
22
29
  import { noRunCollectOnUnboundedStream } from "./no-run-collect-on-unbounded-stream.js";
23
30
  import { noRuntimeTypeof } from "./no-runtime-typeof.js";
24
31
  import { noSequentialEffectAll } from "./no-sequential-effect-all.js";
@@ -31,6 +38,7 @@ import { noTryCatch } from "./no-try-catch.js";
31
38
  import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
32
39
  import { noUnboundedRetry } from "./no-unbounded-retry.js";
33
40
  import { noUnknownParameters } from "./no-unknown-parameters.js";
41
+ import { noUnknownReturns } from "./no-unknown-returns.js";
34
42
  import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
35
43
  import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
36
44
  import { noWidenThenAssert } from "./no-widen-then-assert.js";
@@ -40,10 +48,11 @@ import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
40
48
  import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
41
49
  import { preferServiceOf } from "./prefer-service-of.js";
42
50
  import { requireNamedEffectFn } from "./require-named-effect-fn.js";
51
+ import { requireSuppressionReason } from "./require-suppression-reason.js";
43
52
 
44
53
  //#region src/rules/index.d.ts
45
54
  declare namespace index_d_exports {
46
- export { maxCognitiveComplexity, maxHalsteadDifficulty, noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEffectBind, noEffectDo, noGlobals, noInlineProvide, noKnownValueWidening, noManagedRuntimeInEffect, noModuleMocks, noNestedEffectGen, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noPerCallCacheConstruction, noRunCollectOnUnboundedStream, noRuntimeTypeof, noSequentialEffectAll, noShapeInSymbolNames, noSilentCatchAll, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnboundedConcurrency, noUnboundedRetry, noUnknownParameters, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged, preferServiceOf, requireNamedEffectFn };
55
+ export { maxCognitiveComplexity, maxHalsteadDifficulty, noArrayFilterMap, noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEagerAcquire, noEffectBind, noEffectDo, noEffectRunInTests, noGlobals, noInlineProvide, noKnownValueWidening, noManagedRuntimeInEffect, noModuleLevelMutableState, noModuleMocks, noNestedEffectGen, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noPerCallCacheConstruction, noReduceAccumulatorCopy, noReflectApply, noReflectGet, noRunCollectOnUnboundedStream, noRuntimeTypeof, noSequentialEffectAll, noShapeInSymbolNames, noSilentCatchAll, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnboundedConcurrency, noUnboundedRetry, noUnknownParameters, noUnknownReturns, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged, preferServiceOf, requireNamedEffectFn, requireSuppressionReason };
47
56
  }
48
57
  //#endregion
49
58
  export { index_d_exports };
@@ -3,15 +3,19 @@ import { noEffectBind } from "./no-effect-bind.js";
3
3
  import { noEffectDo } from "./no-effect-do.js";
4
4
  import { maxCognitiveComplexity } from "./max-cognitive-complexity.js";
5
5
  import { maxHalsteadDifficulty } from "./max-halstead-difficulty.js";
6
+ import { noArrayFilterMap } from "./no-array-filter-map.js";
6
7
  import { noAs } from "./no-as.js";
7
8
  import { noAsyncFunction } from "./no-async-function.js";
8
9
  import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
9
10
  import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
10
11
  import { noDynamicImports } from "./no-dynamic-imports.js";
12
+ import { noEagerAcquire } from "./no-eager-acquire.js";
13
+ import { noEffectRunInTests } from "./no-effect-run-in-tests.js";
11
14
  import { noGlobals } from "./no-globals.js";
12
15
  import { noInlineProvide } from "./no-inline-provide.js";
13
16
  import { noKnownValueWidening } from "./no-known-value-widening.js";
14
17
  import { noManagedRuntimeInEffect } from "./no-managed-runtime-in-effect.js";
18
+ import { noModuleLevelMutableState } from "./no-module-level-mutable-state.js";
15
19
  import { noModuleMocks } from "./no-module-mocks.js";
16
20
  import { noNestedEffectGen } from "./no-nested-effect-gen.js";
17
21
  import { noNewError } from "./no-new-error.js";
@@ -20,6 +24,9 @@ import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
20
24
  import { noNullish } from "./no-nullish.js";
21
25
  import { noObjectParameters } from "./no-object-parameters.js";
22
26
  import { noPerCallCacheConstruction } from "./no-per-call-cache-construction.js";
27
+ import { noReduceAccumulatorCopy } from "./no-reduce-accumulator-copy.js";
28
+ import { noReflectApply } from "./no-reflect-apply.js";
29
+ import { noReflectGet } from "./no-reflect-get.js";
23
30
  import { noRunCollectOnUnboundedStream } from "./no-run-collect-on-unbounded-stream.js";
24
31
  import { noRuntimeTypeof } from "./no-runtime-typeof.js";
25
32
  import { noSequentialEffectAll } from "./no-sequential-effect-all.js";
@@ -32,6 +39,7 @@ import { noTryCatch } from "./no-try-catch.js";
32
39
  import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
33
40
  import { noUnboundedRetry } from "./no-unbounded-retry.js";
34
41
  import { noUnknownParameters } from "./no-unknown-parameters.js";
42
+ import { noUnknownReturns } from "./no-unknown-returns.js";
35
43
  import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
36
44
  import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
37
45
  import { noWidenThenAssert } from "./no-widen-then-assert.js";
@@ -41,21 +49,26 @@ import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
41
49
  import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
42
50
  import { preferServiceOf } from "./prefer-service-of.js";
43
51
  import { requireNamedEffectFn } from "./require-named-effect-fn.js";
52
+ import { requireSuppressionReason } from "./require-suppression-reason.js";
44
53
  //#region src/rules/index.ts
45
54
  var rules_exports = /* @__PURE__ */ __exportAll({
46
55
  maxCognitiveComplexity: () => maxCognitiveComplexity,
47
56
  maxHalsteadDifficulty: () => maxHalsteadDifficulty,
57
+ noArrayFilterMap: () => noArrayFilterMap,
48
58
  noAs: () => noAs,
49
59
  noAsyncFunction: () => noAsyncFunction,
50
60
  noChainedTypeAssertions: () => noChainedTypeAssertions,
51
61
  noConditionalEmptyObjectSpread: () => noConditionalEmptyObjectSpread,
52
62
  noDynamicImports: () => noDynamicImports,
63
+ noEagerAcquire: () => noEagerAcquire,
53
64
  noEffectBind: () => noEffectBind,
54
65
  noEffectDo: () => noEffectDo,
66
+ noEffectRunInTests: () => noEffectRunInTests,
55
67
  noGlobals: () => noGlobals,
56
68
  noInlineProvide: () => noInlineProvide,
57
69
  noKnownValueWidening: () => noKnownValueWidening,
58
70
  noManagedRuntimeInEffect: () => noManagedRuntimeInEffect,
71
+ noModuleLevelMutableState: () => noModuleLevelMutableState,
59
72
  noModuleMocks: () => noModuleMocks,
60
73
  noNestedEffectGen: () => noNestedEffectGen,
61
74
  noNewError: () => noNewError,
@@ -64,6 +77,9 @@ var rules_exports = /* @__PURE__ */ __exportAll({
64
77
  noNullish: () => noNullish,
65
78
  noObjectParameters: () => noObjectParameters,
66
79
  noPerCallCacheConstruction: () => noPerCallCacheConstruction,
80
+ noReduceAccumulatorCopy: () => noReduceAccumulatorCopy,
81
+ noReflectApply: () => noReflectApply,
82
+ noReflectGet: () => noReflectGet,
67
83
  noRunCollectOnUnboundedStream: () => noRunCollectOnUnboundedStream,
68
84
  noRuntimeTypeof: () => noRuntimeTypeof,
69
85
  noSequentialEffectAll: () => noSequentialEffectAll,
@@ -76,6 +92,7 @@ var rules_exports = /* @__PURE__ */ __exportAll({
76
92
  noUnboundedConcurrency: () => noUnboundedConcurrency,
77
93
  noUnboundedRetry: () => noUnboundedRetry,
78
94
  noUnknownParameters: () => noUnknownParameters,
95
+ noUnknownReturns: () => noUnknownReturns,
79
96
  noUnknownTypeAliases: () => noUnknownTypeAliases,
80
97
  noUnsafeDictionaryType: () => noUnsafeDictionaryType,
81
98
  noWidenThenAssert: () => noWidenThenAssert,
@@ -84,7 +101,8 @@ var rules_exports = /* @__PURE__ */ __exportAll({
84
101
  preferMatchTagsExhaustive: () => preferMatchTagsExhaustive,
85
102
  preferPredicateIsTagged: () => preferPredicateIsTagged,
86
103
  preferServiceOf: () => preferServiceOf,
87
- requireNamedEffectFn: () => requireNamedEffectFn
104
+ requireNamedEffectFn: () => requireNamedEffectFn,
105
+ requireSuppressionReason: () => requireSuppressionReason
88
106
  });
89
107
  //#endregion
90
108
  export { rules_exports };
@@ -0,0 +1,7 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-array-filter-map.d.ts
4
+ /** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
5
+ declare const noArrayFilterMap: _$_oxlint_plugins0.CreateRule;
6
+ //#endregion
7
+ export { noArrayFilterMap };
@@ -0,0 +1,39 @@
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 { arrayMethodTarget, isKnownArrayExpression, unwrapArrayExpression } from "./_anti-slop-array-method.js";
6
+ import * as Effect from "effect/Effect";
7
+ //#region src/rules/no-array-filter-map.ts
8
+ /** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
9
+ const noArrayFilterMap = define({
10
+ name: "no-array-filter-map",
11
+ meta: meta({
12
+ type: "suggestion",
13
+ description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation.",
14
+ messages: { arrayFilterMap: "Avoid consecutive array `{{first}}` and `{{second}}` passes. Prefer `.values().{{first}}(...).{{second}}(...).toArray()` where iterator helpers are supported, or a single `flatMap`/mutating reducer. Preserve callback ordering, indexes, and filtering semantics." }
15
+ }),
16
+ create: function* () {
17
+ const context = yield* RuleContext;
18
+ return { CallExpression: (node) => {
19
+ const outer = arrayMethodTarget(node.callee);
20
+ if (outer === null || outer.name !== "map" && outer.name !== "filter") return Effect.void;
21
+ const innerCall = unwrapArrayExpression(outer.object);
22
+ if (innerCall.type !== "CallExpression") return Effect.void;
23
+ const inner = arrayMethodTarget(innerCall.callee);
24
+ const pairedName = outer.name === "map" ? "filter" : "map";
25
+ if (inner === null || inner.name !== pairedName) return Effect.void;
26
+ if (!isKnownArrayExpression(context.sourceCode, inner.object)) return Effect.void;
27
+ return context.report(fromId({
28
+ node,
29
+ messageId: "arrayFilterMap",
30
+ data: {
31
+ first: inner.name,
32
+ second: outer.name
33
+ }
34
+ }));
35
+ } };
36
+ }
37
+ });
38
+ //#endregion
39
+ export { noArrayFilterMap };
@@ -1,7 +1,7 @@
1
1
  import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
2
 
3
3
  //#region src/rules/no-conditional-empty-object-spread.d.ts
4
- /** Ban conditional empty-object spreads and autofix equivalent direct property declarations. */
4
+ /** Ban conditional empty-object spreads without changing their omission semantics. */
5
5
  declare const noConditionalEmptyObjectSpread: _$_oxlint_plugins0.CreateRule;
6
6
  //#endregion
7
7
  export { noConditionalEmptyObjectSpread };
@@ -1,4 +1,4 @@
1
- import { fromId, replaceText, withFix } from "../vendor/effect-oxlint/Diagnostic.js";
1
+ import { fromId } 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";
@@ -12,67 +12,24 @@ function unwrapParentheses(node) {
12
12
  function isEmptyObjectExpression(node) {
13
13
  return node.type === "ObjectExpression" && node.properties.length === 0;
14
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) {
15
+ function isConditionalEmptyObjectSpread(node) {
22
16
  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);
17
+ return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
52
18
  }
53
- /** Ban conditional empty-object spreads and autofix equivalent direct property declarations. */
19
+ /** Ban conditional empty-object spreads without changing their omission semantics. */
54
20
  const noConditionalEmptyObjectSpread = define({
55
21
  name: "no-conditional-empty-object-spread",
56
22
  meta: meta({
57
23
  type: "suggestion",
58
- fixable: "code",
59
24
  description: "Disallow object spreads that conditionally spread an empty object to omit fields.",
60
25
  messages: { avoid: "Do not use conditional empty-object spreads. Prefer a direct property or build the object in separate statements." }
61
26
  }),
62
27
  create: function* () {
63
28
  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
- } };
29
+ return { SpreadElement: (node) => node.parent?.type === "ObjectExpression" && isConditionalEmptyObjectSpread(node.argument) ? context.report(fromId({
30
+ node,
31
+ messageId: "avoid"
32
+ })) : Effect.void };
76
33
  }
77
34
  });
78
35
  //#endregion
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-eager-acquire.d.ts
4
+ declare const noEagerAcquire: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noEagerAcquire };
@@ -0,0 +1,92 @@
1
+ import { make } 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 { findVariableUp } from "../vendor/effect-oxlint/Scope.js";
5
+ import "../vendor/effect-oxlint/index.js";
6
+ import { importedNamespaces, isStaticCall, visibleNamespaces } from "./_effect-namespaces.js";
7
+ import * as Option from "effect/Option";
8
+ import * as Effect from "effect/Effect";
9
+ //#region src/rules/no-eager-acquire.ts
10
+ const unwrapExpression = (node) => {
11
+ let current = node;
12
+ while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion") current = current.expression;
13
+ return current;
14
+ };
15
+ const isWithin = (node, ancestor) => {
16
+ let current = node;
17
+ while (current !== null) {
18
+ if (current === ancestor) return true;
19
+ current = current.parent;
20
+ }
21
+ return false;
22
+ };
23
+ const singleReturnedValue = (thunk) => {
24
+ const body = thunk.body;
25
+ if (body === null) return void 0;
26
+ if (body.type !== "BlockStatement") return body;
27
+ const returns = body.body.filter((statement) => statement.type === "ReturnStatement");
28
+ const [returned] = returns;
29
+ if (returns.length !== 1 || returned === void 0) return void 0;
30
+ return returned.argument ?? void 0;
31
+ };
32
+ const noEagerAcquire = define({
33
+ name: "no-eager-acquire",
34
+ meta: meta({
35
+ type: "problem",
36
+ description: "Construct a resource inside the Effect.acquireRelease acquire Effect, not before it."
37
+ }),
38
+ create: function* () {
39
+ const ctx = yield* RuleContext;
40
+ const effectNamespaces = new Set(["Effect"]);
41
+ /** Whether the value was bound outside the thunk, so it exists before acquire runs. */
42
+ const isCaptured = (expression, thunk, seen) => {
43
+ const value = unwrapExpression(expression);
44
+ if (value.type === "ThisExpression") return true;
45
+ if (value.type === "MemberExpression") return value.object.type !== "Super" && isCaptured(value.object, thunk, seen);
46
+ if (value.type !== "Identifier") return false;
47
+ return Option.match(findVariableUp(ctx.sourceCode.getScope(value), value.name), {
48
+ onNone: () => true,
49
+ onSome: (variable) => {
50
+ const [definition] = variable.defs;
51
+ if (definition === void 0) return true;
52
+ if (seen.has(definition.node)) return false;
53
+ seen.add(definition.node);
54
+ if (!isWithin(definition.node, thunk)) return true;
55
+ if (definition.node.type === "VariableDeclarator") return definition.node.init !== null && isCaptured(definition.node.init, thunk, seen);
56
+ const defaulted = thunk.params.find((parameter) => parameter.type === "AssignmentPattern" && parameter.left.type === "Identifier" && parameter.left.name === value.name);
57
+ return defaulted !== void 0 && isCaptured(defaulted.right, thunk, seen);
58
+ }
59
+ });
60
+ };
61
+ const returnsCapturedHandle = (expression, effects) => {
62
+ if (!isStaticCall(expression, effects, "sync")) return false;
63
+ const [argument] = expression.arguments;
64
+ if (argument === void 0 || argument.type === "SpreadElement") return false;
65
+ const thunk = unwrapExpression(argument);
66
+ if (thunk.type !== "ArrowFunctionExpression" && thunk.type !== "FunctionExpression") return false;
67
+ const returned = singleReturnedValue(thunk);
68
+ return returned !== void 0 && isCaptured(returned, thunk, /* @__PURE__ */ new Set());
69
+ };
70
+ return {
71
+ ImportDeclaration: (node) => {
72
+ for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
73
+ return Effect.void;
74
+ },
75
+ CallExpression: (node) => {
76
+ if (node.callee.type === "Super") return Effect.void;
77
+ const effects = visibleNamespaces(ctx, node, effectNamespaces);
78
+ if (!isStaticCall(node, effects, "acquireRelease")) return Effect.void;
79
+ const [acquire] = node.arguments;
80
+ if (acquire === void 0 || acquire.type === "SpreadElement") return Effect.void;
81
+ const expression = unwrapExpression(acquire);
82
+ if (!isStaticCall(expression, effects, "succeed") && !returnsCapturedHandle(expression, effects)) return Effect.void;
83
+ return ctx.report(make({
84
+ node: acquire,
85
+ message: "Build the resource inside acquire (Effect.sync(() => new Resource()), Effect.tryPromise, or another lazy Effect). Effect.succeed(handle) or Effect.sync(() => handle) creates it before acquire runs, so an interruption in between leaks it."
86
+ }));
87
+ }
88
+ };
89
+ }
90
+ });
91
+ //#endregion
92
+ export { noEagerAcquire };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-effect-run-in-tests.d.ts
4
+ declare const noEffectRunInTests: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noEffectRunInTests };