@simbiat/eslint-plugin-simbiat 1.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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @file Rule: simbiat/require-super-first-in-constructor.
3
+ *
4
+ * Enforces that the constructor of a Custom Element class:
5
+ *
6
+ * 1. Has `super()` as its very first statement.
7
+ * 2. Calls `super()` with no arguments.
8
+ *
9
+ * Per the Custom Elements spec: "A parameter-less call to super() must be the first statement in the constructor body, to establish the correct prototype chain and this value before any further code is run."
10
+ *
11
+ * Only applies to classes that directly extend HTMLElement (or configured
12
+ * base classes). Does not recurse into nested classes.
13
+ *
14
+ * Options: baseClasses: string[] – additional class names to treat as HTMLElement. Defaults to ['HTMLElement'].
15
+ */
16
+ import type { Rule } from 'eslint';
17
+ declare const requireSuperFirstInConstructor: Rule.RuleModule;
18
+ export default requireSuperFirstInConstructor;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @file Rule: simbiat/require-super-first-in-constructor.
3
+ *
4
+ * Enforces that the constructor of a Custom Element class:
5
+ *
6
+ * 1. Has `super()` as its very first statement.
7
+ * 2. Calls `super()` with no arguments.
8
+ *
9
+ * Per the Custom Elements spec: "A parameter-less call to super() must be the first statement in the constructor body, to establish the correct prototype chain and this value before any further code is run."
10
+ *
11
+ * Only applies to classes that directly extend HTMLElement (or configured
12
+ * base classes). Does not recurse into nested classes.
13
+ *
14
+ * Options: baseClasses: string[] – additional class names to treat as HTMLElement. Defaults to ['HTMLElement'].
15
+ */
16
+ import { adaptNodeHandler } from '../utils/Adapters.mjs';
17
+ import { baseClassesSchema } from '../utils/CustomElementsScope.mjs';
18
+ // Helpers
19
+ /**
20
+ * Returns true if `stmt` is an expression statement containing a bare
21
+ * `super(…)` call (not `super.method(…)` or any other form).
22
+ * @param stmt - Statement node to test.
23
+ * @returns True when the statement is a bare super() call.
24
+ */
25
+ function isSuperCallStatement(stmt) {
26
+ return (stmt?.type === 'ExpressionStatement'
27
+ && stmt.expression?.type === 'CallExpression'
28
+ && stmt.expression.callee?.type === 'Super');
29
+ }
30
+ // Rule visitor handler
31
+ /**
32
+ * Checks a MethodDefinition node and reports if super() is not the first
33
+ * statement or is called with arguments in a Custom Element constructor.
34
+ * @param context - ESLint rule context for reporting.
35
+ * @param base_classes - Class names to treat as HTMLElement base classes.
36
+ * @param node - MethodDefinition node to inspect.
37
+ */
38
+ function checkMethodDefinition(context, base_classes, node) {
39
+ const method = node;
40
+ if (method.kind !== 'constructor') {
41
+ return;
42
+ }
43
+ // node → MethodDefinition, node.parent → ClassBody, node.parent.parent → Class
44
+ const class_node = method.parent?.parent;
45
+ if (!class_node) {
46
+ return;
47
+ }
48
+ const super_class = class_node.superClass;
49
+ if (super_class === null || typeof super_class === 'undefined') {
50
+ return;
51
+ }
52
+ if (super_class.type !== 'Identifier') {
53
+ return;
54
+ }
55
+ if (!base_classes.includes(super_class.name ?? '')) {
56
+ return;
57
+ }
58
+ const body = method.value.body.body;
59
+ // Check 1: super() must be the first statement.
60
+ if (!isSuperCallStatement(body[0])) {
61
+ context.report({
62
+ node,
63
+ messageId: 'missingSuperFirst',
64
+ });
65
+ return; // no point checking arguments if super() isn't first
66
+ }
67
+ // Check 2: super() must have no arguments.
68
+ const super_call = body[0]?.expression;
69
+ if ((super_call?.arguments.length ?? 0) > 0) {
70
+ context.report({
71
+ node: super_call,
72
+ messageId: 'superHasArguments',
73
+ });
74
+ }
75
+ }
76
+ // Rule visitor handler (top-level, adapted via adaptNodeHandler in `create`)
77
+ /**
78
+ * Top-level ESLint visitor for MethodDefinition nodes.
79
+ * @param state - Rule check state containing context and base classes.
80
+ * @param node - MethodDefinition node from ESLint.
81
+ */
82
+ function onMethodDefinition(state, node) {
83
+ checkMethodDefinition(state.context, state.base_classes, node);
84
+ }
85
+ // Rule definition
86
+ const requireSuperFirstInConstructor = {
87
+ meta: {
88
+ type: 'problem',
89
+ docs: {
90
+ description: 'Require a parameter-less super() as the first statement in Custom Element constructors.',
91
+ url: 'https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance',
92
+ },
93
+ messages: {
94
+ missingSuperFirst: 'The first statement in a Custom Element constructor must be a bare super() call.',
95
+ superHasArguments: 'super() in a Custom Element constructor must be called without arguments.',
96
+ },
97
+ schema: baseClassesSchema,
98
+ hasSuggestions: false,
99
+ },
100
+ /**
101
+ * Creates the rule listeners.
102
+ * @param context - ESLint rule context.
103
+ * @returns Rule listener object.
104
+ */
105
+ create(context) {
106
+ const options = context.options[0];
107
+ const base_classes = options?.baseClasses ?? ['HTMLElement'];
108
+ const check_state = {
109
+ context,
110
+ base_classes,
111
+ };
112
+ return {
113
+ MethodDefinition: adaptNodeHandler(check_state, onMethodDefinition),
114
+ };
115
+ },
116
+ };
117
+ export default requireSuperFirstInConstructor;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Rule: simbiat/require-type-parameter.
3
+ *
4
+ * Flags `querySelector`, `querySelectorAll`, and `closest` calls in TypeScript
5
+ * files that lack a type parameter, e.g. `querySelector<HTMLAnchorElement>('.link')`.
6
+ *
7
+ * Only activates on .ts / .tsx files; JS files are left alone.
8
+ * No auto-fix is provided: the correct type depends on the selector and must
9
+ * be supplied by the developer.
10
+ */
11
+ import type { Rule } from 'eslint';
12
+ declare const requireTypeParameter: Rule.RuleModule;
13
+ export default requireTypeParameter;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @file Rule: simbiat/require-type-parameter.
3
+ *
4
+ * Flags `querySelector`, `querySelectorAll`, and `closest` calls in TypeScript
5
+ * files that lack a type parameter, e.g. `querySelector<HTMLAnchorElement>('.link')`.
6
+ *
7
+ * Only activates on .ts / .tsx files; JS files are left alone.
8
+ * No auto-fix is provided: the correct type depends on the selector and must
9
+ * be supplied by the developer.
10
+ */
11
+ import { adaptNodeHandler } from '../utils/Adapters.mjs';
12
+ /**
13
+ * Top-level ESLint visitor for CallExpression nodes.
14
+ * Checks `querySelector`/`querySelectorAll`/`closest` calls for a missing type parameter.
15
+ * @param context - ESLint rule context.
16
+ * @param node - CallExpression node from ESLint.
17
+ */
18
+ function onCallExpression(context, node) {
19
+ const call = node;
20
+ if (call.callee.type !== 'MemberExpression') {
21
+ return;
22
+ }
23
+ const { property } = call.callee;
24
+ if (property.type !== 'Identifier') {
25
+ return;
26
+ }
27
+ if (property.name !== 'querySelector'
28
+ && property.name !== 'querySelectorAll'
29
+ && property.name !== 'closest') {
30
+ return;
31
+ }
32
+ // @typescript-eslint/parser attaches type arguments as either
33
+ // `typeParameters` (older versions) or `typeArguments` (v6+).
34
+ const has_type_arg = (call.typeParameters?.params.length ?? 0) > 0 || (call.typeArguments?.params.length ?? 0) > 0;
35
+ if (!has_type_arg) {
36
+ context.report({
37
+ node: property,
38
+ messageId: 'missingTypeParam',
39
+ data: { method: property.name },
40
+ });
41
+ }
42
+ }
43
+ const requireTypeParameter = {
44
+ meta: {
45
+ type: 'suggestion',
46
+ docs: {
47
+ description: 'Require a type parameter on querySelector / querySelectorAll / closest calls in TypeScript files.',
48
+ },
49
+ messages: {
50
+ missingTypeParam: 'Provide a type parameter to {{method}} to be more explicit and reduce casting.',
51
+ },
52
+ schema: [],
53
+ hasSuggestions: false,
54
+ },
55
+ /**
56
+ * Creates the rule listeners.
57
+ * @param context - ESLint rule context.
58
+ * @returns Rule listener object.
59
+ */
60
+ create(context) {
61
+ // Limit to TypeScript source files only.
62
+ const filename = context.filename ?? '';
63
+ if (!filename.endsWith('.ts') && !filename.endsWith('.tsx')) {
64
+ return {};
65
+ }
66
+ return {
67
+ CallExpression: adaptNodeHandler(context, onCallExpression),
68
+ };
69
+ },
70
+ };
71
+ export default requireTypeParameter;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @file AST helper utilities for the prefer-field-initializer and related rules.
3
+ */
4
+ /**
5
+ * Collects all declared field names (PropertyDefinition keys) from a class
6
+ * node's body into a `Set<string>`.
7
+ * @param class_node - ClassDeclaration or ClassExpression node.
8
+ * @returns Set of declared field name strings.
9
+ */
10
+ export declare function collectFieldNames(class_node: unknown): Set<string>;
11
+ /**
12
+ * Recursively collects all binding names introduced by a parameter or
13
+ * destructuring pattern node into `out`.
14
+ * Handles: Identifier, AssignmentPattern, RestElement, ObjectPattern,
15
+ * ArrayPattern, and TypeScript's TSParameterProperty.
16
+ * @param param - Parameter or pattern node to collect names from.
17
+ * @param out - Set to accumulate discovered binding names into.
18
+ */
19
+ export declare function collectParamNames(param: unknown, out: Set<string>): void;
20
+ /**
21
+ * Pre-scans a constructor body (BlockStatement) and collects the binding
22
+ * names of every VariableDeclarator into `out`, without descending into
23
+ * nested FunctionExpression / FunctionDeclaration / ArrowFunctionExpression
24
+ * nodes (those have their own scope and are irrelevant here).
25
+ *
26
+ * This is called once at constructor-entry time so that later
27
+ * `this.x = rhs` checks can suppress false positives when `rhs` references
28
+ * a locally declared variable rather than a constructor parameter.
29
+ * @param body_node - BlockStatement (the constructor body).
30
+ * @param out - Set to accumulate discovered local names into.
31
+ */
32
+ export declare function collectLocalNames(body_node: unknown, out: Set<string>): void;
33
+ /**
34
+ * Returns true if `node` (or any descendant) is a MemberExpression whose
35
+ * object is a ThisExpression, e.g. `this.foo`.
36
+ *
37
+ * Does NOT recurse into regular FunctionExpression / FunctionDeclaration
38
+ * because `this` is rebound there. DOES recurse into ArrowFunctionExpression
39
+ * because arrow functions inherit `this` lexically.
40
+ * @param node - AST node to inspect.
41
+ * @returns True if the node or a descendant accesses `this`.
42
+ */
43
+ export declare function containsThisAccess(node: unknown): boolean;
44
+ /**
45
+ * Returns true if `node` (or any descendant) contains an Identifier whose
46
+ * name is in `names`.
47
+ *
48
+ * Same `this`-rebinding rules as `containsThisAccess`.
49
+ * Stops at non-computed property keys in MemberExpression and Property to
50
+ * avoid false positives on `{ foo: bar }` or `obj.foo` where `foo` is in names.
51
+ * @param node - AST node to inspect.
52
+ * @param names - Set of identifier names to search for.
53
+ * @returns True if the node or a descendant references one of the given names.
54
+ */
55
+ export declare function containsIdentifierRef(node: unknown, names: Set<string>): boolean;
56
+ /**
57
+ * Human-readable stringification of a known external-target node.
58
+ * @param node - AST node representing the target.
59
+ * @param text - Fallback text when the target cannot be stringified.
60
+ * @returns Human-readable name for the target node.
61
+ */
62
+ export declare function targetName(node: unknown, text?: string): string;
63
+ /**
64
+ * Returns true for nodes that represent a "global" event-listener target:
65
+ * `document`, `window`, `document.body`, `document.documentElement`,
66
+ * `document.head`.
67
+ * @param node - AST node to test.
68
+ * @returns True when the node is a known external event-listener target.
69
+ */
70
+ export declare function isExternalTarget(node: unknown): boolean;
@@ -0,0 +1,275 @@
1
+ /**
2
+ * @file AST helper utilities for the prefer-field-initializer and related rules.
3
+ */
4
+ // Field / parameter name collection
5
+ /**
6
+ * Collects all declared field names (PropertyDefinition keys) from a class
7
+ * node's body into a `Set<string>`.
8
+ * @param class_node - ClassDeclaration or ClassExpression node.
9
+ * @returns Set of declared field name strings.
10
+ */
11
+ export function collectFieldNames(class_node) {
12
+ const node = class_node;
13
+ const names = new Set();
14
+ for (const member of node.body?.body ?? []) {
15
+ if (member.type !== 'PropertyDefinition') {
16
+ continue;
17
+ }
18
+ const key = member.key;
19
+ let name;
20
+ if (key?.type === 'Identifier' && typeof key.name === 'string') {
21
+ name = key.name;
22
+ }
23
+ else if (key?.type === 'Literal') {
24
+ name = String(key.value);
25
+ }
26
+ else {
27
+ name = null;
28
+ }
29
+ if (name !== null) {
30
+ names.add(name);
31
+ }
32
+ }
33
+ return names;
34
+ }
35
+ /**
36
+ * Recursively collects all binding names introduced by a parameter or
37
+ * destructuring pattern node into `out`.
38
+ * Handles: Identifier, AssignmentPattern, RestElement, ObjectPattern,
39
+ * ArrayPattern, and TypeScript's TSParameterProperty.
40
+ * @param param - Parameter or pattern node to collect names from.
41
+ * @param out - Set to accumulate discovered binding names into.
42
+ */
43
+ export function collectParamNames(param, out) {
44
+ if (param === null || typeof param === 'undefined' || typeof param !== 'object') {
45
+ return;
46
+ }
47
+ const p = param;
48
+ switch (p.type) {
49
+ case 'Identifier':
50
+ if (typeof p.name === 'string') {
51
+ out.add(p.name);
52
+ }
53
+ break;
54
+ case 'AssignmentPattern':
55
+ collectParamNames(p.left, out);
56
+ break;
57
+ case 'RestElement':
58
+ collectParamNames(p.argument, out);
59
+ break;
60
+ case 'ObjectPattern':
61
+ for (const prop of p.properties ?? []) {
62
+ collectParamNames(prop.type === 'RestElement' ? prop : prop.value, out);
63
+ }
64
+ break;
65
+ case 'ArrayPattern':
66
+ for (const el of p.elements ?? []) {
67
+ collectParamNames(el, out); // el may be null for holes
68
+ }
69
+ break;
70
+ case 'TSParameterProperty':
71
+ // TypeScript: constructor(private foo: string) – foo is both param and field.
72
+ collectParamNames(p.parameter, out);
73
+ break;
74
+ default:
75
+ break;
76
+ }
77
+ }
78
+ // AST traversal predicates
79
+ /** Keys that should never be traversed as child AST nodes. */
80
+ const SKIP_KEYS = new Set(['type', 'parent', 'loc', 'range', 'start', 'end']);
81
+ /**
82
+ * Recursively walks an AST node, collecting variable declarator binding names into `out`,
83
+ * without descending into nested function boundaries.
84
+ * @param node - AST node to walk.
85
+ * @param out - Set to accumulate discovered local variable names into.
86
+ */
87
+ function walkNode(node, out) {
88
+ if (node === null || typeof node === 'undefined' || typeof node !== 'object') {
89
+ return;
90
+ }
91
+ const n = node;
92
+ // Stop at nested-function boundaries – their locals are a different scope.
93
+ if (n.type === 'FunctionExpression'
94
+ || n.type === 'FunctionDeclaration'
95
+ || n.type === 'ArrowFunctionExpression') {
96
+ return;
97
+ }
98
+ if (n.type === 'VariableDeclarator') {
99
+ // id may be Identifier, ObjectPattern, ArrayPattern, etc.
100
+ collectParamNames(n.id, out);
101
+ // Don't walk the init expression – we only care about declared names.
102
+ return;
103
+ }
104
+ for (const key of Object.keys(n)) {
105
+ if (SKIP_KEYS.has(key)) {
106
+ continue;
107
+ }
108
+ // eslint-disable-next-line security/detect-object-injection
109
+ const val = n[key];
110
+ if (Array.isArray(val)) {
111
+ for (const child of val) {
112
+ walkNode(child, out);
113
+ }
114
+ }
115
+ else if (val !== null && typeof val === 'object' && typeof val.type === 'string') {
116
+ walkNode(val, out);
117
+ }
118
+ }
119
+ }
120
+ /**
121
+ * Pre-scans a constructor body (BlockStatement) and collects the binding
122
+ * names of every VariableDeclarator into `out`, without descending into
123
+ * nested FunctionExpression / FunctionDeclaration / ArrowFunctionExpression
124
+ * nodes (those have their own scope and are irrelevant here).
125
+ *
126
+ * This is called once at constructor-entry time so that later
127
+ * `this.x = rhs` checks can suppress false positives when `rhs` references
128
+ * a locally declared variable rather than a constructor parameter.
129
+ * @param body_node - BlockStatement (the constructor body).
130
+ * @param out - Set to accumulate discovered local names into.
131
+ */
132
+ export function collectLocalNames(body_node, out) {
133
+ walkNode(body_node, out);
134
+ }
135
+ /**
136
+ * Returns true if `node` (or any descendant) is a MemberExpression whose
137
+ * object is a ThisExpression, e.g. `this.foo`.
138
+ *
139
+ * Does NOT recurse into regular FunctionExpression / FunctionDeclaration
140
+ * because `this` is rebound there. DOES recurse into ArrowFunctionExpression
141
+ * because arrow functions inherit `this` lexically.
142
+ * @param node - AST node to inspect.
143
+ * @returns True if the node or a descendant accesses `this`.
144
+ */
145
+ export function containsThisAccess(node) {
146
+ if (node === null || typeof node === 'undefined' || typeof node !== 'object') {
147
+ return false;
148
+ }
149
+ const n = node;
150
+ // `this` is rebound in regular functions – stop recursing.
151
+ if (n.type === 'FunctionExpression' || n.type === 'FunctionDeclaration') {
152
+ return false;
153
+ }
154
+ if (n.type === 'MemberExpression'
155
+ && n.object?.type === 'ThisExpression') {
156
+ return true;
157
+ }
158
+ for (const key of Object.keys(n)) {
159
+ if (SKIP_KEYS.has(key)) {
160
+ continue;
161
+ }
162
+ // eslint-disable-next-line security/detect-object-injection
163
+ const val = n[key];
164
+ if (Array.isArray(val)) {
165
+ for (const child of val) {
166
+ if (containsThisAccess(child)) {
167
+ return true;
168
+ }
169
+ }
170
+ }
171
+ else if (val !== null
172
+ && typeof val === 'object'
173
+ && typeof val.type === 'string'
174
+ && containsThisAccess(val)) {
175
+ return true;
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+ /**
181
+ * Returns true if `node` (or any descendant) contains an Identifier whose
182
+ * name is in `names`.
183
+ *
184
+ * Same `this`-rebinding rules as `containsThisAccess`.
185
+ * Stops at non-computed property keys in MemberExpression and Property to
186
+ * avoid false positives on `{ foo: bar }` or `obj.foo` where `foo` is in names.
187
+ * @param node - AST node to inspect.
188
+ * @param names - Set of identifier names to search for.
189
+ * @returns True if the node or a descendant references one of the given names.
190
+ */
191
+ export function containsIdentifierRef(node, names) {
192
+ if (names.size === 0) {
193
+ return false;
194
+ }
195
+ if (node === null || typeof node === 'undefined' || typeof node !== 'object') {
196
+ return false;
197
+ }
198
+ const n = node;
199
+ if (n.type === 'FunctionExpression' || n.type === 'FunctionDeclaration') {
200
+ return false;
201
+ }
202
+ if (n.type === 'Identifier') {
203
+ return typeof n.name === 'string' && names.has(n.name);
204
+ }
205
+ for (const key of Object.keys(n)) {
206
+ if (SKIP_KEYS.has(key)) {
207
+ continue;
208
+ }
209
+ // Skip non-computed property keys to avoid treating `{ paramName: val }`
210
+ // or `obj.paramName` as a reference to the parameter.
211
+ if ((n.type === 'Property' && key === 'key' && n.computed !== true)
212
+ || (n.type === 'MemberExpression' && key === 'property' && n.computed !== true)) {
213
+ continue;
214
+ }
215
+ // eslint-disable-next-line security/detect-object-injection
216
+ const val = n[key];
217
+ if (Array.isArray(val)) {
218
+ for (const child of val) {
219
+ if (containsIdentifierRef(child, names)) {
220
+ return true;
221
+ }
222
+ }
223
+ }
224
+ else if (val !== null
225
+ && typeof val === 'object'
226
+ && typeof val.type === 'string'
227
+ && containsIdentifierRef(val, names)) {
228
+ return true;
229
+ }
230
+ }
231
+ return false;
232
+ }
233
+ /**
234
+ * Human-readable stringification of a known external-target node.
235
+ * @param node - AST node representing the target.
236
+ * @param text - Fallback text when the target cannot be stringified.
237
+ * @returns Human-readable name for the target node.
238
+ */
239
+ export function targetName(node, text = 'external target') {
240
+ const n = node;
241
+ if (n.type === 'Identifier') {
242
+ return typeof n.name === 'string' ? n.name : text;
243
+ }
244
+ if (n.type === 'MemberExpression' && n.computed !== true) {
245
+ const obj = n.object?.type === 'Identifier' && typeof (n.object).name === 'string'
246
+ ? String((n.object).name)
247
+ : '…';
248
+ const prop = n.property?.type === 'Identifier' && typeof (n.property).name === 'string'
249
+ ? String((n.property).name)
250
+ : '…';
251
+ return `${obj}.${prop}`;
252
+ }
253
+ return text;
254
+ }
255
+ /**
256
+ * Returns true for nodes that represent a "global" event-listener target:
257
+ * `document`, `window`, `document.body`, `document.documentElement`,
258
+ * `document.head`.
259
+ * @param node - AST node to test.
260
+ * @returns True when the node is a known external event-listener target.
261
+ */
262
+ export function isExternalTarget(node) {
263
+ const n = node;
264
+ if (n.type === 'Identifier') {
265
+ return n.name === 'document' || n.name === 'window';
266
+ }
267
+ if (n.type === 'MemberExpression'
268
+ && n.computed !== true
269
+ && n.object?.type === 'Identifier'
270
+ && (n.object).name === 'document'
271
+ && n.property?.type === 'Identifier') {
272
+ return ['body', 'documentElement', 'head'].includes(String((n.property).name));
273
+ }
274
+ return false;
275
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file Adapter utilities to wrap stateful handlers into ESLint-compatible visitor callbacks.
3
+ */
4
+ /**
5
+ * Wraps a handler that expects (state, node) into the single-argument
6
+ * signature ESLint passes to visitor callbacks.
7
+ * @template S - The state type.
8
+ * @param state - Rule state object to be threaded through.
9
+ * @param handler - Two-argument handler to wrap.
10
+ * @returns A single-argument visitor callback suitable for ESLint.
11
+ */
12
+ export declare function adaptNodeHandler<S>(state: S, handler: (state: S, node: unknown) => void): (node: unknown) => void;
13
+ /**
14
+ * Wraps a handler that expects only (state) into the no-argument
15
+ * signature ESLint passes to `:exit` visitor callbacks.
16
+ * @template S - The state type.
17
+ * @param state - Rule state object to be threaded through.
18
+ * @param handler - Single-argument handler to wrap.
19
+ * @returns A no-argument visitor callback suitable for ESLint.
20
+ */
21
+ export declare function adaptStateHandler<S>(state: S, handler: (state: S) => void): () => void;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file Adapter utilities to wrap stateful handlers into ESLint-compatible visitor callbacks.
3
+ */
4
+ /**
5
+ * Wraps a handler that expects (state, node) into the single-argument
6
+ * signature ESLint passes to visitor callbacks.
7
+ * @template S - The state type.
8
+ * @param state - Rule state object to be threaded through.
9
+ * @param handler - Two-argument handler to wrap.
10
+ * @returns A single-argument visitor callback suitable for ESLint.
11
+ */
12
+ export function adaptNodeHandler(state, handler) {
13
+ return function adaptedNodeHandler(node) {
14
+ handler(state, node);
15
+ };
16
+ }
17
+ /**
18
+ * Wraps a handler that expects only (state) into the no-argument
19
+ * signature ESLint passes to `:exit` visitor callbacks.
20
+ * @template S - The state type.
21
+ * @param state - Rule state object to be threaded through.
22
+ * @param handler - Single-argument handler to wrap.
23
+ * @returns A no-argument visitor callback suitable for ESLint.
24
+ */
25
+ export function adaptStateHandler(state, handler) {
26
+ return function adaptedStateHandler() {
27
+ handler(state);
28
+ };
29
+ }