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,528 +0,0 @@
1
- /**
2
- * Core rule builder for Effect-first oxlint rules.
3
- *
4
- * `Rule.define` is the primary entry point. It produces a standard
5
- * `CreateRule` that oxlint can consume, while letting rule authors
6
- * write fully effectful create generators and visitor handlers.
7
- *
8
- * @since 0.1.0
9
- */
10
- import type { CreateRule, ESTree, RuleDocs, RuleMeta } from '@oxlint/plugins';
11
- import * as Arr from 'effect/Array';
12
- import * as Effect from 'effect/Effect';
13
- import * as Option from 'effect/Option';
14
- import * as P from 'effect/Predicate';
15
- import * as Schema from 'effect/Schema';
16
-
17
- import { pipe } from 'effect/Function';
18
-
19
- import * as AST from './AST.ts';
20
- import { make as makeDiagnostic } from './Diagnostic.ts';
21
- import { fromOxlintContext, RuleContext } from './RuleContext.ts';
22
- import type { EffectVisitor } from './Visitor.ts';
23
- import { merge as mergeVisitors, toOxlintVisitor } from './Visitor.ts';
24
-
25
- // ---------------------------------------------------------------------------
26
- // Internal helpers
27
- // ---------------------------------------------------------------------------
28
-
29
- /**
30
- * Extract the identifier name from a `NewExpression` callee.
31
- *
32
- * `AST.calleeName` only accepts `CallExpression`. This mirrors the
33
- * same logic for `NewExpression.callee`.
34
- *
35
- * @internal
36
- */
37
- const newExprCalleeName = (callee: ESTree.Expression): Option.Option<string> =>
38
- callee.type === 'Identifier' && 'name' in callee && P.isString(callee.name)
39
- ? Option.some(callee.name)
40
- : Option.none();
41
-
42
- // ---------------------------------------------------------------------------
43
- // Types
44
- // ---------------------------------------------------------------------------
45
-
46
- /**
47
- * Configuration object for `Rule.define`.
48
- *
49
- * @since 0.1.0
50
- */
51
- export interface RuleConfig<Options = undefined> {
52
- /** Rule name (used for tracing spans). */
53
- readonly name: string;
54
- /** Oxlint rule metadata. */
55
- readonly meta: RuleMeta;
56
- /**
57
- * Optional Schema for rule options.
58
- *
59
- * When provided, the first element of the raw JSON options array
60
- * is decoded at `create` time against this schema.
61
- */
62
- readonly options?: Schema.Decoder<Options> | undefined;
63
- /**
64
- * The create generator.
65
- *
66
- * Receives decoded options and returns an `EffectVisitor`.
67
- * Runs inside an Effect context where `RuleContext` is available.
68
- *
69
- * May `yield* Ref.make(...)` for state, `yield* RuleContext` for
70
- * context access, and return a visitor built with `Visitor.*` helpers.
71
- */
72
- readonly create: (
73
- options: Options
74
- ) => Effect.gen.Return<EffectVisitor, never, RuleContext>;
75
- }
76
-
77
- // ---------------------------------------------------------------------------
78
- // Builder
79
- // ---------------------------------------------------------------------------
80
-
81
- /**
82
- * Define an Effect-first oxlint lint rule.
83
- *
84
- * The `create` generator runs once per file via `Effect.runSync`.
85
- * Each visitor handler is also executed via `Effect.runSync` per node.
86
- * `Ref`-based state created in `create` persists across handler calls
87
- * via closure.
88
- *
89
- * `Effect.runSync` is used here at the runtime boundary — the bridge
90
- * between oxlint's synchronous plugin API and our Effect world.
91
- *
92
- * @since 0.1.0
93
- */
94
- export const define = <Options = undefined>(
95
- config: RuleConfig<Options>
96
- ): CreateRule => ({
97
- meta: config.meta,
98
- create(oxlintContext) {
99
- const ruleCtx = fromOxlintContext(oxlintContext);
100
-
101
- // Runtime boundary: execute effects with RuleContext provided.
102
- // This is the FFI bridge between oxlint's sync API and Effect.
103
- const run = <A>(effect: Effect.Effect<A, never, RuleContext>): A =>
104
- Effect.runSync(Effect.provideService(effect, RuleContext, ruleCtx));
105
-
106
- // Decode options from the raw JSON array.
107
- // When no schema is configured, `Options` defaults to `undefined`.
108
- const decodeOptions = (): Options => {
109
- const schema = config.options;
110
- if (schema === undefined) return undefined as Options;
111
- return Schema.decodeUnknownSync(schema)(oxlintContext.options[0]);
112
- };
113
- const options = run(Effect.sync(decodeOptions));
114
-
115
- // Run the create generator to set up Refs and get the visitor map.
116
- // TypedEffectVisitor → EffectVisitor: the typed keys provide
117
- // narrowed nodes to callers, but at runtime all handlers receive
118
- // the same ESTree.Node values. The variance mismatch is safe
119
- // because oxlint guarantees the node type matches the key.
120
- const effectVisitor = run(
121
- Effect.gen(() => config.create(options))
122
- ) as EffectVisitor;
123
-
124
- // Wrap each handler: Effect<void> → plain () => void
125
- return toOxlintVisitor(effectVisitor, run);
126
- }
127
- });
128
-
129
- // ---------------------------------------------------------------------------
130
- // Metadata helper
131
- // ---------------------------------------------------------------------------
132
-
133
- /**
134
- * Build `RuleMeta` with sensible defaults.
135
- *
136
- * @since 0.1.0
137
- */
138
- export const meta = (opts: {
139
- readonly type: 'problem' | 'suggestion' | 'layout';
140
- readonly description: string;
141
- readonly fixable?: 'code' | 'whitespace' | undefined;
142
- readonly hasSuggestions?: boolean | undefined;
143
- readonly messages?: Record<string, string> | undefined;
144
- readonly docs?: RuleDocs | undefined;
145
- }): RuleMeta => ({
146
- type: opts.type,
147
- ...(opts.fixable !== undefined ? { fixable: opts.fixable } : {}),
148
- ...(opts.hasSuggestions !== undefined
149
- ? { hasSuggestions: opts.hasSuggestions }
150
- : {}),
151
- ...(opts.messages !== undefined ? { messages: opts.messages } : {}),
152
- docs: {
153
- description: opts.description,
154
- ...opts.docs
155
- }
156
- });
157
-
158
- // ---------------------------------------------------------------------------
159
- // Convenience rule factories (common patterns)
160
- // ---------------------------------------------------------------------------
161
-
162
- /**
163
- * Create a rule that bans `obj.prop` member expression access.
164
- *
165
- * Replaces the common `memberExprRule` utility pattern.
166
- *
167
- * @since 0.1.0
168
- */
169
- export const banMember = (
170
- obj: string,
171
- prop: string | ReadonlyArray<string>,
172
- opts: {
173
- readonly message: string;
174
- readonly meta?:
175
- | { readonly type?: 'problem' | 'suggestion' }
176
- | undefined;
177
- }
178
- ): CreateRule =>
179
- define({
180
- name: `ban-${obj}-${P.isString(prop) ? prop : Arr.join(prop, '-')}`,
181
- meta: meta({
182
- type: opts.meta?.type ?? 'suggestion',
183
- description: opts.message
184
- }),
185
- create: function* () {
186
- const ctx = yield* RuleContext;
187
- return {
188
- MemberExpression: (node: ESTree.Node) =>
189
- pipe(
190
- AST.narrow(node, 'MemberExpression'),
191
- Option.flatMap(AST.matchMember(obj, prop)),
192
- Option.match({
193
- onNone: () => Effect.void,
194
- onSome: (matched) =>
195
- ctx.report(
196
- makeDiagnostic({
197
- node: matched,
198
- message: opts.message
199
- })
200
- )
201
- })
202
- )
203
- };
204
- }
205
- });
206
-
207
- /**
208
- * Create a rule that bans imports matching a source string or predicate.
209
- *
210
- * Replaces the common `importRule` utility pattern.
211
- *
212
- * @since 0.1.0
213
- */
214
- export const banImport = (
215
- source: string | ((source: string) => boolean),
216
- opts: {
217
- readonly message: string;
218
- readonly meta?:
219
- | { readonly type?: 'problem' | 'suggestion' }
220
- | undefined;
221
- }
222
- ): CreateRule =>
223
- define({
224
- name: 'ban-import',
225
- meta: meta({
226
- type: opts.meta?.type ?? 'suggestion',
227
- description: opts.message
228
- }),
229
- create: function* () {
230
- const ctx = yield* RuleContext;
231
- return {
232
- ImportDeclaration: (node: ESTree.Node) =>
233
- pipe(
234
- AST.narrow(node, 'ImportDeclaration'),
235
- Option.flatMap(AST.matchImport(source)),
236
- Option.match({
237
- onNone: () => Effect.void,
238
- onSome: (matched) =>
239
- ctx.report(
240
- makeDiagnostic({
241
- node: matched,
242
- message: opts.message
243
- })
244
- )
245
- })
246
- )
247
- };
248
- }
249
- });
250
-
251
- /**
252
- * Create a rule that bans bare identifier call expressions.
253
- *
254
- * Matches `CallExpression` nodes whose callee is an identifier in
255
- * the given list (e.g. `fetch()`, `useState()`, `readFileSync()`).
256
- *
257
- * @example
258
- * ```ts
259
- * // Ban a single call
260
- * Rule.banCallOf('fetch', { message: 'Use Effect HTTP client' })
261
- *
262
- * // Ban multiple calls
263
- * Rule.banCallOf(['useState', 'useEffect'], { message: 'Use Effect' })
264
- * ```
265
- *
266
- * @since 0.2.0
267
- */
268
- export const banCallOf = (
269
- name: string | ReadonlyArray<string>,
270
- opts: {
271
- readonly message: string;
272
- readonly meta?:
273
- | { readonly type?: 'problem' | 'suggestion' }
274
- | undefined;
275
- }
276
- ): CreateRule => {
277
- const names = P.isString(name) ? [name] : name;
278
- return define({
279
- name: `ban-call-${Arr.join(names, '-')}`,
280
- meta: meta({
281
- type: opts.meta?.type ?? 'suggestion',
282
- description: opts.message
283
- }),
284
- create: function* () {
285
- const ctx = yield* RuleContext;
286
- return {
287
- CallExpression: (node: ESTree.Node) =>
288
- pipe(
289
- AST.narrow(node, 'CallExpression'),
290
- Option.flatMap(AST.calleeName),
291
- Option.filter((n) => Arr.contains(names, n)),
292
- Option.match({
293
- onNone: () => Effect.void,
294
- onSome: () =>
295
- ctx.report(
296
- makeDiagnostic({
297
- node,
298
- message: opts.message
299
- })
300
- )
301
- })
302
- )
303
- };
304
- }
305
- });
306
- };
307
-
308
- /**
309
- * Create a rule that bans `new` expressions with the given callee name.
310
- *
311
- * Matches `NewExpression` nodes whose callee is an identifier in
312
- * the given list (e.g. `new Date()`, `new Error()`).
313
- *
314
- * @example
315
- * ```ts
316
- * // Ban a single constructor
317
- * Rule.banNewExpr('Date', { message: 'Use Clock service' })
318
- *
319
- * // Ban multiple constructors
320
- * Rule.banNewExpr(['Error', 'TypeError'], { message: 'Use tagged errors' })
321
- * ```
322
- *
323
- * @since 0.2.0
324
- */
325
- export const banNewExpr = (
326
- name: string | ReadonlyArray<string>,
327
- opts: {
328
- readonly message: string;
329
- readonly meta?:
330
- | { readonly type?: 'problem' | 'suggestion' }
331
- | undefined;
332
- }
333
- ): CreateRule => {
334
- const names = P.isString(name) ? [name] : name;
335
- return define({
336
- name: `ban-new-${Arr.join(names, '-')}`,
337
- meta: meta({
338
- type: opts.meta?.type ?? 'suggestion',
339
- description: opts.message
340
- }),
341
- create: function* () {
342
- const ctx = yield* RuleContext;
343
- return {
344
- NewExpression: (node: ESTree.Node) =>
345
- pipe(
346
- AST.narrow(node, 'NewExpression'),
347
- Option.flatMap((n) => newExprCalleeName(n.callee)),
348
- Option.filter((n) => Arr.contains(names, n)),
349
- Option.match({
350
- onNone: () => Effect.void,
351
- onSome: () =>
352
- ctx.report(
353
- makeDiagnostic({
354
- node,
355
- message: opts.message
356
- })
357
- )
358
- })
359
- )
360
- };
361
- }
362
- });
363
- };
364
-
365
- /**
366
- * Create a rule that bans a specific statement type.
367
- *
368
- * @since 0.1.0
369
- */
370
- export const banStatement = (
371
- nodeType: string,
372
- opts: {
373
- readonly message: string;
374
- readonly meta?:
375
- | { readonly type?: 'problem' | 'suggestion' }
376
- | undefined;
377
- }
378
- ): CreateRule =>
379
- define({
380
- name: `ban-${nodeType}`,
381
- meta: meta({
382
- type: opts.meta?.type ?? 'suggestion',
383
- description: opts.message
384
- }),
385
- create: function* () {
386
- const ctx = yield* RuleContext;
387
- return {
388
- [nodeType]: (node: ESTree.Node) =>
389
- ctx.report(makeDiagnostic({ node, message: opts.message }))
390
- };
391
- }
392
- });
393
-
394
- // ---------------------------------------------------------------------------
395
- // Multi-ban combinator
396
- // ---------------------------------------------------------------------------
397
-
398
- /**
399
- * A single ban specification. Each spec carries its own message so
400
- * diagnostics are specific to the exact pattern matched.
401
- *
402
- * @since 0.3.0
403
- */
404
- export type BanSpec =
405
- | { readonly type: 'member'; readonly object: string; readonly property: string | ReadonlyArray<string>; readonly message: string }
406
- | { readonly type: 'call'; readonly name: string | ReadonlyArray<string>; readonly message: string }
407
- | { readonly type: 'new'; readonly name: string | ReadonlyArray<string>; readonly message: string }
408
- | { readonly type: 'import'; readonly source: string | ((source: string) => boolean); readonly message: string }
409
- | { readonly type: 'statement'; readonly nodeType: string; readonly message: string };
410
-
411
- /**
412
- * Create a rule that bans multiple patterns, each with its own message.
413
- *
414
- * @example
415
- * ```ts
416
- * Rule.banMultiple({
417
- * name: "no-global-date",
418
- * meta: { type: "suggestion" },
419
- * specs: [
420
- * { type: "new", name: "Date", message: "Avoid new Date(). Use Clock service." },
421
- * { type: "member", object: "Date", property: "now", message: "Avoid Date.now(). Use Clock service." },
422
- * ],
423
- * })
424
- * ```
425
- *
426
- * @since 0.3.0
427
- */
428
- export const banMultiple = (config: {
429
- readonly name: string;
430
- readonly meta?: { readonly type?: 'problem' | 'suggestion' } | undefined;
431
- readonly specs: ReadonlyArray<BanSpec>;
432
- }): CreateRule => {
433
- const description = config.specs.map((s) => s.message).join(' ');
434
- return define({
435
- name: config.name,
436
- meta: meta({
437
- type: config.meta?.type ?? 'suggestion',
438
- description
439
- }),
440
- create: function* () {
441
- const ctx = yield* RuleContext;
442
-
443
- const visitors: Array<EffectVisitor> = [];
444
-
445
- for (const spec of config.specs) {
446
- switch (spec.type) {
447
- case 'member': {
448
- const report = (node: ESTree.Node) =>
449
- ctx.report(makeDiagnostic({ node, message: spec.message }));
450
- visitors.push({
451
- MemberExpression: (node: ESTree.Node) =>
452
- pipe(
453
- AST.narrow(node, 'MemberExpression'),
454
- Option.flatMap(AST.matchMember(spec.object, spec.property)),
455
- Option.match({
456
- onNone: () => Effect.void,
457
- onSome: (matched) => report(matched)
458
- })
459
- )
460
- });
461
- break;
462
- }
463
- case 'call': {
464
- const names = P.isString(spec.name) ? [spec.name] : spec.name;
465
- const report = (node: ESTree.Node) =>
466
- ctx.report(makeDiagnostic({ node, message: spec.message }));
467
- visitors.push({
468
- CallExpression: (node: ESTree.Node) =>
469
- pipe(
470
- AST.narrow(node, 'CallExpression'),
471
- Option.flatMap(AST.calleeName),
472
- Option.filter((n) => Arr.contains(names, n)),
473
- Option.match({
474
- onNone: () => Effect.void,
475
- onSome: () => report(node)
476
- })
477
- )
478
- });
479
- break;
480
- }
481
- case 'new': {
482
- const names = P.isString(spec.name) ? [spec.name] : spec.name;
483
- const report = (node: ESTree.Node) =>
484
- ctx.report(makeDiagnostic({ node, message: spec.message }));
485
- visitors.push({
486
- NewExpression: (node: ESTree.Node) =>
487
- pipe(
488
- AST.narrow(node, 'NewExpression'),
489
- Option.flatMap((n) => newExprCalleeName(n.callee)),
490
- Option.filter((n) => Arr.contains(names, n)),
491
- Option.match({
492
- onNone: () => Effect.void,
493
- onSome: () => report(node)
494
- })
495
- )
496
- });
497
- break;
498
- }
499
- case 'import': {
500
- const report = (node: ESTree.Node) =>
501
- ctx.report(makeDiagnostic({ node, message: spec.message }));
502
- visitors.push({
503
- ImportDeclaration: (node: ESTree.Node) =>
504
- pipe(
505
- AST.narrow(node, 'ImportDeclaration'),
506
- Option.flatMap(AST.matchImport(spec.source)),
507
- Option.match({
508
- onNone: () => Effect.void,
509
- onSome: (matched) => report(matched)
510
- })
511
- )
512
- });
513
- break;
514
- }
515
- case 'statement': {
516
- visitors.push({
517
- [spec.nodeType]: (node: ESTree.Node) =>
518
- ctx.report(makeDiagnostic({ node, message: spec.message }))
519
- });
520
- break;
521
- }
522
- }
523
- }
524
-
525
- return mergeVisitors(...visitors);
526
- }
527
- });
528
- };
@@ -1,144 +0,0 @@
1
- /**
2
- * Effect service wrapping the oxlint rule context.
3
- *
4
- * Provides effectful access to the linting context — file info,
5
- * source code queries, diagnostics reporting, and rule options.
6
- *
7
- * @since 0.1.0
8
- */
9
- import type {
10
- Context as OxlintContext,
11
- Diagnostic as OxlintDiagnostic,
12
- ESTree,
13
- LanguageOptions,
14
- Options,
15
- Settings,
16
- SourceCode
17
- } from '@oxlint/plugins';
18
- import * as Context from 'effect/Context';
19
- import * as Effect from 'effect/Effect';
20
-
21
- // ---------------------------------------------------------------------------
22
- // Service
23
- // ---------------------------------------------------------------------------
24
-
25
- /**
26
- * The lint rule context, provided as an Effect service.
27
- *
28
- * Available inside `Rule.define`'s `create` generator and every
29
- * visitor handler via `yield* RuleContext`.
30
- *
31
- * @since 0.1.0
32
- */
33
- export class RuleContext extends Context.Service<
34
- RuleContext,
35
- {
36
- /** Report a lint diagnostic for the current file. */
37
- readonly report: (diagnostic: OxlintDiagnostic) => Effect.Effect<void>;
38
- /** Rule ID in `plugin/rule` form. */
39
- readonly id: string;
40
- /** Absolute path of the file being linted. */
41
- readonly filename: string;
42
- /** Current working directory. */
43
- readonly cwd: string;
44
- /** Raw rule options (JSON values). */
45
- readonly options: Readonly<Options>;
46
- /** Source code access (tokens, comments, text, scope, etc.). */
47
- readonly sourceCode: SourceCode;
48
- /** Language / parser options for this file. */
49
- readonly languageOptions: Readonly<LanguageOptions>;
50
- /** Shared settings from the oxlint config. */
51
- readonly settings: Readonly<Settings>;
52
- }
53
- >()('effect-oxlint/RuleContext') {}
54
-
55
- // ---------------------------------------------------------------------------
56
- // Constructor from raw oxlint Context
57
- // ---------------------------------------------------------------------------
58
-
59
- /**
60
- * Build a `RuleContext` value from the raw oxlint `Context`.
61
- *
62
- * @internal
63
- */
64
- export const fromOxlintContext = (ctx: OxlintContext): RuleContext['Service'] =>
65
- RuleContext.of({
66
- report: (diagnostic) => Effect.sync(() => ctx.report(diagnostic)),
67
- id: ctx.id,
68
- filename: ctx.filename,
69
- cwd: ctx.cwd,
70
- options: ctx.options,
71
- sourceCode: ctx.sourceCode,
72
- languageOptions: ctx.languageOptions,
73
- settings: ctx.settings
74
- });
75
-
76
- // ---------------------------------------------------------------------------
77
- // Convenience accessors (yield*-able inside handlers)
78
- // ---------------------------------------------------------------------------
79
-
80
- /**
81
- * Effectful access to the rule ID.
82
- *
83
- * @since 0.1.0
84
- */
85
- export const id: Effect.Effect<string, never, RuleContext> = Effect.service(
86
- RuleContext
87
- ).pipe(Effect.map((ctx) => ctx.id));
88
-
89
- /**
90
- * Effectful access to the filename of the file being linted.
91
- *
92
- * @since 0.1.0
93
- */
94
- export const filename: Effect.Effect<string, never, RuleContext> =
95
- Effect.service(RuleContext).pipe(Effect.map((ctx) => ctx.filename));
96
-
97
- /**
98
- * Effectful access to the working directory.
99
- *
100
- * @since 0.1.0
101
- */
102
- export const cwd: Effect.Effect<string, never, RuleContext> = Effect.service(
103
- RuleContext
104
- ).pipe(Effect.map((ctx) => ctx.cwd));
105
-
106
- /**
107
- * Effectful access to the SourceCode object.
108
- *
109
- * @since 0.1.0
110
- */
111
- export const sourceCode: Effect.Effect<SourceCode, never, RuleContext> =
112
- Effect.service(RuleContext).pipe(Effect.map((ctx) => ctx.sourceCode));
113
-
114
- /**
115
- * Effectful access to the raw source text.
116
- *
117
- * @since 0.1.0
118
- */
119
- export const text: Effect.Effect<string, never, RuleContext> = Effect.service(
120
- RuleContext
121
- ).pipe(Effect.map((ctx) => ctx.sourceCode.text));
122
-
123
- /**
124
- * Effectful access to the AST root node.
125
- *
126
- * @since 0.1.0
127
- */
128
- export const ast: Effect.Effect<ESTree.Program, never, RuleContext> =
129
- Effect.service(RuleContext).pipe(Effect.map((ctx) => ctx.sourceCode.ast));
130
-
131
- /**
132
- * Report a lint diagnostic.
133
- *
134
- * Shorthand so handlers can `yield* RuleContext.report(diagnostic)`
135
- * without first resolving the full service.
136
- *
137
- * @since 0.1.0
138
- */
139
- export const report = (
140
- diagnostic: OxlintDiagnostic
141
- ): Effect.Effect<void, never, RuleContext> =>
142
- Effect.service(RuleContext).pipe(
143
- Effect.flatMap((ctx) => ctx.report(diagnostic))
144
- );