oxlint-plugin-effect 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.effect-lsp.json +60 -0
  2. package/.oxlintrc.json +33 -0
  3. package/package.json +51 -0
  4. package/scripts/add-rule.ts +87 -0
  5. package/scripts/codegen.ts +108 -0
  6. package/src/index.ts +9 -0
  7. package/src/plugin.ts +16 -0
  8. package/src/presets/core.ts +36 -0
  9. package/src/presets/effect-native.ts +41 -0
  10. package/src/presets/full.ts +84 -0
  11. package/src/presets/index.ts +3 -0
  12. package/src/rules/_effect-context.ts +28 -0
  13. package/src/rules/index.ts +83 -0
  14. package/src/rules/no-arrow-ladder.ts +71 -0
  15. package/src/rules/no-async-function.ts +48 -0
  16. package/src/rules/no-console-in-effect.ts +46 -0
  17. package/src/rules/no-date-in-effect.ts +61 -0
  18. package/src/rules/no-dynamic-import.ts +13 -0
  19. package/src/rules/no-effect-as.ts +14 -0
  20. package/src/rules/no-effect-async.ts +14 -0
  21. package/src/rules/no-effect-bind.ts +13 -0
  22. package/src/rules/no-effect-do.ts +14 -0
  23. package/src/rules/no-effect-fn-generator.ts +59 -0
  24. package/src/rules/no-effect-gen-adapter.ts +51 -0
  25. package/src/rules/no-effect-map-void.ts +81 -0
  26. package/src/rules/no-effect-never.ts +14 -0
  27. package/src/rules/no-effect-orElse-ladder.ts +80 -0
  28. package/src/rules/no-effect-succeed-string.ts +53 -0
  29. package/src/rules/no-effect-succeed-variable.ts +51 -0
  30. package/src/rules/no-effect-succeed-void.ts +64 -0
  31. package/src/rules/no-effect-sync-console.ts +92 -0
  32. package/src/rules/no-effect-sync-wrapper.ts +68 -0
  33. package/src/rules/no-effect-type-alias.ts +72 -0
  34. package/src/rules/no-effect-wrapper-alias.ts +98 -0
  35. package/src/rules/no-extends-native-error.ts +55 -0
  36. package/src/rules/no-fetch-in-effect.ts +47 -0
  37. package/src/rules/no-flatmap-ladder.ts +79 -0
  38. package/src/rules/no-fromnullable-coalesce.ts +63 -0
  39. package/src/rules/no-global-console.ts +17 -0
  40. package/src/rules/no-global-date.ts +20 -0
  41. package/src/rules/no-global-fetch.ts +14 -0
  42. package/src/rules/no-global-random.ts +13 -0
  43. package/src/rules/no-global-timers.ts +17 -0
  44. package/src/rules/no-if-statement.ts +16 -0
  45. package/src/rules/no-iife-wrapper.ts +52 -0
  46. package/src/rules/no-inline-runtime-provide.ts +42 -0
  47. package/src/rules/no-instanceof-schema.ts +43 -0
  48. package/src/rules/no-json-in-effect.ts +45 -0
  49. package/src/rules/no-json-parse.ts +18 -0
  50. package/src/rules/no-manual-effect-channels.ts +59 -0
  51. package/src/rules/no-match-effect-branch.ts +83 -0
  52. package/src/rules/no-match-void-branch.ts +73 -0
  53. package/src/rules/no-nested-effect-call.ts +59 -0
  54. package/src/rules/no-nested-effect-gen.ts +44 -0
  55. package/src/rules/no-nested-pipe.ts +76 -0
  56. package/src/rules/no-new-error.ts +17 -0
  57. package/src/rules/no-new-promise.ts +14 -0
  58. package/src/rules/no-node-builtin-import.ts +70 -0
  59. package/src/rules/no-option-as.ts +13 -0
  60. package/src/rules/no-option-boolean-normalization.ts +89 -0
  61. package/src/rules/no-platform-globals.ts +117 -0
  62. package/src/rules/no-process-env-in-effect.ts +39 -0
  63. package/src/rules/no-process-env.ts +13 -0
  64. package/src/rules/no-random-in-effect.ts +39 -0
  65. package/src/rules/no-return-in-arrow.ts +77 -0
  66. package/src/rules/no-return-null.ts +47 -0
  67. package/src/rules/no-run-in-effect.ts +18 -0
  68. package/src/rules/no-runtime-run-fork.ts +14 -0
  69. package/src/rules/no-string-sentinel-const.ts +50 -0
  70. package/src/rules/no-switch-statement.ts +13 -0
  71. package/src/rules/no-ternary.ts +32 -0
  72. package/src/rules/no-throw-in-effect-gen.ts +42 -0
  73. package/src/rules/no-throw-statement.ts +13 -0
  74. package/src/rules/no-timers-in-effect.ts +47 -0
  75. package/src/rules/no-try-catch-in-effect-gen.ts +42 -0
  76. package/src/rules/no-try-catch.ts +14 -0
  77. package/src/rules/no-unnecessary-arrow-block.ts +52 -0
  78. package/src/rules/no-unnecessary-effect-gen.ts +85 -0
  79. package/src/rules/no-unnecessary-pipe.ts +46 -0
  80. package/src/vendor/effect-oxlint/AST.ts +516 -0
  81. package/src/vendor/effect-oxlint/Comment.ts +86 -0
  82. package/src/vendor/effect-oxlint/Diagnostic.ts +199 -0
  83. package/src/vendor/effect-oxlint/Plugin.ts +71 -0
  84. package/src/vendor/effect-oxlint/Rule.ts +590 -0
  85. package/src/vendor/effect-oxlint/RuleContext.ts +144 -0
  86. package/src/vendor/effect-oxlint/Scope.ts +177 -0
  87. package/src/vendor/effect-oxlint/SourceCode.ts +377 -0
  88. package/src/vendor/effect-oxlint/Testing.ts +1227 -0
  89. package/src/vendor/effect-oxlint/Token.ts +131 -0
  90. package/src/vendor/effect-oxlint/Visitor.ts +287 -0
  91. package/src/vendor/effect-oxlint/index.ts +88 -0
  92. package/tests/ban-rules.test.ts +252 -0
  93. package/tests/context-rules.test.ts +281 -0
  94. package/tests/pattern-rules.test.ts +258 -0
  95. package/tsconfig.json +15 -0
