@eslint-react/jsx 5.17.0 → 5.17.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.
- package/dist/index.d.ts +194 -209
- package/dist/index.js +723 -277
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -2,45 +2,482 @@ import { Traverse } from "@eslint-react/ast";
|
|
|
2
2
|
import { resolve } from "@eslint-react/var";
|
|
3
3
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
4
4
|
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
5
|
-
import { P, match } from "ts-pattern";
|
|
6
5
|
|
|
7
|
-
//#region
|
|
6
|
+
//#region ../../.pkgs/eff/dist/index.js
|
|
8
7
|
/**
|
|
9
|
-
*
|
|
8
|
+
* Applies a `pipe` method's variadic arguments to an initial value from left
|
|
9
|
+
* to right.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
11
|
+
* **When to use**
|
|
12
|
+
*
|
|
13
|
+
* Use to implement a custom `.pipe(...)` method from JavaScript's `arguments`
|
|
14
|
+
* object.
|
|
15
|
+
*
|
|
16
|
+
* **Details**
|
|
17
|
+
*
|
|
18
|
+
* This helper is intended for implementing `Pipeable.pipe` methods that
|
|
19
|
+
* receive JavaScript's `arguments` object. With no functions it returns the
|
|
20
|
+
* original value; otherwise it feeds each result into the next function.
|
|
21
|
+
*
|
|
22
|
+
* **Example** (Implementing a pipe method)
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { Pipeable } from "effect"
|
|
26
|
+
*
|
|
27
|
+
* class NumberBox {
|
|
28
|
+
* constructor(readonly value: number) {}
|
|
29
|
+
*
|
|
30
|
+
* pipe(..._fns: ReadonlyArray<(value: number) => number>): number {
|
|
31
|
+
* return Pipeable.pipeArguments(this.value, arguments) as number
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* const result = new NumberBox(5).pipe(
|
|
36
|
+
* (n) => n + 2,
|
|
37
|
+
* (n) => n * 3
|
|
38
|
+
* )
|
|
39
|
+
* console.log(result) // 21
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @category combinators
|
|
43
|
+
* @since 2.0.0
|
|
20
44
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
const pipeArguments = (self, args) => {
|
|
46
|
+
switch (args.length) {
|
|
47
|
+
case 0: return self;
|
|
48
|
+
case 1: return args[0](self);
|
|
49
|
+
case 2: return args[1](args[0](self));
|
|
50
|
+
case 3: return args[2](args[1](args[0](self)));
|
|
51
|
+
case 4: return args[3](args[2](args[1](args[0](self))));
|
|
52
|
+
case 5: return args[4](args[3](args[2](args[1](args[0](self)))));
|
|
53
|
+
case 6: return args[5](args[4](args[3](args[2](args[1](args[0](self))))));
|
|
54
|
+
case 7: return args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))));
|
|
55
|
+
case 8: return args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self))))))));
|
|
56
|
+
case 9: return args[8](args[7](args[6](args[5](args[4](args[3](args[2](args[1](args[0](self)))))))));
|
|
57
|
+
default: {
|
|
58
|
+
let ret = self;
|
|
59
|
+
for (let i = 0, len = args.length; i < len; i++) ret = args[i](ret);
|
|
60
|
+
return ret;
|
|
37
61
|
}
|
|
38
62
|
}
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Reusable prototype that implements `Pipeable.pipe`.
|
|
66
|
+
*
|
|
67
|
+
* **When to use**
|
|
68
|
+
*
|
|
69
|
+
* Use when classes or object prototypes can reuse this value when they need the
|
|
70
|
+
* standard pipe implementation backed by `pipeArguments`.
|
|
71
|
+
*
|
|
72
|
+
* @category prototypes
|
|
73
|
+
* @since 3.15.0
|
|
74
|
+
*/
|
|
75
|
+
const Prototype = { pipe() {
|
|
76
|
+
return pipeArguments(this, arguments);
|
|
77
|
+
} };
|
|
78
|
+
/**
|
|
79
|
+
* Provides a base constructor whose instances implement the standard `Pipeable.pipe`
|
|
80
|
+
* method.
|
|
81
|
+
*
|
|
82
|
+
* **When to use**
|
|
83
|
+
*
|
|
84
|
+
* Use when you need to define a class that supports Effect-style method
|
|
85
|
+
* chaining through `.pipe(...)`.
|
|
86
|
+
*
|
|
87
|
+
* @category constructors
|
|
88
|
+
* @since 3.15.0
|
|
89
|
+
*/
|
|
90
|
+
const Class = (function() {
|
|
91
|
+
function PipeableBase() {}
|
|
92
|
+
PipeableBase.prototype = Prototype;
|
|
93
|
+
return PipeableBase;
|
|
94
|
+
})();
|
|
95
|
+
/**
|
|
96
|
+
* Provides small helpers for defining and reusing TypeScript functions.
|
|
97
|
+
*
|
|
98
|
+
* The main helpers are `pipe` and `flow` for left-to-right composition and
|
|
99
|
+
* `dual` for APIs that support both direct and pipe-friendly call styles. The
|
|
100
|
+
* module also contains small identity, constant, tuple, type-level, and
|
|
101
|
+
* memoization helpers used across the library.
|
|
102
|
+
*
|
|
103
|
+
* @since 2.0.0
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* Creates a function that can be called in data-first style or data-last
|
|
107
|
+
* (`pipe`-friendly) style.
|
|
108
|
+
*
|
|
109
|
+
* **When to use**
|
|
110
|
+
*
|
|
111
|
+
* Use to expose one implementation through both direct and `pipe`-friendly
|
|
112
|
+
* call styles.
|
|
113
|
+
*
|
|
114
|
+
* **Details**
|
|
115
|
+
*
|
|
116
|
+
* Pass either the arity of the uncurried function or a predicate that decides
|
|
117
|
+
* whether the current call is data-first. Arity is the common case. Use a
|
|
118
|
+
* predicate when optional arguments make arity ambiguous.
|
|
119
|
+
*
|
|
120
|
+
* **Example** (Selecting data-first or data-last style by arity)
|
|
121
|
+
*
|
|
122
|
+
* ```ts
|
|
123
|
+
* import { Function, pipe } from "effect"
|
|
124
|
+
*
|
|
125
|
+
* const sum = Function.dual<
|
|
126
|
+
* (that: number) => (self: number) => number,
|
|
127
|
+
* (self: number, that: number) => number
|
|
128
|
+
* >(2, (self, that) => self + that)
|
|
129
|
+
*
|
|
130
|
+
* console.log(sum(2, 3)) // 5
|
|
131
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* **Example** (Defining overloads with call signatures)
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { Function, pipe } from "effect"
|
|
138
|
+
*
|
|
139
|
+
* const sum: {
|
|
140
|
+
* (that: number): (self: number) => number
|
|
141
|
+
* (self: number, that: number): number
|
|
142
|
+
* } = Function.dual(2, (self: number, that: number): number => self + that)
|
|
143
|
+
*
|
|
144
|
+
* console.log(sum(2, 3)) // 5
|
|
145
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* **Example** (Selecting data-first or data-last style with a predicate)
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* import { Function, pipe } from "effect"
|
|
152
|
+
*
|
|
153
|
+
* const sum = Function.dual<
|
|
154
|
+
* (that: number) => (self: number) => number,
|
|
155
|
+
* (self: number, that: number) => number
|
|
156
|
+
* >(
|
|
157
|
+
* (args) => args.length === 2,
|
|
158
|
+
* (self, that) => self + that
|
|
159
|
+
* )
|
|
160
|
+
*
|
|
161
|
+
* console.log(sum(2, 3)) // 5
|
|
162
|
+
* console.log(pipe(2, sum(3))) // 5
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @category combinators
|
|
166
|
+
* @since 2.0.0
|
|
167
|
+
*/
|
|
168
|
+
const dual = function(arity, body) {
|
|
169
|
+
if (typeof arity === "function") return function() {
|
|
170
|
+
return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
|
|
171
|
+
};
|
|
172
|
+
switch (arity) {
|
|
173
|
+
case 0:
|
|
174
|
+
case 1: throw new RangeError(`Invalid arity ${arity}`);
|
|
175
|
+
case 2: return function(a, b) {
|
|
176
|
+
if (arguments.length >= 2) return body(a, b);
|
|
177
|
+
return function(self) {
|
|
178
|
+
return body(self, a);
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
case 3: return function(a, b, c) {
|
|
182
|
+
if (arguments.length >= 3) return body(a, b, c);
|
|
183
|
+
return function(self) {
|
|
184
|
+
return body(self, a, b);
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
default: return function() {
|
|
188
|
+
if (arguments.length >= arity) return body.apply(this, arguments);
|
|
189
|
+
const args = arguments;
|
|
190
|
+
return function(self) {
|
|
191
|
+
return body(self, ...args);
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Returns its input argument unchanged.
|
|
198
|
+
*
|
|
199
|
+
* **When to use**
|
|
200
|
+
*
|
|
201
|
+
* Use to return a value unchanged where a function is required.
|
|
202
|
+
*
|
|
203
|
+
* **Example** (Returning the same value)
|
|
204
|
+
*
|
|
205
|
+
* ```ts
|
|
206
|
+
* import { identity } from "effect"
|
|
207
|
+
* import * as assert from "node:assert"
|
|
208
|
+
*
|
|
209
|
+
* assert.deepStrictEqual(identity(5), 5)
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* @category combinators
|
|
213
|
+
* @since 2.0.0
|
|
214
|
+
*/
|
|
215
|
+
const identity = (a) => a;
|
|
216
|
+
/**
|
|
217
|
+
* Returns the input value with a different static type.
|
|
218
|
+
*
|
|
219
|
+
* **When to use**
|
|
220
|
+
*
|
|
221
|
+
* Use when you need an explicit type-level cast and accept that the value is
|
|
222
|
+
* returned unchanged at runtime.
|
|
223
|
+
*
|
|
224
|
+
* **Gotchas**
|
|
225
|
+
*
|
|
226
|
+
* This is a type-level cast only; it performs no runtime validation or
|
|
227
|
+
* conversion.
|
|
228
|
+
*
|
|
229
|
+
* @see {@link satisfies} for checking assignability without changing the resulting type
|
|
230
|
+
*
|
|
231
|
+
* @category utility types
|
|
232
|
+
* @since 4.0.0
|
|
233
|
+
*/
|
|
234
|
+
const cast = identity;
|
|
235
|
+
/**
|
|
236
|
+
* Creates a zero-argument function that always returns the provided value.
|
|
237
|
+
*
|
|
238
|
+
* **When to use**
|
|
239
|
+
*
|
|
240
|
+
* Use when you need a thunk or callback that returns the same value on every
|
|
241
|
+
* invocation.
|
|
242
|
+
*
|
|
243
|
+
* **Example** (Creating a constant thunk)
|
|
244
|
+
*
|
|
245
|
+
* ```ts
|
|
246
|
+
* import { Function } from "effect"
|
|
247
|
+
* import * as assert from "node:assert"
|
|
248
|
+
*
|
|
249
|
+
* const constNull = Function.constant(null)
|
|
250
|
+
*
|
|
251
|
+
* assert.deepStrictEqual(constNull(), null)
|
|
252
|
+
* assert.deepStrictEqual(constNull(), null)
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* @category constructors
|
|
256
|
+
* @since 2.0.0
|
|
257
|
+
*/
|
|
258
|
+
const constant = (value) => () => value;
|
|
259
|
+
/**
|
|
260
|
+
* Returns `true` when called.
|
|
261
|
+
*
|
|
262
|
+
* **When to use**
|
|
263
|
+
*
|
|
264
|
+
* Use when you need a thunk that returns `true` on every invocation.
|
|
265
|
+
*
|
|
266
|
+
* **Example** (Returning true from a thunk)
|
|
267
|
+
*
|
|
268
|
+
* ```ts
|
|
269
|
+
* import { Function } from "effect"
|
|
270
|
+
* import * as assert from "node:assert"
|
|
271
|
+
*
|
|
272
|
+
* assert.deepStrictEqual(Function.constTrue(), true)
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* @category constants
|
|
276
|
+
* @since 2.0.0
|
|
277
|
+
*/
|
|
278
|
+
const constTrue = constant(true);
|
|
279
|
+
/**
|
|
280
|
+
* Returns `false` when called.
|
|
281
|
+
*
|
|
282
|
+
* **When to use**
|
|
283
|
+
*
|
|
284
|
+
* Use when you need a thunk that returns `false` on every invocation.
|
|
285
|
+
*
|
|
286
|
+
* **Example** (Returning false from a thunk)
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* import { Function } from "effect"
|
|
290
|
+
* import * as assert from "node:assert"
|
|
291
|
+
*
|
|
292
|
+
* assert.deepStrictEqual(Function.constFalse(), false)
|
|
293
|
+
* ```
|
|
294
|
+
*
|
|
295
|
+
* @category constants
|
|
296
|
+
* @since 2.0.0
|
|
297
|
+
*/
|
|
298
|
+
const constFalse = constant(false);
|
|
299
|
+
/**
|
|
300
|
+
* Returns `null` when called.
|
|
301
|
+
*
|
|
302
|
+
* **When to use**
|
|
303
|
+
*
|
|
304
|
+
* Use when you need a thunk that returns `null` on every invocation.
|
|
305
|
+
*
|
|
306
|
+
* **Example** (Returning null from a thunk)
|
|
307
|
+
*
|
|
308
|
+
* ```ts
|
|
309
|
+
* import { Function } from "effect"
|
|
310
|
+
* import * as assert from "node:assert"
|
|
311
|
+
*
|
|
312
|
+
* assert.deepStrictEqual(Function.constNull(), null)
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* @category constants
|
|
316
|
+
* @since 2.0.0
|
|
317
|
+
*/
|
|
318
|
+
const constNull = constant(null);
|
|
319
|
+
/**
|
|
320
|
+
* Returns `undefined` when called.
|
|
321
|
+
*
|
|
322
|
+
* **When to use**
|
|
323
|
+
*
|
|
324
|
+
* Use when you need a thunk that returns `undefined` on every invocation.
|
|
325
|
+
*
|
|
326
|
+
* **Example** (Returning undefined from a thunk)
|
|
327
|
+
*
|
|
328
|
+
* ```ts
|
|
329
|
+
* import { Function } from "effect"
|
|
330
|
+
* import * as assert from "node:assert"
|
|
331
|
+
*
|
|
332
|
+
* assert.deepStrictEqual(Function.constUndefined(), undefined)
|
|
333
|
+
* ```
|
|
334
|
+
*
|
|
335
|
+
* @category constants
|
|
336
|
+
* @since 2.0.0
|
|
337
|
+
*/
|
|
338
|
+
const constUndefined = constant(void 0);
|
|
339
|
+
/**
|
|
340
|
+
* Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
|
|
341
|
+
* The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
|
|
342
|
+
*
|
|
343
|
+
* **When to use**
|
|
344
|
+
*
|
|
345
|
+
* Use to compose exactly two unary functions into a reusable unary function.
|
|
346
|
+
*
|
|
347
|
+
* **Example** (Composing two functions)
|
|
348
|
+
*
|
|
349
|
+
* ```ts
|
|
350
|
+
* import { Function } from "effect"
|
|
351
|
+
* import * as assert from "node:assert"
|
|
352
|
+
*
|
|
353
|
+
* const increment = (n: number) => n + 1
|
|
354
|
+
* const square = (n: number) => n * n
|
|
355
|
+
*
|
|
356
|
+
* assert.strictEqual(Function.compose(increment, square)(2), 9)
|
|
357
|
+
* ```
|
|
358
|
+
*
|
|
359
|
+
* @see {@link flow} for composing a left-to-right sequence of functions
|
|
360
|
+
* @see {@link pipe} for applying a value through a left-to-right sequence immediately
|
|
361
|
+
*
|
|
362
|
+
* @category combinators
|
|
363
|
+
* @since 2.0.0
|
|
364
|
+
*/
|
|
365
|
+
const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
|
|
366
|
+
/**
|
|
367
|
+
* Marks an impossible branch by accepting a `never` value and returning any
|
|
368
|
+
* type.
|
|
369
|
+
*
|
|
370
|
+
* **When to use**
|
|
371
|
+
*
|
|
372
|
+
* Use when you need a return value in a branch that exhaustive checks prove
|
|
373
|
+
* cannot be reached.
|
|
374
|
+
*
|
|
375
|
+
* **Gotchas**
|
|
376
|
+
*
|
|
377
|
+
* Calling `absurd` throws, because a value of type `never` should be
|
|
378
|
+
* impossible at runtime.
|
|
379
|
+
*
|
|
380
|
+
* **Example** (Handling impossible values)
|
|
381
|
+
*
|
|
382
|
+
* ```ts
|
|
383
|
+
* import { absurd } from "effect"
|
|
384
|
+
*
|
|
385
|
+
* const handleNever = (value: never) => {
|
|
386
|
+
* return absurd(value) // This will throw an error if called
|
|
387
|
+
* }
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* @category utility types
|
|
391
|
+
* @since 2.0.0
|
|
392
|
+
*/
|
|
393
|
+
const absurd = (_) => {
|
|
394
|
+
throw new Error("Called `absurd` function which should be uncallable");
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* Creates a compile-time placeholder for a value of any type.
|
|
398
|
+
*
|
|
399
|
+
* **When to use**
|
|
400
|
+
*
|
|
401
|
+
* Use as a temporary typed placeholder while developing incomplete code.
|
|
402
|
+
*
|
|
403
|
+
* **Gotchas**
|
|
404
|
+
*
|
|
405
|
+
* `hole` is intended for temporary development use. If the placeholder is
|
|
406
|
+
* evaluated at runtime, it throws.
|
|
407
|
+
*
|
|
408
|
+
* **Example** (Creating a development placeholder)
|
|
409
|
+
*
|
|
410
|
+
* ```ts
|
|
411
|
+
* import { hole } from "effect"
|
|
412
|
+
*
|
|
413
|
+
* // Intentionally not called: `hole` throws if the placeholder is evaluated.
|
|
414
|
+
* const buildUser = (id: number): { readonly id: number; readonly name: string } => ({
|
|
415
|
+
* id,
|
|
416
|
+
* name: hole<string>()
|
|
417
|
+
* })
|
|
418
|
+
*
|
|
419
|
+
* console.log(typeof buildUser) // "function"
|
|
420
|
+
* ```
|
|
421
|
+
*
|
|
422
|
+
* @category utility types
|
|
423
|
+
* @since 2.0.0
|
|
424
|
+
*/
|
|
425
|
+
const hole = cast(absurd);
|
|
426
|
+
/**
|
|
427
|
+
* Drops the longest prefix of elements from an array that satisfy the given predicate.
|
|
428
|
+
*
|
|
429
|
+
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
430
|
+
*
|
|
431
|
+
* @param pred - The predicate to test each element with.
|
|
432
|
+
* @returns A new array without the matching prefix.
|
|
433
|
+
* @example
|
|
434
|
+
* ```ts
|
|
435
|
+
* import * as assert from "node:assert"
|
|
436
|
+
* import { dropWhile, pipe } from "@local/eff"
|
|
437
|
+
*
|
|
438
|
+
* // data-first
|
|
439
|
+
* assert.deepStrictEqual(dropWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [3, 2, 1])
|
|
440
|
+
*
|
|
441
|
+
* // data-last
|
|
442
|
+
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], dropWhile((n: number) => n < 3)), [3, 2, 1])
|
|
443
|
+
* ```
|
|
444
|
+
* @category array
|
|
445
|
+
*/
|
|
446
|
+
const dropWhile = dual(2, (xs, pred) => {
|
|
447
|
+
const len = xs.length;
|
|
448
|
+
let idx = 0;
|
|
449
|
+
while (idx < len && pred(xs[idx])) idx++;
|
|
450
|
+
return xs.slice(idx);
|
|
451
|
+
});
|
|
452
|
+
/**
|
|
453
|
+
* Takes the longest prefix of elements from an array that satisfy the given predicate.
|
|
454
|
+
*
|
|
455
|
+
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
456
|
+
*
|
|
457
|
+
* @param pred - The predicate to test each element with.
|
|
458
|
+
* @returns A new array containing only the matching prefix.
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* import * as assert from "node:assert"
|
|
462
|
+
* import { pipe, takeWhile } from "@local/eff"
|
|
463
|
+
*
|
|
464
|
+
* // data-first
|
|
465
|
+
* assert.deepStrictEqual(takeWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [1, 2])
|
|
466
|
+
*
|
|
467
|
+
* // data-last
|
|
468
|
+
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], takeWhile((n: number) => n < 3)), [1, 2])
|
|
469
|
+
* ```
|
|
470
|
+
* @category array
|
|
471
|
+
*/
|
|
472
|
+
const takeWhile = dual(2, (xs, pred) => {
|
|
473
|
+
const len = xs.length;
|
|
474
|
+
let idx = 0;
|
|
475
|
+
while (idx < len && pred(xs[idx])) idx++;
|
|
476
|
+
return xs.slice(0, idx);
|
|
477
|
+
});
|
|
41
478
|
|
|
42
479
|
//#endregion
|
|
43
|
-
//#region src/
|
|
480
|
+
//#region src/attribute-name.ts
|
|
44
481
|
/**
|
|
45
482
|
* Get the stringified name of a `JSXAttribute` node.
|
|
46
483
|
*
|
|
@@ -55,9 +492,25 @@ function getAttributeName(node) {
|
|
|
55
492
|
if (node.name.type === AST_NODE_TYPES.JSXIdentifier) return node.name.name;
|
|
56
493
|
return node.name.namespace.name + ":" + node.name.name.name;
|
|
57
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Check whether a node is a `JSXAttribute` with the given name.
|
|
497
|
+
*
|
|
498
|
+
* Only plain identifier names are matched (ex: `className`); namespaced
|
|
499
|
+
* attributes (ex: `xml:space`) do not match.
|
|
500
|
+
*
|
|
501
|
+
* Supports both data-first and data-last (curried) call styles:
|
|
502
|
+
* - `isAttribute(node, "className")`
|
|
503
|
+
* - `isAttribute("className")(node)`.
|
|
504
|
+
* @param node The AST node to test.
|
|
505
|
+
* @param name The attribute name to match (ex: "className").
|
|
506
|
+
* @returns `true` when the node is a `JSXAttribute` named `name`.
|
|
507
|
+
*/
|
|
508
|
+
const isAttribute = dual(2, (node, name) => {
|
|
509
|
+
return node.type === AST_NODE_TYPES.JSXAttribute && node.name.type === AST_NODE_TYPES.JSXIdentifier && node.name.name === name;
|
|
510
|
+
});
|
|
58
511
|
|
|
59
512
|
//#endregion
|
|
60
|
-
//#region src/find
|
|
513
|
+
//#region src/attribute-find.ts
|
|
61
514
|
/**
|
|
62
515
|
* Find a JSX attribute (or spread attribute containing the property) by name on a given element.
|
|
63
516
|
*
|
|
@@ -66,53 +519,19 @@ function getAttributeName(node) {
|
|
|
66
519
|
*
|
|
67
520
|
* Spread attributes are resolved when possible: if the spread argument is an identifier
|
|
68
521
|
* that resolves to an object expression, the object's properties are searched for a matching key.
|
|
69
|
-
* Nested object expressions and nested spread identifiers are also resolved
|
|
522
|
+
* Nested object expressions and nested spread identifiers are also resolved
|
|
523
|
+
* (see {@link findSpreadProperty}).
|
|
70
524
|
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
71
525
|
* @param element The `JSXElement` node to search.
|
|
72
526
|
* @param name The attribute name to look for (ex: "className").
|
|
73
527
|
* @returns The matching `JSXAttribute` or `JSXSpreadAttribute`, or `undefined` when not found.
|
|
74
528
|
*/
|
|
75
529
|
function findAttribute(context, element, name) {
|
|
76
|
-
function findProperty(properties, name, seen = /* @__PURE__ */ new Set()) {
|
|
77
|
-
for (const property of properties) {
|
|
78
|
-
if (property.type === AST_NODE_TYPES.Property && !property.computed && property.key.type === AST_NODE_TYPES.Identifier && property.key.name === name) return property;
|
|
79
|
-
if (property.type !== AST_NODE_TYPES.SpreadElement) continue;
|
|
80
|
-
const argument = property.argument;
|
|
81
|
-
if (argument.type === AST_NODE_TYPES.Identifier) {
|
|
82
|
-
const initNode = resolve(context, argument);
|
|
83
|
-
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) {
|
|
84
|
-
if (seen.has(initNode)) continue;
|
|
85
|
-
seen.add(initNode);
|
|
86
|
-
const found = findProperty(initNode.properties, name, seen);
|
|
87
|
-
if (found != null) return found;
|
|
88
|
-
}
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (argument.type === AST_NODE_TYPES.ObjectExpression) {
|
|
92
|
-
if (seen.has(argument)) continue;
|
|
93
|
-
seen.add(argument);
|
|
94
|
-
const found = findProperty(argument.properties, name, seen);
|
|
95
|
-
if (found != null) return found;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
530
|
return element.openingElement.attributes.findLast((attr) => {
|
|
101
531
|
if (attr.type === AST_NODE_TYPES.JSXAttribute) return getAttributeName(attr) === name;
|
|
102
|
-
|
|
103
|
-
case AST_NODE_TYPES.Identifier: {
|
|
104
|
-
const initNode = resolve(context, attr.argument);
|
|
105
|
-
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return findProperty(initNode.properties, name) != null;
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
case AST_NODE_TYPES.ObjectExpression: return findProperty(attr.argument.properties, name) != null;
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
532
|
+
return findSpreadProperty(context, attr.argument, name) != null;
|
|
111
533
|
});
|
|
112
534
|
}
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
//#region src/find-parent-attribute.ts
|
|
116
535
|
/**
|
|
117
536
|
* Walk up the AST from `node` to find the nearest ancestor that is a `JSXAttribute`
|
|
118
537
|
* and (optionally) passes a predicate.
|
|
@@ -121,31 +540,173 @@ function findAttribute(context, element, name) {
|
|
|
121
540
|
* inside an expression container) and needs to know which JSX attribute it belongs to.
|
|
122
541
|
* @param node The starting node for the upward search.
|
|
123
542
|
* @param test Optional predicate to filter candidate `JSXAttribute` nodes. When omitted every `JSXAttribute` ancestor matches.
|
|
124
|
-
* @returns The first matching `JSXAttribute` ancestor, or `
|
|
543
|
+
* @returns The first matching `JSXAttribute` ancestor, or `undefined` if none is found before reaching the root.
|
|
125
544
|
*/
|
|
126
545
|
function findParentAttribute(node, test = () => true) {
|
|
127
546
|
const guard = (n) => {
|
|
128
547
|
return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
|
|
129
548
|
};
|
|
130
|
-
return Traverse.findParent(node, guard);
|
|
549
|
+
return Traverse.findParent(node, guard) ?? void 0;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Find the `Property` node that provides a given key inside a spread argument.
|
|
553
|
+
*
|
|
554
|
+
* This is the single resolution routine shared by {@link findAttribute} (existence
|
|
555
|
+
* checks) and the `spreadProps` variant of `resolveAttributeValue` (value extraction):
|
|
556
|
+
*
|
|
557
|
+
* - An `Identifier` argument is resolved to its initializer via variable
|
|
558
|
+
* resolution, following alias chains (`const b = a`) like `getStaticValue`
|
|
559
|
+
* does; an `ObjectExpression` argument is searched directly.
|
|
560
|
+
* - Properties are walked **in reverse** so that later entries win, matching
|
|
561
|
+
* JavaScript object semantics (`{ ...a, k: 1 }` -> the literal `k`).
|
|
562
|
+
* - Nested `SpreadElement`s (identifiers or inline object expressions) are
|
|
563
|
+
* searched recursively; a `seen` set guards against circular references.
|
|
564
|
+
* - Plain identifier keys and string literal keys are matched directly;
|
|
565
|
+
* computed keys are matched when they are statically evaluable
|
|
566
|
+
* (ex: `{ ["class" + "Name"]: 1 }`).
|
|
567
|
+
* @param context The ESLint rule context (needed for variable resolution).
|
|
568
|
+
* @param argument The spread argument expression to search.
|
|
569
|
+
* @param name The property name to look for.
|
|
570
|
+
* @param seen Internal set of already-visited nodes (cycle guard).
|
|
571
|
+
* @returns The matching `Property` node, or `undefined` when the key is not found.
|
|
572
|
+
*/
|
|
573
|
+
function findSpreadProperty(context, argument, name, seen = /* @__PURE__ */ new Set()) {
|
|
574
|
+
let objectExpression;
|
|
575
|
+
if (argument.type === AST_NODE_TYPES.Identifier) {
|
|
576
|
+
let initNode = resolve(context, argument);
|
|
577
|
+
while (initNode != null && initNode.type === AST_NODE_TYPES.Identifier && !seen.has(initNode)) {
|
|
578
|
+
seen.add(initNode);
|
|
579
|
+
initNode = resolve(context, initNode);
|
|
580
|
+
}
|
|
581
|
+
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) objectExpression = initNode;
|
|
582
|
+
} else if (argument.type === AST_NODE_TYPES.ObjectExpression) objectExpression = argument;
|
|
583
|
+
if (objectExpression == null || seen.has(objectExpression)) return void 0;
|
|
584
|
+
seen.add(objectExpression);
|
|
585
|
+
const { properties } = objectExpression;
|
|
586
|
+
for (let i = properties.length - 1; i >= 0; i--) {
|
|
587
|
+
const property = properties[i];
|
|
588
|
+
if (property == null) continue;
|
|
589
|
+
if (property.type === AST_NODE_TYPES.Property) {
|
|
590
|
+
const { key } = property;
|
|
591
|
+
if (property.computed) {
|
|
592
|
+
if (getStaticValue(key, context.sourceCode.getScope(key))?.value === name) return property;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (key.type === AST_NODE_TYPES.Identifier && key.name === name) return property;
|
|
596
|
+
if (key.type === AST_NODE_TYPES.Literal && key.value === name) return property;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const found = findSpreadProperty(context, property.argument, name, seen);
|
|
600
|
+
if (found != null) return found;
|
|
601
|
+
}
|
|
131
602
|
}
|
|
132
603
|
|
|
133
604
|
//#endregion
|
|
134
|
-
//#region src/
|
|
605
|
+
//#region src/attribute-has.ts
|
|
135
606
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
607
|
+
* Check whether a JSX element carries a given attribute (prop).
|
|
608
|
+
*
|
|
609
|
+
* This is a thin convenience wrapper around {@link findAttribute} for the
|
|
610
|
+
* common case where you only need a boolean answer.
|
|
611
|
+
*
|
|
612
|
+
* Spread attributes are taken into account: `<Comp {...{ disabled: true }} />`
|
|
613
|
+
* will report `true` for `"disabled"`.
|
|
614
|
+
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
615
|
+
* @param element The `JSXElement` node to inspect.
|
|
616
|
+
* @param name The attribute name to look for (ex: "className").
|
|
617
|
+
* @returns `true` when the attribute is present on the element.
|
|
618
|
+
*/
|
|
619
|
+
function hasAttribute(context, element, name) {
|
|
620
|
+
return findAttribute(context, element, name) != null;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Check whether a JSX element carries at least one of the given attributes.
|
|
624
|
+
*
|
|
625
|
+
* This is a batch variant of {@link hasAttribute} for the common pattern of
|
|
626
|
+
* short-circuiting on multiple prop names.
|
|
627
|
+
*
|
|
628
|
+
* Spread attributes are taken into account (see {@link findAttribute}).
|
|
629
|
+
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
630
|
+
* @param element The `JSXElement` node to inspect.
|
|
631
|
+
* @param names The attribute names to look for.
|
|
632
|
+
* @returns `true` when at least one of the attributes is present.
|
|
633
|
+
*/
|
|
634
|
+
function hasAnyAttribute(context, element, names) {
|
|
635
|
+
return names.some((name) => findAttribute(context, element, name) != null);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Check whether a JSX element carries all of the given attributes (props).
|
|
639
|
+
*
|
|
640
|
+
* This is a batch variant of {@link hasAttribute} for the common pattern
|
|
641
|
+
* where a rule needs to verify that a set of required props are all present.
|
|
642
|
+
*
|
|
643
|
+
* Spread attributes are taken into account (see {@link findAttribute}).
|
|
644
|
+
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
645
|
+
* @param element The `JSXElement` node to inspect.
|
|
646
|
+
* @param names The attribute names to look for.
|
|
647
|
+
* @returns `true` when every name in `names` is present on the element.
|
|
648
|
+
*/
|
|
649
|
+
function hasEveryAttribute(context, element, names) {
|
|
650
|
+
return names.every((name) => findAttribute(context, element, name) != null);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/attribute-value.ts
|
|
655
|
+
/**
|
|
656
|
+
* Resolve the value of a JSX attribute (or spread attribute) into an
|
|
657
|
+
* {@link AttributeValue} descriptor that can be inspected further.
|
|
138
658
|
*
|
|
139
659
|
* This is the low-level building block; it operates on a single attribute
|
|
140
660
|
* node that the caller has already located. For the higher-level "find by
|
|
141
661
|
* name and resolve" combo, see {@link getAttributeValue}.
|
|
662
|
+
*
|
|
663
|
+
* When the attribute is a `JSXSpreadAttribute`, passing `name` (typically the
|
|
664
|
+
* same name the attribute was found by) makes `toStatic()` return the static
|
|
665
|
+
* value of that named property, eliminating the need to branch on
|
|
666
|
+
* `kind === "spreadProps"` at the call site.
|
|
142
667
|
* @param context The ESLint rule context (needed for scope look-ups).
|
|
143
668
|
* @param attribute A `JSXAttribute` or `JSXSpreadAttribute` node.
|
|
669
|
+
* @param name Optional property name used to resolve `toStatic()` for spread attributes.
|
|
144
670
|
* @returns A discriminated-union descriptor of the attribute's value.
|
|
145
671
|
*/
|
|
146
|
-
function resolveAttributeValue(context, attribute) {
|
|
672
|
+
function resolveAttributeValue(context, attribute, name) {
|
|
147
673
|
if (attribute.type === AST_NODE_TYPES.JSXAttribute) return resolveJsxAttribute(context, attribute);
|
|
148
|
-
return resolveJsxSpreadAttribute(context, attribute);
|
|
674
|
+
return resolveJsxSpreadAttribute(context, attribute, name);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Find an attribute by name on a JSX element and resolve its value in a single call.
|
|
678
|
+
*
|
|
679
|
+
* This is a convenience composition of {@link findAttribute} and
|
|
680
|
+
* {@link resolveAttributeValue} that eliminates the most common two-step
|
|
681
|
+
* pattern in lint rules.
|
|
682
|
+
* @param context The ESLint rule context.
|
|
683
|
+
* @param element The `JSXElement` node to search.
|
|
684
|
+
* @param name The attribute name to look up (ex: "className").
|
|
685
|
+
* @returns An {@link AttributeValue} descriptor, or `undefined` when the attribute is not present on the element.
|
|
686
|
+
*/
|
|
687
|
+
function getAttributeValue(context, element, name) {
|
|
688
|
+
const attr = findAttribute(context, element, name);
|
|
689
|
+
if (attr == null) return void 0;
|
|
690
|
+
return resolveAttributeValue(context, attr, name);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Find an attribute by name on a JSX element and collapse its value to a plain
|
|
694
|
+
* JavaScript value in a single step.
|
|
695
|
+
*
|
|
696
|
+
* This is a convenience composition of {@link findAttribute} ->
|
|
697
|
+
* {@link resolveAttributeValue} -> `toStatic()`, with automatic handling of the
|
|
698
|
+
* `spreadProps` case (extracts the named property from the spread object).
|
|
699
|
+
*
|
|
700
|
+
* Returns `undefined` both when the attribute is absent and when its value
|
|
701
|
+
* cannot be statically determined; use {@link findAttribute} or
|
|
702
|
+
* {@link hasAttribute} when presence itself matters.
|
|
703
|
+
* @param context The ESLint rule context.
|
|
704
|
+
* @param element The `JSXElement` node to inspect.
|
|
705
|
+
* @param name The attribute name to look up (ex: "className").
|
|
706
|
+
* @returns The static value of the attribute, or `undefined` when absent or indeterminate.
|
|
707
|
+
*/
|
|
708
|
+
function getAttributeStaticValue(context, element, name) {
|
|
709
|
+
return getAttributeValue(context, element, name)?.toStatic();
|
|
149
710
|
}
|
|
150
711
|
function resolveJsxAttribute(context, node) {
|
|
151
712
|
const scope = context.sourceCode.getScope(node);
|
|
@@ -172,9 +733,7 @@ function resolveJsxAttribute(context, node) {
|
|
|
172
733
|
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
|
|
173
734
|
kind: "missing",
|
|
174
735
|
node: expr,
|
|
175
|
-
toStatic() {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
736
|
+
toStatic() {}
|
|
178
737
|
};
|
|
179
738
|
return {
|
|
180
739
|
kind: "unknown",
|
|
@@ -187,84 +746,67 @@ function resolveJsxAttribute(context, node) {
|
|
|
187
746
|
case AST_NODE_TYPES.JSXElement: return {
|
|
188
747
|
kind: "element",
|
|
189
748
|
node: node.value,
|
|
190
|
-
toStatic() {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
749
|
+
toStatic() {}
|
|
193
750
|
};
|
|
194
751
|
case AST_NODE_TYPES.JSXSpreadChild: return {
|
|
195
752
|
kind: "spreadChild",
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
},
|
|
199
|
-
node: node.value.expression,
|
|
200
|
-
toStatic() {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
753
|
+
node: node.value,
|
|
754
|
+
toStatic() {}
|
|
203
755
|
};
|
|
204
756
|
}
|
|
205
757
|
}
|
|
206
|
-
function resolveJsxSpreadAttribute(context, node) {
|
|
207
|
-
const
|
|
758
|
+
function resolveJsxSpreadAttribute(context, node, name) {
|
|
759
|
+
const getProperty = (propertyName) => {
|
|
760
|
+
const property = findSpreadProperty(context, node.argument, propertyName);
|
|
761
|
+
if (property == null) return void 0;
|
|
762
|
+
const propertyScope = context.sourceCode.getScope(property.value);
|
|
763
|
+
return getStaticValue(property.value, propertyScope)?.value;
|
|
764
|
+
};
|
|
208
765
|
return {
|
|
209
766
|
kind: "spreadProps",
|
|
210
|
-
getProperty
|
|
211
|
-
return match(getStaticValue(node.argument, scope)?.value).with({ [name]: P.select(P.unknown) }, (v) => v).otherwise(() => null);
|
|
212
|
-
},
|
|
767
|
+
getProperty,
|
|
213
768
|
node: node.argument,
|
|
214
769
|
toStatic() {
|
|
215
|
-
return null;
|
|
770
|
+
return name == null ? void 0 : getProperty(name);
|
|
216
771
|
}
|
|
217
772
|
};
|
|
218
773
|
}
|
|
219
774
|
|
|
220
775
|
//#endregion
|
|
221
|
-
//#region src/
|
|
776
|
+
//#region src/text.ts
|
|
222
777
|
/**
|
|
223
|
-
*
|
|
224
|
-
* JavaScript value in a single step.
|
|
225
|
-
*
|
|
226
|
-
* This is a convenience composition of {@link findAttribute} ->
|
|
227
|
-
* {@link resolveAttributeValue} -> `toStatic()`, with automatic handling of the
|
|
228
|
-
* `spreadProps` case (extracts the named property from the spread object).
|
|
229
|
-
*
|
|
230
|
-
* Returns `null` when the attribute is absent, `undefined` when the value cannot
|
|
231
|
-
* be statically determined (including empty expression containers), and the
|
|
232
|
-
* resolved static value otherwise.
|
|
233
|
-
* @param context The ESLint rule context.
|
|
234
|
-
* @param element The `JSXElement` node to inspect.
|
|
235
|
-
* @param name The attribute name to look up (ex: "className").
|
|
236
|
-
* @returns The static value of the attribute, `null` when absent, or `undefined` when indeterminate.
|
|
237
|
-
*/
|
|
238
|
-
function getAttributeStaticValue(context, element, name) {
|
|
239
|
-
const attr = findAttribute(context, element, name);
|
|
240
|
-
if (attr == null) return null;
|
|
241
|
-
const resolved = resolveAttributeValue(context, attr);
|
|
242
|
-
if (resolved.kind === "spreadProps") return resolved.getProperty(name);
|
|
243
|
-
if (resolved.kind === "missing") return;
|
|
244
|
-
return resolved.toStatic();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
//#endregion
|
|
248
|
-
//#region src/get-attribute-value.ts
|
|
249
|
-
/**
|
|
250
|
-
* Find an attribute by name on a JSX element and resolve its value in a single call.
|
|
778
|
+
* Collapse a multiline JSX text string following React's whitespace rules.
|
|
251
779
|
*
|
|
252
|
-
* This
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* @
|
|
780
|
+
* This mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
|
|
781
|
+
* 1. Split the raw text into lines.
|
|
782
|
+
* 2. Find the last non-empty line.
|
|
783
|
+
* 3. Trim leading spaces on non-first lines and trailing spaces on non-last lines.
|
|
784
|
+
* 4. Collapse tabs into spaces.
|
|
785
|
+
* 5. Append a single space after each non-last non-empty line.
|
|
786
|
+
* @param text The raw JSX text string to collapse.
|
|
787
|
+
* @returns The collapsed string, or `null` if the text contains only whitespace.
|
|
788
|
+
* @see https://github.com/babel/babel/blob/main/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
|
|
259
789
|
*/
|
|
260
|
-
function
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
790
|
+
function collapseMultilineText(text) {
|
|
791
|
+
const lines = text.split(/\r\n|\n|\r/);
|
|
792
|
+
let lastNonEmptyLine = 0;
|
|
793
|
+
for (let i = 0; i < lines.length; i++) if (/[^ \t]/.exec(lines[i] ?? "") != null) lastNonEmptyLine = i;
|
|
794
|
+
let str = "";
|
|
795
|
+
for (let i = 0; i < lines.length; i++) {
|
|
796
|
+
const line = lines[i] ?? "";
|
|
797
|
+
const isFirstLine = i === 0;
|
|
798
|
+
const isLastLine = i === lines.length - 1;
|
|
799
|
+
const isLastNonEmptyLine = i === lastNonEmptyLine;
|
|
800
|
+
let trimmedLine = line.replace(/\t/g, " ");
|
|
801
|
+
if (!isFirstLine) trimmedLine = trimmedLine.replace(/^ +/, "");
|
|
802
|
+
if (!isLastLine) trimmedLine = trimmedLine.replace(/ +$/, "");
|
|
803
|
+
if (trimmedLine.length > 0) {
|
|
804
|
+
if (!isLastNonEmptyLine) trimmedLine += " ";
|
|
805
|
+
str += trimmedLine;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return str === "" ? null : str;
|
|
264
809
|
}
|
|
265
|
-
|
|
266
|
-
//#endregion
|
|
267
|
-
//#region src/is-whitespace.ts
|
|
268
810
|
/**
|
|
269
811
|
* Check whether a JSX child node is whitespace padding that React would
|
|
270
812
|
* trim away during rendering.
|
|
@@ -272,19 +814,21 @@ function getAttributeValue(context, element, name) {
|
|
|
272
814
|
* A child is considered whitespace padding when it is a `JSXText` node whose
|
|
273
815
|
* content is empty after applying React's whitespace normalization
|
|
274
816
|
* (see {@link collapseMultilineText}, modelled after Babel's
|
|
275
|
-
* `cleanJSXElementLiteralChild`). This is the
|
|
276
|
-
* JSX tags purely for formatting.
|
|
817
|
+
* `cleanJSXElementLiteralChild`) **and** it contains a newline. This is the
|
|
818
|
+
* whitespace that appears between JSX tags purely for formatting.
|
|
819
|
+
*
|
|
820
|
+
* For the looser "any whitespace-only text" check, see {@link isWhitespaceText}.
|
|
277
821
|
* @param node A JSX child node.
|
|
278
822
|
* @returns `true` when the node is purely formatting whitespace.
|
|
279
823
|
*/
|
|
280
|
-
function
|
|
824
|
+
function isPaddingWhitespace(node) {
|
|
281
825
|
if (node.type !== AST_NODE_TYPES.JSXText) return false;
|
|
282
826
|
return collapseMultilineText(node.value) == null && node.value.includes("\n");
|
|
283
827
|
}
|
|
284
828
|
/**
|
|
285
829
|
* Check whether a JSX child node is any whitespace-only text.
|
|
286
830
|
*
|
|
287
|
-
* This is a looser variant of {@link
|
|
831
|
+
* This is a looser variant of {@link isPaddingWhitespace}; it matches every
|
|
288
832
|
* `JSXText` node whose raw content is empty after trimming, regardless of
|
|
289
833
|
* whether it contains a newline.
|
|
290
834
|
* @param node A JSX child node.
|
|
@@ -312,7 +856,7 @@ function isEmptyStringExpression(node) {
|
|
|
312
856
|
}
|
|
313
857
|
|
|
314
858
|
//#endregion
|
|
315
|
-
//#region src/
|
|
859
|
+
//#region src/children.ts
|
|
316
860
|
/**
|
|
317
861
|
* Get the meaningful children of a JSX element or fragment.
|
|
318
862
|
*
|
|
@@ -320,39 +864,59 @@ function isEmptyStringExpression(node) {
|
|
|
320
864
|
* 1. Iterate over `element.children`.
|
|
321
865
|
* 2. Skip `JSXText` nodes that clean to nothing (padding whitespace).
|
|
322
866
|
* 3. Skip `JSXExpressionContainer` nodes whose expression is empty.
|
|
323
|
-
* 4. Skip `
|
|
867
|
+
* 4. Skip empty string expressions (`{""}`), which produce no DOM node.
|
|
324
868
|
* 5. Collect everything else.
|
|
325
869
|
* @param element A `JSXElement` or `JSXFragment` node.
|
|
326
870
|
* @returns An array of children nodes that contribute to rendered output.
|
|
327
871
|
*/
|
|
328
872
|
function getChildren(element) {
|
|
329
|
-
const
|
|
873
|
+
const children = [];
|
|
330
874
|
for (const child of element.children) {
|
|
331
|
-
if (child
|
|
332
|
-
if (collapseMultilineText(child.value) == null && child.value.includes("\n")) continue;
|
|
333
|
-
elements.push(child);
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
875
|
+
if (isPaddingWhitespace(child)) continue;
|
|
336
876
|
if (child.type === AST_NODE_TYPES.JSXExpressionContainer) {
|
|
337
|
-
|
|
338
|
-
if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) continue;
|
|
877
|
+
if (child.expression.type === AST_NODE_TYPES.JSXEmptyExpression) continue;
|
|
339
878
|
if (isEmptyStringExpression(child)) continue;
|
|
340
|
-
elements.push(child);
|
|
341
|
-
continue;
|
|
342
879
|
}
|
|
343
|
-
|
|
880
|
+
children.push(child);
|
|
344
881
|
}
|
|
345
|
-
return
|
|
882
|
+
return children;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Check whether a JSX element (or fragment) has meaningful children, that is,
|
|
886
|
+
* at least one child that is not purely whitespace text or an empty string expression.
|
|
887
|
+
*
|
|
888
|
+
* A `JSXText` child whose `raw` content is empty after trimming is considered
|
|
889
|
+
* non-meaningful because it is typically a code-formatting artifact
|
|
890
|
+
* (indentation between tags). While React's client renderer preserves these
|
|
891
|
+
* nodes as text nodes, they rarely represent intentionally rendered content.
|
|
892
|
+
*
|
|
893
|
+
* An empty string expression (`children={""}`) is also considered
|
|
894
|
+
* non-meaningful because React's reconciler and SSR renderer explicitly skip
|
|
895
|
+
* empty strings, producing no DOM node.
|
|
896
|
+
*
|
|
897
|
+
* Unlike {@link getChildren} (which only filters whitespace that contains a
|
|
898
|
+
* newline) this check treats any whitespace-only text as non-meaningful
|
|
899
|
+
* (see {@link isWhitespaceText}). As a result `hasChildren(node)` is not
|
|
900
|
+
* always equal to `getChildren(node).length > 0`: they differ for
|
|
901
|
+
* whitespace-only children that have no newline, such as `<div> </div>` or
|
|
902
|
+
* `<div>\t\t</div>`. Choose the API that matches your rule's intent.
|
|
903
|
+
* @param element A `JSXElement` or `JSXFragment` node.
|
|
904
|
+
* @returns `true` when the element has at least one meaningful child.
|
|
905
|
+
*/
|
|
906
|
+
function hasChildren(element) {
|
|
907
|
+
if (element.children.length === 0) return false;
|
|
908
|
+
return !element.children.every((child) => isWhitespaceText(child) || isEmptyStringExpression(child));
|
|
346
909
|
}
|
|
347
910
|
|
|
348
911
|
//#endregion
|
|
349
|
-
//#region src/
|
|
912
|
+
//#region src/element-type.ts
|
|
350
913
|
/**
|
|
351
914
|
* Get the string representation of a JSX element's type.
|
|
352
915
|
*
|
|
353
916
|
* - `<div>` -> `"div"`
|
|
354
917
|
* - `<Foo.Bar>` -> `"Foo.Bar"`
|
|
355
918
|
* - `<React.Fragment>` -> `"React.Fragment"`
|
|
919
|
+
* - `<xml:space>` -> `"xml:space"`
|
|
356
920
|
* - `<></>` -> `""`.
|
|
357
921
|
* @param node A `JSXElement` or `JSXFragment` node.
|
|
358
922
|
* @returns The fully-qualified element type string.
|
|
@@ -382,91 +946,7 @@ function getElementSelfType(node) {
|
|
|
382
946
|
}
|
|
383
947
|
|
|
384
948
|
//#endregion
|
|
385
|
-
//#region src/
|
|
386
|
-
/**
|
|
387
|
-
* Check whether a JSX element carries at least one of the given attributes.
|
|
388
|
-
*
|
|
389
|
-
* This is a batch variant of {@link hasAttribute} for the common pattern of
|
|
390
|
-
* short-circuiting on multiple prop names.
|
|
391
|
-
*
|
|
392
|
-
* Spread attributes are taken into account (see {@link findAttribute}).
|
|
393
|
-
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
394
|
-
* @param element The `JSXElement` node to inspect.
|
|
395
|
-
* @param names The attribute names to look for.
|
|
396
|
-
* @returns `true` when at least one of the attributes is present.
|
|
397
|
-
*/
|
|
398
|
-
function hasAnyAttribute(context, element, names) {
|
|
399
|
-
return names.some((name) => findAttribute(context, element, name) != null);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
//#endregion
|
|
403
|
-
//#region src/has-attribute.ts
|
|
404
|
-
/**
|
|
405
|
-
* Check whether a JSX element carries a given attribute (prop).
|
|
406
|
-
*
|
|
407
|
-
* This is a thin convenience wrapper around {@link findAttribute} for the
|
|
408
|
-
* common case where you only need a boolean answer.
|
|
409
|
-
*
|
|
410
|
-
* Spread attributes are taken into account: `<Comp {...{ disabled: true }} />`
|
|
411
|
-
* will report `true` for `"disabled"`.
|
|
412
|
-
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
413
|
-
* @param element The `JSXElement` node to inspect.
|
|
414
|
-
* @param name The attribute name to look for (ex: "className").
|
|
415
|
-
* @returns `true` when the attribute is present on the element.
|
|
416
|
-
*/
|
|
417
|
-
function hasAttribute(context, element, name) {
|
|
418
|
-
return findAttribute(context, element, name) != null;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
//#endregion
|
|
422
|
-
//#region src/has-children.ts
|
|
423
|
-
/**
|
|
424
|
-
* Check whether a JSX element (or fragment) has meaningful children, that is,
|
|
425
|
-
* at least one child that is not purely whitespace text or an empty string expression.
|
|
426
|
-
*
|
|
427
|
-
* A `JSXText` child whose `raw` content is empty after trimming is considered
|
|
428
|
-
* non-meaningful because it is typically a code-formatting artifact
|
|
429
|
-
* (indentation between tags). While React's client renderer preserves these
|
|
430
|
-
* nodes as text nodes, they rarely represent intentionally rendered content.
|
|
431
|
-
*
|
|
432
|
-
* An empty string expression (`children={""}`) is also considered
|
|
433
|
-
* non-meaningful because React's reconciler and SSR renderer explicitly skip
|
|
434
|
-
* empty strings, producing no DOM node.
|
|
435
|
-
*
|
|
436
|
-
* Unlike {@link getChildren} (which only filters whitespace that contains a
|
|
437
|
-
* newline) this check treats any whitespace-only text as non-meaningful
|
|
438
|
-
* (see {@link isWhitespaceText}). As a result `hasChildren(node)` is not
|
|
439
|
-
* always equal to `getChildren(node).length > 0`: they differ for
|
|
440
|
-
* whitespace-only children that have no newline, such as `<div> </div>` or
|
|
441
|
-
* `<div>\t\t</div>`. Choose the API that matches your rule's intent.
|
|
442
|
-
* @param element A `JSXElement` or `JSXFragment` node.
|
|
443
|
-
* @returns `true` when the element has at least one meaningful child.
|
|
444
|
-
*/
|
|
445
|
-
function hasChildren(element) {
|
|
446
|
-
if (element.children.length === 0) return false;
|
|
447
|
-
return !element.children.every((child) => isWhitespaceText(child) || isEmptyStringExpression(child));
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
//#endregion
|
|
451
|
-
//#region src/has-every-attribute.ts
|
|
452
|
-
/**
|
|
453
|
-
* Check whether a JSX element carries all of the given attributes (props).
|
|
454
|
-
*
|
|
455
|
-
* This is a batch variant of {@link hasAttribute} for the common pattern
|
|
456
|
-
* where a rule needs to verify that a set of required props are all present.
|
|
457
|
-
*
|
|
458
|
-
* Spread attributes are taken into account (see {@link findAttribute}).
|
|
459
|
-
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
460
|
-
* @param element The `JSXElement` node to inspect.
|
|
461
|
-
* @param names The attribute names to look for.
|
|
462
|
-
* @returns `true` when every name in `names` is present on the element.
|
|
463
|
-
*/
|
|
464
|
-
function hasEveryAttribute(context, element, names) {
|
|
465
|
-
return names.every((name) => findAttribute(context, element, name) != null);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
//#endregion
|
|
469
|
-
//#region src/is-element.ts
|
|
949
|
+
//#region src/element-is.ts
|
|
470
950
|
/**
|
|
471
951
|
* Check whether a node is a `JSXElement` (or `JSXFragment`) and optionally
|
|
472
952
|
* matches a given test.
|
|
@@ -492,9 +972,6 @@ function isElement(node, test) {
|
|
|
492
972
|
default: return test.includes(elementType);
|
|
493
973
|
}
|
|
494
974
|
}
|
|
495
|
-
|
|
496
|
-
//#endregion
|
|
497
|
-
//#region src/is-fragment-element.ts
|
|
498
975
|
/**
|
|
499
976
|
* Check whether a node is a React Fragment element.
|
|
500
977
|
*
|
|
@@ -514,9 +991,6 @@ function isFragmentElement(node, jsxFragmentFactory = "React.Fragment") {
|
|
|
514
991
|
const fragment = jsxFragmentFactory.split(".").at(-1) ?? "Fragment";
|
|
515
992
|
return getElementFullType(node).split(".").at(-1) === fragment;
|
|
516
993
|
}
|
|
517
|
-
|
|
518
|
-
//#endregion
|
|
519
|
-
//#region src/is-host-element.ts
|
|
520
994
|
/**
|
|
521
995
|
* Check whether a node is a host (intrinsic / DOM) element.
|
|
522
996
|
*
|
|
@@ -535,32 +1009,4 @@ function isHostElement(node) {
|
|
|
535
1009
|
}
|
|
536
1010
|
|
|
537
1011
|
//#endregion
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Hints for JSX detection.
|
|
541
|
-
*/
|
|
542
|
-
const JsxDetectionHint = {
|
|
543
|
-
None: 0n,
|
|
544
|
-
DoNotIncludeJsxWithNullValue: 1n << 0n,
|
|
545
|
-
DoNotIncludeJsxWithNumberValue: 1n << 1n,
|
|
546
|
-
DoNotIncludeJsxWithBigIntValue: 1n << 2n,
|
|
547
|
-
DoNotIncludeJsxWithStringValue: 1n << 3n,
|
|
548
|
-
DoNotIncludeJsxWithBooleanValue: 1n << 4n,
|
|
549
|
-
DoNotIncludeJsxWithUndefinedValue: 1n << 5n,
|
|
550
|
-
DoNotIncludeJsxWithEmptyArrayValue: 1n << 6n,
|
|
551
|
-
DoNotIncludeJsxWithCreateElementValue: 1n << 7n,
|
|
552
|
-
RequireAllArrayElementsToBeJsx: 1n << 8n,
|
|
553
|
-
RequireBothSidesOfLogicalExpressionToBeJsx: 1n << 9n,
|
|
554
|
-
RequireBothBranchesOfConditionalExpressionToBeJsx: 1n << 10n
|
|
555
|
-
};
|
|
556
|
-
/**
|
|
557
|
-
* Default JSX detection hint.
|
|
558
|
-
*
|
|
559
|
-
* Skips number, bigint, boolean, string, and undefined literals,
|
|
560
|
-
* the value types that are commonly returned alongside JSX in React
|
|
561
|
-
* components but are not themselves renderable elements.
|
|
562
|
-
*/
|
|
563
|
-
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
|
|
564
|
-
|
|
565
|
-
//#endregion
|
|
566
|
-
export { DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, collapseMultilineText, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isEmptyStringExpression, isFragmentElement, isHostElement, isWhitespace, isWhitespaceText, resolveAttributeValue };
|
|
1012
|
+
export { collapseMultilineText, findAttribute, findParentAttribute, findSpreadProperty, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isAttribute, isElement, isEmptyStringExpression, isFragmentElement, isHostElement, isPaddingWhitespace, isWhitespaceText, resolveAttributeValue };
|