@thethracian/oxlint-config 0.2.0 → 0.3.0

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