oxlint-plugin-effect 0.1.0 → 0.2.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.
Files changed (256) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +13 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.js +4 -0
  4. package/dist/plugin.d.ts +5 -0
  5. package/dist/plugin.js +18 -0
  6. package/dist/presets/core.d.ts +43 -0
  7. package/dist/presets/core.js +43 -0
  8. package/dist/presets/effect-native.d.ts +28 -0
  9. package/dist/presets/effect-native.js +28 -0
  10. package/dist/presets/full.d.ts +60 -0
  11. package/dist/presets/full.js +60 -0
  12. package/dist/presets/functional.d.ts +21 -0
  13. package/dist/presets/functional.js +21 -0
  14. package/dist/presets/index.d.ts +13 -0
  15. package/dist/presets/index.js +18 -0
  16. package/dist/presets/strict.d.ts +69 -0
  17. package/dist/presets/strict.js +69 -0
  18. package/dist/presets/v3.d.ts +15 -0
  19. package/dist/presets/v3.js +15 -0
  20. package/dist/rules/_effect-context.d.ts +19 -0
  21. package/dist/rules/_effect-context.js +22 -0
  22. package/dist/rules/index.d.ts +66 -0
  23. package/dist/rules/index.js +124 -0
  24. package/dist/rules/no-arrow-ladder.d.ts +6 -0
  25. package/dist/rules/no-arrow-ladder.js +61 -0
  26. package/dist/rules/no-async-function.d.ts +6 -0
  27. package/dist/rules/no-async-function.js +46 -0
  28. package/dist/rules/no-catch-all-to-map-error.d.ts +6 -0
  29. package/dist/rules/no-catch-all-to-map-error.js +67 -0
  30. package/dist/rules/no-effect-as.d.ts +6 -0
  31. package/dist/rules/no-effect-as.js +16 -0
  32. package/dist/rules/no-effect-async.d.ts +6 -0
  33. package/dist/rules/no-effect-async.js +16 -0
  34. package/dist/rules/no-effect-bind.d.ts +6 -0
  35. package/dist/rules/no-effect-bind.js +16 -0
  36. package/dist/rules/no-effect-do.d.ts +6 -0
  37. package/dist/rules/no-effect-do.js +16 -0
  38. package/dist/rules/no-effect-fn-generator.d.ts +6 -0
  39. package/dist/rules/no-effect-fn-generator.js +30 -0
  40. package/dist/rules/no-effect-gen-adapter.d.ts +6 -0
  41. package/dist/rules/no-effect-gen-adapter.js +30 -0
  42. package/dist/rules/no-effect-map-flatten.d.ts +6 -0
  43. package/dist/rules/no-effect-map-flatten.js +53 -0
  44. package/dist/rules/no-effect-map-void.d.ts +6 -0
  45. package/dist/rules/no-effect-map-void.js +40 -0
  46. package/dist/rules/no-effect-never.d.ts +6 -0
  47. package/dist/rules/no-effect-never.js +16 -0
  48. package/dist/rules/no-effect-orElse-ladder.d.ts +6 -0
  49. package/dist/rules/no-effect-orElse-ladder.js +53 -0
  50. package/dist/rules/no-effect-succeed-string.d.ts +6 -0
  51. package/dist/rules/no-effect-succeed-string.js +30 -0
  52. package/dist/rules/no-effect-succeed-variable.d.ts +6 -0
  53. package/dist/rules/no-effect-succeed-variable.js +30 -0
  54. package/dist/rules/no-effect-succeed-void.d.ts +6 -0
  55. package/dist/rules/no-effect-succeed-void.js +33 -0
  56. package/dist/rules/no-effect-sync-console.d.ts +6 -0
  57. package/dist/rules/no-effect-sync-console.js +63 -0
  58. package/dist/rules/no-effect-sync-wrapper.d.ts +6 -0
  59. package/dist/rules/no-effect-sync-wrapper.js +37 -0
  60. package/dist/rules/no-effect-type-alias.d.ts +6 -0
  61. package/dist/rules/no-effect-type-alias.js +52 -0
  62. package/dist/rules/no-effect-wrapper-alias.d.ts +6 -0
  63. package/dist/rules/no-effect-wrapper-alias.js +69 -0
  64. package/dist/rules/no-extends-native-error.d.ts +6 -0
  65. package/dist/rules/no-extends-native-error.js +41 -0
  66. package/dist/rules/no-flatmap-ladder.d.ts +6 -0
  67. package/dist/rules/no-flatmap-ladder.js +48 -0
  68. package/dist/rules/no-fromnullable-coalesce.d.ts +6 -0
  69. package/dist/rules/no-fromnullable-coalesce.js +36 -0
  70. package/dist/rules/no-global-error-in-catch.d.ts +6 -0
  71. package/dist/rules/no-global-error-in-catch.js +75 -0
  72. package/dist/rules/no-global-error-in-failure.d.ts +6 -0
  73. package/dist/rules/no-global-error-in-failure.js +54 -0
  74. package/dist/rules/no-globals.d.ts +6 -0
  75. package/dist/rules/no-globals.js +164 -0
  76. package/dist/rules/no-if-statement.d.ts +6 -0
  77. package/dist/rules/no-if-statement.js +18 -0
  78. package/dist/rules/no-iife-wrapper.d.ts +6 -0
  79. package/dist/rules/no-iife-wrapper.js +37 -0
  80. package/dist/rules/no-inline-provide.d.ts +6 -0
  81. package/dist/rules/no-inline-provide.js +31 -0
  82. package/dist/rules/no-instanceof-schema.d.ts +6 -0
  83. package/dist/rules/no-instanceof-schema.js +35 -0
  84. package/dist/rules/no-manual-effect-channels.d.ts +6 -0
  85. package/dist/rules/no-manual-effect-channels.js +40 -0
  86. package/dist/rules/no-match-effect-branch.d.ts +6 -0
  87. package/dist/rules/no-match-effect-branch.js +64 -0
  88. package/dist/rules/no-match-void-branch.d.ts +6 -0
  89. package/dist/rules/no-match-void-branch.js +50 -0
  90. package/dist/rules/no-multiple-effect-provide.d.ts +6 -0
  91. package/dist/rules/no-multiple-effect-provide.js +45 -0
  92. package/dist/rules/no-nested-effect-call.d.ts +6 -0
  93. package/dist/rules/no-nested-effect-call.js +97 -0
  94. package/dist/rules/no-nested-effect-gen.d.ts +6 -0
  95. package/dist/rules/no-nested-effect-gen.js +46 -0
  96. package/dist/rules/no-nested-pipe.d.ts +6 -0
  97. package/dist/rules/no-nested-pipe.js +38 -0
  98. package/dist/rules/no-new-error.d.ts +6 -0
  99. package/dist/rules/no-new-error.js +39 -0
  100. package/dist/rules/no-new-promise.d.ts +6 -0
  101. package/dist/rules/no-new-promise.js +38 -0
  102. package/dist/rules/no-node-builtin-import.d.ts +6 -0
  103. package/dist/rules/no-node-builtin-import.js +61 -0
  104. package/dist/rules/no-option-as.d.ts +6 -0
  105. package/dist/rules/no-option-as.js +16 -0
  106. package/dist/rules/no-option-boolean-normalization.d.ts +6 -0
  107. package/dist/rules/no-option-boolean-normalization.js +50 -0
  108. package/dist/rules/no-positional-log-error.d.ts +6 -0
  109. package/dist/rules/no-positional-log-error.js +52 -0
  110. package/dist/rules/no-redundant-schema-tag-identifier.d.ts +6 -0
  111. package/dist/rules/no-redundant-schema-tag-identifier.js +46 -0
  112. package/dist/rules/no-return-in-arrow.d.ts +6 -0
  113. package/dist/rules/no-return-in-arrow.js +41 -0
  114. package/dist/rules/no-return-nullish.d.ts +6 -0
  115. package/dist/rules/no-return-nullish.js +47 -0
  116. package/dist/rules/no-run-in-effect-gen.d.ts +6 -0
  117. package/dist/rules/no-run-in-effect-gen.js +48 -0
  118. package/dist/rules/no-runtime-run-fork.d.ts +6 -0
  119. package/dist/rules/no-runtime-run-fork.js +19 -0
  120. package/dist/rules/no-schema-struct-with-tag.d.ts +6 -0
  121. package/dist/rules/no-schema-struct-with-tag.js +48 -0
  122. package/dist/rules/no-schema-union-of-literals.d.ts +6 -0
  123. package/dist/rules/no-schema-union-of-literals.js +35 -0
  124. package/dist/rules/no-string-sentinel-const.d.ts +6 -0
  125. package/dist/rules/no-string-sentinel-const.js +38 -0
  126. package/dist/rules/no-switch-statement.d.ts +6 -0
  127. package/dist/rules/no-switch-statement.js +16 -0
  128. package/dist/rules/no-ternary.d.ts +6 -0
  129. package/dist/rules/no-ternary.js +30 -0
  130. package/dist/rules/no-throw-statement.d.ts +6 -0
  131. package/dist/rules/no-throw-statement.js +34 -0
  132. package/dist/rules/no-try-catch.d.ts +6 -0
  133. package/dist/rules/no-try-catch.js +34 -0
  134. package/dist/rules/no-unnecessary-arrow-block.d.ts +6 -0
  135. package/dist/rules/no-unnecessary-arrow-block.js +34 -0
  136. package/dist/rules/no-unnecessary-effect-gen.d.ts +6 -0
  137. package/dist/rules/no-unnecessary-effect-gen.js +35 -0
  138. package/dist/rules/no-unnecessary-pipe-chain.d.ts +6 -0
  139. package/dist/rules/no-unnecessary-pipe-chain.js +36 -0
  140. package/dist/rules/no-unnecessary-pipe.d.ts +6 -0
  141. package/dist/rules/no-unnecessary-pipe.js +29 -0
  142. package/dist/vendor/effect-oxlint/AST.d.ts +199 -0
  143. package/dist/vendor/effect-oxlint/AST.js +214 -0
  144. package/dist/vendor/effect-oxlint/Comment.d.ts +55 -0
  145. package/dist/vendor/effect-oxlint/Comment.js +67 -0
  146. package/dist/vendor/effect-oxlint/Diagnostic.d.ts +86 -0
  147. package/dist/vendor/effect-oxlint/Diagnostic.js +115 -0
  148. package/dist/vendor/effect-oxlint/Plugin.d.ts +38 -0
  149. package/dist/vendor/effect-oxlint/Plugin.js +45 -0
  150. package/dist/vendor/effect-oxlint/Rule.d.ts +218 -0
  151. package/dist/vendor/effect-oxlint/Rule.js +309 -0
  152. package/dist/vendor/effect-oxlint/RuleContext.d.ts +77 -0
  153. package/dist/vendor/effect-oxlint/RuleContext.js +74 -0
  154. package/dist/vendor/effect-oxlint/Scope.d.ts +100 -0
  155. package/dist/vendor/effect-oxlint/Scope.js +110 -0
  156. package/dist/vendor/effect-oxlint/SourceCode.d.ts +180 -0
  157. package/dist/vendor/effect-oxlint/SourceCode.js +203 -0
  158. package/dist/vendor/effect-oxlint/Testing.d.ts +510 -0
  159. package/dist/vendor/effect-oxlint/Testing.js +963 -0
  160. package/dist/vendor/effect-oxlint/Token.d.ts +98 -0
  161. package/dist/vendor/effect-oxlint/Token.js +103 -0
  162. package/dist/vendor/effect-oxlint/Visitor.d.ts +166 -0
  163. package/dist/vendor/effect-oxlint/Visitor.js +138 -0
  164. package/dist/vendor/effect-oxlint/index.d.ts +13 -0
  165. package/dist/vendor/effect-oxlint/index.js +12 -0
  166. package/package.json +24 -5
  167. package/.effect-lsp.json +0 -60
  168. package/.oxlintrc.json +0 -33
  169. package/CHANGELOG.md +0 -37
  170. package/scripts/add-rule.ts +0 -87
  171. package/scripts/codegen.ts +0 -108
  172. package/src/index.ts +0 -9
  173. package/src/plugin.ts +0 -16
  174. package/src/presets/core.ts +0 -47
  175. package/src/presets/effect-native.ts +0 -34
  176. package/src/presets/full.ts +0 -70
  177. package/src/presets/functional.ts +0 -18
  178. package/src/presets/index.ts +0 -5
  179. package/src/presets/strict.ts +0 -77
  180. package/src/rules/_effect-context.ts +0 -28
  181. package/src/rules/index.ts +0 -72
  182. package/src/rules/no-arrow-ladder.ts +0 -70
  183. package/src/rules/no-async-function.ts +0 -47
  184. package/src/rules/no-catch-all-to-map-error.ts +0 -89
  185. package/src/rules/no-effect-as.ts +0 -14
  186. package/src/rules/no-effect-async.ts +0 -14
  187. package/src/rules/no-effect-bind.ts +0 -13
  188. package/src/rules/no-effect-do.ts +0 -14
  189. package/src/rules/no-effect-fn-generator.ts +0 -58
  190. package/src/rules/no-effect-gen-adapter.ts +0 -50
  191. package/src/rules/no-effect-map-flatten.ts +0 -82
  192. package/src/rules/no-effect-map-void.ts +0 -80
  193. package/src/rules/no-effect-never.ts +0 -14
  194. package/src/rules/no-effect-orElse-ladder.ts +0 -79
  195. package/src/rules/no-effect-succeed-string.ts +0 -52
  196. package/src/rules/no-effect-succeed-variable.ts +0 -50
  197. package/src/rules/no-effect-succeed-void.ts +0 -63
  198. package/src/rules/no-effect-sync-console.ts +0 -91
  199. package/src/rules/no-effect-sync-wrapper.ts +0 -67
  200. package/src/rules/no-effect-type-alias.ts +0 -71
  201. package/src/rules/no-effect-wrapper-alias.ts +0 -95
  202. package/src/rules/no-extends-native-error.ts +0 -54
  203. package/src/rules/no-flatmap-ladder.ts +0 -77
  204. package/src/rules/no-fromnullable-coalesce.ts +0 -62
  205. package/src/rules/no-global-error-in-catch.ts +0 -104
  206. package/src/rules/no-global-error-in-failure.ts +0 -83
  207. package/src/rules/no-globals.ts +0 -178
  208. package/src/rules/no-if-statement.ts +0 -16
  209. package/src/rules/no-iife-wrapper.ts +0 -51
  210. package/src/rules/no-inline-provide.ts +0 -41
  211. package/src/rules/no-instanceof-schema.ts +0 -42
  212. package/src/rules/no-manual-effect-channels.ts +0 -58
  213. package/src/rules/no-match-effect-branch.ts +0 -82
  214. package/src/rules/no-match-void-branch.ts +0 -72
  215. package/src/rules/no-multiple-effect-provide.ts +0 -75
  216. package/src/rules/no-nested-effect-call.ts +0 -57
  217. package/src/rules/no-nested-effect-gen.ts +0 -43
  218. package/src/rules/no-nested-pipe.ts +0 -73
  219. package/src/rules/no-new-error.ts +0 -53
  220. package/src/rules/no-new-promise.ts +0 -50
  221. package/src/rules/no-node-builtin-import.ts +0 -68
  222. package/src/rules/no-option-as.ts +0 -13
  223. package/src/rules/no-option-boolean-normalization.ts +0 -88
  224. package/src/rules/no-positional-log-error.ts +0 -76
  225. package/src/rules/no-redundant-schema-tag-identifier.ts +0 -62
  226. package/src/rules/no-return-in-arrow.ts +0 -76
  227. package/src/rules/no-return-nullish.ts +0 -80
  228. package/src/rules/no-run-in-effect.ts +0 -18
  229. package/src/rules/no-runtime-run-fork.ts +0 -14
  230. package/src/rules/no-schema-struct-with-tag.ts +0 -64
  231. package/src/rules/no-schema-union-of-literals.ts +0 -48
  232. package/src/rules/no-string-sentinel-const.ts +0 -49
  233. package/src/rules/no-switch-statement.ts +0 -13
  234. package/src/rules/no-ternary.ts +0 -30
  235. package/src/rules/no-throw-statement.ts +0 -39
  236. package/src/rules/no-try-catch.ts +0 -39
  237. package/src/rules/no-unnecessary-arrow-block.ts +0 -51
  238. package/src/rules/no-unnecessary-effect-gen.ts +0 -83
  239. package/src/rules/no-unnecessary-pipe-chain.ts +0 -47
  240. package/src/rules/no-unnecessary-pipe.ts +0 -45
  241. package/src/vendor/effect-oxlint/AST.ts +0 -516
  242. package/src/vendor/effect-oxlint/Comment.ts +0 -86
  243. package/src/vendor/effect-oxlint/Diagnostic.ts +0 -199
  244. package/src/vendor/effect-oxlint/Plugin.ts +0 -71
  245. package/src/vendor/effect-oxlint/Rule.ts +0 -528
  246. package/src/vendor/effect-oxlint/RuleContext.ts +0 -144
  247. package/src/vendor/effect-oxlint/Scope.ts +0 -177
  248. package/src/vendor/effect-oxlint/SourceCode.ts +0 -377
  249. package/src/vendor/effect-oxlint/Testing.ts +0 -1227
  250. package/src/vendor/effect-oxlint/Token.ts +0 -131
  251. package/src/vendor/effect-oxlint/Visitor.ts +0 -287
  252. package/src/vendor/effect-oxlint/index.ts +0 -88
  253. package/tests/ban-rules.test.ts +0 -85
  254. package/tests/context-rules.test.ts +0 -198
  255. package/tests/pattern-rules.test.ts +0 -247
  256. package/tsconfig.json +0 -15
