oxlint-plugin-effect 0.2.0 → 0.2.2

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 (258) 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 +14 -0
  15. package/dist/presets/index.js +20 -0
  16. package/dist/presets/strict.d.ts +64 -0
  17. package/dist/presets/strict.js +64 -0
  18. package/dist/presets/v3-strict.d.ts +16 -0
  19. package/dist/presets/v3-strict.js +16 -0
  20. package/dist/presets/v3.d.ts +18 -0
  21. package/dist/presets/v3.js +18 -0
  22. package/dist/rules/_effect-context.d.ts +19 -0
  23. package/dist/rules/_effect-context.js +22 -0
  24. package/dist/rules/index.d.ts +66 -0
  25. package/dist/rules/index.js +124 -0
  26. package/dist/rules/no-arrow-ladder.d.ts +6 -0
  27. package/dist/rules/no-arrow-ladder.js +61 -0
  28. package/dist/rules/no-async-function.d.ts +6 -0
  29. package/dist/rules/no-async-function.js +46 -0
  30. package/dist/rules/no-catch-all-to-map-error.d.ts +6 -0
  31. package/dist/rules/no-catch-all-to-map-error.js +67 -0
  32. package/dist/rules/no-effect-as.d.ts +6 -0
  33. package/dist/rules/no-effect-as.js +16 -0
  34. package/dist/rules/no-effect-async.d.ts +6 -0
  35. package/dist/rules/no-effect-async.js +16 -0
  36. package/dist/rules/no-effect-bind.d.ts +6 -0
  37. package/dist/rules/no-effect-bind.js +16 -0
  38. package/dist/rules/no-effect-do.d.ts +6 -0
  39. package/dist/rules/no-effect-do.js +16 -0
  40. package/dist/rules/no-effect-fn-generator.d.ts +6 -0
  41. package/dist/rules/no-effect-fn-generator.js +30 -0
  42. package/dist/rules/no-effect-gen-adapter.d.ts +6 -0
  43. package/dist/rules/no-effect-gen-adapter.js +30 -0
  44. package/dist/rules/no-effect-map-flatten.d.ts +6 -0
  45. package/dist/rules/no-effect-map-flatten.js +53 -0
  46. package/dist/rules/no-effect-map-void.d.ts +6 -0
  47. package/dist/rules/no-effect-map-void.js +40 -0
  48. package/dist/rules/no-effect-never.d.ts +6 -0
  49. package/dist/rules/no-effect-never.js +16 -0
  50. package/dist/rules/no-effect-orElse-ladder.d.ts +6 -0
  51. package/dist/rules/no-effect-orElse-ladder.js +53 -0
  52. package/dist/rules/no-effect-succeed-string.d.ts +6 -0
  53. package/dist/rules/no-effect-succeed-string.js +30 -0
  54. package/dist/rules/no-effect-succeed-variable.d.ts +6 -0
  55. package/dist/rules/no-effect-succeed-variable.js +30 -0
  56. package/dist/rules/no-effect-succeed-void.d.ts +6 -0
  57. package/dist/rules/no-effect-succeed-void.js +33 -0
  58. package/dist/rules/no-effect-sync-console.d.ts +6 -0
  59. package/dist/rules/no-effect-sync-console.js +63 -0
  60. package/dist/rules/no-effect-sync-wrapper.d.ts +6 -0
  61. package/dist/rules/no-effect-sync-wrapper.js +37 -0
  62. package/dist/rules/no-effect-type-alias.d.ts +6 -0
  63. package/dist/rules/no-effect-type-alias.js +52 -0
  64. package/dist/rules/no-effect-wrapper-alias.d.ts +6 -0
  65. package/dist/rules/no-effect-wrapper-alias.js +69 -0
  66. package/dist/rules/no-extends-native-error.d.ts +6 -0
  67. package/dist/rules/no-extends-native-error.js +41 -0
  68. package/dist/rules/no-flatmap-ladder.d.ts +6 -0
  69. package/dist/rules/no-flatmap-ladder.js +48 -0
  70. package/dist/rules/no-fromnullable-coalesce.d.ts +6 -0
  71. package/dist/rules/no-fromnullable-coalesce.js +36 -0
  72. package/dist/rules/no-global-error-in-catch.d.ts +6 -0
  73. package/dist/rules/no-global-error-in-catch.js +75 -0
  74. package/dist/rules/no-global-error-in-failure.d.ts +6 -0
  75. package/dist/rules/no-global-error-in-failure.js +54 -0
  76. package/dist/rules/no-globals.d.ts +6 -0
  77. package/dist/rules/no-globals.js +164 -0
  78. package/dist/rules/no-if-statement.d.ts +6 -0
  79. package/dist/rules/no-if-statement.js +18 -0
  80. package/dist/rules/no-iife-wrapper.d.ts +6 -0
  81. package/dist/rules/no-iife-wrapper.js +37 -0
  82. package/dist/rules/no-inline-provide.d.ts +6 -0
  83. package/dist/rules/no-inline-provide.js +31 -0
  84. package/dist/rules/no-instanceof-schema.d.ts +6 -0
  85. package/dist/rules/no-instanceof-schema.js +35 -0
  86. package/dist/rules/no-manual-effect-channels.d.ts +6 -0
  87. package/dist/rules/no-manual-effect-channels.js +40 -0
  88. package/dist/rules/no-match-effect-branch.d.ts +6 -0
  89. package/dist/rules/no-match-effect-branch.js +64 -0
  90. package/dist/rules/no-match-void-branch.d.ts +6 -0
  91. package/dist/rules/no-match-void-branch.js +50 -0
  92. package/dist/rules/no-multiple-effect-provide.d.ts +6 -0
  93. package/dist/rules/no-multiple-effect-provide.js +45 -0
  94. package/dist/rules/no-nested-effect-call.d.ts +6 -0
  95. package/dist/rules/no-nested-effect-call.js +97 -0
  96. package/dist/rules/no-nested-effect-gen.d.ts +6 -0
  97. package/dist/rules/no-nested-effect-gen.js +46 -0
  98. package/dist/rules/no-nested-pipe.d.ts +6 -0
  99. package/dist/rules/no-nested-pipe.js +38 -0
  100. package/dist/rules/no-new-error.d.ts +6 -0
  101. package/dist/rules/no-new-error.js +39 -0
  102. package/dist/rules/no-new-promise.d.ts +6 -0
  103. package/dist/rules/no-new-promise.js +38 -0
  104. package/dist/rules/no-node-builtin-import.d.ts +6 -0
  105. package/dist/rules/no-node-builtin-import.js +61 -0
  106. package/dist/rules/no-option-as.d.ts +6 -0
  107. package/dist/rules/no-option-as.js +16 -0
  108. package/dist/rules/no-option-boolean-normalization.d.ts +6 -0
  109. package/dist/rules/no-option-boolean-normalization.js +50 -0
  110. package/dist/rules/no-positional-log-error.d.ts +6 -0
  111. package/dist/rules/no-positional-log-error.js +52 -0
  112. package/dist/rules/no-redundant-schema-tag-identifier.d.ts +6 -0
  113. package/dist/rules/no-redundant-schema-tag-identifier.js +46 -0
  114. package/dist/rules/no-return-in-arrow.d.ts +6 -0
  115. package/dist/rules/no-return-in-arrow.js +41 -0
  116. package/dist/rules/no-return-nullish.d.ts +6 -0
  117. package/dist/rules/no-return-nullish.js +47 -0
  118. package/dist/rules/no-run-in-effect-gen.d.ts +6 -0
  119. package/dist/rules/no-run-in-effect-gen.js +48 -0
  120. package/dist/rules/no-runtime-run-fork.d.ts +6 -0
  121. package/dist/rules/no-runtime-run-fork.js +19 -0
  122. package/dist/rules/no-schema-struct-with-tag.d.ts +6 -0
  123. package/dist/rules/no-schema-struct-with-tag.js +48 -0
  124. package/dist/rules/no-schema-union-of-literals.d.ts +6 -0
  125. package/dist/rules/no-schema-union-of-literals.js +35 -0
  126. package/dist/rules/no-string-sentinel-const.d.ts +6 -0
  127. package/dist/rules/no-string-sentinel-const.js +38 -0
  128. package/dist/rules/no-switch-statement.d.ts +6 -0
  129. package/dist/rules/no-switch-statement.js +16 -0
  130. package/dist/rules/no-ternary.d.ts +6 -0
  131. package/dist/rules/no-ternary.js +30 -0
  132. package/dist/rules/no-throw-statement.d.ts +6 -0
  133. package/dist/rules/no-throw-statement.js +34 -0
  134. package/dist/rules/no-try-catch.d.ts +6 -0
  135. package/dist/rules/no-try-catch.js +34 -0
  136. package/dist/rules/no-unnecessary-arrow-block.d.ts +6 -0
  137. package/dist/rules/no-unnecessary-arrow-block.js +34 -0
  138. package/dist/rules/no-unnecessary-effect-gen.d.ts +6 -0
  139. package/dist/rules/no-unnecessary-effect-gen.js +35 -0
  140. package/dist/rules/no-unnecessary-pipe-chain.d.ts +6 -0
  141. package/dist/rules/no-unnecessary-pipe-chain.js +36 -0
  142. package/dist/rules/no-unnecessary-pipe.d.ts +6 -0
  143. package/dist/rules/no-unnecessary-pipe.js +29 -0
  144. package/dist/vendor/effect-oxlint/AST.d.ts +199 -0
  145. package/dist/vendor/effect-oxlint/AST.js +214 -0
  146. package/dist/vendor/effect-oxlint/Comment.d.ts +55 -0
  147. package/dist/vendor/effect-oxlint/Comment.js +67 -0
  148. package/dist/vendor/effect-oxlint/Diagnostic.d.ts +86 -0
  149. package/dist/vendor/effect-oxlint/Diagnostic.js +115 -0
  150. package/dist/vendor/effect-oxlint/Plugin.d.ts +38 -0
  151. package/dist/vendor/effect-oxlint/Plugin.js +45 -0
  152. package/dist/vendor/effect-oxlint/Rule.d.ts +218 -0
  153. package/dist/vendor/effect-oxlint/Rule.js +309 -0
  154. package/dist/vendor/effect-oxlint/RuleContext.d.ts +77 -0
  155. package/dist/vendor/effect-oxlint/RuleContext.js +74 -0
  156. package/dist/vendor/effect-oxlint/Scope.d.ts +100 -0
  157. package/dist/vendor/effect-oxlint/Scope.js +110 -0
  158. package/dist/vendor/effect-oxlint/SourceCode.d.ts +180 -0
  159. package/dist/vendor/effect-oxlint/SourceCode.js +203 -0
  160. package/dist/vendor/effect-oxlint/Testing.d.ts +510 -0
  161. package/dist/vendor/effect-oxlint/Testing.js +963 -0
  162. package/dist/vendor/effect-oxlint/Token.d.ts +98 -0
  163. package/dist/vendor/effect-oxlint/Token.js +103 -0
  164. package/dist/vendor/effect-oxlint/Visitor.d.ts +166 -0
  165. package/dist/vendor/effect-oxlint/Visitor.js +138 -0
  166. package/dist/vendor/effect-oxlint/index.d.ts +13 -0
  167. package/dist/vendor/effect-oxlint/index.js +12 -0
  168. package/package.json +24 -5
  169. package/.effect-lsp.json +0 -60
  170. package/.oxlintrc.json +0 -33
  171. package/CHANGELOG.md +0 -55
  172. package/scripts/add-rule.ts +0 -87
  173. package/scripts/codegen.ts +0 -108
  174. package/src/index.ts +0 -9
  175. package/src/plugin.ts +0 -16
  176. package/src/presets/core.ts +0 -47
  177. package/src/presets/effect-native.ts +0 -34
  178. package/src/presets/full.ts +0 -70
  179. package/src/presets/functional.ts +0 -18
  180. package/src/presets/index.ts +0 -5
  181. package/src/presets/strict.ts +0 -77
  182. package/src/rules/_effect-context.ts +0 -28
  183. package/src/rules/index.ts +0 -72
  184. package/src/rules/no-arrow-ladder.ts +0 -70
  185. package/src/rules/no-async-function.ts +0 -47
  186. package/src/rules/no-catch-all-to-map-error.ts +0 -90
  187. package/src/rules/no-effect-as.ts +0 -14
  188. package/src/rules/no-effect-async.ts +0 -14
  189. package/src/rules/no-effect-bind.ts +0 -13
  190. package/src/rules/no-effect-do.ts +0 -14
  191. package/src/rules/no-effect-fn-generator.ts +0 -58
  192. package/src/rules/no-effect-gen-adapter.ts +0 -51
  193. package/src/rules/no-effect-map-flatten.ts +0 -82
  194. package/src/rules/no-effect-map-void.ts +0 -80
  195. package/src/rules/no-effect-never.ts +0 -14
  196. package/src/rules/no-effect-orElse-ladder.ts +0 -79
  197. package/src/rules/no-effect-succeed-string.ts +0 -52
  198. package/src/rules/no-effect-succeed-variable.ts +0 -50
  199. package/src/rules/no-effect-succeed-void.ts +0 -63
  200. package/src/rules/no-effect-sync-console.ts +0 -91
  201. package/src/rules/no-effect-sync-wrapper.ts +0 -67
  202. package/src/rules/no-effect-type-alias.ts +0 -71
  203. package/src/rules/no-effect-wrapper-alias.ts +0 -95
  204. package/src/rules/no-extends-native-error.ts +0 -54
  205. package/src/rules/no-flatmap-ladder.ts +0 -77
  206. package/src/rules/no-fromnullable-coalesce.ts +0 -62
  207. package/src/rules/no-global-error-in-catch.ts +0 -104
  208. package/src/rules/no-global-error-in-failure.ts +0 -83
  209. package/src/rules/no-globals.ts +0 -178
  210. package/src/rules/no-if-statement.ts +0 -16
  211. package/src/rules/no-iife-wrapper.ts +0 -51
  212. package/src/rules/no-inline-provide.ts +0 -41
  213. package/src/rules/no-instanceof-schema.ts +0 -42
  214. package/src/rules/no-manual-effect-channels.ts +0 -58
  215. package/src/rules/no-match-effect-branch.ts +0 -82
  216. package/src/rules/no-match-void-branch.ts +0 -72
  217. package/src/rules/no-multiple-effect-provide.ts +0 -75
  218. package/src/rules/no-nested-effect-call.ts +0 -57
  219. package/src/rules/no-nested-effect-gen.ts +0 -43
  220. package/src/rules/no-nested-pipe.ts +0 -73
  221. package/src/rules/no-new-error.ts +0 -53
  222. package/src/rules/no-new-promise.ts +0 -50
  223. package/src/rules/no-node-builtin-import.ts +0 -68
  224. package/src/rules/no-option-as.ts +0 -13
  225. package/src/rules/no-option-boolean-normalization.ts +0 -88
  226. package/src/rules/no-positional-log-error.ts +0 -76
  227. package/src/rules/no-redundant-schema-tag-identifier.ts +0 -62
  228. package/src/rules/no-return-in-arrow.ts +0 -76
  229. package/src/rules/no-return-nullish.ts +0 -80
  230. package/src/rules/no-run-in-effect-gen.ts +0 -66
  231. package/src/rules/no-runtime-run-fork.ts +0 -14
  232. package/src/rules/no-schema-struct-with-tag.ts +0 -64
  233. package/src/rules/no-schema-union-of-literals.ts +0 -48
  234. package/src/rules/no-string-sentinel-const.ts +0 -49
  235. package/src/rules/no-switch-statement.ts +0 -13
  236. package/src/rules/no-ternary.ts +0 -30
  237. package/src/rules/no-throw-statement.ts +0 -39
  238. package/src/rules/no-try-catch.ts +0 -39
  239. package/src/rules/no-unnecessary-arrow-block.ts +0 -51
  240. package/src/rules/no-unnecessary-effect-gen.ts +0 -83
  241. package/src/rules/no-unnecessary-pipe-chain.ts +0 -47
  242. package/src/rules/no-unnecessary-pipe.ts +0 -45
  243. package/src/vendor/effect-oxlint/AST.ts +0 -516
  244. package/src/vendor/effect-oxlint/Comment.ts +0 -86
  245. package/src/vendor/effect-oxlint/Diagnostic.ts +0 -199
  246. package/src/vendor/effect-oxlint/Plugin.ts +0 -71
  247. package/src/vendor/effect-oxlint/Rule.ts +0 -548
  248. package/src/vendor/effect-oxlint/RuleContext.ts +0 -144
  249. package/src/vendor/effect-oxlint/Scope.ts +0 -177
  250. package/src/vendor/effect-oxlint/SourceCode.ts +0 -377
  251. package/src/vendor/effect-oxlint/Testing.ts +0 -1227
  252. package/src/vendor/effect-oxlint/Token.ts +0 -131
  253. package/src/vendor/effect-oxlint/Visitor.ts +0 -287
  254. package/src/vendor/effect-oxlint/index.ts +0 -88
  255. package/tests/ban-rules.test.ts +0 -68
  256. package/tests/context-rules.test.ts +0 -263
  257. package/tests/pattern-rules.test.ts +0 -247
  258. package/tsconfig.json +0 -15