@@ -0,0 +1,144 @@
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
+ );
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Scope analysis helpers for Effect-first lint rules.
3
+ *
4
+ * Wraps oxlint's scope manager types (`Scope`, `Variable`, `Reference`,
5
+ * `Definition`) with `Option`-returning lookups and convenience predicates.
6
+ *
7
+ * For scope queries that require `SourceCode` access (e.g. `getScope`,
8
+ * `getDeclaredVariables`), see the `SourceCode` module which provides
9
+ * effectful wrappers.
10
+ *
11
+ * @since 0.2.0
12
+ */
13
+ import type {
14
+ Reference,
15
+ Scope as OxlintScope,
16
+ Variable
17
+ } from '@oxlint/plugins';
18
+ import * as Arr from 'effect/Array';
19
+ import { dual, pipe } from 'effect/Function';
20
+ import * as Option from 'effect/Option';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Variable lookup
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * Find a variable by name in a scope.
28
+ *
29
+ * Searches the scope's `set` (a `Map<string, Variable>`).
30
+ *
31
+ * @since 0.2.0
32
+ */
33
+ export const findVariable: {
34
+ (name: string): (scope: OxlintScope) => Option.Option<Variable>;
35
+ (scope: OxlintScope, name: string): Option.Option<Variable>;
36
+ } = dual(
37
+ 2,
38
+ (scope: OxlintScope, name: string): Option.Option<Variable> =>
39
+ Option.fromNullishOr(scope.set.get(name))
40
+ );
41
+
42
+ /**
43
+ * Find a variable by name, walking up the scope chain.
44
+ *
45
+ * Starts at the given scope and checks each `upper` scope until found
46
+ * or the global scope is exhausted.
47
+ *
48
+ * @since 0.2.0
49
+ */
50
+ export const findVariableUp: {
51
+ (name: string): (scope: OxlintScope) => Option.Option<Variable>;
52
+ (scope: OxlintScope, name: string): Option.Option<Variable>;
53
+ } = dual(2, (scope: OxlintScope, name: string): Option.Option<Variable> => {
54
+ const walk = (current: OxlintScope): Option.Option<Variable> =>
55
+ pipe(
56
+ Option.fromNullishOr(current.set.get(name)),
57
+ Option.orElse(() =>
58
+ pipe(
59
+ Option.fromNullishOr(current.upper),
60
+ Option.flatMap((parent) => walk(parent))
61
+ )
62
+ )
63
+ );
64
+ return walk(scope);
65
+ });
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Variable predicates
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /**
72
+ * Check whether a variable has any read references.
73
+ *
74
+ * A variable is considered "used" if at least one reference reads it.
75
+ *
76
+ * @since 0.2.0
77
+ */
78
+ export const isUsed = (variable: Variable): boolean =>
79
+ Arr.some(variable.references, (ref) => ref.isRead());
80
+
81
+ /**
82
+ * Check whether a variable has any write references.
83
+ *
84
+ * @since 0.2.0
85
+ */
86
+ export const isWritten = (variable: Variable): boolean =>
87
+ Arr.some(variable.references, (ref) => ref.isWrite());
88
+
89
+ /**
90
+ * Check whether a variable is only read (no writes).
91
+ *
92
+ * @since 0.2.0
93
+ */
94
+ export const isReadOnly = (variable: Variable): boolean =>
95
+ Arr.some(variable.references, (ref) => ref.isReadOnly());
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Reference helpers
99
+ // ---------------------------------------------------------------------------
100
+
101
+ /**
102
+ * Get all references for a variable.
103
+ *
104
+ * @since 0.2.0
105
+ */
106
+ export const getReferences = (variable: Variable): ReadonlyArray<Reference> =>
107
+ variable.references;
108
+
109
+ /**
110
+ * Get the read references for a variable.
111
+ *
112
+ * @since 0.2.0
113
+ */
114
+ export const getReadReferences = (
115
+ variable: Variable
116
+ ): ReadonlyArray<Reference> =>
117
+ pipe(
118
+ variable.references,
119
+ Arr.filter((ref) => ref.isRead())
120
+ );
121
+
122
+ /**
123
+ * Get the write references for a variable.
124
+ *
125
+ * @since 0.2.0
126
+ */
127
+ export const getWriteReferences = (
128
+ variable: Variable
129
+ ): ReadonlyArray<Reference> =>
130
+ pipe(
131
+ variable.references,
132
+ Arr.filter((ref) => ref.isWrite())
133
+ );
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Scope navigation
137
+ // ---------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Get the parent (upper) scope.
141
+ *
142
+ * @since 0.2.0
143
+ */
144
+ export const upper = (scope: OxlintScope): Option.Option<OxlintScope> =>
145
+ Option.fromNullishOr(scope.upper);
146
+
147
+ /**
148
+ * Get the child scopes.
149
+ *
150
+ * @since 0.2.0
151
+ */
152
+ export const childScopes = (scope: OxlintScope): ReadonlyArray<OxlintScope> =>
153
+ scope.childScopes;
154
+
155
+ /**
156
+ * Get all variables in a scope.
157
+ *
158
+ * @since 0.2.0
159
+ */
160
+ export const variables = (scope: OxlintScope): ReadonlyArray<Variable> =>
161
+ scope.variables;
162
+
163
+ /**
164
+ * Get "through" references (unresolved in this scope).
165
+ *
166
+ * @since 0.2.0
167
+ */
168
+ export const throughReferences = (
169
+ scope: OxlintScope
170
+ ): ReadonlyArray<Reference> => scope.through;
171
+
172
+ /**
173
+ * Check whether a scope is strict mode.
174
+ *
175
+ * @since 0.2.0
176
+ */
177
+ export const isStrict = (scope: OxlintScope): boolean => scope.isStrict;
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Effect-wrapped queries over oxlint's `SourceCode` object.
3
+ *
4
+ * Every method that may return `null` is lifted to `Option`. Methods
5
+ * that return arrays stay as-is (empty array = no results). All
6
+ * functions read from the `RuleContext` service, so they return
7
+ * `Effect<T, never, RuleContext>` and can be `yield*`'d in handlers.
8
+ *
9
+ * @since 0.2.0
10
+ */
11
+ import type {
12
+ Comment,
13
+ ESTree,
14
+ LineColumn,
15
+ Range,
16
+ Scope,
17
+ SourceCode as OxlintSourceCode,
18
+ Token,
19
+ Variable
20
+ } from '@oxlint/plugins';
21
+ import * as Effect from 'effect/Effect';
22
+ import * as Option from 'effect/Option';
23
+
24
+ import { RuleContext } from './RuleContext.ts';
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Internal: read sourceCode from RuleContext
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /** @internal */
31
+ const withSourceCode = <A>(
32
+ fn: (sc: OxlintSourceCode) => A
33
+ ): Effect.Effect<A, never, RuleContext> =>
34
+ Effect.service(RuleContext).pipe(Effect.map((ctx) => fn(ctx.sourceCode)));
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Source text
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * Get the source text for a node, optionally with surrounding characters.
42
+ *
43
+ * When called with no arguments, returns the entire source text.
44
+ * Pass an `Option<ESTree.Node>` to get text for a specific node.
45
+ *
46
+ * @since 0.2.0
47
+ */
48
+ export const getText: {
49
+ (): Effect.Effect<string, never, RuleContext>;
50
+ (
51
+ node: Option.Option<ESTree.Node>,
52
+ beforeCount?: number,
53
+ afterCount?: number
54
+ ): Effect.Effect<string, never, RuleContext>;
55
+ } = (
56
+ node?: Option.Option<ESTree.Node>,
57
+ beforeCount?: number,
58
+ afterCount?: number
59
+ ): Effect.Effect<string, never, RuleContext> =>
60
+ withSourceCode((sc) =>
61
+ sc.getText(
62
+ node === undefined
63
+ ? null
64
+ : Option.getOrElse(node, () => null as never),
65
+ beforeCount,
66
+ afterCount
67
+ )
68
+ );
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Ancestry
72
+ // ---------------------------------------------------------------------------
73
+
74
+ /**
75
+ * Get the ancestor nodes of the given node, from innermost to outermost.
76
+ *
77
+ * Note: oxlint types `getAncestors` as returning `Node[]` where `Node`
78
+ * is the base `Span` interface. At runtime these are full `ESTree.Node`
79
+ * values — the cast bridges this upstream type gap at the FFI boundary.
80
+ *
81
+ * @since 0.2.0
82
+ */
83
+ export const getAncestors = (
84
+ node: ESTree.Node
85
+ ): Effect.Effect<ReadonlyArray<ESTree.Node>, never, RuleContext> =>
86
+ withSourceCode(
87
+ // oxlint-ignore(casting-awareness,avoid-any): FFI boundary —
88
+ // oxlint's `Node` (Span) is structurally narrower than `ESTree.Node`
89
+ // but the runtime values are full ESTree nodes.
90
+ (sc) => sc.getAncestors(node) as unknown as ReadonlyArray<ESTree.Node>
91
+ );
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Location / Range
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Get the AST node that contains the given source offset.
99
+ *
100
+ * Returns `Option.none()` when no node spans that offset.
101
+ *
102
+ * @since 0.2.0
103
+ */
104
+ export const getNodeByRangeIndex = (
105
+ offset: number
106
+ ): Effect.Effect<Option.Option<ESTree.Node>, never, RuleContext> =>
107
+ withSourceCode((sc) =>
108
+ Option.fromNullishOr(sc.getNodeByRangeIndex(offset))
109
+ );
110
+
111
+ /**
112
+ * Convert a source offset (0-based) to a `{ line, column }` location.
113
+ *
114
+ * @since 0.2.0
115
+ */
116
+ export const getLocFromIndex = (
117
+ offset: number
118
+ ): Effect.Effect<LineColumn, never, RuleContext> =>
119
+ withSourceCode((sc) => sc.getLocFromIndex(offset));
120
+
121
+ /**
122
+ * Convert a `{ line, column }` location to a source offset (0-based).
123
+ *
124
+ * @since 0.2.0
125
+ */
126
+ export const getIndexFromLoc = (
127
+ loc: LineColumn
128
+ ): Effect.Effect<number, never, RuleContext> =>
129
+ withSourceCode((sc) => sc.getIndexFromLoc(loc));
130
+
131
+ /**
132
+ * Get the range `[start, end]` for a node or token.
133
+ *
134
+ * @since 0.2.0
135
+ */
136
+ export const getRange = (
137
+ nodeOrToken: ESTree.Node | Token | Comment
138
+ ): Effect.Effect<Range, never, RuleContext> =>
139
+ withSourceCode((sc) => sc.getRange(nodeOrToken));
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Token queries (single node)
143
+ // ---------------------------------------------------------------------------
144
+
145
+ /**
146
+ * Get the first token of a node.
147
+ *
148
+ * @since 0.2.0
149
+ */
150
+ export const getFirstToken = (
151
+ node: ESTree.Node
152
+ ): Effect.Effect<Option.Option<Token>, never, RuleContext> =>
153
+ withSourceCode((sc) => Option.fromNullishOr(sc.getFirstToken(node)));
154
+
155
+ /**
156
+ * Get the last token of a node.
157
+ *
158
+ * @since 0.2.0
159
+ */
160
+ export const getLastToken = (
161
+ node: ESTree.Node
162
+ ): Effect.Effect<Option.Option<Token>, never, RuleContext> =>
163
+ withSourceCode((sc) => Option.fromNullishOr(sc.getLastToken(node)));
164
+
165
+ /**
166
+ * Get all tokens for a node.
167
+ *
168
+ * @since 0.2.0
169
+ */
170
+ export const getTokens = (
171
+ node: ESTree.Node
172
+ ): Effect.Effect<ReadonlyArray<Token>, never, RuleContext> =>
173
+ withSourceCode((sc) => sc.getTokens(node));
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Token queries (before / after)
177
+ // ---------------------------------------------------------------------------
178
+
179
+ /**
180
+ * Get the token before a node or token.
181
+ *
182
+ * @since 0.2.0
183
+ */
184
+ export const getTokenBefore = (
185
+ nodeOrToken: ESTree.Node | Token | Comment
186
+ ): Effect.Effect<Option.Option<Token>, never, RuleContext> =>
187
+ withSourceCode((sc) =>
188
+ Option.fromNullishOr(sc.getTokenBefore(nodeOrToken))
189
+ );
190
+
191
+ /**
192
+ * Get the token after a node or token.
193
+ *
194
+ * @since 0.2.0
195
+ */
196
+ export const getTokenAfter = (
197
+ nodeOrToken: ESTree.Node | Token | Comment
198
+ ): Effect.Effect<Option.Option<Token>, never, RuleContext> =>
199
+ withSourceCode((sc) => Option.fromNullishOr(sc.getTokenAfter(nodeOrToken)));
200
+
201
+ /**
202
+ * Get all tokens between two nodes/tokens.
203
+ *
204
+ * @since 0.2.0
205
+ */
206
+ export const getTokensBetween = (
207
+ left: ESTree.Node | Token | Comment,
208
+ right: ESTree.Node | Token | Comment
209
+ ): Effect.Effect<ReadonlyArray<Token>, never, RuleContext> =>
210
+ withSourceCode((sc) => sc.getTokensBetween(left, right));
211
+
212
+ /**
213
+ * Get the first token between two nodes/tokens.
214
+ *
215
+ * @since 0.2.0
216
+ */
217
+ export const getFirstTokenBetween = (
218
+ left: ESTree.Node | Token | Comment,
219
+ right: ESTree.Node | Token | Comment
220
+ ): Effect.Effect<Option.Option<Token>, never, RuleContext> =>
221
+ withSourceCode((sc) =>
222
+ Option.fromNullishOr(sc.getFirstTokenBetween(left, right))
223
+ );
224
+
225
+ /**
226
+ * Find a token by its range start offset.
227
+ *
228
+ * @since 0.2.0
229
+ */
230
+ export const getTokenByRangeStart = (
231
+ offset: number
232
+ ): Effect.Effect<Option.Option<Token>, never, RuleContext> =>
233
+ withSourceCode((sc) =>
234
+ Option.fromNullishOr(sc.getTokenByRangeStart(offset))
235
+ );
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Comment queries
239
+ // ---------------------------------------------------------------------------
240
+
241
+ /**
242
+ * Get all comments in the file.
243
+ *
244
+ * @since 0.2.0
245
+ */
246
+ export const getAllComments = (): Effect.Effect<
247
+ ReadonlyArray<Comment>,
248
+ never,
249
+ RuleContext
250
+ > => withSourceCode((sc) => sc.getAllComments());
251
+
252
+ /**
253
+ * Get comments before a node or token.
254
+ *
255
+ * @since 0.2.0
256
+ */
257
+ export const getCommentsBefore = (
258
+ nodeOrToken: ESTree.Node | Token | Comment
259
+ ): Effect.Effect<ReadonlyArray<Comment>, never, RuleContext> =>
260
+ withSourceCode((sc) => sc.getCommentsBefore(nodeOrToken));
261
+
262
+ /**
263
+ * Get comments after a node or token.
264
+ *
265
+ * @since 0.2.0
266
+ */
267
+ export const getCommentsAfter = (
268
+ nodeOrToken: ESTree.Node | Token | Comment
269
+ ): Effect.Effect<ReadonlyArray<Comment>, never, RuleContext> =>
270
+ withSourceCode((sc) => sc.getCommentsAfter(nodeOrToken));
271
+
272
+ /**
273
+ * Get comments inside a node.
274
+ *
275
+ * @since 0.2.0
276
+ */
277
+ export const getCommentsInside = (
278
+ node: ESTree.Node
279
+ ): Effect.Effect<ReadonlyArray<Comment>, never, RuleContext> =>
280
+ withSourceCode((sc) => sc.getCommentsInside(node));
281
+
282
+ /**
283
+ * Check whether comments exist between two nodes/tokens.
284
+ *
285
+ * @since 0.2.0
286
+ */
287
+ export const commentsExistBetween = (
288
+ left: ESTree.Node | Token | Comment,
289
+ right: ESTree.Node | Token | Comment
290
+ ): Effect.Effect<boolean, never, RuleContext> =>
291
+ withSourceCode((sc) => sc.commentsExistBetween(left, right));
292
+
293
+ /**
294
+ * Get the JSDoc comment for a node.
295
+ *
296
+ * @deprecated Upstream deprecation. Use `getCommentsBefore` instead.
297
+ * @since 0.2.0
298
+ */
299
+ export const getJSDocComment = (
300
+ node: ESTree.Node
301
+ ): Effect.Effect<Option.Option<Comment>, never, RuleContext> =>
302
+ withSourceCode((sc) => Option.fromNullishOr(sc.getJSDocComment(node)));
303
+
304
+ // ---------------------------------------------------------------------------
305
+ // Scope queries (delegated to SourceCode)
306
+ // ---------------------------------------------------------------------------
307
+
308
+ /**
309
+ * Get the scope for a node.
310
+ *
311
+ * @since 0.2.0
312
+ */
313
+ export const getScope = (
314
+ node: ESTree.Node
315
+ ): Effect.Effect<Scope, never, RuleContext> =>
316
+ withSourceCode((sc) => sc.getScope(node));
317
+
318
+ /**
319
+ * Get declared variables for a node.
320
+ *
321
+ * @since 0.2.0
322
+ */
323
+ export const getDeclaredVariables = (
324
+ node: ESTree.Node
325
+ ): Effect.Effect<ReadonlyArray<Variable>, never, RuleContext> =>
326
+ withSourceCode((sc) => sc.getDeclaredVariables(node));
327
+
328
+ /**
329
+ * Check whether a node is a global reference.
330
+ *
331
+ * @since 0.2.0
332
+ */
333
+ export const isGlobalReference = (
334
+ node: ESTree.Node
335
+ ): Effect.Effect<boolean, never, RuleContext> =>
336
+ withSourceCode((sc) => sc.isGlobalReference(node));
337
+
338
+ /**
339
+ * Mark a variable as used in the scope of the given node.
340
+ *
341
+ * @since 0.2.0
342
+ */
343
+ export const markVariableAsUsed = (
344
+ name: string,
345
+ refNode?: ESTree.Node
346
+ ): Effect.Effect<boolean, never, RuleContext> =>
347
+ withSourceCode((sc) => sc.markVariableAsUsed(name, refNode));
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // Spacing
351
+ // ---------------------------------------------------------------------------
352
+
353
+ /**
354
+ * Check whether there is a space between two nodes/tokens.
355
+ *
356
+ * @since 0.2.0
357
+ */
358
+ export const isSpaceBetween = (
359
+ first: ESTree.Node | Token | Comment,
360
+ second: ESTree.Node | Token | Comment
361
+ ): Effect.Effect<boolean, never, RuleContext> =>
362
+ withSourceCode((sc) => sc.isSpaceBetween(first, second));
363
+
364
+ // ---------------------------------------------------------------------------
365
+ // Lines
366
+ // ---------------------------------------------------------------------------
367
+
368
+ /**
369
+ * Get all source lines.
370
+ *
371
+ * @since 0.2.0
372
+ */
373
+ export const getLines = (): Effect.Effect<
374
+ ReadonlyArray<string>,
375
+ never,
376
+ RuleContext
377
+ > => withSourceCode((sc) => sc.getLines());