@@ -1,1227 +0,0 @@
1
- /**
2
- * Public testing infrastructure for Effect-first oxlint rules.
3
- *
4
- * Provides AST node builders, mock context factories, rule runners,
5
- * and assertion helpers for use with `@effect/vitest`.
6
- *
7
- * @example
8
- * ```ts
9
- * import { Testing, Rule } from 'effect-oxlint'
10
- *
11
- * const result = Testing.runRule(myRule, 'ThrowStatement', Testing.throwStmt())
12
- * Testing.expectDiagnostics(result, [{ message: 'No throw in Effect.gen' }])
13
- * ```
14
- *
15
- * @since 0.2.0
16
- */
17
- import type {
18
- Comment,
19
- Context as OxlintContext,
20
- CreateRule,
21
- Diagnostic as OxlintDiagnostic,
22
- ESTree,
23
- Scope as OxlintScope,
24
- Token,
25
- Variable,
26
- Visitor as OxlintVisitor
27
- } from '@oxlint/plugins';
28
- import * as Arr from 'effect/Array';
29
- import * as Effect from 'effect/Effect';
30
- import * as Layer from 'effect/Layer';
31
- import * as Option from 'effect/Option';
32
- import * as P from 'effect/Predicate';
33
-
34
- import { fromOxlintContext, RuleContext } from './RuleContext.ts';
35
-
36
- // ---------------------------------------------------------------------------
37
- // AST Node Builders
38
- // ---------------------------------------------------------------------------
39
-
40
- /**
41
- * Minimal AST node builders for tests.
42
- *
43
- * These produce mock objects satisfying the type shapes that AST/Visitor/Rule
44
- * modules expect. They use boundary casts at the seam between test mock
45
- * data and oxlint's strict branded types.
46
- *
47
- * @since 0.2.0
48
- */
49
-
50
- /**
51
- * Identifier: `{ type: "Identifier", name }`
52
- *
53
- * @since 0.2.0
54
- */
55
- export const id = (name: string): ESTree.IdentifierName =>
56
- ({ type: 'Identifier', name }) as never;
57
-
58
- /**
59
- * MemberExpression: `obj.prop` (non-computed)
60
- *
61
- * @since 0.2.0
62
- */
63
- export const memberExpr = (
64
- obj: string,
65
- prop: string
66
- ): ESTree.MemberExpression =>
67
- ({
68
- type: 'MemberExpression',
69
- object: id(obj),
70
- property: id(prop),
71
- computed: false,
72
- optional: false
73
- }) as never;
74
-
75
- /**
76
- * MemberExpression: `obj[prop]` (computed)
77
- *
78
- * @since 0.2.0
79
- */
80
- export const computedMemberExpr = (
81
- obj: string,
82
- prop: string
83
- ): ESTree.MemberExpression =>
84
- ({
85
- type: 'MemberExpression',
86
- object: id(obj),
87
- property: id(prop),
88
- computed: true,
89
- optional: false
90
- }) as never;
91
-
92
- /**
93
- * Chained MemberExpression: `a.b.c` (non-computed)
94
- *
95
- * @since 0.2.0
96
- */
97
- export const chainedMemberExpr = (
98
- ...names: readonly [string, string, ...ReadonlyArray<string>]
99
- ): ESTree.MemberExpression => {
100
- const [first, second, ...rest] = names;
101
- const initial = memberExpr(first, second);
102
- return Arr.reduce(
103
- rest,
104
- initial,
105
- (acc, name) =>
106
- ({
107
- type: 'MemberExpression',
108
- object: acc,
109
- property: id(name),
110
- computed: false,
111
- optional: false
112
- }) as never
113
- );
114
- };
115
-
116
- /**
117
- * CallExpression with bare identifier callee: `name(args)`
118
- *
119
- * @since 0.2.0
120
- */
121
- export const callExpr = (
122
- name: string,
123
- args: ReadonlyArray<unknown> = []
124
- ): ESTree.CallExpression =>
125
- ({
126
- type: 'CallExpression',
127
- callee: id(name),
128
- arguments: args
129
- }) as never;
130
-
131
- /**
132
- * CallExpression with MemberExpression callee: `obj.prop(args)`
133
- *
134
- * @since 0.2.0
135
- */
136
- export const callOfMember = (
137
- obj: string,
138
- prop: string,
139
- args: ReadonlyArray<unknown> = []
140
- ): ESTree.CallExpression =>
141
- ({
142
- type: 'CallExpression',
143
- callee: memberExpr(obj, prop),
144
- arguments: args
145
- }) as never;
146
-
147
- /**
148
- * ImportDeclaration: `import ... from "source"`
149
- *
150
- * @since 0.2.0
151
- */
152
- export const importDecl = (source: string): ESTree.ImportDeclaration =>
153
- ({
154
- type: 'ImportDeclaration',
155
- source: { type: 'Literal', value: source },
156
- specifiers: []
157
- }) as never;
158
-
159
- /**
160
- * String literal: `{ type: "Literal", value }`
161
- *
162
- * @since 0.2.0
163
- */
164
- export const strLiteral = (value: string): ESTree.StringLiteral =>
165
- ({ type: 'Literal', value }) as never;
166
-
167
- /**
168
- * Numeric literal: `{ type: "Literal", value }`
169
- *
170
- * @since 0.2.0
171
- */
172
- export const numLiteral = (value: number): ESTree.NumericLiteral =>
173
- ({ type: 'Literal', value }) as never;
174
-
175
- /**
176
- * Boolean literal: `{ type: "Literal", value }`
177
- *
178
- * @since 0.2.0
179
- */
180
- export const boolLiteral = (value: boolean): ESTree.BooleanLiteral =>
181
- ({ type: 'Literal', value }) as never;
182
-
183
- /**
184
- * ObjectExpression with identifier-keyed properties.
185
- *
186
- * @since 0.2.0
187
- */
188
- export const objectExpr = (
189
- properties: ReadonlyArray<{
190
- readonly key: string;
191
- readonly value?: unknown;
192
- }>
193
- ): ESTree.ObjectExpression =>
194
- ({
195
- type: 'ObjectExpression',
196
- properties: Arr.map(properties, (p) => ({
197
- type: 'Property',
198
- key: id(p.key),
199
- value: p.value ?? strLiteral('')
200
- }))
201
- }) as never;
202
-
203
- /**
204
- * ObjectExpression with string literal keys.
205
- *
206
- * @since 0.2.0
207
- */
208
- export const objectExprLiteralKeys = (
209
- properties: ReadonlyArray<{
210
- readonly key: string;
211
- readonly value?: unknown;
212
- }>
213
- ): ESTree.ObjectExpression =>
214
- ({
215
- type: 'ObjectExpression',
216
- properties: Arr.map(properties, (p) => ({
217
- type: 'Property',
218
- key: strLiteral(p.key),
219
- value: p.value ?? strLiteral('')
220
- }))
221
- }) as never;
222
-
223
- /**
224
- * ObjectExpression with a SpreadElement.
225
- *
226
- * @since 0.2.0
227
- */
228
- export const objectExprWithSpread = (
229
- spreadArg: unknown
230
- ): ESTree.ObjectExpression =>
231
- ({
232
- type: 'ObjectExpression',
233
- properties: [{ type: 'SpreadElement', argument: spreadArg }]
234
- }) as never;
235
-
236
- /**
237
- * ThrowStatement.
238
- *
239
- * @since 0.2.0
240
- */
241
- export const throwStmt = (): ESTree.ThrowStatement =>
242
- ({ type: 'ThrowStatement' }) as never;
243
-
244
- /**
245
- * TryStatement.
246
- *
247
- * @since 0.2.0
248
- */
249
- export const tryStmt = (): ESTree.Node => ({ type: 'TryStatement' }) as never;
250
-
251
- /**
252
- * ReturnStatement.
253
- *
254
- * @since 0.2.0
255
- */
256
- export const returnStmt = (argument?: unknown): ESTree.ReturnStatement =>
257
- ({ type: 'ReturnStatement', argument: argument ?? null }) as never;
258
-
259
- /**
260
- * BlockStatement.
261
- *
262
- * @since 0.2.0
263
- */
264
- export const blockStmt = (
265
- body: ReadonlyArray<unknown> = []
266
- ): ESTree.BlockStatement =>
267
- ({ type: 'BlockStatement', body: Array.from(body) }) as never;
268
-
269
- /**
270
- * ArrowFunctionExpression.
271
- *
272
- * @since 0.2.0
273
- */
274
- export const arrowFn = (
275
- body?: unknown,
276
- params: ReadonlyArray<unknown> = []
277
- ): ESTree.ArrowFunctionExpression =>
278
- ({
279
- type: 'ArrowFunctionExpression',
280
- params: Array.from(params),
281
- body: body ?? blockStmt(),
282
- expression: false,
283
- async: false
284
- }) as never;
285
-
286
- /**
287
- * VariableDeclaration: `const/let/var name = init`
288
- *
289
- * @since 0.2.0
290
- */
291
- export const varDecl = (
292
- kind: 'const' | 'let' | 'var',
293
- name: string,
294
- init?: unknown
295
- ): ESTree.VariableDeclaration =>
296
- ({
297
- type: 'VariableDeclaration',
298
- kind,
299
- declarations: [
300
- {
301
- type: 'VariableDeclarator',
302
- id: id(name),
303
- init: init ?? null
304
- }
305
- ]
306
- }) as never;
307
-
308
- /**
309
- * ExpressionStatement.
310
- *
311
- * @since 0.2.0
312
- */
313
- export const exprStmt = (expression: unknown): ESTree.ExpressionStatement =>
314
- ({ type: 'ExpressionStatement', expression }) as never;
315
-
316
- /**
317
- * Program node.
318
- *
319
- * @since 0.2.0
320
- */
321
- export const program = (
322
- body: ReadonlyArray<unknown> = [],
323
- comments: ReadonlyArray<unknown> = []
324
- ): ESTree.Program =>
325
- ({
326
- type: 'Program',
327
- body: Array.from(body),
328
- comments: Array.from(comments),
329
- sourceType: 'module'
330
- }) as never;
331
-
332
- /**
333
- * IfStatement.
334
- *
335
- * All parameters are optional — `ifStmt()` produces a minimal
336
- * `{ type: 'IfStatement' }` node suitable for enter/exit tracking.
337
- *
338
- * @since 0.2.0
339
- */
340
- export const ifStmt = (
341
- test?: unknown,
342
- consequent?: unknown,
343
- alternate?: unknown
344
- ): ESTree.IfStatement =>
345
- ({
346
- type: 'IfStatement',
347
- test: test ?? null,
348
- consequent: consequent ?? null,
349
- alternate: alternate ?? null
350
- }) as never;
351
-
352
- /**
353
- * BinaryExpression.
354
- *
355
- * @since 0.2.0
356
- */
357
- export const binaryExpr = (
358
- operator: string,
359
- left: unknown,
360
- right: unknown
361
- ): ESTree.BinaryExpression =>
362
- ({ type: 'BinaryExpression', operator, left, right }) as never;
363
-
364
- /**
365
- * NewExpression: `new callee(args)`
366
- *
367
- * When `callee` is a string it is auto-wrapped in `id()`, so
368
- * `newExpr('Date')` is equivalent to `newExpr(id('Date'))`.
369
- *
370
- * @since 0.2.0
371
- */
372
- export const newExpr: {
373
- (callee: string, args?: ReadonlyArray<unknown>): ESTree.NewExpression;
374
- (callee: unknown, args?: ReadonlyArray<unknown>): ESTree.NewExpression;
375
- } = (
376
- callee: unknown,
377
- args: ReadonlyArray<unknown> = []
378
- ): ESTree.NewExpression =>
379
- ({
380
- type: 'NewExpression',
381
- callee: P.isString(callee) ? id(callee) : callee,
382
- arguments: Array.from(args)
383
- }) as never;
384
-
385
- /**
386
- * A generic AST node with type and optional parent pointer.
387
- *
388
- * @since 0.2.0
389
- */
390
- export const astNode = (
391
- type: string,
392
- parent?: { readonly type: string; readonly parent?: unknown }
393
- ): { readonly type: string; readonly parent?: unknown } => ({
394
- type,
395
- parent
396
- });
397
-
398
- /**
399
- * Build a parent chain from outermost → innermost.
400
- *
401
- * Returns the innermost node with `.parent` links to each ancestor.
402
- *
403
- * @example
404
- * ```ts
405
- * // Creates: FunctionDeclaration → BlockStatement → ThrowStatement
406
- * withParentChain('FunctionDeclaration', 'BlockStatement', 'ThrowStatement')
407
- * ```
408
- *
409
- * @since 0.2.0
410
- */
411
- export const withParentChain = (
412
- first: string,
413
- ...rest: ReadonlyArray<string>
414
- ): { readonly type: string; readonly parent?: unknown } =>
415
- Arr.reduce(
416
- rest,
417
- astNode(first) satisfies {
418
- readonly type: string;
419
- readonly parent?: unknown;
420
- },
421
- (parent, type) => astNode(type, parent)
422
- );
423
-
424
- /**
425
- * Mock Token.
426
- *
427
- * @since 0.2.0
428
- */
429
- export const token = (type: Token['type'], value: string): Token =>
430
- ({
431
- type,
432
- value,
433
- start: 0,
434
- end: value.length,
435
- range: [0, value.length],
436
- loc: {
437
- start: { line: 1, column: 0 },
438
- end: { line: 1, column: value.length }
439
- },
440
- regex: undefined
441
- }) as never;
442
-
443
- /**
444
- * Mock Comment.
445
- *
446
- * @since 0.2.0
447
- */
448
- export const comment = (type: Comment['type'], value: string): Comment =>
449
- ({
450
- type,
451
- value,
452
- start: 0,
453
- end: value.length + 4,
454
- range: [0, value.length + 4],
455
- loc: {
456
- start: { line: 1, column: 0 },
457
- end: { line: 1, column: value.length + 4 }
458
- }
459
- }) as never;
460
-
461
- /**
462
- * Mock Scope with minimal surface.
463
- *
464
- * @since 0.2.0
465
- */
466
- export const scope = (
467
- opts: {
468
- readonly type?: OxlintScope['type'];
469
- readonly isStrict?: boolean;
470
- readonly variables?: ReadonlyArray<Variable>;
471
- readonly upper?: OxlintScope | null;
472
- } = {}
473
- ): OxlintScope => {
474
- const vars = Array.from(opts.variables ?? []);
475
- const set = new Map<string, Variable>(vars.map((v) => [v.name, v]));
476
- return {
477
- type: opts.type ?? 'function',
478
- isStrict: opts.isStrict ?? false,
479
- upper: opts.upper ?? null,
480
- childScopes: [],
481
- variableScope: null as never,
482
- block: {} as never,
483
- variables: vars,
484
- set,
485
- references: [],
486
- through: [],
487
- functionExpressionScope: false
488
- };
489
- };
490
-
491
- /**
492
- * Mock Variable.
493
- *
494
- * @since 0.2.0
495
- */
496
- export const variable = (
497
- name: string,
498
- opts: {
499
- readonly references?: ReadonlyArray<{
500
- readonly isRead: () => boolean;
501
- readonly isWrite: () => boolean;
502
- readonly isReadOnly: () => boolean;
503
- readonly isWriteOnly: () => boolean;
504
- readonly isReadWrite: () => boolean;
505
- }>;
506
- } = {}
507
- ): Variable =>
508
- ({
509
- name,
510
- scope: {} as never,
511
- identifiers: [id(name)],
512
- references: Array.from(opts.references ?? []),
513
- defs: []
514
- }) as never;
515
-
516
- // ---------------------------------------------------------------------------
517
- // Statement Builders
518
- // ---------------------------------------------------------------------------
519
-
520
- /**
521
- * SwitchStatement.
522
- *
523
- * @since 0.2.0
524
- */
525
- export const switchStmt = (): ESTree.SwitchStatement =>
526
- ({ type: 'SwitchStatement', discriminant: null, cases: [] }) as never;
527
-
528
- /**
529
- * ForStatement.
530
- *
531
- * @since 0.2.0
532
- */
533
- export const forStmt = (): ESTree.ForStatement =>
534
- ({
535
- type: 'ForStatement',
536
- init: null,
537
- test: null,
538
- update: null,
539
- body: blockStmt()
540
- }) as never;
541
-
542
- /**
543
- * ForInStatement.
544
- *
545
- * @since 0.2.0
546
- */
547
- export const forInStmt = (): ESTree.ForInStatement =>
548
- ({
549
- type: 'ForInStatement',
550
- left: id('_'),
551
- right: id('_'),
552
- body: blockStmt()
553
- }) as never;
554
-
555
- /**
556
- * ForOfStatement.
557
- *
558
- * @since 0.2.0
559
- */
560
- export const forOfStmt = (): ESTree.ForOfStatement =>
561
- ({
562
- type: 'ForOfStatement',
563
- await: false,
564
- left: id('_'),
565
- right: id('_'),
566
- body: blockStmt()
567
- }) as never;
568
-
569
- /**
570
- * WhileStatement.
571
- *
572
- * @since 0.2.0
573
- */
574
- export const whileStmt = (): ESTree.WhileStatement =>
575
- ({
576
- type: 'WhileStatement',
577
- test: boolLiteral(true),
578
- body: blockStmt()
579
- }) as never;
580
-
581
- /**
582
- * DoWhileStatement.
583
- *
584
- * @since 0.2.0
585
- */
586
- export const doWhileStmt = (): ESTree.DoWhileStatement =>
587
- ({
588
- type: 'DoWhileStatement',
589
- test: boolLiteral(true),
590
- body: blockStmt()
591
- }) as never;
592
-
593
- // ---------------------------------------------------------------------------
594
- // Expression Builders
595
- // ---------------------------------------------------------------------------
596
-
597
- /**
598
- * YieldExpression.
599
- *
600
- * @since 0.2.0
601
- */
602
- export const yieldExpr = (
603
- argument?: unknown,
604
- delegate: boolean = false
605
- ): ESTree.YieldExpression =>
606
- ({
607
- type: 'YieldExpression',
608
- argument: argument ?? null,
609
- delegate
610
- }) as never;
611
-
612
- /**
613
- * UnaryExpression: `operator argument`
614
- *
615
- * @since 0.2.0
616
- */
617
- export const unaryExpr = (
618
- operator: string,
619
- argument: unknown
620
- ): ESTree.UnaryExpression =>
621
- ({ type: 'UnaryExpression', operator, prefix: true, argument }) as never;
622
-
623
- // ---------------------------------------------------------------------------
624
- // Declaration Builders
625
- // ---------------------------------------------------------------------------
626
-
627
- /**
628
- * VariableDeclarator (standalone, without wrapping VariableDeclaration).
629
- *
630
- * @since 0.2.0
631
- */
632
- export const varDeclarator = (
633
- name: string,
634
- init?: unknown
635
- ): ESTree.VariableDeclarator =>
636
- ({
637
- type: 'VariableDeclarator',
638
- id: id(name),
639
- init: init ?? null
640
- }) as never;
641
-
642
- /**
643
- * ExportNamedDeclaration.
644
- *
645
- * @since 0.2.0
646
- */
647
- export const exportNamedDecl = (
648
- declaration?: unknown
649
- ): ESTree.ExportNamedDeclaration =>
650
- ({
651
- type: 'ExportNamedDeclaration',
652
- declaration: declaration ?? null,
653
- specifiers: [],
654
- source: null
655
- }) as never;
656
-
657
- // ---------------------------------------------------------------------------
658
- // Import Specifier Builders
659
- // ---------------------------------------------------------------------------
660
-
661
- /**
662
- * ImportDeclaration with specifiers: `import { a, b } from "source"`
663
- *
664
- * @since 0.2.0
665
- */
666
- export const importDeclWithSpecifiers = (
667
- source: string,
668
- specifiers: ReadonlyArray<unknown>,
669
- importKind: string = 'value'
670
- ): ESTree.ImportDeclaration =>
671
- ({
672
- type: 'ImportDeclaration',
673
- source: { type: 'Literal', value: source },
674
- specifiers: Array.from(specifiers),
675
- importKind
676
- }) as never;
677
-
678
- /**
679
- * ImportSpecifier: `{ imported as local }`
680
- *
681
- * @since 0.2.0
682
- */
683
- export const importSpecifier = (
684
- imported: string,
685
- local?: string,
686
- importKind: string = 'value'
687
- ): ESTree.ImportSpecifier =>
688
- ({
689
- type: 'ImportSpecifier',
690
- imported: id(imported),
691
- local: id(local ?? imported),
692
- importKind
693
- }) as never;
694
-
695
- /**
696
- * ImportNamespaceSpecifier: `* as local`
697
- *
698
- * @since 0.2.0
699
- */
700
- export const importNamespaceSpecifier = (
701
- local: string
702
- ): ESTree.ImportNamespaceSpecifier =>
703
- ({
704
- type: 'ImportNamespaceSpecifier',
705
- local: id(local)
706
- }) as never;
707
-
708
- // ---------------------------------------------------------------------------
709
- // TypeScript Node Builders
710
- // ---------------------------------------------------------------------------
711
-
712
- /**
713
- * TSAsExpression: `expr as Type`
714
- *
715
- * @since 0.2.0
716
- */
717
- export const tsAsExpr = (
718
- typeKind: string,
719
- parent?: { readonly type: string; readonly parent?: unknown }
720
- ): ESTree.TSAsExpression =>
721
- ({
722
- type: 'TSAsExpression',
723
- expression: id('_'),
724
- typeAnnotation: { type: typeKind },
725
- parent
726
- }) as never;
727
-
728
- /**
729
- * TSUnionType: `A | B | C`
730
- *
731
- * @since 0.2.0
732
- */
733
- export const tsUnionType = (
734
- typeKinds: ReadonlyArray<string>
735
- ): ESTree.TSUnionType =>
736
- ({
737
- type: 'TSUnionType',
738
- types: Arr.map(typeKinds, (t) => ({ type: t }))
739
- }) as never;
740
-
741
- /**
742
- * TSTypeReference: `TypeName`
743
- *
744
- * @since 0.2.0
745
- */
746
- export const tsTypeRef = (name: string): ESTree.TSTypeReference =>
747
- ({
748
- type: 'TSTypeReference',
749
- typeName: id(name),
750
- typeArguments: null
751
- }) as never;
752
-
753
- /**
754
- * TSTypeLiteral: `{ ... }` with N members.
755
- *
756
- * @since 0.2.0
757
- */
758
- export const tsTypeLiteral = (memberCount: number): ESTree.TSTypeLiteral =>
759
- ({
760
- type: 'TSTypeLiteral',
761
- members: Array.from({ length: memberCount }, () => ({
762
- type: 'TSPropertySignature'
763
- }))
764
- }) as never;
765
-
766
- /**
767
- * TSInterfaceDeclaration: `interface Name { }`
768
- *
769
- * @since 0.2.0
770
- */
771
- export const interfaceDecl = (name: string): ESTree.TSInterfaceDeclaration =>
772
- ({
773
- type: 'TSInterfaceDeclaration',
774
- id: { type: 'BindingIdentifier', name },
775
- body: { type: 'TSInterfaceBody', body: [] }
776
- }) as never;
777
-
778
- /**
779
- * TSTypeAliasDeclaration: `type Name = ...`
780
- *
781
- * @since 0.2.0
782
- */
783
- export const typeAliasDecl = (name: string): ESTree.TSTypeAliasDeclaration =>
784
- ({
785
- type: 'TSTypeAliasDeclaration',
786
- id: { type: 'BindingIdentifier', name },
787
- typeAnnotation: null
788
- }) as never;
789
-
790
- // ---------------------------------------------------------------------------
791
- // Class Node Builders
792
- // ---------------------------------------------------------------------------
793
-
794
- /**
795
- * ClassDeclaration with optional superClass and body members.
796
- *
797
- * @example
798
- * ```ts
799
- * // Simple: class Foo {}
800
- * classDecl('Foo')
801
- *
802
- * // With super: class Foo extends Bar {}
803
- * classDecl('Foo', { superClass: Testing.id('Bar') })
804
- *
805
- * // With members: class Foo { x; static y() {} }
806
- * classDecl('Foo', {
807
- * members: [Testing.propertyDef('x'), Testing.methodDef('y', true)]
808
- * })
809
- * ```
810
- *
811
- * @since 0.2.0
812
- */
813
- export const classDecl = (
814
- name: string,
815
- opts: {
816
- readonly superClass?: unknown;
817
- readonly members?: ReadonlyArray<unknown>;
818
- } = {}
819
- ): ESTree.Class =>
820
- ({
821
- type: 'ClassDeclaration',
822
- id: { type: 'BindingIdentifier', name },
823
- superClass: opts.superClass ?? null,
824
- body: {
825
- type: 'ClassBody',
826
- body: Array.from(opts.members ?? [])
827
- },
828
- decorators: []
829
- }) as never;
830
-
831
- /**
832
- * PropertyDefinition: class field.
833
- *
834
- * @since 0.2.0
835
- */
836
- export const propertyDef = (
837
- name: string,
838
- isStatic: boolean = false
839
- ): ESTree.PropertyDefinition =>
840
- ({
841
- type: 'PropertyDefinition',
842
- key: id(name),
843
- value: null,
844
- computed: false,
845
- static: isStatic,
846
- decorators: []
847
- }) as never;
848
-
849
- /**
850
- * MethodDefinition: class method.
851
- *
852
- * @since 0.2.0
853
- */
854
- export const methodDef = (
855
- name: string,
856
- isStatic: boolean = false
857
- ): ESTree.MethodDefinition =>
858
- ({
859
- type: 'MethodDefinition',
860
- key: id(name),
861
- kind: 'method',
862
- value: arrowFn(),
863
- computed: false,
864
- static: isStatic,
865
- decorators: []
866
- }) as never;
867
-
868
- // ---------------------------------------------------------------------------
869
- // Mock Context
870
- // ---------------------------------------------------------------------------
871
-
872
- /**
873
- * A collected diagnostic from the mock context's `report`.
874
- *
875
- * @since 0.2.0
876
- */
877
- export interface ReportedDiagnostic {
878
- readonly diagnostic: OxlintDiagnostic;
879
- }
880
-
881
- /**
882
- * Options for creating a mock oxlint Context.
883
- *
884
- * @since 0.2.0
885
- */
886
- export interface MockContextOptions {
887
- readonly filename?: string;
888
- readonly cwd?: string;
889
- readonly options?: ReadonlyArray<unknown>;
890
- readonly sourceText?: string;
891
- readonly comments?: ReadonlyArray<Comment>;
892
- }
893
-
894
- /**
895
- * Create a mock oxlint `Context` and a diagnostics collector.
896
- *
897
- * The mock provides the minimal surface required by `RuleContext.fromOxlintContext`.
898
- *
899
- * @since 0.2.0
900
- */
901
- export const createMockContext = (opts: MockContextOptions = {}) => {
902
- const diagnostics: Array<ReportedDiagnostic> = [];
903
- const filename = opts.filename ?? '/test/file.ts';
904
- const cwd = opts.cwd ?? '/test';
905
- const text = opts.sourceText ?? '';
906
- const comments = Array.from(opts.comments ?? []);
907
- const sourceCode = {
908
- text,
909
- ast: { type: 'Program', body: [], comments },
910
- getText() {
911
- return text;
912
- },
913
- getAllComments() {
914
- return comments;
915
- },
916
- getLocFromIndex(index: number) {
917
- return { line: 1, column: index };
918
- },
919
- getIndexFromLoc(loc: { line: number; column: number }) {
920
- return loc.column;
921
- },
922
- getAncestors() {
923
- return [];
924
- },
925
- getScope() {
926
- return scope();
927
- },
928
- getDeclaredVariables() {
929
- return [];
930
- },
931
- isGlobalReference() {
932
- return false;
933
- },
934
- markVariableAsUsed() {
935
- return false;
936
- },
937
- getFirstToken() {
938
- return null;
939
- },
940
- getLastToken() {
941
- return null;
942
- },
943
- getTokens() {
944
- return [];
945
- },
946
- getTokenBefore() {
947
- return null;
948
- },
949
- getTokenAfter() {
950
- return null;
951
- },
952
- getTokensBetween() {
953
- return [];
954
- },
955
- getFirstTokenBetween() {
956
- return null;
957
- },
958
- getTokenByRangeStart() {
959
- return null;
960
- },
961
- getCommentsBefore() {
962
- return [];
963
- },
964
- getCommentsAfter() {
965
- return [];
966
- },
967
- getCommentsInside() {
968
- return [];
969
- },
970
- commentsExistBetween() {
971
- return false;
972
- },
973
- getJSDocComment() {
974
- return null;
975
- },
976
- getNodeByRangeIndex() {
977
- return null;
978
- },
979
- getRange() {
980
- return [0, 0];
981
- },
982
- getLoc() {
983
- return {
984
- start: { line: 1, column: 0 },
985
- end: { line: 1, column: 0 }
986
- };
987
- },
988
- getLines() {
989
- return text.split('\n');
990
- },
991
- isSpaceBetween() {
992
- return false;
993
- }
994
- };
995
- const context = {
996
- id: 'effect/test-rule',
997
- filename,
998
- physicalFilename: filename,
999
- cwd,
1000
- options: opts.options ?? [],
1001
- report(diagnostic: OxlintDiagnostic) {
1002
- diagnostics.push({ diagnostic });
1003
- },
1004
- getFilename: () => filename,
1005
- getCwd: () => cwd,
1006
- sourceCode,
1007
- languageOptions: {
1008
- sourceType: 'module',
1009
- ecmaVersion: 2024
1010
- },
1011
- settings: {},
1012
- getSourceCode: () => sourceCode,
1013
- getPhysicalFilename: () => filename,
1014
- parserOptions: {},
1015
- parserPath: undefined
1016
- } as unknown as OxlintContext;
1017
-
1018
- return { context, diagnostics } as const;
1019
- };
1020
-
1021
- // ---------------------------------------------------------------------------
1022
- // Mock RuleContext Layer
1023
- // ---------------------------------------------------------------------------
1024
-
1025
- /**
1026
- * Create a `Layer` that provides a mock `RuleContext` service.
1027
- *
1028
- * Use in `it.effect` tests that need to `yield*` visitor handlers
1029
- * (which carry `RuleContext` in their context type).
1030
- *
1031
- * @since 0.2.0
1032
- */
1033
- export const mockRuleContextLayer = (
1034
- opts?: MockContextOptions
1035
- ): Layer.Layer<RuleContext> => {
1036
- const { context } = createMockContext(opts);
1037
- return Layer.succeed(RuleContext, fromOxlintContext(context));
1038
- };
1039
-
1040
- /**
1041
- * Provide a mock `RuleContext` to an effect for testing.
1042
- *
1043
- * @since 0.2.0
1044
- */
1045
- export const withMockRuleContext = <A, E>(
1046
- effect: Effect.Effect<A, E, RuleContext>,
1047
- opts?: MockContextOptions
1048
- ): Effect.Effect<A, E> => Effect.provide(effect, mockRuleContextLayer(opts));
1049
-
1050
- // ---------------------------------------------------------------------------
1051
- // Rule Runner
1052
- // ---------------------------------------------------------------------------
1053
-
1054
- /** @internal Call a visitor handler on a mock AST node. */
1055
- const callHandler = (
1056
- visitors: OxlintVisitor,
1057
- key: string,
1058
- visitorNode: unknown
1059
- ): void => {
1060
- const handler = visitors[key];
1061
- // Boundary cast: test nodes are plain objects, not real AST nodes
1062
- if (handler) handler(visitorNode as never);
1063
- };
1064
-
1065
- /**
1066
- * Run a rule with a single visitor event and collect diagnostics.
1067
- *
1068
- * @since 0.2.0
1069
- */
1070
- export const runRule = (
1071
- rule: CreateRule,
1072
- visitor: string,
1073
- visitorNode: unknown,
1074
- opts?: MockContextOptions
1075
- ): ReadonlyArray<ReportedDiagnostic> => {
1076
- const { context, diagnostics } = createMockContext(opts);
1077
- const visitors = rule.create(context);
1078
- callHandler(visitors, visitor, visitorNode);
1079
- return diagnostics;
1080
- };
1081
-
1082
- /**
1083
- * Run a rule with multiple visitor/node events sequentially.
1084
- *
1085
- * Same context is shared so `Ref` state persists across calls.
1086
- *
1087
- * @since 0.2.0
1088
- */
1089
- export const runRuleMulti = (
1090
- rule: CreateRule,
1091
- pairs: ReadonlyArray<readonly [visitor: string, node: unknown]>,
1092
- opts?: MockContextOptions
1093
- ): ReadonlyArray<ReportedDiagnostic> => {
1094
- const { context, diagnostics } = createMockContext(opts);
1095
- const visitors = rule.create(context);
1096
- Arr.forEach(pairs, ([visitor, visitorNode]) => {
1097
- callHandler(visitors, visitor, visitorNode);
1098
- });
1099
- return diagnostics;
1100
- };
1101
-
1102
- // ---------------------------------------------------------------------------
1103
- // Diagnostic Accessors
1104
- // ---------------------------------------------------------------------------
1105
-
1106
- /**
1107
- * Extract the diagnostic messages from a result array.
1108
- *
1109
- * Returns `Option.none()` when a diagnostic uses `messageId` instead
1110
- * of `message`, or when the message is `null`.
1111
- *
1112
- * @example
1113
- * ```ts
1114
- * import * as Option from 'effect/Option'
1115
- *
1116
- * const result = Testing.runRule(rule, 'ThrowStatement', Testing.throwStmt())
1117
- * expect(Testing.messages(result)).toEqual([Option.some('Use Effect.fail instead')])
1118
- * ```
1119
- *
1120
- * @since 0.2.0
1121
- */
1122
- export const messages = (
1123
- result: ReadonlyArray<ReportedDiagnostic>
1124
- ): ReadonlyArray<Option.Option<string>> =>
1125
- Arr.map(result, (r) => Option.fromNullishOr(r.diagnostic.message));
1126
-
1127
- /**
1128
- * Extract the diagnostic messageIds from a result array.
1129
- *
1130
- * Returns `Option.none()` when a diagnostic uses `message` instead
1131
- * of `messageId`, or when the messageId is `null`.
1132
- *
1133
- * @since 0.2.0
1134
- */
1135
- export const messageIds = (
1136
- result: ReadonlyArray<ReportedDiagnostic>
1137
- ): ReadonlyArray<Option.Option<string>> =>
1138
- Arr.map(result, (r) => Option.fromNullishOr(r.diagnostic.messageId));
1139
-
1140
- // ---------------------------------------------------------------------------
1141
- // Assertion Helpers
1142
- // ---------------------------------------------------------------------------
1143
-
1144
- /**
1145
- * Assert that diagnostics match expected patterns.
1146
- *
1147
- * Each matcher is partially checked — only provided fields are compared.
1148
- * This allows flexible matching without specifying every field.
1149
- *
1150
- * @example
1151
- * ```ts
1152
- * Testing.expectDiagnostics(result, [
1153
- * { message: 'No throw in Effect.gen' },
1154
- * { messageId: 'noTryCatch' }
1155
- * ])
1156
- * ```
1157
- *
1158
- * @since 0.2.0
1159
- */
1160
- export const expectDiagnostics = (
1161
- result: ReadonlyArray<ReportedDiagnostic>,
1162
- expected: ReadonlyArray<{
1163
- readonly message?: string;
1164
- readonly messageId?: string;
1165
- }>
1166
- ): void => {
1167
- if (result.length !== expected.length) {
1168
- throw new Error(
1169
- `Expected ${expected.length} diagnostics, got ${result.length}:\n` +
1170
- Arr.join(
1171
- Arr.map(
1172
- result,
1173
- (r) =>
1174
- ` - ${r.diagnostic.message ?? r.diagnostic.messageId ?? '(unknown)'}`
1175
- ),
1176
- '\n'
1177
- )
1178
- );
1179
- }
1180
- Arr.forEach(expected, (exp, i) => {
1181
- const actual = result[i];
1182
- if (actual === undefined) {
1183
- throw new Error(`Missing diagnostic at index ${i}`);
1184
- }
1185
- if (
1186
- exp.message !== undefined &&
1187
- actual.diagnostic.message !== exp.message
1188
- ) {
1189
- throw new Error(
1190
- `Diagnostic ${i}: expected message "${exp.message}", got "${actual.diagnostic.message}"`
1191
- );
1192
- }
1193
- if (
1194
- exp.messageId !== undefined &&
1195
- actual.diagnostic.messageId !== exp.messageId
1196
- ) {
1197
- throw new Error(
1198
- `Diagnostic ${i}: expected messageId "${exp.messageId}", got "${actual.diagnostic.messageId}"`
1199
- );
1200
- }
1201
- });
1202
- };
1203
-
1204
- /**
1205
- * Assert that no diagnostics were reported.
1206
- *
1207
- * @since 0.2.0
1208
- */
1209
- export const expectNoDiagnostics = (
1210
- result: ReadonlyArray<ReportedDiagnostic>
1211
- ): void =>
1212
- Arr.match(result, {
1213
- onEmpty: () => {},
1214
- onNonEmpty: (items) => {
1215
- throw new Error(
1216
- `Expected no diagnostics, got ${items.length}:\n` +
1217
- Arr.join(
1218
- Arr.map(
1219
- items,
1220
- (r) =>
1221
- ` - ${r.diagnostic.message ?? r.diagnostic.messageId ?? '(unknown)'}`
1222
- ),
1223
- '\n'
1224
- )
1225
- );
1226
- }
1227
- });