@@ -1,548 +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
- * Which Effect version a rule applies to.
135
- *
136
- * Stored on `meta.docs.effectVersion` so preset authors can filter rules
137
- * per project. Default `both`, meaning the rule is version-agnostic.
138
- *
139
- * @since 0.2.0
140
- */
141
- export type EffectVersion = 'v3' | 'v4' | 'both';
142
-
143
- /**
144
- * Build `RuleMeta` with sensible defaults.
145
- *
146
- * @since 0.1.0
147
- */
148
- export const meta = (opts: {
149
- readonly type: 'problem' | 'suggestion' | 'layout';
150
- readonly description: string;
151
- readonly fixable?: 'code' | 'whitespace' | undefined;
152
- readonly hasSuggestions?: boolean | undefined;
153
- readonly messages?: Record<string, string> | undefined;
154
- readonly docs?: RuleDocs | undefined;
155
- readonly effectVersion?: EffectVersion | undefined;
156
- }): RuleMeta => ({
157
- type: opts.type,
158
- ...(opts.fixable !== undefined ? { fixable: opts.fixable } : {}),
159
- ...(opts.hasSuggestions !== undefined
160
- ? { hasSuggestions: opts.hasSuggestions }
161
- : {}),
162
- ...(opts.messages !== undefined ? { messages: opts.messages } : {}),
163
- docs: {
164
- description: opts.description,
165
- effectVersion: opts.effectVersion ?? 'both',
166
- ...opts.docs
167
- }
168
- });
169
-
170
- // ---------------------------------------------------------------------------
171
- // Convenience rule factories (common patterns)
172
- // ---------------------------------------------------------------------------
173
-
174
- /**
175
- * Create a rule that bans `obj.prop` member expression access.
176
- *
177
- * Replaces the common `memberExprRule` utility pattern.
178
- *
179
- * @since 0.1.0
180
- */
181
- export const banMember = (
182
- obj: string,
183
- prop: string | ReadonlyArray<string>,
184
- opts: {
185
- readonly message: string;
186
- readonly meta?:
187
- | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
188
- | undefined;
189
- }
190
- ): CreateRule =>
191
- define({
192
- name: `ban-${obj}-${P.isString(prop) ? prop : Arr.join(prop, '-')}`,
193
- meta: meta({
194
- type: opts.meta?.type ?? 'suggestion',
195
- description: opts.message,
196
- effectVersion: opts.meta?.effectVersion
197
- }),
198
- create: function* () {
199
- const ctx = yield* RuleContext;
200
- return {
201
- MemberExpression: (node: ESTree.Node) =>
202
- pipe(
203
- AST.narrow(node, 'MemberExpression'),
204
- Option.flatMap(AST.matchMember(obj, prop)),
205
- Option.match({
206
- onNone: () => Effect.void,
207
- onSome: (matched) =>
208
- ctx.report(
209
- makeDiagnostic({
210
- node: matched,
211
- message: opts.message
212
- })
213
- )
214
- })
215
- )
216
- };
217
- }
218
- });
219
-
220
- /**
221
- * Create a rule that bans imports matching a source string or predicate.
222
- *
223
- * Replaces the common `importRule` utility pattern.
224
- *
225
- * @since 0.1.0
226
- */
227
- export const banImport = (
228
- source: string | ((source: string) => boolean),
229
- opts: {
230
- readonly message: string;
231
- readonly meta?:
232
- | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
233
- | undefined;
234
- }
235
- ): CreateRule =>
236
- define({
237
- name: 'ban-import',
238
- meta: meta({
239
- type: opts.meta?.type ?? 'suggestion',
240
- description: opts.message,
241
- effectVersion: opts.meta?.effectVersion
242
- }),
243
- create: function* () {
244
- const ctx = yield* RuleContext;
245
- return {
246
- ImportDeclaration: (node: ESTree.Node) =>
247
- pipe(
248
- AST.narrow(node, 'ImportDeclaration'),
249
- Option.flatMap(AST.matchImport(source)),
250
- Option.match({
251
- onNone: () => Effect.void,
252
- onSome: (matched) =>
253
- ctx.report(
254
- makeDiagnostic({
255
- node: matched,
256
- message: opts.message
257
- })
258
- )
259
- })
260
- )
261
- };
262
- }
263
- });
264
-
265
- /**
266
- * Create a rule that bans bare identifier call expressions.
267
- *
268
- * Matches `CallExpression` nodes whose callee is an identifier in
269
- * the given list (e.g. `fetch()`, `useState()`, `readFileSync()`).
270
- *
271
- * @example
272
- * ```ts
273
- * // Ban a single call
274
- * Rule.banCallOf('fetch', { message: 'Use Effect HTTP client' })
275
- *
276
- * // Ban multiple calls
277
- * Rule.banCallOf(['useState', 'useEffect'], { message: 'Use Effect' })
278
- * ```
279
- *
280
- * @since 0.2.0
281
- */
282
- export const banCallOf = (
283
- name: string | ReadonlyArray<string>,
284
- opts: {
285
- readonly message: string;
286
- readonly meta?:
287
- | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
288
- | undefined;
289
- }
290
- ): CreateRule => {
291
- const names = P.isString(name) ? [name] : name;
292
- return define({
293
- name: `ban-call-${Arr.join(names, '-')}`,
294
- meta: meta({
295
- type: opts.meta?.type ?? 'suggestion',
296
- description: opts.message,
297
- effectVersion: opts.meta?.effectVersion
298
- }),
299
- create: function* () {
300
- const ctx = yield* RuleContext;
301
- return {
302
- CallExpression: (node: ESTree.Node) =>
303
- pipe(
304
- AST.narrow(node, 'CallExpression'),
305
- Option.flatMap(AST.calleeName),
306
- Option.filter((n) => Arr.contains(names, n)),
307
- Option.match({
308
- onNone: () => Effect.void,
309
- onSome: () =>
310
- ctx.report(
311
- makeDiagnostic({
312
- node,
313
- message: opts.message
314
- })
315
- )
316
- })
317
- )
318
- };
319
- }
320
- });
321
- };
322
-
323
- /**
324
- * Create a rule that bans `new` expressions with the given callee name.
325
- *
326
- * Matches `NewExpression` nodes whose callee is an identifier in
327
- * the given list (e.g. `new Date()`, `new Error()`).
328
- *
329
- * @example
330
- * ```ts
331
- * // Ban a single constructor
332
- * Rule.banNewExpr('Date', { message: 'Use Clock service' })
333
- *
334
- * // Ban multiple constructors
335
- * Rule.banNewExpr(['Error', 'TypeError'], { message: 'Use tagged errors' })
336
- * ```
337
- *
338
- * @since 0.2.0
339
- */
340
- export const banNewExpr = (
341
- name: string | ReadonlyArray<string>,
342
- opts: {
343
- readonly message: string;
344
- readonly meta?:
345
- | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
346
- | undefined;
347
- }
348
- ): CreateRule => {
349
- const names = P.isString(name) ? [name] : name;
350
- return define({
351
- name: `ban-new-${Arr.join(names, '-')}`,
352
- meta: meta({
353
- type: opts.meta?.type ?? 'suggestion',
354
- description: opts.message,
355
- effectVersion: opts.meta?.effectVersion
356
- }),
357
- create: function* () {
358
- const ctx = yield* RuleContext;
359
- return {
360
- NewExpression: (node: ESTree.Node) =>
361
- pipe(
362
- AST.narrow(node, 'NewExpression'),
363
- Option.flatMap((n) => newExprCalleeName(n.callee)),
364
- Option.filter((n) => Arr.contains(names, n)),
365
- Option.match({
366
- onNone: () => Effect.void,
367
- onSome: () =>
368
- ctx.report(
369
- makeDiagnostic({
370
- node,
371
- message: opts.message
372
- })
373
- )
374
- })
375
- )
376
- };
377
- }
378
- });
379
- };
380
-
381
- /**
382
- * Create a rule that bans a specific statement type.
383
- *
384
- * @since 0.1.0
385
- */
386
- export const banStatement = (
387
- nodeType: string,
388
- opts: {
389
- readonly message: string;
390
- readonly meta?:
391
- | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
392
- | undefined;
393
- }
394
- ): CreateRule =>
395
- define({
396
- name: `ban-${nodeType}`,
397
- meta: meta({
398
- type: opts.meta?.type ?? 'suggestion',
399
- description: opts.message,
400
- effectVersion: opts.meta?.effectVersion
401
- }),
402
- create: function* () {
403
- const ctx = yield* RuleContext;
404
- return {
405
- [nodeType]: (node: ESTree.Node) =>
406
- ctx.report(makeDiagnostic({ node, message: opts.message }))
407
- };
408
- }
409
- });
410
-
411
- // ---------------------------------------------------------------------------
412
- // Multi-ban combinator
413
- // ---------------------------------------------------------------------------
414
-
415
- /**
416
- * A single ban specification. Each spec carries its own message so
417
- * diagnostics are specific to the exact pattern matched.
418
- *
419
- * @since 0.3.0
420
- */
421
- export type BanSpec =
422
- | { readonly type: 'member'; readonly object: string; readonly property: string | ReadonlyArray<string>; readonly message: string }
423
- | { readonly type: 'call'; readonly name: string | ReadonlyArray<string>; readonly message: string }
424
- | { readonly type: 'new'; readonly name: string | ReadonlyArray<string>; readonly message: string }
425
- | { readonly type: 'import'; readonly source: string | ((source: string) => boolean); readonly message: string }
426
- | { readonly type: 'statement'; readonly nodeType: string; readonly message: string };
427
-
428
- /**
429
- * Create a rule that bans multiple patterns, each with its own message.
430
- *
431
- * @example
432
- * ```ts
433
- * Rule.banMultiple({
434
- * name: "no-global-date",
435
- * meta: { type: "suggestion" },
436
- * specs: [
437
- * { type: "new", name: "Date", message: "Avoid new Date(). Use Clock service." },
438
- * { type: "member", object: "Date", property: "now", message: "Avoid Date.now(). Use Clock service." },
439
- * ],
440
- * })
441
- * ```
442
- *
443
- * @since 0.3.0
444
- */
445
- export const banMultiple = (config: {
446
- readonly name: string;
447
- readonly meta?:
448
- | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
449
- | undefined;
450
- readonly specs: ReadonlyArray<BanSpec>;
451
- }): CreateRule => {
452
- const description = config.specs.map((s) => s.message).join(' ');
453
- return define({
454
- name: config.name,
455
- meta: meta({
456
- type: config.meta?.type ?? 'suggestion',
457
- description,
458
- effectVersion: config.meta?.effectVersion
459
- }),
460
- create: function* () {
461
- const ctx = yield* RuleContext;
462
-
463
- const visitors: Array<EffectVisitor> = [];
464
-
465
- for (const spec of config.specs) {
466
- switch (spec.type) {
467
- case 'member': {
468
- const report = (node: ESTree.Node) =>
469
- ctx.report(makeDiagnostic({ node, message: spec.message }));
470
- visitors.push({
471
- MemberExpression: (node: ESTree.Node) =>
472
- pipe(
473
- AST.narrow(node, 'MemberExpression'),
474
- Option.flatMap(AST.matchMember(spec.object, spec.property)),
475
- Option.match({
476
- onNone: () => Effect.void,
477
- onSome: (matched) => report(matched)
478
- })
479
- )
480
- });
481
- break;
482
- }
483
- case 'call': {
484
- const names = P.isString(spec.name) ? [spec.name] : spec.name;
485
- const report = (node: ESTree.Node) =>
486
- ctx.report(makeDiagnostic({ node, message: spec.message }));
487
- visitors.push({
488
- CallExpression: (node: ESTree.Node) =>
489
- pipe(
490
- AST.narrow(node, 'CallExpression'),
491
- Option.flatMap(AST.calleeName),
492
- Option.filter((n) => Arr.contains(names, n)),
493
- Option.match({
494
- onNone: () => Effect.void,
495
- onSome: () => report(node)
496
- })
497
- )
498
- });
499
- break;
500
- }
501
- case 'new': {
502
- const names = P.isString(spec.name) ? [spec.name] : spec.name;
503
- const report = (node: ESTree.Node) =>
504
- ctx.report(makeDiagnostic({ node, message: spec.message }));
505
- visitors.push({
506
- NewExpression: (node: ESTree.Node) =>
507
- pipe(
508
- AST.narrow(node, 'NewExpression'),
509
- Option.flatMap((n) => newExprCalleeName(n.callee)),
510
- Option.filter((n) => Arr.contains(names, n)),
511
- Option.match({
512
- onNone: () => Effect.void,
513
- onSome: () => report(node)
514
- })
515
- )
516
- });
517
- break;
518
- }
519
- case 'import': {
520
- const report = (node: ESTree.Node) =>
521
- ctx.report(makeDiagnostic({ node, message: spec.message }));
522
- visitors.push({
523
- ImportDeclaration: (node: ESTree.Node) =>
524
- pipe(
525
- AST.narrow(node, 'ImportDeclaration'),
526
- Option.flatMap(AST.matchImport(spec.source)),
527
- Option.match({
528
- onNone: () => Effect.void,
529
- onSome: (matched) => report(matched)
530
- })
531
- )
532
- });
533
- break;
534
- }
535
- case 'statement': {
536
- visitors.push({
537
- [spec.nodeType]: (node: ESTree.Node) =>
538
- ctx.report(makeDiagnostic({ node, message: spec.message }))
539
- });
540
- break;
541
- }
542
- }
543
- }
544
-
545
- return mergeVisitors(...visitors);
546
- }
547
- });
548
- };
@@ -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
- );