oxlint-plugin-effect 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -14
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/presets/recommended.d.ts +10 -0
- package/dist/presets/recommended.js +11 -1
- package/dist/rules/_anti-slop-array-method.js +78 -0
- package/dist/rules/_anti-slop-dictionary-types.js +198 -150
- package/dist/rules/_anti-slop-function-parameters.js +30 -0
- package/dist/rules/_anti-slop-lexical-type-parameters.js +25 -0
- package/dist/rules/_anti-slop-reflect-method.js +25 -0
- package/dist/rules/_anti-slop-scope.js +15 -0
- package/dist/rules/_anti-slop-type-alias-resolution.js +139 -0
- package/dist/rules/_ast-ancestors.js +14 -0
- package/dist/rules/_ast-children.js +18 -0
- package/dist/rules/_call-arguments.js +11 -0
- package/dist/rules/_effect-context.js +25 -20
- package/dist/rules/_function-metrics.js +94 -75
- package/dist/rules/_tagged-control-flow.js +38 -60
- package/dist/rules/_test-apis.js +47 -0
- package/dist/rules/_test-files.js +8 -0
- package/dist/rules/index.d.ts +11 -1
- package/dist/rules/index.js +21 -1
- package/dist/rules/no-array-filter-map.d.ts +7 -0
- package/dist/rules/no-array-filter-map.js +49 -0
- package/dist/rules/no-chained-type-assertions.js +7 -4
- package/dist/rules/no-conditional-empty-object-spread.d.ts +1 -1
- package/dist/rules/no-conditional-empty-object-spread.js +7 -47
- package/dist/rules/no-dynamic-imports.js +33 -25
- package/dist/rules/no-eager-acquire.d.ts +6 -0
- package/dist/rules/no-eager-acquire.js +98 -0
- package/dist/rules/no-effect-run-in-tests.d.ts +6 -0
- package/dist/rules/no-effect-run-in-tests.js +60 -0
- package/dist/rules/no-globals.js +42 -31
- package/dist/rules/no-known-value-widening.js +67 -50
- package/dist/rules/no-module-level-mutable-state.d.ts +6 -0
- package/dist/rules/no-module-level-mutable-state.js +47 -0
- package/dist/rules/no-module-mocks.js +37 -48
- package/dist/rules/no-new-promise.js +6 -2
- package/dist/rules/no-node-builtin-import.js +56 -33
- package/dist/rules/no-nullish.js +5 -1
- package/dist/rules/no-object-parameters.js +16 -30
- package/dist/rules/no-per-call-cache-construction.js +2 -2
- package/dist/rules/no-reduce-accumulator-copy.d.ts +7 -0
- package/dist/rules/no-reduce-accumulator-copy.js +106 -0
- package/dist/rules/no-reflect-apply.d.ts +7 -0
- package/dist/rules/no-reflect-apply.js +28 -0
- package/dist/rules/no-reflect-get.d.ts +7 -0
- package/dist/rules/no-reflect-get.js +28 -0
- package/dist/rules/no-run-collect-on-unbounded-stream.js +23 -25
- package/dist/rules/no-runtime-typeof.js +21 -4
- package/dist/rules/no-sequential-effect-all.js +18 -11
- package/dist/rules/no-shape-in-symbol-names.js +15 -5
- package/dist/rules/no-silent-catch-all.js +20 -14
- package/dist/rules/no-test-globals.d.ts +6 -0
- package/dist/rules/no-test-globals.js +147 -0
- package/dist/rules/no-test-lifecycle-hooks.js +4 -2
- package/dist/rules/no-unbounded-concurrency.js +22 -17
- package/dist/rules/no-unbounded-retry.js +47 -62
- package/dist/rules/no-unknown-parameters.d.ts +1 -1
- package/dist/rules/no-unknown-parameters.js +26 -17
- package/dist/rules/no-unknown-returns.d.ts +7 -0
- package/dist/rules/no-unknown-returns.js +74 -0
- package/dist/rules/no-unknown-type-aliases.js +21 -27
- package/dist/rules/no-unsafe-dictionary-type.js +71 -25
- package/dist/rules/no-widen-then-assert.js +96 -73
- package/dist/rules/prefer-catch-tag.js +29 -37
- package/dist/rules/prefer-match-tags-exhaustive.js +35 -47
- package/dist/rules/prefer-predicate-is-tagged.js +4 -6
- package/dist/rules/prefer-service-of.js +39 -26
- package/dist/rules/require-named-effect-fn.js +4 -2
- package/dist/rules/require-suppression-reason.d.ts +6 -0
- package/dist/rules/require-suppression-reason.js +92 -0
- package/dist/vendor/effect-oxlint/Diagnostic.js +1 -1
- package/dist/vendor/effect-oxlint/Scope.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { importSource, narrow } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
+
import { findVariableUp } from "../vendor/effect-oxlint/Scope.js";
|
|
3
|
+
import "../vendor/effect-oxlint/index.js";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
//#region src/rules/_test-apis.ts
|
|
6
|
+
const bindingsBySource = new Map([
|
|
7
|
+
["vitest", new Map([["vi", "vi"]])],
|
|
8
|
+
["@jest/globals", new Map([["jest", "jest"]])],
|
|
9
|
+
["bun:test", new Map([
|
|
10
|
+
["jest", "jest"],
|
|
11
|
+
["mock", "mock"],
|
|
12
|
+
["spyOn", "spyOn"],
|
|
13
|
+
["vi", "vi"]
|
|
14
|
+
])]
|
|
15
|
+
]);
|
|
16
|
+
/** Runner objects that exist as ambient globals when no import provides them. */
|
|
17
|
+
const globalBinding = (name) => {
|
|
18
|
+
if (name === "jest" || name === "vi") return Option.some(name);
|
|
19
|
+
return Option.none();
|
|
20
|
+
};
|
|
21
|
+
const identifierName = (node) => {
|
|
22
|
+
if (node.type === "Identifier") return Option.some(node.name);
|
|
23
|
+
return Option.none();
|
|
24
|
+
};
|
|
25
|
+
/** Record each named import of a runner binding under its local name. */
|
|
26
|
+
const collectTestBindings = (node, bindings) => {
|
|
27
|
+
const declaration = narrow(node, "ImportDeclaration");
|
|
28
|
+
if (Option.isNone(declaration)) return;
|
|
29
|
+
const bySource = Option.fromUndefinedOr(bindingsBySource.get(importSource(declaration.value)));
|
|
30
|
+
if (Option.isNone(bySource)) return;
|
|
31
|
+
for (const specifier of declaration.value.specifiers) {
|
|
32
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
33
|
+
const binding = Option.flatMap(identifierName(specifier.imported), (imported) => Option.fromUndefinedOr(bySource.value.get(imported)));
|
|
34
|
+
if (Option.isSome(binding)) bindings.set(specifier.local.name, binding.value);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const bindingFromVariable = (variable, bindings) => {
|
|
38
|
+
if (variable.defs.some((definition) => definition.type === "ImportBinding")) return Option.fromUndefinedOr(bindings.get(variable.name));
|
|
39
|
+
return Option.none();
|
|
40
|
+
};
|
|
41
|
+
/** The runner binding `name` refers to at `scope`: an imported binding, or the unshadowed global. */
|
|
42
|
+
const resolveTestBinding = (scope, name, bindings) => Option.match(findVariableUp(scope, name), {
|
|
43
|
+
onNone: () => globalBinding(name),
|
|
44
|
+
onSome: (variable) => Option.orElse(bindingFromVariable(variable, bindings), () => Option.filter(globalBinding(name), () => variable.defs.length === 0))
|
|
45
|
+
});
|
|
46
|
+
//#endregion
|
|
47
|
+
export { collectTestBindings, resolveTestBinding };
|
|
@@ -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 };
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -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,18 +23,23 @@ 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";
|
|
25
32
|
import { noShapeInSymbolNames } from "./no-shape-in-symbol-names.js";
|
|
26
33
|
import { noSilentCatchAll } from "./no-silent-catch-all.js";
|
|
27
34
|
import { noTernary } from "./no-ternary.js";
|
|
35
|
+
import { noTestGlobals } from "./no-test-globals.js";
|
|
28
36
|
import { noTestLifecycleHooks } from "./no-test-lifecycle-hooks.js";
|
|
29
37
|
import { noThrowStatement } from "./no-throw-statement.js";
|
|
30
38
|
import { noTryCatch } from "./no-try-catch.js";
|
|
31
39
|
import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
|
|
32
40
|
import { noUnboundedRetry } from "./no-unbounded-retry.js";
|
|
33
41
|
import { noUnknownParameters } from "./no-unknown-parameters.js";
|
|
42
|
+
import { noUnknownReturns } from "./no-unknown-returns.js";
|
|
34
43
|
import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
|
|
35
44
|
import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
|
|
36
45
|
import { noWidenThenAssert } from "./no-widen-then-assert.js";
|
|
@@ -40,10 +49,11 @@ import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
|
|
|
40
49
|
import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
|
|
41
50
|
import { preferServiceOf } from "./prefer-service-of.js";
|
|
42
51
|
import { requireNamedEffectFn } from "./require-named-effect-fn.js";
|
|
52
|
+
import { requireSuppressionReason } from "./require-suppression-reason.js";
|
|
43
53
|
|
|
44
54
|
//#region src/rules/index.d.ts
|
|
45
55
|
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 };
|
|
56
|
+
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, noTestGlobals, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnboundedConcurrency, noUnboundedRetry, noUnknownParameters, noUnknownReturns, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged, preferServiceOf, requireNamedEffectFn, requireSuppressionReason };
|
|
47
57
|
}
|
|
48
58
|
//#endregion
|
|
49
59
|
export { index_d_exports };
|
package/dist/rules/index.js
CHANGED
|
@@ -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,18 +24,23 @@ 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";
|
|
26
33
|
import { noShapeInSymbolNames } from "./no-shape-in-symbol-names.js";
|
|
27
34
|
import { noSilentCatchAll } from "./no-silent-catch-all.js";
|
|
28
35
|
import { noTernary } from "./no-ternary.js";
|
|
36
|
+
import { noTestGlobals } from "./no-test-globals.js";
|
|
29
37
|
import { noTestLifecycleHooks } from "./no-test-lifecycle-hooks.js";
|
|
30
38
|
import { noThrowStatement } from "./no-throw-statement.js";
|
|
31
39
|
import { noTryCatch } from "./no-try-catch.js";
|
|
32
40
|
import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
|
|
33
41
|
import { noUnboundedRetry } from "./no-unbounded-retry.js";
|
|
34
42
|
import { noUnknownParameters } from "./no-unknown-parameters.js";
|
|
43
|
+
import { noUnknownReturns } from "./no-unknown-returns.js";
|
|
35
44
|
import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
|
|
36
45
|
import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
|
|
37
46
|
import { noWidenThenAssert } from "./no-widen-then-assert.js";
|
|
@@ -41,21 +50,26 @@ import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
|
|
|
41
50
|
import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
|
|
42
51
|
import { preferServiceOf } from "./prefer-service-of.js";
|
|
43
52
|
import { requireNamedEffectFn } from "./require-named-effect-fn.js";
|
|
53
|
+
import { requireSuppressionReason } from "./require-suppression-reason.js";
|
|
44
54
|
//#region src/rules/index.ts
|
|
45
55
|
var rules_exports = /* @__PURE__ */ __exportAll({
|
|
46
56
|
maxCognitiveComplexity: () => maxCognitiveComplexity,
|
|
47
57
|
maxHalsteadDifficulty: () => maxHalsteadDifficulty,
|
|
58
|
+
noArrayFilterMap: () => noArrayFilterMap,
|
|
48
59
|
noAs: () => noAs,
|
|
49
60
|
noAsyncFunction: () => noAsyncFunction,
|
|
50
61
|
noChainedTypeAssertions: () => noChainedTypeAssertions,
|
|
51
62
|
noConditionalEmptyObjectSpread: () => noConditionalEmptyObjectSpread,
|
|
52
63
|
noDynamicImports: () => noDynamicImports,
|
|
64
|
+
noEagerAcquire: () => noEagerAcquire,
|
|
53
65
|
noEffectBind: () => noEffectBind,
|
|
54
66
|
noEffectDo: () => noEffectDo,
|
|
67
|
+
noEffectRunInTests: () => noEffectRunInTests,
|
|
55
68
|
noGlobals: () => noGlobals,
|
|
56
69
|
noInlineProvide: () => noInlineProvide,
|
|
57
70
|
noKnownValueWidening: () => noKnownValueWidening,
|
|
58
71
|
noManagedRuntimeInEffect: () => noManagedRuntimeInEffect,
|
|
72
|
+
noModuleLevelMutableState: () => noModuleLevelMutableState,
|
|
59
73
|
noModuleMocks: () => noModuleMocks,
|
|
60
74
|
noNestedEffectGen: () => noNestedEffectGen,
|
|
61
75
|
noNewError: () => noNewError,
|
|
@@ -64,18 +78,23 @@ var rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
64
78
|
noNullish: () => noNullish,
|
|
65
79
|
noObjectParameters: () => noObjectParameters,
|
|
66
80
|
noPerCallCacheConstruction: () => noPerCallCacheConstruction,
|
|
81
|
+
noReduceAccumulatorCopy: () => noReduceAccumulatorCopy,
|
|
82
|
+
noReflectApply: () => noReflectApply,
|
|
83
|
+
noReflectGet: () => noReflectGet,
|
|
67
84
|
noRunCollectOnUnboundedStream: () => noRunCollectOnUnboundedStream,
|
|
68
85
|
noRuntimeTypeof: () => noRuntimeTypeof,
|
|
69
86
|
noSequentialEffectAll: () => noSequentialEffectAll,
|
|
70
87
|
noShapeInSymbolNames: () => noShapeInSymbolNames,
|
|
71
88
|
noSilentCatchAll: () => noSilentCatchAll,
|
|
72
89
|
noTernary: () => noTernary,
|
|
90
|
+
noTestGlobals: () => noTestGlobals,
|
|
73
91
|
noTestLifecycleHooks: () => noTestLifecycleHooks,
|
|
74
92
|
noThrowStatement: () => noThrowStatement,
|
|
75
93
|
noTryCatch: () => noTryCatch,
|
|
76
94
|
noUnboundedConcurrency: () => noUnboundedConcurrency,
|
|
77
95
|
noUnboundedRetry: () => noUnboundedRetry,
|
|
78
96
|
noUnknownParameters: () => noUnknownParameters,
|
|
97
|
+
noUnknownReturns: () => noUnknownReturns,
|
|
79
98
|
noUnknownTypeAliases: () => noUnknownTypeAliases,
|
|
80
99
|
noUnsafeDictionaryType: () => noUnsafeDictionaryType,
|
|
81
100
|
noWidenThenAssert: () => noWidenThenAssert,
|
|
@@ -84,7 +103,8 @@ var rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
84
103
|
preferMatchTagsExhaustive: () => preferMatchTagsExhaustive,
|
|
85
104
|
preferPredicateIsTagged: () => preferPredicateIsTagged,
|
|
86
105
|
preferServiceOf: () => preferServiceOf,
|
|
87
|
-
requireNamedEffectFn: () => requireNamedEffectFn
|
|
106
|
+
requireNamedEffectFn: () => requireNamedEffectFn,
|
|
107
|
+
requireSuppressionReason: () => requireSuppressionReason
|
|
88
108
|
});
|
|
89
109
|
//#endregion
|
|
90
110
|
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,49 @@
|
|
|
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 Option from "effect/Option";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
//#region src/rules/no-array-filter-map.ts
|
|
9
|
+
const pairedMethod = (name) => {
|
|
10
|
+
if (name === "map") return Option.some("filter");
|
|
11
|
+
if (name === "filter") return Option.some("map");
|
|
12
|
+
return Option.none();
|
|
13
|
+
};
|
|
14
|
+
function filterMapPair(sourceCode, node) {
|
|
15
|
+
return Option.flatMap(arrayMethodTarget(node.callee), (outer) => Option.flatMap(pairedMethod(outer.name), (pairedName) => {
|
|
16
|
+
const innerCall = unwrapArrayExpression(outer.object);
|
|
17
|
+
if (innerCall.type !== "CallExpression") return Option.none();
|
|
18
|
+
const inner = Option.filter(arrayMethodTarget(innerCall.callee), (method) => method.name === pairedName && isKnownArrayExpression(sourceCode, method.object));
|
|
19
|
+
return Option.map(inner, (method) => ({
|
|
20
|
+
first: method.name,
|
|
21
|
+
second: outer.name
|
|
22
|
+
}));
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
/** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
|
|
26
|
+
const noArrayFilterMap = define({
|
|
27
|
+
name: "no-array-filter-map",
|
|
28
|
+
meta: meta({
|
|
29
|
+
type: "suggestion",
|
|
30
|
+
description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation.",
|
|
31
|
+
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." }
|
|
32
|
+
}),
|
|
33
|
+
create: function* () {
|
|
34
|
+
const context = yield* RuleContext;
|
|
35
|
+
return { CallExpression: (node) => Option.match(filterMapPair(context.sourceCode, node), {
|
|
36
|
+
onNone: () => Effect.void,
|
|
37
|
+
onSome: ({ first, second }) => context.report(fromId({
|
|
38
|
+
node,
|
|
39
|
+
messageId: "arrayFilterMap",
|
|
40
|
+
data: {
|
|
41
|
+
first,
|
|
42
|
+
second
|
|
43
|
+
}
|
|
44
|
+
}))
|
|
45
|
+
}) };
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
export { noArrayFilterMap };
|
|
@@ -46,10 +46,13 @@ const noChainedTypeAssertions = define({
|
|
|
46
46
|
}),
|
|
47
47
|
create: function* () {
|
|
48
48
|
const context = yield* RuleContext;
|
|
49
|
-
const checkTypeAssertion = (node) =>
|
|
50
|
-
node
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const checkTypeAssertion = (node) => {
|
|
50
|
+
if (!isOutermostAssertionInChain(node) || !isForbiddenAssertionChain(node)) return Effect.void;
|
|
51
|
+
return context.report(fromId({
|
|
52
|
+
node,
|
|
53
|
+
messageId: "chained"
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
53
56
|
return {
|
|
54
57
|
TSAsExpression: checkTypeAssertion,
|
|
55
58
|
TSTypeAssertion: checkTypeAssertion
|
|
@@ -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
|
|
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
|
|
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,66 +12,26 @@ function unwrapParentheses(node) {
|
|
|
12
12
|
function isEmptyObjectExpression(node) {
|
|
13
13
|
return node.type === "ObjectExpression" && node.properties.length === 0;
|
|
14
14
|
}
|
|
15
|
-
function
|
|
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
|
-
|
|
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
|
|
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
29
|
return { SpreadElement: (node) => {
|
|
65
|
-
if (node.parent?.type !== "ObjectExpression") return Effect.void;
|
|
66
|
-
|
|
67
|
-
if (match === null) return Effect.void;
|
|
68
|
-
const { conditional, property } = match;
|
|
69
|
-
const diagnostic = fromId({
|
|
30
|
+
if (node.parent?.type !== "ObjectExpression" || !isConditionalEmptyObjectSpread(node.argument)) return Effect.void;
|
|
31
|
+
return context.report(fromId({
|
|
70
32
|
node,
|
|
71
33
|
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))));
|
|
34
|
+
}));
|
|
75
35
|
} };
|
|
76
36
|
}
|
|
77
37
|
});
|
|
@@ -3,14 +3,20 @@ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
|
3
3
|
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
4
|
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
5
|
import "../vendor/effect-oxlint/index.js";
|
|
6
|
+
import { ancestors, parentOf } from "./_ast-ancestors.js";
|
|
6
7
|
import * as Option from "effect/Option";
|
|
8
|
+
import * as Predicate from "effect/Predicate";
|
|
7
9
|
import * as Effect from "effect/Effect";
|
|
8
10
|
//#region src/rules/no-dynamic-imports.ts
|
|
9
|
-
const identifierName = (node) =>
|
|
11
|
+
const identifierName = (node) => {
|
|
12
|
+
if (node.type === "Identifier") return Option.some(node.name);
|
|
13
|
+
return Option.none();
|
|
14
|
+
};
|
|
15
|
+
const isIdentifierNamed = (node, name) => Option.contains(identifierName(node), name);
|
|
10
16
|
const hasNamedBinding = (pattern) => {
|
|
11
17
|
if (pattern.type === "Identifier") return true;
|
|
12
18
|
if (pattern.type === "ObjectPattern") return pattern.properties.length > 0;
|
|
13
|
-
if (pattern.type === "ArrayPattern") return pattern.elements.some(
|
|
19
|
+
if (pattern.type === "ArrayPattern") return pattern.elements.some(Predicate.isNotNull);
|
|
14
20
|
return false;
|
|
15
21
|
};
|
|
16
22
|
const isNamedVariable = (node, value) => node.type === "VariableDeclarator" && node.init === value && hasNamedBinding(node.id);
|
|
@@ -22,42 +28,45 @@ const transparentWrappers = new Set([
|
|
|
22
28
|
"TSTypeAssertion",
|
|
23
29
|
"YieldExpression"
|
|
24
30
|
]);
|
|
31
|
+
/** The outermost node reached from `start` through transparent wrapper ancestors. */
|
|
25
32
|
const climbTransparent = (start) => {
|
|
26
33
|
let current = start;
|
|
27
|
-
|
|
34
|
+
for (const parent of ancestors(start)) {
|
|
35
|
+
if (!transparentWrappers.has(parent.type)) break;
|
|
36
|
+
current = parent;
|
|
37
|
+
}
|
|
28
38
|
return current;
|
|
29
39
|
};
|
|
30
40
|
const isNamedDirectBinding = (node) => {
|
|
31
41
|
const value = climbTransparent(node);
|
|
32
|
-
return value
|
|
42
|
+
return Option.exists(parentOf(value), (parent) => isNamedVariable(parent, value));
|
|
33
43
|
};
|
|
44
|
+
const isNamedFunctionDeclarationBody = (block) => block.type === "BlockStatement" && Option.exists(parentOf(block), (fn) => fn.type === "FunctionDeclaration" && Predicate.isNotNull(fn.id));
|
|
34
45
|
const isNamedFunctionBoundary = (node) => {
|
|
35
46
|
const parent = node.parent;
|
|
36
|
-
if (parent?.type === "ArrowFunctionExpression" && parent.body === node) return
|
|
47
|
+
if (parent?.type === "ArrowFunctionExpression" && parent.body === node) return Option.exists(parentOf(parent), (declarator) => isNamedVariable(declarator, parent));
|
|
37
48
|
if (parent?.type !== "ReturnStatement" || parent.argument !== node) return false;
|
|
38
|
-
|
|
39
|
-
const fn = block?.parent;
|
|
40
|
-
return block?.type === "BlockStatement" && fn?.type === "FunctionDeclaration" && fn.id !== null;
|
|
49
|
+
return Option.exists(parentOf(parent), isNamedFunctionDeclarationBody);
|
|
41
50
|
};
|
|
42
51
|
const isEffectPromiseCall = (node) => {
|
|
43
52
|
if (node.type !== "CallExpression") return false;
|
|
44
53
|
const callee = node.callee;
|
|
45
54
|
if (callee.type !== "MemberExpression" || callee.computed) return false;
|
|
46
|
-
return
|
|
55
|
+
return isIdentifierNamed(callee.object, "Effect") && Option.exists(identifierName(callee.property), (name) => ["promise", "tryPromise"].includes(name));
|
|
47
56
|
};
|
|
48
57
|
const isEffectPromiseBoundary = (node) => {
|
|
49
58
|
const callback = node.parent;
|
|
50
59
|
if (callback?.type !== "ArrowFunctionExpression" || callback.body !== node) return false;
|
|
51
|
-
|
|
52
|
-
return call != null && isEffectPromiseCall(call);
|
|
60
|
+
return Option.exists(parentOf(callback), isEffectPromiseCall);
|
|
53
61
|
};
|
|
54
62
|
const isNamedLazyBoundary = (node) => {
|
|
55
63
|
return isNamedDirectBinding(node) || isNamedFunctionBoundary(node) || isEffectPromiseBoundary(node);
|
|
56
64
|
};
|
|
57
65
|
const dynamicRequireMessage = (callee) => {
|
|
58
|
-
if (
|
|
59
|
-
if (callee
|
|
60
|
-
|
|
66
|
+
if (isIdentifierNamed(callee, "require")) return Option.some("Avoid require(). Use a static import.");
|
|
67
|
+
if (callee.type !== "MemberExpression") return Option.none();
|
|
68
|
+
if (isIdentifierNamed(callee.object, "module") && isIdentifierNamed(callee.property, "require")) return Option.some("Avoid module.require(). Use a static import.");
|
|
69
|
+
return Option.none();
|
|
61
70
|
};
|
|
62
71
|
const noDynamicImports = define({
|
|
63
72
|
name: "no-dynamic-imports",
|
|
@@ -78,7 +87,7 @@ const noDynamicImports = define({
|
|
|
78
87
|
onNone: () => Effect.void,
|
|
79
88
|
onSome: (declaration) => {
|
|
80
89
|
if (!["module", "node:module"].includes(importSource(declaration))) return Effect.void;
|
|
81
|
-
for (const specifier of declaration.specifiers) if (specifier.type === "ImportSpecifier" &&
|
|
90
|
+
for (const specifier of declaration.specifiers) if (specifier.type === "ImportSpecifier" && isIdentifierNamed(specifier.imported, "createRequire")) createRequireNames.add(specifier.local.name);
|
|
82
91
|
return Effect.void;
|
|
83
92
|
}
|
|
84
93
|
}),
|
|
@@ -94,21 +103,20 @@ const noDynamicImports = define({
|
|
|
94
103
|
}),
|
|
95
104
|
ImportExpression: (node) => Option.match(narrow(node, "ImportExpression"), {
|
|
96
105
|
onNone: () => Effect.void,
|
|
97
|
-
onSome: (importExpression) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
106
|
+
onSome: (importExpression) => {
|
|
107
|
+
if (isNamedLazyBoundary(importExpression)) return Effect.void;
|
|
108
|
+
return report(node, "Avoid inline dynamic imports. Bind the imported module or a lazy loader to a descriptive name before using it.");
|
|
109
|
+
}
|
|
101
110
|
}),
|
|
102
111
|
CallExpression: (node) => {
|
|
103
112
|
return Option.match(narrow(node, "CallExpression"), {
|
|
104
113
|
onNone: () => Effect.void,
|
|
105
114
|
onSome: (call) => {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}));
|
|
115
|
+
const aliasMessage = Option.map(Option.filter(identifierName(call.callee), (name) => requireAliases.has(name)), () => "Avoid createRequire aliases. Keep module loading static.");
|
|
116
|
+
return Option.match(Option.orElse(aliasMessage, () => dynamicRequireMessage(call.callee)), {
|
|
117
|
+
onNone: () => Effect.void,
|
|
118
|
+
onSome: (message) => report(call, message)
|
|
119
|
+
});
|
|
112
120
|
}
|
|
113
121
|
});
|
|
114
122
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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 { ancestors } from "./_ast-ancestors.js";
|
|
7
|
+
import { expressionArgument } from "./_call-arguments.js";
|
|
8
|
+
import { importedNamespaces, isStaticCall, visibleNamespaces } from "./_effect-namespaces.js";
|
|
9
|
+
import * as Arr from "effect/Array";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as Predicate from "effect/Predicate";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
//#region src/rules/no-eager-acquire.ts
|
|
14
|
+
const unwrapExpression = (node) => {
|
|
15
|
+
let current = node;
|
|
16
|
+
while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion") current = current.expression;
|
|
17
|
+
return current;
|
|
18
|
+
};
|
|
19
|
+
const isWithin = (node, ancestor) => {
|
|
20
|
+
if (node === ancestor) return true;
|
|
21
|
+
for (const current of ancestors(node)) if (current === ancestor) return true;
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
const isThunk = (node) => node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
|
|
25
|
+
/** The value a thunk returns, when its body is an expression or holds exactly one return. */
|
|
26
|
+
const singleReturnedValue = (thunk) => {
|
|
27
|
+
const body = thunk.body;
|
|
28
|
+
if (Predicate.isNull(body)) return Option.none();
|
|
29
|
+
if (body.type !== "BlockStatement") return Option.some(body);
|
|
30
|
+
const returns = body.body.filter((statement) => statement.type === "ReturnStatement");
|
|
31
|
+
if (returns.length !== 1) return Option.none();
|
|
32
|
+
return Option.flatMap(Arr.head(returns), (returned) => Option.fromNullOr(returned.argument));
|
|
33
|
+
};
|
|
34
|
+
const noEagerAcquire = define({
|
|
35
|
+
name: "no-eager-acquire",
|
|
36
|
+
meta: meta({
|
|
37
|
+
type: "problem",
|
|
38
|
+
description: "Construct a resource inside the Effect.acquireRelease acquire Effect, not before it."
|
|
39
|
+
}),
|
|
40
|
+
create: function* () {
|
|
41
|
+
const ctx = yield* RuleContext;
|
|
42
|
+
const effectNamespaces = new Set(["Effect"]);
|
|
43
|
+
/** Whether the value was bound outside the thunk, so it exists before acquire runs. */
|
|
44
|
+
const isCaptured = (expression, thunk, seen) => {
|
|
45
|
+
const value = unwrapExpression(expression);
|
|
46
|
+
if (value.type === "ThisExpression") return true;
|
|
47
|
+
if (value.type === "MemberExpression") return value.object.type !== "Super" && isCaptured(value.object, thunk, seen);
|
|
48
|
+
if (value.type !== "Identifier") return false;
|
|
49
|
+
return Option.match(findVariableUp(ctx.sourceCode.getScope(value), value.name), {
|
|
50
|
+
onNone: () => true,
|
|
51
|
+
onSome: (variable) => Option.match(Arr.head(variable.defs), {
|
|
52
|
+
onNone: () => true,
|
|
53
|
+
onSome: (definition) => isCapturedDefinition(definition.node, value.name, thunk, seen)
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
/** Whether a binding's definition holds a value that exists before the thunk runs. */
|
|
58
|
+
const isCapturedDefinition = (definition, name, thunk, seen) => {
|
|
59
|
+
if (seen.has(definition)) return false;
|
|
60
|
+
seen.add(definition);
|
|
61
|
+
if (!isWithin(definition, thunk)) return true;
|
|
62
|
+
if (definition.type === "VariableDeclarator") {
|
|
63
|
+
const init = definition.init;
|
|
64
|
+
return Predicate.isNotNull(init) && isCaptured(init, thunk, seen);
|
|
65
|
+
}
|
|
66
|
+
return Option.exists(Arr.findFirst(thunk.params, (parameter) => parameter.type === "AssignmentPattern" && parameter.left.type === "Identifier" && parameter.left.name === name), (defaulted) => isCaptured(defaulted.right, thunk, seen));
|
|
67
|
+
};
|
|
68
|
+
const returnsCapturedHandle = (expression, effects) => {
|
|
69
|
+
if (!isStaticCall(expression, effects, "sync")) return false;
|
|
70
|
+
const thunk = Option.filter(Option.map(expressionArgument(expression, 0), unwrapExpression), isThunk);
|
|
71
|
+
return Option.exists(thunk, (lazy) => Option.exists(singleReturnedValue(lazy), (returned) => isCaptured(returned, lazy, /* @__PURE__ */ new Set())));
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
ImportDeclaration: (node) => {
|
|
75
|
+
for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
|
|
76
|
+
return Effect.void;
|
|
77
|
+
},
|
|
78
|
+
CallExpression: (node) => {
|
|
79
|
+
if (node.callee.type === "Super") return Effect.void;
|
|
80
|
+
const effects = visibleNamespaces(ctx, node, effectNamespaces);
|
|
81
|
+
if (!isStaticCall(node, effects, "acquireRelease")) return Effect.void;
|
|
82
|
+
const eagerAcquire = Option.filter(expressionArgument(node, 0), (acquire) => {
|
|
83
|
+
const expression = unwrapExpression(acquire);
|
|
84
|
+
return isStaticCall(expression, effects, "succeed") || returnsCapturedHandle(expression, effects);
|
|
85
|
+
});
|
|
86
|
+
return Option.match(eagerAcquire, {
|
|
87
|
+
onNone: () => Effect.void,
|
|
88
|
+
onSome: (acquire) => ctx.report(make({
|
|
89
|
+
node: acquire,
|
|
90
|
+
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."
|
|
91
|
+
}))
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
//#endregion
|
|
98
|
+
export { noEagerAcquire };
|