@thethracian/oxlint-config 0.2.1 → 0.3.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/CHANGELOG.md +13 -0
- package/README.md +124 -18
- package/dist/codemod-fix/cli.d.ts +3 -0
- package/dist/codemod-fix/cli.d.ts.map +1 -0
- package/dist/codemod-fix/cli.js +22 -0
- package/dist/codemod-fix/cli.js.map +1 -0
- package/dist/codemod-fix/index.d.ts +44 -0
- package/dist/codemod-fix/index.d.ts.map +1 -0
- package/dist/codemod-fix/index.js +110 -0
- package/dist/codemod-fix/index.js.map +1 -0
- package/dist/codemods/arrow-body-style.d.ts +7 -0
- package/dist/codemods/arrow-body-style.d.ts.map +1 -0
- package/dist/codemods/arrow-body-style.js +106 -0
- package/dist/codemods/arrow-body-style.js.map +1 -0
- package/dist/codemods/comment-format.d.ts +24 -0
- package/dist/codemods/comment-format.d.ts.map +1 -0
- package/dist/codemods/comment-format.js +100 -0
- package/dist/codemods/comment-format.js.map +1 -0
- package/dist/codemods/explicit-return-types.d.ts +7 -0
- package/dist/codemods/explicit-return-types.d.ts.map +1 -0
- package/dist/codemods/explicit-return-types.js +350 -0
- package/dist/codemods/explicit-return-types.js.map +1 -0
- package/dist/codemods/format-file-header.d.ts +7 -0
- package/dist/codemods/format-file-header.d.ts.map +1 -0
- package/dist/codemods/format-file-header.js +163 -0
- package/dist/codemods/format-file-header.js.map +1 -0
- package/dist/codemods/format-jsdoc-comments.d.ts +7 -0
- package/dist/codemods/format-jsdoc-comments.d.ts.map +1 -0
- package/dist/codemods/format-jsdoc-comments.js +129 -0
- package/dist/codemods/format-jsdoc-comments.js.map +1 -0
- package/dist/codemods/function-declarations.d.ts +7 -0
- package/dist/codemods/function-declarations.d.ts.map +1 -0
- package/dist/codemods/function-declarations.js +202 -0
- package/dist/codemods/function-declarations.js.map +1 -0
- package/dist/codemods/inline-export-lists.d.ts +7 -0
- package/dist/codemods/inline-export-lists.d.ts.map +1 -0
- package/dist/codemods/inline-export-lists.js +168 -0
- package/dist/codemods/inline-export-lists.js.map +1 -0
- package/dist/codemods/internal-export-docs.d.ts +7 -0
- package/dist/codemods/internal-export-docs.d.ts.map +1 -0
- package/dist/codemods/internal-export-docs.js +93 -0
- package/dist/codemods/internal-export-docs.js.map +1 -0
- package/dist/codemods/no-ternary-branch-initializers.d.ts +14 -0
- package/dist/codemods/no-ternary-branch-initializers.d.ts.map +1 -0
- package/dist/codemods/no-ternary-branch-initializers.js +240 -0
- package/dist/codemods/no-ternary-branch-initializers.js.map +1 -0
- package/dist/codemods/no-ternary-variable-initializers.d.ts +14 -0
- package/dist/codemods/no-ternary-variable-initializers.d.ts.map +1 -0
- package/dist/codemods/no-ternary-variable-initializers.js +235 -0
- package/dist/codemods/no-ternary-variable-initializers.js.map +1 -0
- package/dist/codemods/no-ternary.d.ts +7 -0
- package/dist/codemods/no-ternary.d.ts.map +1 -0
- package/dist/codemods/no-ternary.js +215 -0
- package/dist/codemods/no-ternary.js.map +1 -0
- package/dist/codemods/rename-acronyms.d.ts +8 -0
- package/dist/codemods/rename-acronyms.d.ts.map +1 -0
- package/dist/codemods/rename-acronyms.js +193 -0
- package/dist/codemods/rename-acronyms.js.map +1 -0
- package/dist/codemods/run.d.ts +2 -0
- package/dist/codemods/run.d.ts.map +1 -0
- package/dist/codemods/run.js +14 -0
- package/dist/codemods/run.js.map +1 -0
- package/dist/codemods/sort-imports.d.ts +7 -0
- package/dist/codemods/sort-imports.d.ts.map +1 -0
- package/dist/codemods/sort-imports.js +235 -0
- package/dist/codemods/sort-imports.js.map +1 -0
- package/dist/index.d.ts +30 -96
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -94
- package/dist/index.js.map +1 -1
- package/dist/rules/acronym-case.d.ts +7 -3
- package/dist/rules/acronym-case.d.ts.map +1 -1
- package/dist/rules/acronym-case.js +129 -118
- package/dist/rules/acronym-case.js.map +1 -1
- package/dist/rules/acronyms-1.d.ts +3 -0
- package/dist/rules/acronyms-1.d.ts.map +1 -0
- package/dist/rules/acronyms-1.js +407 -0
- package/dist/rules/acronyms-1.js.map +1 -0
- package/dist/rules/acronyms-2.d.ts +3 -0
- package/dist/rules/acronyms-2.d.ts.map +1 -0
- package/dist/rules/acronyms-2.js +407 -0
- package/dist/rules/acronyms-2.js.map +1 -0
- package/dist/rules/acronyms-3.d.ts +3 -0
- package/dist/rules/acronyms-3.d.ts.map +1 -0
- package/dist/rules/acronyms-3.js +407 -0
- package/dist/rules/acronyms-3.js.map +1 -0
- package/dist/rules/acronyms-4.d.ts +3 -0
- package/dist/rules/acronyms-4.d.ts.map +1 -0
- package/dist/rules/acronyms-4.js +377 -0
- package/dist/rules/acronyms-4.js.map +1 -0
- package/dist/rules/acronyms.d.ts +1 -8
- package/dist/rules/acronyms.d.ts.map +1 -1
- package/dist/rules/acronyms.js +14 -1578
- package/dist/rules/acronyms.js.map +1 -1
- package/dist/rules/boolean-prefix.d.ts +11 -2
- package/dist/rules/boolean-prefix.d.ts.map +1 -1
- package/dist/rules/boolean-prefix.js +95 -59
- package/dist/rules/boolean-prefix.js.map +1 -1
- package/dist/rules/camel-case-identifiers.d.ts +3 -4
- package/dist/rules/camel-case-identifiers.d.ts.map +1 -1
- package/dist/rules/camel-case-identifiers.js +59 -40
- package/dist/rules/camel-case-identifiers.js.map +1 -1
- package/dist/rules/char-class.d.ts +26 -13
- package/dist/rules/char-class.d.ts.map +1 -1
- package/dist/rules/char-class.js +56 -24
- package/dist/rules/char-class.js.map +1 -1
- package/dist/rules/diagnostic-guidance.d.ts +91 -0
- package/dist/rules/diagnostic-guidance.d.ts.map +1 -0
- package/dist/rules/diagnostic-guidance.js +243 -0
- package/dist/rules/diagnostic-guidance.js.map +1 -0
- package/dist/rules/effect-default-ast.d.ts +89 -0
- package/dist/rules/effect-default-ast.d.ts.map +1 -0
- package/dist/rules/effect-default-ast.js +208 -0
- package/dist/rules/effect-default-ast.js.map +1 -0
- package/dist/rules/effect-default-compat-rules.d.ts +76 -0
- package/dist/rules/effect-default-compat-rules.d.ts.map +1 -0
- package/dist/rules/effect-default-compat-rules.js +270 -0
- package/dist/rules/effect-default-compat-rules.js.map +1 -0
- package/dist/rules/effect-default-env-rules.d.ts +60 -0
- package/dist/rules/effect-default-env-rules.d.ts.map +1 -0
- package/dist/rules/effect-default-env-rules.js +259 -0
- package/dist/rules/effect-default-env-rules.js.map +1 -0
- package/dist/rules/effect-default-fiber-helpers.d.ts +13 -0
- package/dist/rules/effect-default-fiber-helpers.d.ts.map +1 -0
- package/dist/rules/effect-default-fiber-helpers.js +76 -0
- package/dist/rules/effect-default-fiber-helpers.js.map +1 -0
- package/dist/rules/effect-default-floating-helpers.d.ts +7 -0
- package/dist/rules/effect-default-floating-helpers.d.ts.map +1 -0
- package/dist/rules/effect-default-floating-helpers.js +113 -0
- package/dist/rules/effect-default-floating-helpers.js.map +1 -0
- package/dist/rules/effect-default-helpers.d.ts +8 -32
- package/dist/rules/effect-default-helpers.d.ts.map +1 -1
- package/dist/rules/effect-default-helpers.js +11 -535
- package/dist/rules/effect-default-helpers.js.map +1 -1
- package/dist/rules/effect-default-resource-helpers.d.ts +18 -4
- package/dist/rules/effect-default-resource-helpers.d.ts.map +1 -1
- package/dist/rules/effect-default-resource-helpers.js +30 -13
- package/dist/rules/effect-default-resource-helpers.js.map +1 -1
- package/dist/rules/effect-default-safety-helpers.d.ts +43 -0
- package/dist/rules/effect-default-safety-helpers.d.ts.map +1 -0
- package/dist/rules/effect-default-safety-helpers.js +126 -0
- package/dist/rules/effect-default-safety-helpers.js.map +1 -0
- package/dist/rules/effect-default-scan-helpers.d.ts +43 -0
- package/dist/rules/effect-default-scan-helpers.d.ts.map +1 -0
- package/dist/rules/effect-default-scan-helpers.js +141 -0
- package/dist/rules/effect-default-scan-helpers.js.map +1 -0
- package/dist/rules/effect-default-schema-helpers.d.ts +37 -0
- package/dist/rules/effect-default-schema-helpers.d.ts.map +1 -0
- package/dist/rules/effect-default-schema-helpers.js +104 -0
- package/dist/rules/effect-default-schema-helpers.js.map +1 -0
- package/dist/rules/effect-default-test-helpers.d.ts +18 -4
- package/dist/rules/effect-default-test-helpers.d.ts.map +1 -1
- package/dist/rules/effect-default-test-helpers.js +41 -20
- package/dist/rules/effect-default-test-helpers.js.map +1 -1
- package/dist/rules/effect-default-workflow-helpers.d.ts +91 -0
- package/dist/rules/effect-default-workflow-helpers.d.ts.map +1 -0
- package/dist/rules/effect-default-workflow-helpers.js +247 -0
- package/dist/rules/effect-default-workflow-helpers.js.map +1 -0
- package/dist/rules/effect-default.d.ts +1 -1
- package/dist/rules/effect-default.d.ts.map +1 -1
- package/dist/rules/effect-default.js +105 -680
- package/dist/rules/effect-default.js.map +1 -1
- package/dist/rules/effect-exported-declarations.d.ts +19 -0
- package/dist/rules/effect-exported-declarations.d.ts.map +1 -0
- package/dist/rules/effect-exported-declarations.js +237 -0
- package/dist/rules/effect-exported-declarations.js.map +1 -0
- package/dist/rules/effect-path-options.d.ts +49 -11
- package/dist/rules/effect-path-options.d.ts.map +1 -1
- package/dist/rules/effect-path-options.js +85 -52
- package/dist/rules/effect-path-options.js.map +1 -1
- package/dist/rules/effect-rule-aliases.d.ts +55 -0
- package/dist/rules/effect-rule-aliases.d.ts.map +1 -0
- package/dist/rules/effect-rule-aliases.js +278 -0
- package/dist/rules/effect-rule-aliases.js.map +1 -0
- package/dist/rules/effect-rule-core.d.ts +38 -24
- package/dist/rules/effect-rule-core.d.ts.map +1 -1
- package/dist/rules/effect-rule-core.js +157 -344
- package/dist/rules/effect-rule-core.js.map +1 -1
- package/dist/rules/effect-rule-names.d.ts +12 -3
- package/dist/rules/effect-rule-names.d.ts.map +1 -1
- package/dist/rules/effect-rule-names.js +15 -3
- package/dist/rules/effect-rule-names.js.map +1 -1
- package/dist/rules/effect-source-comments.d.ts +7 -0
- package/dist/rules/effect-source-comments.d.ts.map +1 -0
- package/dist/rules/effect-source-comments.js +227 -0
- package/dist/rules/effect-source-comments.js.map +1 -0
- package/dist/rules/effect-source-helpers.d.ts +4 -9
- package/dist/rules/effect-source-helpers.d.ts.map +1 -1
- package/dist/rules/effect-source-helpers.js +7 -358
- package/dist/rules/effect-source-helpers.js.map +1 -1
- package/dist/rules/effect-source-navigation.d.ts +25 -0
- package/dist/rules/effect-source-navigation.d.ts.map +1 -0
- package/dist/rules/effect-source-navigation.js +244 -0
- package/dist/rules/effect-source-navigation.js.map +1 -0
- package/dist/rules/effect-source-scan.d.ts +30 -7
- package/dist/rules/effect-source-scan.d.ts.map +1 -1
- package/dist/rules/effect-source-scan.js +372 -300
- package/dist/rules/effect-source-scan.js.map +1 -1
- package/dist/rules/effect-strict-ast-specs.d.ts +8 -0
- package/dist/rules/effect-strict-ast-specs.d.ts.map +1 -0
- package/dist/rules/effect-strict-ast-specs.js +166 -0
- package/dist/rules/effect-strict-ast-specs.js.map +1 -0
- package/dist/rules/effect-strict-boundary-helpers.d.ts +25 -0
- package/dist/rules/effect-strict-boundary-helpers.d.ts.map +1 -0
- package/dist/rules/effect-strict-boundary-helpers.js +127 -0
- package/dist/rules/effect-strict-boundary-helpers.js.map +1 -0
- package/dist/rules/effect-strict-core-specs.d.ts +8 -0
- package/dist/rules/effect-strict-core-specs.d.ts.map +1 -0
- package/dist/rules/effect-strict-core-specs.js +334 -0
- package/dist/rules/effect-strict-core-specs.js.map +1 -0
- package/dist/rules/effect-strict-external-helpers.d.ts +25 -0
- package/dist/rules/effect-strict-external-helpers.d.ts.map +1 -0
- package/dist/rules/effect-strict-external-helpers.js +87 -0
- package/dist/rules/effect-strict-external-helpers.js.map +1 -0
- package/dist/rules/effect-strict-helpers.d.ts +75 -33
- package/dist/rules/effect-strict-helpers.d.ts.map +1 -1
- package/dist/rules/effect-strict-helpers.js +93 -384
- package/dist/rules/effect-strict-helpers.js.map +1 -1
- package/dist/rules/effect-strict-internals.d.ts +198 -0
- package/dist/rules/effect-strict-internals.d.ts.map +1 -0
- package/dist/rules/effect-strict-internals.js +477 -0
- package/dist/rules/effect-strict-internals.js.map +1 -0
- package/dist/rules/effect-strict-segment-helpers.d.ts +79 -0
- package/dist/rules/effect-strict-segment-helpers.d.ts.map +1 -0
- package/dist/rules/effect-strict-segment-helpers.js +308 -0
- package/dist/rules/effect-strict-segment-helpers.js.map +1 -0
- package/dist/rules/effect-strict.d.ts +1 -1
- package/dist/rules/effect-strict.d.ts.map +1 -1
- package/dist/rules/effect-strict.js +10 -725
- package/dist/rules/effect-strict.js.map +1 -1
- package/dist/rules/max-import-depth.d.ts +2 -2
- package/dist/rules/max-import-depth.d.ts.map +1 -1
- package/dist/rules/max-import-depth.js +52 -17
- package/dist/rules/max-import-depth.js.map +1 -1
- package/dist/rules/max-line-length.d.ts +9 -2
- package/dist/rules/max-line-length.d.ts.map +1 -1
- package/dist/rules/max-line-length.js +52 -15
- package/dist/rules/max-line-length.js.map +1 -1
- package/dist/rules/no-commented-out-code.d.ts +3 -7
- package/dist/rules/no-commented-out-code.d.ts.map +1 -1
- package/dist/rules/no-commented-out-code.js +157 -102
- package/dist/rules/no-commented-out-code.js.map +1 -1
- package/dist/rules/pascal-case-types.d.ts +1 -1
- package/dist/rules/pascal-case-types.d.ts.map +1 -1
- package/dist/rules/pascal-case-types.js +65 -46
- package/dist/rules/pascal-case-types.js.map +1 -1
- package/dist/rules/plugin-commented-out-code-rule.d.ts +32 -0
- package/dist/rules/plugin-commented-out-code-rule.d.ts.map +1 -0
- package/dist/rules/plugin-commented-out-code-rule.js +135 -0
- package/dist/rules/plugin-commented-out-code-rule.js.map +1 -0
- package/dist/rules/plugin.d.ts +5 -0
- package/dist/rules/plugin.d.ts.map +1 -1
- package/dist/rules/plugin.js +168 -154
- package/dist/rules/plugin.js.map +1 -1
- package/dist/rules/private-underscore.d.ts +1 -1
- package/dist/rules/private-underscore.d.ts.map +1 -1
- package/dist/rules/private-underscore.js +11 -4
- package/dist/rules/private-underscore.js.map +1 -1
- package/dist/rules/require-file-doc.d.ts +5 -6
- package/dist/rules/require-file-doc.d.ts.map +1 -1
- package/dist/rules/require-file-doc.js +127 -78
- package/dist/rules/require-file-doc.js.map +1 -1
- package/dist/rules/require-function-doc-ignored-text.d.ts +7 -0
- package/dist/rules/require-function-doc-ignored-text.d.ts.map +1 -0
- package/dist/rules/require-function-doc-ignored-text.js +67 -0
- package/dist/rules/require-function-doc-ignored-text.js.map +1 -0
- package/dist/rules/require-function-doc-local-exports.d.ts +7 -0
- package/dist/rules/require-function-doc-local-exports.d.ts.map +1 -0
- package/dist/rules/require-function-doc-local-exports.js +114 -0
- package/dist/rules/require-function-doc-local-exports.js.map +1 -0
- package/dist/rules/require-function-doc.d.ts +3 -18
- package/dist/rules/require-function-doc.d.ts.map +1 -1
- package/dist/rules/require-function-doc.js +396 -221
- package/dist/rules/require-function-doc.js.map +1 -1
- package/dist/rules/source-cache.d.ts +13 -5
- package/dist/rules/source-cache.d.ts.map +1 -1
- package/dist/rules/source-cache.js +35 -22
- package/dist/rules/source-cache.js.map +1 -1
- package/package.json +10 -2
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
/* -------------------------------------------------------------------------- */
|
|
2
|
+
/* Always-on custom Effect lint rule definitions. */
|
|
3
|
+
/* -------------------------------------------------------------------------- */
|
|
4
|
+
import { arrayValue, effectCallPredicate, identifierName, isStringLikeLiteral, memberParts, nodeType, objectValue, propertyKeyName, reportAST, } from './effect-default-ast.js';
|
|
5
|
+
import { exportedCallableDeclarationSegments, exportedDeclarationSegments, stripCommentsAndStrings, } from './effect-source-helpers.js';
|
|
6
|
+
import { hasAsyncAwaitInEffect, hasBroadCatchAllWithoutRethrow, hasEffectInArrayForEach, hasEffectInPromiseCallback, hasErrorMappingWithoutCause, hasFloatingEffect, hasMultipleCatchTagsInOnePipe, hasNestedFlatMap, hasRecursiveEffectWithoutSuspend, hasReturnEffectInGen, hasRunForkWithoutObserver, hasRuntimeInEffect, hasSyncForPromise, hasSyncForThrowingOPS, hasThrowInEffect, hasTryPromiseWithoutTypedCatch, hasUnloggedIgnore, hasUnobservedFork, hasUnsafeLazyEvaluation, hasYieldWithoutStarInGen, } from './effect-default-helpers.js';
|
|
7
|
+
import { hasEffectSignal, makeRules } from './effect-rule-core.js';
|
|
8
|
+
import { effectDefaultCompatibilitySpecs } from './effect-default-compat-rules.js';
|
|
9
|
+
import { effectDefaultEnvironmentSpecs } from './effect-default-env-rules.js';
|
|
10
|
+
import { strictPathOptionsSchema } from './effect-path-options.js';
|
|
7
11
|
const effectDefaultRuleTokens = [
|
|
8
12
|
'Effect',
|
|
9
13
|
'Schema',
|
|
@@ -22,130 +26,11 @@ const effectDefaultRuleTokens = [
|
|
|
22
26
|
'JSON.parse',
|
|
23
27
|
'response.json',
|
|
24
28
|
];
|
|
25
|
-
|
|
26
|
-
context.report({ message, node });
|
|
27
|
-
}
|
|
28
|
-
function nodeType(node) {
|
|
29
|
-
return typeof node === 'object' && node !== null ? node.type : undefined;
|
|
30
|
-
}
|
|
31
|
-
function identifierName(node) {
|
|
32
|
-
return nodeType(node) === 'Identifier' ? node.name : undefined;
|
|
33
|
-
}
|
|
34
|
-
function literalValue(node) {
|
|
35
|
-
return nodeType(node) === 'Literal' ? node.value : undefined;
|
|
36
|
-
}
|
|
37
|
-
function isStringLikeLiteral(node) {
|
|
38
|
-
if (typeof literalValue(node) === 'string') {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
if (nodeType(node) !== 'TemplateLiteral') {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
const { expressions } = node;
|
|
45
|
-
return expressions?.length === 0;
|
|
46
|
-
}
|
|
47
|
-
function memberParts(node) {
|
|
48
|
-
if (nodeType(node) !== 'MemberExpression') {
|
|
49
|
-
return {};
|
|
50
|
-
}
|
|
51
|
-
const member = node;
|
|
52
|
-
return {
|
|
53
|
-
objectName: identifierName(member.object),
|
|
54
|
-
propertyName: identifierName(member.property),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
function effectCallPredicate(source, names) {
|
|
58
|
-
const memberNames = new Set(names);
|
|
59
|
-
const importAliases = new Set(effectImportAliases(source));
|
|
60
|
-
const functionAliases = new Set(names.flatMap((name) => effectFunctionAliases(source, 'Effect', name)));
|
|
61
|
-
return (callee) => {
|
|
62
|
-
const { objectName, propertyName } = memberParts(callee);
|
|
63
|
-
if (objectName && propertyName) {
|
|
64
|
-
return importAliases.has(objectName) && memberNames.has(propertyName);
|
|
65
|
-
}
|
|
66
|
-
const calleeName = identifierName(callee);
|
|
67
|
-
return Boolean(calleeName && functionAliases.has(calleeName));
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function propertyKeyName(node) {
|
|
71
|
-
const value = literalValue(node);
|
|
72
|
-
return identifierName(node) ?? (typeof value === 'string' ? value : undefined);
|
|
73
|
-
}
|
|
74
|
-
function typeReferenceName(node) {
|
|
75
|
-
if (nodeType(node) !== 'TSTypeReference') {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
const { typeName } = node;
|
|
79
|
-
if (nodeType(typeName) === 'Identifier') {
|
|
80
|
-
return identifierName(typeName);
|
|
81
|
-
}
|
|
82
|
-
if (nodeType(typeName) !== 'TSQualifiedName') {
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
const qualified = typeName;
|
|
86
|
-
const leftName = identifierName(qualified.left);
|
|
87
|
-
const rightName = identifierName(qualified.right);
|
|
88
|
-
return leftName && rightName ? `${leftName}.${rightName}` : undefined;
|
|
89
|
-
}
|
|
90
|
-
function firstTypeArgumentName(node) {
|
|
91
|
-
const params = node.typeArguments?.params;
|
|
92
|
-
return typeReferenceName(params?.[0]);
|
|
93
|
-
}
|
|
94
|
-
function effectServiceSelfName(superClass, source) {
|
|
95
|
-
if (nodeType(superClass) !== 'CallExpression') {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
const outer = superClass;
|
|
99
|
-
const outerSelf = typeReferenceName(outer.typeArguments?.params?.[0]);
|
|
100
|
-
const inner = outer.callee;
|
|
101
|
-
if (nodeType(inner) !== 'CallExpression') {
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
const innerCall = inner;
|
|
105
|
-
const { objectName, propertyName } = memberParts(innerCall.callee);
|
|
106
|
-
if (objectName === 'Context' && propertyName === 'Tag') {
|
|
107
|
-
return outerSelf;
|
|
108
|
-
}
|
|
109
|
-
if (objectName &&
|
|
110
|
-
propertyName === 'Service' &&
|
|
111
|
-
effectImportAliases(source).includes(objectName)) {
|
|
112
|
-
return firstTypeArgumentName(inner);
|
|
113
|
-
}
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
function hasMixedEffectImportStyles(source) {
|
|
117
|
-
const hasNamedRootEffectImport = /import\s*{[^}]*\bEffect\b[^}]*}\s*from\s*['"]effect['"]/.test(source);
|
|
118
|
-
const hasNamespaceEffectImport = /import\s+\*\s+as\s+[A-Za-z_$][\w$]*\s+from\s*['"]effect(?:\/Effect)?['"]/.test(source);
|
|
119
|
-
return hasNamedRootEffectImport && hasNamespaceEffectImport;
|
|
120
|
-
}
|
|
121
|
-
function escapeRegExp(value) {
|
|
122
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
123
|
-
}
|
|
124
|
-
function hasTryCatchInEffectGen(source) {
|
|
125
|
-
const code = stripCommentsAndStrings(source);
|
|
126
|
-
const aliases = effectImportAliases(source).map(escapeRegExp).join('|');
|
|
127
|
-
if (!aliases) {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
for (const match of code.matchAll(new RegExp(`\\b(?:${aliases})\\.gen\\s*\\(`, 'g'))) {
|
|
131
|
-
const openParenIndex = code.indexOf('(', match.index);
|
|
132
|
-
if (openParenIndex === -1) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const bodyStart = openParenIndex + 1;
|
|
136
|
-
const body = code.slice(bodyStart, findBalancedCallEnd(code, openParenIndex));
|
|
137
|
-
const tryCatchMatch = /\btry\s*{[\s\S]*?\bcatch\s*\(/.exec(body);
|
|
138
|
-
if (tryCatchMatch?.index !== undefined) {
|
|
139
|
-
return bodyStart + tryCatchMatch.index;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
function hasStringErrorFailure(source) {
|
|
29
|
+
const hasStringErrorFailure = (source) => {
|
|
145
30
|
const match = /\bEffect\.fail\s*\(\s*["'`]/.exec(stripCommentsAndStrings(source));
|
|
146
31
|
return match?.index ?? false;
|
|
147
|
-
}
|
|
148
|
-
|
|
32
|
+
};
|
|
33
|
+
const hasUntaggedErrorFailure = (source) => {
|
|
149
34
|
const code = stripCommentsAndStrings(source);
|
|
150
35
|
const nativeErrorMatch = /\bEffect\.fail\s*\(\s*new\s+Error\s*\(/.exec(code);
|
|
151
36
|
if (nativeErrorMatch) {
|
|
@@ -153,124 +38,94 @@ function hasUntaggedErrorFailure(source) {
|
|
|
153
38
|
}
|
|
154
39
|
const objectErrorMatch = /\bEffect\.fail\s*\(\s*{(?![^}]*\b_tag\s*:)/.exec(code);
|
|
155
40
|
return objectErrorMatch?.index ?? false;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const match = /\bclass\s+[A-Z][\w$]*\s+extends\s+Error\b/.exec(stripCommentsAndStrings(source));
|
|
162
|
-
return match?.index ?? false;
|
|
163
|
-
}
|
|
164
|
-
function hasUnsafeEffectTypeAssertion(source) {
|
|
165
|
-
const code = stripCommentsAndStrings(source);
|
|
166
|
-
const match = /\bas\s+Effect\.Effect\s*<[^>]*>/.exec(code);
|
|
167
|
-
return match?.index ?? false;
|
|
168
|
-
}
|
|
169
|
-
function hasServiceSelfMismatch(source) {
|
|
170
|
-
const code = stripCommentsAndStrings(source);
|
|
171
|
-
const legacyPattern = /\bclass\s+([A-Z][\w$]*)\s+extends\s+(?:Context\.Tag|Effect\.Service|Effect\.Tag)\s*\([^)]*\)\s*<\s*([A-Z][\w$]*)\b/g;
|
|
172
|
-
for (const match of code.matchAll(legacyPattern)) {
|
|
173
|
-
const [, className, selfName] = match;
|
|
174
|
-
if (className !== selfName) {
|
|
175
|
-
return match.index;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const servicePattern = /\bclass\s+([A-Z][\w$]*)\s+extends\s+Effect\.Service\s*<\s*([A-Z][\w$]*)\s*>\s*\(\s*\)/g;
|
|
179
|
-
for (const match of code.matchAll(servicePattern)) {
|
|
180
|
-
const [, className, selfName] = match;
|
|
181
|
-
if (className !== selfName) {
|
|
182
|
-
return match.index;
|
|
183
|
-
}
|
|
41
|
+
};
|
|
42
|
+
const isUntaggedErrorFailureArgument = (firstArg) => {
|
|
43
|
+
if (nodeType(firstArg) === 'NewExpression' &&
|
|
44
|
+
identifierName(objectValue(firstArg, 'callee')) === 'Error') {
|
|
45
|
+
return true;
|
|
184
46
|
}
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const code = stripCommentsAndStrings(source);
|
|
189
|
-
const match = /\bEffect\.fn(?:Untraced|UntracedEager)?\s*\([\s\S]*?\)\s*\([\s\S]*?\)\s*\(/.exec(code);
|
|
190
|
-
return match?.index ?? false;
|
|
191
|
-
}
|
|
47
|
+
return (nodeType(firstArg) === 'ObjectExpression' &&
|
|
48
|
+
!arrayValue(objectValue(firstArg, 'properties')).some((property) => propertyKeyName(objectValue(property, 'key')) === '_tag'));
|
|
49
|
+
};
|
|
192
50
|
const effectDefaultSpecs = [
|
|
193
51
|
{
|
|
194
|
-
name: 'effect-no-floating-effect',
|
|
195
|
-
message: 'Return, yield, assign, or compose Effect values; bare Effect calls never execute.',
|
|
196
52
|
check: hasFloatingEffect,
|
|
53
|
+
message: 'Return, yield, assign, or compose Effect values; bare Effect calls never execute.',
|
|
54
|
+
name: 'effect-no-floating-effect',
|
|
197
55
|
},
|
|
198
56
|
{
|
|
199
|
-
|
|
57
|
+
check: hasYieldWithoutStarInGen,
|
|
200
58
|
message: 'Use yield* inside Effect.gen so generator composition unwraps the Effect value.',
|
|
59
|
+
name: 'effect-require-yield-star',
|
|
201
60
|
tokenGroups: [['gen'], ['yield']],
|
|
202
|
-
check: hasYieldWithoutStarInGen,
|
|
203
61
|
},
|
|
204
62
|
{
|
|
205
|
-
|
|
63
|
+
check: hasReturnEffectInGen,
|
|
206
64
|
message: 'Do not return an Effect from Effect.gen; return a value or return yield* the Effect.',
|
|
65
|
+
name: 'effect-require-return-yield-star',
|
|
207
66
|
tokenGroups: [['gen'], ['return']],
|
|
208
|
-
check: hasReturnEffectInGen,
|
|
209
67
|
},
|
|
210
68
|
{
|
|
211
|
-
|
|
69
|
+
check: hasNestedFlatMap,
|
|
212
70
|
message: 'Replace nested Effect.flatMap callbacks with Effect.gen for readable sequencing.',
|
|
71
|
+
name: 'effect-prefer-gen-for-nested-flatmap',
|
|
213
72
|
tokens: ['flatMap'],
|
|
214
|
-
check: hasNestedFlatMap,
|
|
215
73
|
},
|
|
216
74
|
{
|
|
217
|
-
|
|
75
|
+
check: (source) => exportedCallableDeclarationSegments(source).some((segment) => /(?:^|\breturn\s+)Effect\.gen\s*\(/.test(segment.trim())),
|
|
218
76
|
message: 'Use Effect.fn for exported effectful functions instead of returning Effect.gen.',
|
|
77
|
+
name: 'effect-no-function-returning-gen',
|
|
219
78
|
tokens: ['gen'],
|
|
220
|
-
check: (source) => exportedCallableDeclarationSegments(source).some((segment) => /(?:^|\breturn\s+)Effect\.gen\s*\(/.test(segment.trim())),
|
|
221
79
|
},
|
|
222
80
|
{
|
|
223
|
-
|
|
81
|
+
check: (source) => exportedCallableDeclarationSegments(source).some((segment) => /(?:^|\breturn\s+)Effect\.(?!fn\b|gen\b|isEffect\b|serviceFunction\b|runPromise\b)/.test(segment.trim())),
|
|
224
82
|
message: 'Exported effectful functions should use Effect.fn for tracing and stable contracts.',
|
|
83
|
+
name: 'effect-prefer-effect-fn-for-exported-effects',
|
|
225
84
|
tokens: ['export'],
|
|
226
|
-
check: (source) => exportedCallableDeclarationSegments(source).some((segment) => /(?:^|\breturn\s+)Effect\.(?!fn\b|gen\b|isEffect\b|serviceFunction\b|runPromise\b)/.test(segment.trim())),
|
|
227
85
|
},
|
|
228
86
|
{
|
|
229
|
-
name: 'effect-no-unnecessary-gen',
|
|
230
87
|
message: 'Do not wrap a single Effect in Effect.gen when direct composition is clearer.',
|
|
231
|
-
|
|
88
|
+
name: 'effect-no-unnecessary-gen',
|
|
232
89
|
patterns: [/Effect\.gen\s*\(\s*function\*\s*\([^)]*\)\s*{\s*return\s+yield\*\s+Effect\./],
|
|
90
|
+
tokenGroups: [['gen'], ['yield']],
|
|
233
91
|
},
|
|
234
92
|
{
|
|
235
|
-
|
|
93
|
+
check: hasEffectInArrayForEach,
|
|
236
94
|
message: 'Use Effect.forEach instead of Array.forEach with Effect-returning callbacks.',
|
|
95
|
+
name: 'effect-no-effect-in-array-foreach',
|
|
237
96
|
tokens: ['forEach'],
|
|
238
|
-
check: hasEffectInArrayForEach,
|
|
239
97
|
},
|
|
240
98
|
{
|
|
241
|
-
|
|
99
|
+
check: hasEffectInPromiseCallback,
|
|
242
100
|
message: 'Do not create Effect values inside Promise callbacks; compose at the Effect boundary.',
|
|
101
|
+
name: 'effect-no-effect-in-promise-callback',
|
|
243
102
|
tokens: ['.then', '.catch'],
|
|
244
|
-
check: hasEffectInPromiseCallback,
|
|
245
103
|
},
|
|
246
104
|
{
|
|
247
|
-
|
|
105
|
+
check: hasUnobservedFork,
|
|
248
106
|
message: 'Forked fibers must be joined, interrupted, scoped, supervised, or returned.',
|
|
107
|
+
name: 'effect-no-floating-fiber',
|
|
249
108
|
tokens: ['fork'],
|
|
250
|
-
check: hasUnobservedFork,
|
|
251
109
|
},
|
|
252
110
|
{
|
|
253
|
-
|
|
111
|
+
check: hasRecursiveEffectWithoutSuspend,
|
|
254
112
|
message: 'Recursive Effect construction must be wrapped in Effect.suspend.',
|
|
113
|
+
name: 'effect-require-suspend-for-recursion',
|
|
255
114
|
tokens: ['function', '=>'],
|
|
256
|
-
check: hasRecursiveEffectWithoutSuspend,
|
|
257
115
|
},
|
|
258
116
|
{
|
|
259
|
-
|
|
117
|
+
check: hasUnsafeLazyEvaluation,
|
|
260
118
|
message: 'Use Effect.suspend when Effect construction must defer eager JavaScript work.',
|
|
119
|
+
name: 'effect-require-suspend-for-lazy-evaluation',
|
|
261
120
|
tokens: ['Date.now', 'Math.random', 'new Date', 'JSON.parse'],
|
|
262
|
-
check: hasUnsafeLazyEvaluation,
|
|
263
121
|
},
|
|
264
122
|
{
|
|
265
|
-
|
|
123
|
+
check: hasAsyncAwaitInEffect,
|
|
266
124
|
message: 'Use Effect.tryPromise or Effect.promise boundaries instead of async/await in Effect code.',
|
|
125
|
+
name: 'effect-no-async-await-in-effect',
|
|
267
126
|
tokens: ['async', 'await'],
|
|
268
|
-
check: hasAsyncAwaitInEffect,
|
|
269
127
|
},
|
|
270
128
|
{
|
|
271
|
-
name: 'effect-no-promise-then-in-effect',
|
|
272
|
-
message: 'Use Effect combinators instead of Promise then/catch chains in Effect modules.',
|
|
273
|
-
tokens: ['.then', '.catch'],
|
|
274
129
|
ast: (context, source) => {
|
|
275
130
|
const isEffectModule = hasEffectSignal(source);
|
|
276
131
|
return {
|
|
@@ -278,601 +133,171 @@ const effectDefaultSpecs = [
|
|
|
278
133
|
if (!isEffectModule) {
|
|
279
134
|
return;
|
|
280
135
|
}
|
|
281
|
-
const {
|
|
282
|
-
const { propertyName } = memberParts(callee);
|
|
136
|
+
const { propertyName } = memberParts(objectValue(node, 'callee'));
|
|
283
137
|
if (propertyName === 'then' || propertyName === 'catch') {
|
|
284
|
-
|
|
138
|
+
reportAST(context, 'Use Effect combinators instead of Promise then/catch chains in Effect modules.', node);
|
|
285
139
|
}
|
|
286
140
|
},
|
|
287
141
|
};
|
|
288
142
|
},
|
|
143
|
+
message: 'Use Effect combinators instead of Promise then/catch chains in Effect modules.',
|
|
144
|
+
name: 'effect-no-promise-then-in-effect',
|
|
145
|
+
tokens: ['.then', '.catch'],
|
|
289
146
|
},
|
|
290
147
|
{
|
|
291
|
-
|
|
148
|
+
check: hasThrowInEffect,
|
|
292
149
|
message: 'Use typed Effect failures instead of throw inside Effect workflows.',
|
|
150
|
+
name: 'effect-no-throw',
|
|
293
151
|
tokens: ['throw'],
|
|
294
|
-
check: hasThrowInEffect,
|
|
295
152
|
},
|
|
296
153
|
{
|
|
297
|
-
name: 'effect-no-string-errors',
|
|
298
|
-
message: 'Use structured tagged errors instead of string failures.',
|
|
299
|
-
tokens: ['fail'],
|
|
300
|
-
check: hasStringErrorFailure,
|
|
301
154
|
ast: (context, source) => {
|
|
302
155
|
const isEffectFail = effectCallPredicate(source, ['fail']);
|
|
303
156
|
return {
|
|
304
157
|
CallExpression(node) {
|
|
305
|
-
const
|
|
306
|
-
if (isEffectFail(
|
|
307
|
-
|
|
158
|
+
const callArguments = arrayValue(objectValue(node, 'arguments'));
|
|
159
|
+
if (isEffectFail(objectValue(node, 'callee')) && isStringLikeLiteral(callArguments[0])) {
|
|
160
|
+
reportAST(context, 'Use structured tagged errors instead of string failures.', node);
|
|
308
161
|
}
|
|
309
162
|
},
|
|
310
163
|
};
|
|
311
164
|
},
|
|
165
|
+
check: hasStringErrorFailure,
|
|
166
|
+
message: 'Use structured tagged errors instead of string failures.',
|
|
167
|
+
name: 'effect-no-string-errors',
|
|
168
|
+
tokens: ['fail'],
|
|
312
169
|
},
|
|
313
170
|
{
|
|
314
|
-
name: 'effect-no-untagged-errors',
|
|
315
|
-
message: 'Use tagged/data/schema errors in Effect.fail instead of global Error values.',
|
|
316
|
-
tokens: ['fail'],
|
|
317
|
-
check: hasUntaggedErrorFailure,
|
|
318
171
|
ast: (context, source) => {
|
|
319
172
|
const isEffectFail = effectCallPredicate(source, ['fail']);
|
|
320
173
|
return {
|
|
321
174
|
CallExpression(node) {
|
|
322
|
-
const
|
|
323
|
-
const firstArg =
|
|
324
|
-
if (!isEffectFail(
|
|
175
|
+
const callArguments = arrayValue(objectValue(node, 'arguments'));
|
|
176
|
+
const [firstArg] = callArguments;
|
|
177
|
+
if (!isEffectFail(objectValue(node, 'callee'))) {
|
|
325
178
|
return;
|
|
326
179
|
}
|
|
327
180
|
if (!firstArg) {
|
|
328
181
|
return;
|
|
329
182
|
}
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
reportAst(context, 'Use tagged/data/schema errors in Effect.fail instead of global Error values.', node);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
if (nodeType(firstArg) === 'ObjectExpression' &&
|
|
336
|
-
!firstArg.properties?.some((property) => propertyKeyName(property.key) === '_tag')) {
|
|
337
|
-
reportAst(context, 'Use tagged/data/schema errors in Effect.fail instead of global Error values.', node);
|
|
183
|
+
if (isUntaggedErrorFailureArgument(firstArg)) {
|
|
184
|
+
reportAST(context, 'Use tagged/data/schema errors in Effect.fail instead of global Error values.', node);
|
|
338
185
|
}
|
|
339
186
|
},
|
|
340
187
|
};
|
|
341
188
|
},
|
|
189
|
+
check: hasUntaggedErrorFailure,
|
|
190
|
+
message: 'Use tagged/data/schema errors in Effect.fail instead of global Error values.',
|
|
191
|
+
name: 'effect-no-untagged-errors',
|
|
192
|
+
tokens: ['fail'],
|
|
342
193
|
},
|
|
343
194
|
{
|
|
344
|
-
name: 'effect-no-silent-error-swallowing',
|
|
345
195
|
message: 'Do not erase Effect failures without recovery, logging, or explicit typed replacement.',
|
|
346
|
-
|
|
196
|
+
name: 'effect-no-silent-error-swallowing',
|
|
347
197
|
patterns: [
|
|
348
198
|
/Effect\.(?:catchAll|ignore)\s*\([\s\S]*?(?:Effect\.void|Effect\.succeed\s*\(\s*undefined|undefined)/,
|
|
349
199
|
],
|
|
200
|
+
tokens: ['catchAll', 'ignore'],
|
|
350
201
|
},
|
|
351
202
|
{
|
|
352
|
-
|
|
203
|
+
check: hasTryPromiseWithoutTypedCatch,
|
|
353
204
|
message: 'Use Effect.tryPromise({ try, catch }) so Promise failures become typed Effect errors.',
|
|
205
|
+
name: 'effect-require-typed-error-in-trypromise',
|
|
354
206
|
tokens: ['tryPromise'],
|
|
355
|
-
check: hasTryPromiseWithoutTypedCatch,
|
|
356
207
|
},
|
|
357
208
|
{
|
|
358
|
-
|
|
209
|
+
check: hasBroadCatchAllWithoutRethrow,
|
|
359
210
|
message: 'Prefer catchTag or catchTags over broad catchAll recovery.',
|
|
211
|
+
name: 'effect-prefer-catchTag-over-catchAll',
|
|
360
212
|
tokens: ['catchAll'],
|
|
361
|
-
check: hasBroadCatchAllWithoutRethrow,
|
|
362
213
|
},
|
|
363
214
|
{
|
|
364
|
-
name: 'effect-no-catchAll-with-mapError',
|
|
365
215
|
message: 'Use mapError directly instead of catchAll when only transforming the error.',
|
|
366
|
-
|
|
216
|
+
name: 'effect-no-catchAll-with-mapError',
|
|
367
217
|
patterns: [/Effect\.catchAll\s*\([\s\S]*?=>\s*Effect\.fail\s*\(/],
|
|
218
|
+
tokens: ['catchAll'],
|
|
368
219
|
},
|
|
369
220
|
{
|
|
370
|
-
name: 'effect-prefer-mapError-over-catchAll-rethrow',
|
|
371
221
|
message: 'Use Effect.mapError instead of catchAll followed by fail.',
|
|
372
|
-
|
|
222
|
+
name: 'effect-prefer-mapError-over-catchAll-rethrow',
|
|
373
223
|
patterns: [/(?:^|[^\w$.])catchAll\s*\([\s\S]*?=>\s*Effect\.fail\s*\(/],
|
|
224
|
+
tokens: ['catchAll'],
|
|
374
225
|
},
|
|
375
226
|
{
|
|
376
|
-
|
|
227
|
+
check: hasErrorMappingWithoutCause,
|
|
377
228
|
message: 'Preserve the original cause when mapping or wrapping Effect errors.',
|
|
229
|
+
name: 'effect-require-error-cause-preserved',
|
|
378
230
|
tokens: ['mapError', 'catchAll'],
|
|
379
|
-
check: hasErrorMappingWithoutCause,
|
|
380
231
|
},
|
|
381
232
|
{
|
|
382
|
-
|
|
233
|
+
check: hasUnloggedIgnore,
|
|
383
234
|
message: 'Ignored Effect failures must be logged or otherwise observable.',
|
|
235
|
+
name: 'effect-prefer-ignore-logged',
|
|
384
236
|
tokens: ['ignore'],
|
|
385
|
-
check: hasUnloggedIgnore,
|
|
386
237
|
},
|
|
387
238
|
{
|
|
388
|
-
|
|
239
|
+
check: hasMultipleCatchTagsInOnePipe,
|
|
389
240
|
message: 'Use Effect.catchTags for multiple tagged Effect recoveries.',
|
|
241
|
+
name: 'effect-prefer-catchTags-for-multiple-tags',
|
|
390
242
|
tokens: ['catchTag'],
|
|
391
|
-
check: hasMultipleCatchTagsInOnePipe,
|
|
392
243
|
},
|
|
393
244
|
{
|
|
394
|
-
name: 'effect-no-error-channel-widening-to-unknown',
|
|
395
245
|
message: 'Do not widen Effect error channels to unknown.',
|
|
396
|
-
|
|
246
|
+
name: 'effect-no-error-channel-widening-to-unknown',
|
|
397
247
|
patterns: [/Effect\s*<[^>]*,\s*unknown\b/, /Effect\.fail\s*<\s*unknown\b/],
|
|
248
|
+
tokens: ['unknown'],
|
|
398
249
|
},
|
|
399
250
|
{
|
|
400
|
-
|
|
251
|
+
check: hasRuntimeInEffect,
|
|
401
252
|
message: 'Do not run an Effect from inside another Effect; yield or compose it instead.',
|
|
253
|
+
name: 'effect-no-run-inside-effect',
|
|
402
254
|
tokens: ['run'],
|
|
403
|
-
check: hasRuntimeInEffect,
|
|
404
255
|
},
|
|
405
256
|
{
|
|
406
|
-
|
|
257
|
+
check: (source) => exportedDeclarationSegments(source).some((segment) => /Effect\.runPromise\s*\(/.test(segment)),
|
|
407
258
|
message: 'Exported APIs should expose Effect values instead of hiding execution behind Promise.',
|
|
259
|
+
name: 'effect-no-runpromise-in-exported-api',
|
|
408
260
|
tokens: ['runPromise'],
|
|
409
|
-
check: (source) => exportedDeclarationSegments(source).some((segment) => /Effect\.runPromise\s*\(/.test(segment)),
|
|
410
261
|
},
|
|
411
262
|
{
|
|
412
|
-
|
|
263
|
+
check: hasRunForkWithoutObserver,
|
|
413
264
|
message: 'Do not call runFork without explicit observation, supervision, or interruption.',
|
|
265
|
+
name: 'effect-no-runfork-without-observer',
|
|
414
266
|
tokens: ['runFork'],
|
|
415
|
-
check: hasRunForkWithoutObserver,
|
|
416
267
|
},
|
|
417
268
|
{
|
|
418
|
-
|
|
269
|
+
check: hasSyncForPromise,
|
|
419
270
|
message: 'Use Effect.tryPromise for Promise-returning code instead of Effect.sync.',
|
|
271
|
+
name: 'effect-no-sync-for-promise',
|
|
420
272
|
tokenGroups: [['sync'], ['async', 'fetch', 'Promise.']],
|
|
421
|
-
check: hasSyncForPromise,
|
|
422
273
|
},
|
|
423
274
|
{
|
|
424
|
-
|
|
275
|
+
check: hasSyncForThrowingOPS,
|
|
425
276
|
message: 'Use Effect.try for synchronous code that can throw instead of Effect.sync.',
|
|
277
|
+
name: 'effect-no-sync-for-throwing-ops',
|
|
426
278
|
tokenGroups: [['sync'], ['throw', 'JSON.parse']],
|
|
427
|
-
check: hasSyncForThrowingOps,
|
|
428
279
|
},
|
|
429
280
|
{
|
|
430
|
-
name: 'effect-no-console-log-in-effect-code',
|
|
431
|
-
message: 'Use Effect logging APIs instead of console logging in Effect code.',
|
|
432
|
-
tokens: ['console'],
|
|
433
281
|
ast: (context, source) => {
|
|
434
282
|
const isEffectModule = hasEffectSignal(source);
|
|
435
283
|
return {
|
|
436
284
|
CallExpression(node) {
|
|
437
|
-
const { objectName, propertyName } = memberParts(node
|
|
285
|
+
const { objectName, propertyName } = memberParts(objectValue(node, 'callee'));
|
|
438
286
|
if (isEffectModule &&
|
|
439
287
|
objectName === 'console' &&
|
|
440
288
|
propertyName &&
|
|
441
289
|
['debug', 'error', 'info', 'log', 'warn'].includes(propertyName)) {
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
name: 'effect-no-process-env-in-effect-code',
|
|
450
|
-
message: 'Use Effect Config instead of process.env in Effect code.',
|
|
451
|
-
tokens: ['process'],
|
|
452
|
-
ast: (context, source) => {
|
|
453
|
-
const isEffectModule = hasEffectSignal(source);
|
|
454
|
-
const isConfigLayer = isConfiguredPath(context, 'configLayers');
|
|
455
|
-
return {
|
|
456
|
-
MemberExpression(node) {
|
|
457
|
-
const { objectName, propertyName } = memberParts(node);
|
|
458
|
-
const processEnv = objectName === 'process' && propertyName === 'env';
|
|
459
|
-
if (isEffectModule && !isConfigLayer && processEnv) {
|
|
460
|
-
reportAst(context, 'Use Effect Config instead of process.env in Effect code.', node);
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
name: 'effect-no-date-now-in-effect-code',
|
|
468
|
-
message: 'Use Effect Clock instead of Date.now in Effect code.',
|
|
469
|
-
tokens: ['Date'],
|
|
470
|
-
ast: (context, source) => {
|
|
471
|
-
const isEffectModule = hasEffectSignal(source);
|
|
472
|
-
const isAdapterLayer = isConfiguredPath(context, 'adapterLayers');
|
|
473
|
-
return {
|
|
474
|
-
CallExpression(node) {
|
|
475
|
-
const { objectName, propertyName } = memberParts(node.callee);
|
|
476
|
-
if (isEffectModule &&
|
|
477
|
-
!isAdapterLayer &&
|
|
478
|
-
objectName === 'Date' &&
|
|
479
|
-
propertyName === 'now') {
|
|
480
|
-
reportAst(context, 'Use Effect Clock instead of Date.now in Effect code.', node);
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
};
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
name: 'effect-no-math-random-in-effect-code',
|
|
488
|
-
message: 'Use Effect Random instead of Math.random in Effect code.',
|
|
489
|
-
tokens: ['Math'],
|
|
490
|
-
ast: (context, source) => {
|
|
491
|
-
const isEffectModule = hasEffectSignal(source);
|
|
492
|
-
const isAdapterLayer = isConfiguredPath(context, 'adapterLayers');
|
|
493
|
-
return {
|
|
494
|
-
CallExpression(node) {
|
|
495
|
-
const { objectName, propertyName } = memberParts(node.callee);
|
|
496
|
-
if (isEffectModule &&
|
|
497
|
-
!isAdapterLayer &&
|
|
498
|
-
objectName === 'Math' &&
|
|
499
|
-
propertyName === 'random') {
|
|
500
|
-
reportAst(context, 'Use Effect Random instead of Math.random in Effect code.', node);
|
|
501
|
-
}
|
|
502
|
-
},
|
|
503
|
-
};
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
name: 'effect-no-json-parse-cast',
|
|
508
|
-
message: 'Decode external JSON with Schema instead of casting parsed unknown data.',
|
|
509
|
-
tokenGroups: [[' as '], ['JSON.parse', '.json']],
|
|
510
|
-
patterns: [
|
|
511
|
-
/\bJSON\.parse\s*\([^)]*\)\s+as\s+[A-Za-z_$][\w$]*/,
|
|
512
|
-
/\.json\s*\(\s*\)\s+as\s+[A-Za-z_$][\w$]*/,
|
|
513
|
-
],
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
name: 'effect-schema-prefer-decodeUnknown-effect',
|
|
517
|
-
message: 'Use Schema.decodeUnknown to decode unknown input into the Effect error channel.',
|
|
518
|
-
tokens: ['decode'],
|
|
519
|
-
check: hasSchemaPromiseDecode,
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
name: 'effect-schema-require-parse-error-handling',
|
|
523
|
-
message: 'Schema parsing must expose parse errors through typed Effect handling.',
|
|
524
|
-
tokens: ['decode'],
|
|
525
|
-
check: hasUnhandledSchemaEffectDecode,
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
name: 'effect-schema-use-decodeUnknown-for-external-data',
|
|
529
|
-
message: 'External data must enter through Schema.decodeUnknown.',
|
|
530
|
-
tokens: ['.json'],
|
|
531
|
-
check: hasExternalJsonWithoutDecodeUnknown,
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
name: 'effect-schema-no-unsafe-sync-decode-in-effect-code',
|
|
535
|
-
message: 'Do not use throwing synchronous Schema decoders in Effect modules.',
|
|
536
|
-
tokens: ['decodeSync', 'decodeUnknownSync'],
|
|
537
|
-
check: hasSchemaSyncDecodeInEffectWorkflow,
|
|
538
|
-
},
|
|
539
|
-
{
|
|
540
|
-
name: 'effect-schema-require-parseJson-for-json-strings',
|
|
541
|
-
message: 'Use Schema.parseJson when decoding JSON strings with Schema.',
|
|
542
|
-
tokens: ['JSON.parse'],
|
|
543
|
-
check: hasJsonParsedBeforeSchemaStringDecode,
|
|
544
|
-
},
|
|
545
|
-
{
|
|
546
|
-
name: 'effect-schema-correct-number-type-for-parsed-json',
|
|
547
|
-
message: 'Use the correct Schema number type for already-parsed JSON numbers.',
|
|
548
|
-
tokens: ['JSON.parse'],
|
|
549
|
-
check: hasParsedJsonNumberFromString,
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
name: 'effect-schema-prefer-taggedClass-over-manual-tag',
|
|
553
|
-
message: 'Prefer Schema.TaggedClass over hand-written _tag fields.',
|
|
554
|
-
tokens: ['_tag'],
|
|
555
|
-
patterns: [/Schema\.Struct\s*\(\s*{[\s\S]*?_tag\s*:\s*Schema\.Literal/],
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
name: 'effect-schema-avoid-old-type-names',
|
|
559
|
-
message: 'Use current Effect Schema API names instead of obsolete lowercase helpers.',
|
|
560
|
-
tokens: ['Schema.'],
|
|
561
|
-
patterns: [/\bSchema\.(?:string|number|boolean|array|object)\s*\(/],
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: 'effect-schema-no-cast-after-decode',
|
|
565
|
-
message: 'Do not cast after Schema decoding; let the schema provide the type.',
|
|
566
|
-
tokenGroups: [['Schema.decode'], [' as ']],
|
|
567
|
-
check: hasCastAfterSchemaDecode,
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
name: 'effect-require-acquire-release',
|
|
571
|
-
message: 'Resource acquisition must use acquireRelease, scoped, or equivalent finalization.',
|
|
572
|
-
tokens: ['open', 'connect', 'subscribe', 'listen'],
|
|
573
|
-
check: hasUnreleasedAcquire,
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
name: 'effect-require-scoped-for-acquireRelease',
|
|
577
|
-
message: 'Use Effect.scoped around acquireRelease when exposing acquired resources.',
|
|
578
|
-
tokens: ['acquireRelease'],
|
|
579
|
-
check: hasUnscopedAcquireRelease,
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
name: 'effect-require-scoped-for-resources',
|
|
583
|
-
message: 'Resourceful workflows must be scoped.',
|
|
584
|
-
tokens: ['Socket.', 'Connection.'],
|
|
585
|
-
check: hasUnscopedResourceWorkflow,
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
name: 'effect-no-fork-daemon-without-cleanup',
|
|
589
|
-
message: 'Daemon fibers must have cleanup, interruption, or supervision.',
|
|
590
|
-
tokens: ['forkDaemon'],
|
|
591
|
-
check: hasForkDaemonWithoutCleanup,
|
|
592
|
-
},
|
|
593
|
-
{
|
|
594
|
-
name: 'effect-prefer-fork-scoped-for-listeners',
|
|
595
|
-
message: 'Long-running listeners should use forkScoped so they follow Scope lifetime.',
|
|
596
|
-
tokens: ['fork'],
|
|
597
|
-
patterns: [
|
|
598
|
-
/Effect\.fork\s*\([\s\S]*?\b(?:listen[A-Z]\w*|subscribe[A-Z]\w*|watch[A-Z]\w*|listen|subscribe|watch)\b/,
|
|
599
|
-
],
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
name: 'effect-require-restore-for-fork-in-uninterruptible',
|
|
603
|
-
message: 'Use restore when forking inside uninterruptible regions.',
|
|
604
|
-
tokenGroups: [['uninterruptible'], ['fork']],
|
|
605
|
-
check: hasForkInUninterruptibleWithoutRestore,
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
name: 'effect-require-bounded-concurrency',
|
|
609
|
-
message: 'Concurrent Effect traversal must declare an explicit concurrency bound.',
|
|
610
|
-
tokens: ['concurrency'],
|
|
611
|
-
check: hasUnboundedEffectConcurrency,
|
|
612
|
-
},
|
|
613
|
-
{
|
|
614
|
-
name: 'effect-require-bounded-flatMap-concurrency',
|
|
615
|
-
message: 'Concurrent Effect.flatMap usage must declare a bounded concurrency value.',
|
|
616
|
-
tokenGroups: [['flatMap'], ['concurrency']],
|
|
617
|
-
check: hasUnboundedFlatMapConcurrency,
|
|
618
|
-
},
|
|
619
|
-
{
|
|
620
|
-
name: 'effect-no-unbounded-queue',
|
|
621
|
-
message: 'Use bounded, sliding, or dropping queues instead of unbounded queues.',
|
|
622
|
-
tokens: ['unbounded'],
|
|
623
|
-
patterns: [/\bQueue\.unbounded\s*\(/],
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
name: 'effect-no-unbounded-stream-buffer',
|
|
627
|
-
message: 'Stream buffers must be explicitly bounded.',
|
|
628
|
-
tokens: ['unbounded', 'Infinity'],
|
|
629
|
-
patterns: [
|
|
630
|
-
/\bStream\.(?:buffer|fromQueue|async|asyncPush)\s*\([^)]*\b(?:Infinity|unbounded)\b/,
|
|
631
|
-
],
|
|
632
|
-
},
|
|
633
|
-
{
|
|
634
|
-
name: 'effect-test-no-runpromise',
|
|
635
|
-
message: 'Use @effect/vitest it.effect instead of manually running Effects in tests.',
|
|
636
|
-
tokens: ['run'],
|
|
637
|
-
check: (source, context) => isEffectTestPath(context) && hasRuntimeCall(source),
|
|
638
|
-
},
|
|
639
|
-
{
|
|
640
|
-
name: 'effect-prefer-it-effect-for-unit-tests',
|
|
641
|
-
message: 'Use it.effect for tests that exercise Effect programs.',
|
|
642
|
-
tokens: ['it('],
|
|
643
|
-
check: (source, context) => isEffectTestPath(context) &&
|
|
644
|
-
!hasRuntimeCall(source) &&
|
|
645
|
-
/\bit\s*\([\s\S]*?Effect\./.test(stripCommentsAndStrings(source)),
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
name: 'effect-testClock-requires-fork',
|
|
649
|
-
message: 'Fork time-dependent work before adjusting TestClock.',
|
|
650
|
-
tokens: ['TestClock.adjust'],
|
|
651
|
-
check: (source) => /TestClock\.adjust\s*\(/.test(stripCommentsAndStrings(source)) &&
|
|
652
|
-
!hasForkBeforeTestClockAdjust(source),
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
name: 'effect-testClock-requires-testContext',
|
|
656
|
-
message: 'Use Effect test context when using TestClock.',
|
|
657
|
-
tokens: ['TestClock'],
|
|
658
|
-
check: (source, context) => isEffectTestPath(context) && hasTestClockWithoutEffectContext(source),
|
|
659
|
-
},
|
|
660
|
-
{
|
|
661
|
-
name: 'effect-no-real-sleep-in-tests',
|
|
662
|
-
message: 'Use TestClock instead of real sleeps in Effect tests.',
|
|
663
|
-
tokens: ['sleep'],
|
|
664
|
-
check: (source, context) => isEffectTestPath(context) && hasRealSleepWithoutTestClock(source),
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
name: 'effect-use-exit-for-failure-tests',
|
|
668
|
-
message: 'Use Effect.exit inside it.effect when asserting Effect failures.',
|
|
669
|
-
tokens: ['rejects', 'toThrow'],
|
|
670
|
-
check: (source, context) => isEffectTestPath(context) &&
|
|
671
|
-
!hasRuntimeCall(source) &&
|
|
672
|
-
/\b(?:rejects|toThrow)\b/.test(stripCommentsAndStrings(source)) &&
|
|
673
|
-
/\bEffect\./.test(stripCommentsAndStrings(source)),
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
name: 'effect-no-focused-effect-tests',
|
|
677
|
-
message: 'Focused Effect tests must not be committed.',
|
|
678
|
-
tokens: ['.only'],
|
|
679
|
-
check: (source, context) => isEffectTestPath(context) &&
|
|
680
|
-
/\b(?:it|describe)\.effect\.only\s*\(/.test(stripCommentsAndStrings(source)),
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
name: 'effect-no-skipped-effect-tests',
|
|
684
|
-
message: 'Skipped Effect tests must not be committed.',
|
|
685
|
-
tokens: ['.skip'],
|
|
686
|
-
check: (source, context) => isEffectTestPath(context) &&
|
|
687
|
-
/\b(?:it|describe)\.effect\.skip\s*\(/.test(stripCommentsAndStrings(source)),
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
name: 'effect-no-obsolete-imports',
|
|
691
|
-
message: 'Import Effect APIs from the main effect package; deprecated split packages are blocked.',
|
|
692
|
-
tokens: ['@effect/io', '@effect/data'],
|
|
693
|
-
patterns: [/from\s+['"]@effect\/(?:io|data)['"]/],
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
name: 'effect-no-known-fake-api',
|
|
697
|
-
message: 'This is not a known Effect API for the configured version.',
|
|
698
|
-
tokens: ['fromPromise', 'tryCatch', 'bracket', 'fromEither'],
|
|
699
|
-
check: (source) => /\bEffect\.(?:fromPromise|tryCatch|bracket|fromEither)\s*\(/.test(stripCommentsAndStrings(source)),
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
name: 'effect-prefer-gen-over-do',
|
|
703
|
-
message: 'Prefer Effect.gen over Effect.Do for agent-readable sequential workflows.',
|
|
704
|
-
tokens: ['Effect.Do'],
|
|
705
|
-
patterns: [/\bEffect\.Do\b/],
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
name: 'effect-prefer-direct-yield-star',
|
|
709
|
-
message: 'Use direct yield* effect style instead of adapter-style Effect.gen helpers.',
|
|
710
|
-
tokenGroups: [['gen'], ['$']],
|
|
711
|
-
patterns: [/Effect\.gen\s*\(\s*function\*\s*\(\s*\$\s*\)/],
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
name: 'effect-prefer-config-redacted',
|
|
715
|
-
message: 'Use Config.redacted for sensitive config values so secrets stay redacted in logs and errors.',
|
|
716
|
-
tokens: ['secret', 'Secret'],
|
|
717
|
-
patterns: [/Config\.(?:secret|Secret)\s*\(/],
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
name: 'effect-no-deprecated-schema-package',
|
|
721
|
-
message: 'Use Schema from effect/Schema instead of @effect/schema.',
|
|
722
|
-
tokens: ['@effect/schema'],
|
|
723
|
-
patterns: [/from\s+['"]@effect\/schema['"]/],
|
|
724
|
-
},
|
|
725
|
-
{
|
|
726
|
-
name: 'effect-no-deprecated-context-tag-function',
|
|
727
|
-
message: 'Use the current Context.Tag class/service pattern instead of deprecated tag helpers.',
|
|
728
|
-
tokens: ['Context.Tag'],
|
|
729
|
-
patterns: [
|
|
730
|
-
/\b(?:const|let|var)\s+[A-Z][\w$]*\s*=\s*Context\.Tag(?:<[^>]+>)?\s*\(\s*['"][^'"]+['"]\s*\)/,
|
|
731
|
-
/(?:^|[;\n]\s*)Context\.Tag(?:<[^>]+>)?\s*\(\s*['"][^'"]+['"]\s*\)/,
|
|
732
|
-
],
|
|
733
|
-
},
|
|
734
|
-
{
|
|
735
|
-
name: 'effect-no-global-error-channel',
|
|
736
|
-
message: 'Use domain-specific tagged errors instead of the global Error type channel.',
|
|
737
|
-
tokenGroups: [['Effect.Effect'], ['Error']],
|
|
738
|
-
patterns: [/Effect\.Effect\s*<[^,>]+,\s*Error\s*[,>]/],
|
|
739
|
-
},
|
|
740
|
-
{
|
|
741
|
-
name: 'effect-use-duration-constructors',
|
|
742
|
-
message: 'Use Duration constructors or string durations instead of naked millisecond numbers.',
|
|
743
|
-
tokens: ['sleep', 'timeout', 'delay'],
|
|
744
|
-
patterns: [
|
|
745
|
-
/Effect\.(?:sleep|timeout|delay)\s*\(\s*\d+\s*\)/,
|
|
746
|
-
/Effect\.(?:timeout|delay)\s*\([^,]+,\s*\d+\s*\)/,
|
|
747
|
-
],
|
|
748
|
-
},
|
|
749
|
-
{
|
|
750
|
-
name: 'effect-no-mixed-effect-import-styles',
|
|
751
|
-
message: 'Use one Effect import style per file.',
|
|
752
|
-
tokens: ['import'],
|
|
753
|
-
check: hasMixedEffectImportStyles,
|
|
754
|
-
},
|
|
755
|
-
{
|
|
756
|
-
name: 'effect-prefer-effect-is',
|
|
757
|
-
message: 'Use Effect.isEffect for Effect type checks.',
|
|
758
|
-
tokens: ['instanceof', '_op'],
|
|
759
|
-
patterns: [/\b[A-Za-z_$][\w$]*\s+instanceof\s+Effect\b/, /\._op\s*===\s*['"]Effect['"]/],
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
name: 'effect-no-try-catch-in-effect-gen',
|
|
763
|
-
message: 'Use Effect error combinators instead of try/catch inside Effect.gen.',
|
|
764
|
-
tokenGroups: [['gen'], ['try']],
|
|
765
|
-
ast: (context, source) => ({
|
|
766
|
-
TryStatement(node) {
|
|
767
|
-
const { start } = node;
|
|
768
|
-
if (typeof start !== 'number') {
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
const index = hasTryCatchInEffectGen(source);
|
|
772
|
-
if (typeof index === 'number' && index === start) {
|
|
773
|
-
reportAst(context, 'Use Effect error combinators instead of try/catch inside Effect.gen.', node);
|
|
774
|
-
}
|
|
775
|
-
},
|
|
776
|
-
}),
|
|
777
|
-
},
|
|
778
|
-
{
|
|
779
|
-
name: 'effect-no-new-promise',
|
|
780
|
-
message: 'Use Effect.async, Effect.promise, or Effect.tryPromise instead of new Promise.',
|
|
781
|
-
tokenGroups: [['Promise'], ['Effect', 'effect']],
|
|
782
|
-
ast: (context, source) => {
|
|
783
|
-
const isEffectModule = hasEffectSignal(source);
|
|
784
|
-
return {
|
|
785
|
-
NewExpression(node) {
|
|
786
|
-
if (isEffectModule && identifierName(node.callee) === 'Promise') {
|
|
787
|
-
reportAst(context, 'Use Effect.async, Effect.promise, or Effect.tryPromise instead of new Promise.', node);
|
|
788
|
-
}
|
|
789
|
-
},
|
|
790
|
-
};
|
|
791
|
-
},
|
|
792
|
-
},
|
|
793
|
-
{
|
|
794
|
-
name: 'effect-no-global-timers',
|
|
795
|
-
message: 'Use Effect.sleep, Schedule, or Clock instead of global timers in Effect modules.',
|
|
796
|
-
tokens: ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'],
|
|
797
|
-
ast: (context, source) => {
|
|
798
|
-
const isEffectModule = hasEffectSignal(source);
|
|
799
|
-
return {
|
|
800
|
-
CallExpression(node) {
|
|
801
|
-
const calleeName = identifierName(node.callee);
|
|
802
|
-
if (isEffectModule &&
|
|
803
|
-
calleeName &&
|
|
804
|
-
['clearInterval', 'clearTimeout', 'setInterval', 'setTimeout'].includes(calleeName)) {
|
|
805
|
-
reportAst(context, 'Use Effect.sleep, Schedule, or Clock instead of global timers in Effect modules.', node);
|
|
806
|
-
}
|
|
807
|
-
},
|
|
808
|
-
};
|
|
809
|
-
},
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
name: 'effect-no-native-error-classes',
|
|
813
|
-
message: 'Use tagged/data/schema errors instead of classes extending native Error.',
|
|
814
|
-
tokens: ['Error'],
|
|
815
|
-
check: hasNativeErrorClassInEffectModule,
|
|
816
|
-
ast: (context, source) => {
|
|
817
|
-
const isEffectModule = hasEffectSignal(source);
|
|
818
|
-
return {
|
|
819
|
-
ClassDeclaration(node) {
|
|
820
|
-
if (isEffectModule && identifierName(node.superClass) === 'Error') {
|
|
821
|
-
reportAst(context, 'Use tagged/data/schema errors instead of classes extending native Error.', node);
|
|
822
|
-
}
|
|
823
|
-
},
|
|
824
|
-
};
|
|
825
|
-
},
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
name: 'effect-no-unsafe-effect-type-assertion',
|
|
829
|
-
message: 'Do not assert Effect error or requirement channels with type casts.',
|
|
830
|
-
tokenGroups: [[' as '], ['Effect.Effect']],
|
|
831
|
-
check: hasUnsafeEffectTypeAssertion,
|
|
832
|
-
ast: (context) => ({
|
|
833
|
-
TSAsExpression(node) {
|
|
834
|
-
if (typeReferenceName(node.typeAnnotation) === 'Effect.Effect') {
|
|
835
|
-
reportAst(context, 'Do not assert Effect error or requirement channels with type casts.', node);
|
|
836
|
-
}
|
|
837
|
-
},
|
|
838
|
-
}),
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
name: 'effect-require-service-self-match',
|
|
842
|
-
message: 'Effect service/tag self type must match the declaring class name.',
|
|
843
|
-
tokenGroups: [['class'], ['extends'], ['Context', 'Service', 'Tag']],
|
|
844
|
-
check: hasServiceSelfMismatch,
|
|
845
|
-
ast: (context, source) => ({
|
|
846
|
-
ClassDeclaration(node) {
|
|
847
|
-
const className = identifierName(node.id);
|
|
848
|
-
const selfName = effectServiceSelfName(node.superClass, source);
|
|
849
|
-
if (className && selfName && className !== selfName) {
|
|
850
|
-
reportAst(context, 'Effect service/tag self type must match the declaring class name.', node);
|
|
851
|
-
}
|
|
852
|
-
},
|
|
853
|
-
}),
|
|
854
|
-
},
|
|
855
|
-
{
|
|
856
|
-
name: 'effect-no-effect-fn-iife',
|
|
857
|
-
message: 'Do not call Effect.fn as an IIFE; use Effect.gen for local one-shot workflows.',
|
|
858
|
-
tokenGroups: [['fn'], ['Effect', 'effect']],
|
|
859
|
-
check: hasEffectFnIife,
|
|
860
|
-
ast: (context, source) => {
|
|
861
|
-
const isEffectFn = effectCallPredicate(source, ['fn', 'fnUntraced', 'fnUntracedEager']);
|
|
862
|
-
return {
|
|
863
|
-
CallExpression(node) {
|
|
864
|
-
const outerCallee = node.callee;
|
|
865
|
-
const middleCallee = outerCallee?.callee;
|
|
866
|
-
const innerCallee = middleCallee?.callee;
|
|
867
|
-
if (nodeType(outerCallee) === 'CallExpression' &&
|
|
868
|
-
nodeType(middleCallee) === 'CallExpression' &&
|
|
869
|
-
isEffectFn(innerCallee)) {
|
|
870
|
-
reportAst(context, 'Do not call Effect.fn as an IIFE; use Effect.gen for local one-shot workflows.', node);
|
|
290
|
+
reportAST(context, 'Use Effect logging APIs instead of console logging in Effect code.', node);
|
|
871
291
|
}
|
|
872
292
|
},
|
|
873
293
|
};
|
|
874
294
|
},
|
|
295
|
+
message: 'Use Effect logging APIs instead of console logging in Effect code.',
|
|
296
|
+
name: 'effect-no-console-log-in-effect-code',
|
|
297
|
+
tokens: ['console'],
|
|
875
298
|
},
|
|
299
|
+
...effectDefaultEnvironmentSpecs,
|
|
300
|
+
...effectDefaultCompatibilitySpecs,
|
|
876
301
|
];
|
|
877
302
|
const effectDefaultRules = makeRules(effectDefaultSpecs, {
|
|
878
303
|
defaultTokens: effectDefaultRuleTokens,
|