@eslint-react/jsx 5.17.1 → 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 -228
- package/dist/index.js +610 -657
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -2,470 +2,7 @@ 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 src/collapse-multiline-text.ts
|
|
8
|
-
/**
|
|
9
|
-
* Collapse a multiline JSX text string following React's whitespace rules.
|
|
10
|
-
*
|
|
11
|
-
* This mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
|
|
12
|
-
* 1. Split the raw text into lines.
|
|
13
|
-
* 2. Find the last non-empty line.
|
|
14
|
-
* 3. Trim leading spaces on non-first lines and trailing spaces on non-last lines.
|
|
15
|
-
* 4. Collapse tabs into spaces.
|
|
16
|
-
* 5. Append a single space after each non-last non-empty line.
|
|
17
|
-
* @param text The raw JSX text string to collapse.
|
|
18
|
-
* @returns The collapsed string, or `null` if the text contains only whitespace.
|
|
19
|
-
* @see https://github.com/babel/babel/blob/main/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
|
|
20
|
-
*/
|
|
21
|
-
function collapseMultilineText(text) {
|
|
22
|
-
const lines = text.split(/\r\n|\n|\r/);
|
|
23
|
-
let lastNonEmptyLine = 0;
|
|
24
|
-
for (let i = 0; i < lines.length; i++) if (/[^ \t]/.exec(lines[i] ?? "") != null) lastNonEmptyLine = i;
|
|
25
|
-
let str = "";
|
|
26
|
-
for (let i = 0; i < lines.length; i++) {
|
|
27
|
-
const line = lines[i] ?? "";
|
|
28
|
-
const isFirstLine = i === 0;
|
|
29
|
-
const isLastLine = i === lines.length - 1;
|
|
30
|
-
const isLastNonEmptyLine = i === lastNonEmptyLine;
|
|
31
|
-
let trimmedLine = line.replace(/\t/g, " ");
|
|
32
|
-
if (!isFirstLine) trimmedLine = trimmedLine.replace(/^ +/, "");
|
|
33
|
-
if (!isLastLine) trimmedLine = trimmedLine.replace(/ +$/, "");
|
|
34
|
-
if (trimmedLine.length > 0) {
|
|
35
|
-
if (!isLastNonEmptyLine) trimmedLine += " ";
|
|
36
|
-
str += trimmedLine;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return str === "" ? null : str;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/get-attribute-name.ts
|
|
44
|
-
/**
|
|
45
|
-
* Get the stringified name of a `JSXAttribute` node.
|
|
46
|
-
*
|
|
47
|
-
* Handles both simple identifiers and namespaced names:
|
|
48
|
-
* - `className` -> `"className"`
|
|
49
|
-
* - `aria-label` -> `"aria-label"`
|
|
50
|
-
* - `xml:space` -> `"xml:space"`.
|
|
51
|
-
* @param node A `JSXAttribute` AST node.
|
|
52
|
-
* @returns The attribute name as a plain string.
|
|
53
|
-
*/
|
|
54
|
-
function getAttributeName(node) {
|
|
55
|
-
if (node.name.type === AST_NODE_TYPES.JSXIdentifier) return node.name.name;
|
|
56
|
-
return node.name.namespace.name + ":" + node.name.name.name;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
//#endregion
|
|
60
|
-
//#region src/find-attribute.ts
|
|
61
|
-
/**
|
|
62
|
-
* Find a JSX attribute (or spread attribute containing the property) by name on a given element.
|
|
63
|
-
*
|
|
64
|
-
* Returns the last matching attribute to mirror React's behavior where later props win,
|
|
65
|
-
* or `undefined` when the attribute is not present.
|
|
66
|
-
*
|
|
67
|
-
* Spread attributes are resolved when possible: if the spread argument is an identifier
|
|
68
|
-
* 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.
|
|
70
|
-
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
71
|
-
* @param element The `JSXElement` node to search.
|
|
72
|
-
* @param name The attribute name to look for (ex: "className").
|
|
73
|
-
* @returns The matching `JSXAttribute` or `JSXSpreadAttribute`, or `undefined` when not found.
|
|
74
|
-
*/
|
|
75
|
-
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
|
-
return element.openingElement.attributes.findLast((attr) => {
|
|
101
|
-
if (attr.type === AST_NODE_TYPES.JSXAttribute) return getAttributeName(attr) === name;
|
|
102
|
-
switch (attr.argument.type) {
|
|
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;
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
//#region src/find-parent-attribute.ts
|
|
116
|
-
/**
|
|
117
|
-
* Walk up the AST from `node` to find the nearest ancestor that is a `JSXAttribute`
|
|
118
|
-
* and (optionally) passes a predicate.
|
|
119
|
-
*
|
|
120
|
-
* This is useful when a rule visitor enters a deeply nested node (ex: a `Literal`
|
|
121
|
-
* inside an expression container) and needs to know which JSX attribute it belongs to.
|
|
122
|
-
* @param node The starting node for the upward search.
|
|
123
|
-
* @param test Optional predicate to filter candidate `JSXAttribute` nodes. When omitted every `JSXAttribute` ancestor matches.
|
|
124
|
-
* @returns The first matching `JSXAttribute` ancestor, or `null` if none is found before reaching the root.
|
|
125
|
-
*/
|
|
126
|
-
function findParentAttribute(node, test = () => true) {
|
|
127
|
-
const guard = (n) => {
|
|
128
|
-
return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
|
|
129
|
-
};
|
|
130
|
-
return Traverse.findParent(node, guard);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
//#endregion
|
|
134
|
-
//#region src/resolve-attribute-value.ts
|
|
135
|
-
/**
|
|
136
|
-
* Resolve the value of a JSX attribute (or spread attribute) into a
|
|
137
|
-
* {@link JsxAttributeValue} descriptor that can be inspected further.
|
|
138
|
-
*
|
|
139
|
-
* This is the low-level building block; it operates on a single attribute
|
|
140
|
-
* node that the caller has already located. For the higher-level "find by
|
|
141
|
-
* name and resolve" combo, see {@link getAttributeValue}.
|
|
142
|
-
* @param context The ESLint rule context (needed for scope look-ups).
|
|
143
|
-
* @param attribute A `JSXAttribute` or `JSXSpreadAttribute` node.
|
|
144
|
-
* @returns A discriminated-union descriptor of the attribute's value.
|
|
145
|
-
*/
|
|
146
|
-
function resolveAttributeValue(context, attribute) {
|
|
147
|
-
if (attribute.type === AST_NODE_TYPES.JSXAttribute) return resolveJsxAttribute(context, attribute);
|
|
148
|
-
return resolveJsxSpreadAttribute(context, attribute);
|
|
149
|
-
}
|
|
150
|
-
function resolveJsxAttribute(context, node) {
|
|
151
|
-
const scope = context.sourceCode.getScope(node);
|
|
152
|
-
if (node.value == null) return {
|
|
153
|
-
kind: "boolean",
|
|
154
|
-
node: null,
|
|
155
|
-
toStatic() {
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
switch (node.value.type) {
|
|
160
|
-
case AST_NODE_TYPES.Literal: {
|
|
161
|
-
const staticValue = node.value.value;
|
|
162
|
-
return {
|
|
163
|
-
kind: "literal",
|
|
164
|
-
node: node.value,
|
|
165
|
-
toStatic() {
|
|
166
|
-
return staticValue;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
case AST_NODE_TYPES.JSXExpressionContainer: {
|
|
171
|
-
const expr = node.value.expression;
|
|
172
|
-
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
|
|
173
|
-
kind: "missing",
|
|
174
|
-
node: expr,
|
|
175
|
-
toStatic() {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
return {
|
|
180
|
-
kind: "unknown",
|
|
181
|
-
node: expr,
|
|
182
|
-
toStatic() {
|
|
183
|
-
return getStaticValue(expr, scope)?.value;
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
case AST_NODE_TYPES.JSXElement: return {
|
|
188
|
-
kind: "element",
|
|
189
|
-
node: node.value,
|
|
190
|
-
toStatic() {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
case AST_NODE_TYPES.JSXSpreadChild: return {
|
|
195
|
-
kind: "spreadChild",
|
|
196
|
-
getChildren() {
|
|
197
|
-
return null;
|
|
198
|
-
},
|
|
199
|
-
node: node.value.expression,
|
|
200
|
-
toStatic() {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
function resolveJsxSpreadAttribute(context, node) {
|
|
207
|
-
const scope = context.sourceCode.getScope(node);
|
|
208
|
-
return {
|
|
209
|
-
kind: "spreadProps",
|
|
210
|
-
getProperty(name) {
|
|
211
|
-
return match(getStaticValue(node.argument, scope)?.value).with({ [name]: P.select(P.unknown) }, (v) => v).otherwise(() => null);
|
|
212
|
-
},
|
|
213
|
-
node: node.argument,
|
|
214
|
-
toStatic() {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
//#endregion
|
|
221
|
-
//#region src/get-attribute-static-value.ts
|
|
222
|
-
/**
|
|
223
|
-
* Find an attribute by name on a JSX element and collapse its value to a plain
|
|
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.
|
|
251
|
-
*
|
|
252
|
-
* This is a convenience composition of {@link findAttribute} and
|
|
253
|
-
* {@link resolveAttributeValue} that eliminates the most common two-step
|
|
254
|
-
* pattern in lint rules.
|
|
255
|
-
* @param context The ESLint rule context.
|
|
256
|
-
* @param element The `JSXElement` node to search.
|
|
257
|
-
* @param name The attribute name to look up (ex: "className").
|
|
258
|
-
* @returns A {@link JsxAttributeValue} descriptor, or `null` when the attribute is not present on the element.
|
|
259
|
-
*/
|
|
260
|
-
function getAttributeValue(context, element, name) {
|
|
261
|
-
const attr = findAttribute(context, element, name);
|
|
262
|
-
if (attr == null) return null;
|
|
263
|
-
return resolveAttributeValue(context, attr);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
//#endregion
|
|
267
|
-
//#region src/is-whitespace.ts
|
|
268
|
-
/**
|
|
269
|
-
* Check whether a JSX child node is whitespace padding that React would
|
|
270
|
-
* trim away during rendering.
|
|
271
|
-
*
|
|
272
|
-
* A child is considered whitespace padding when it is a `JSXText` node whose
|
|
273
|
-
* content is empty after applying React's whitespace normalization
|
|
274
|
-
* (see {@link collapseMultilineText}, modelled after Babel's
|
|
275
|
-
* `cleanJSXElementLiteralChild`). This is the whitespace that appears between
|
|
276
|
-
* JSX tags purely for formatting.
|
|
277
|
-
* @param node A JSX child node.
|
|
278
|
-
* @returns `true` when the node is purely formatting whitespace.
|
|
279
|
-
*/
|
|
280
|
-
function isWhitespace(node) {
|
|
281
|
-
if (node.type !== AST_NODE_TYPES.JSXText) return false;
|
|
282
|
-
return collapseMultilineText(node.value) == null && node.value.includes("\n");
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Check whether a JSX child node is any whitespace-only text.
|
|
286
|
-
*
|
|
287
|
-
* This is a looser variant of {@link isWhitespace}; it matches every
|
|
288
|
-
* `JSXText` node whose raw content is empty after trimming, regardless of
|
|
289
|
-
* whether it contains a newline.
|
|
290
|
-
* @param node A JSX child node.
|
|
291
|
-
* @returns `true` when the node is a whitespace-only `JSXText`.
|
|
292
|
-
*/
|
|
293
|
-
function isWhitespaceText(node) {
|
|
294
|
-
if (node.type !== AST_NODE_TYPES.JSXText) return false;
|
|
295
|
-
return node.raw.trim() === "";
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Check whether a JSX child node is an empty string expression (`{""}`).
|
|
299
|
-
*
|
|
300
|
-
* React's reconciler and SSR renderer explicitly skip empty strings,
|
|
301
|
-
* producing no DOM node (see `ReactChildFiber.js` and `ReactFizzConfigDOM.js`).
|
|
302
|
-
* Such expressions are therefore treated as non-rendered children, in the same
|
|
303
|
-
* way as whitespace padding.
|
|
304
|
-
* @param node A JSX child node.
|
|
305
|
-
* @returns `true` when the node is a `{""}` expression container.
|
|
306
|
-
*/
|
|
307
|
-
function isEmptyStringExpression(node) {
|
|
308
|
-
if (node.type !== AST_NODE_TYPES.JSXExpressionContainer) return false;
|
|
309
|
-
const expr = node.expression;
|
|
310
|
-
if (expr.type !== AST_NODE_TYPES.Literal) return false;
|
|
311
|
-
return expr.value === "";
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
//#endregion
|
|
315
|
-
//#region src/get-children.ts
|
|
316
|
-
/**
|
|
317
|
-
* Get the meaningful children of a JSX element or fragment.
|
|
318
|
-
*
|
|
319
|
-
* Mirrors Babel's `buildChildren` helper:
|
|
320
|
-
* 1. Iterate over `element.children`.
|
|
321
|
-
* 2. Skip `JSXText` nodes that clean to nothing (padding whitespace).
|
|
322
|
-
* 3. Skip `JSXExpressionContainer` nodes whose expression is empty.
|
|
323
|
-
* 4. Skip `JSXEmptyExpression` nodes.
|
|
324
|
-
* 5. Collect everything else.
|
|
325
|
-
* @param element A `JSXElement` or `JSXFragment` node.
|
|
326
|
-
* @returns An array of children nodes that contribute to rendered output.
|
|
327
|
-
*/
|
|
328
|
-
function getChildren(element) {
|
|
329
|
-
const elements = [];
|
|
330
|
-
for (const child of element.children) {
|
|
331
|
-
if (child.type === AST_NODE_TYPES.JSXText) {
|
|
332
|
-
if (collapseMultilineText(child.value) == null && child.value.includes("\n")) continue;
|
|
333
|
-
elements.push(child);
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (child.type === AST_NODE_TYPES.JSXExpressionContainer) {
|
|
337
|
-
const { expression } = child;
|
|
338
|
-
if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) continue;
|
|
339
|
-
if (isEmptyStringExpression(child)) continue;
|
|
340
|
-
elements.push(child);
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
elements.push(child);
|
|
344
|
-
}
|
|
345
|
-
return elements;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
//#endregion
|
|
349
|
-
//#region src/get-element-type.ts
|
|
350
|
-
/**
|
|
351
|
-
* Get the string representation of a JSX element's type.
|
|
352
|
-
*
|
|
353
|
-
* - `<div>` -> `"div"`
|
|
354
|
-
* - `<Foo.Bar>` -> `"Foo.Bar"`
|
|
355
|
-
* - `<React.Fragment>` -> `"React.Fragment"`
|
|
356
|
-
* - `<></>` -> `""`.
|
|
357
|
-
* @param node A `JSXElement` or `JSXFragment` node.
|
|
358
|
-
* @returns The fully-qualified element type string.
|
|
359
|
-
*/
|
|
360
|
-
function getElementFullType(node) {
|
|
361
|
-
if (node.type === AST_NODE_TYPES.JSXFragment) return "";
|
|
362
|
-
function getQualifiedName(node) {
|
|
363
|
-
switch (node.type) {
|
|
364
|
-
case AST_NODE_TYPES.JSXIdentifier: return node.name;
|
|
365
|
-
case AST_NODE_TYPES.JSXNamespacedName: return node.namespace.name + ":" + node.name.name;
|
|
366
|
-
default: return getQualifiedName(node.object) + "." + getQualifiedName(node.property);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return getQualifiedName(node.openingElement.name);
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Get the self name (last dot-separated segment) of a JSX element type.
|
|
373
|
-
*
|
|
374
|
-
* - `<Foo.Bar.Baz>` -> `"Baz"`
|
|
375
|
-
* - `<div>` -> `"div"`
|
|
376
|
-
* - `<></>` -> `""`.
|
|
377
|
-
* @param node A `JSXElement` or `JSXFragment` node.
|
|
378
|
-
* @returns The last segment of the element type, or `""` for fragments.
|
|
379
|
-
*/
|
|
380
|
-
function getElementSelfType(node) {
|
|
381
|
-
return getElementFullType(node).split(".").at(-1) ?? "";
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
//#endregion
|
|
385
|
-
//#region src/has-any-attribute.ts
|
|
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
6
|
//#region ../../.pkgs/eff/dist/index.js
|
|
470
7
|
/**
|
|
471
8
|
* Applies a `pipe` method's variadic arguments to an initial value from left
|
|
@@ -749,217 +286,667 @@ const constTrue = constant(true);
|
|
|
749
286
|
* **Example** (Returning false from a thunk)
|
|
750
287
|
*
|
|
751
288
|
* ```ts
|
|
752
|
-
* import { Function } from "effect"
|
|
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
|
|
753
461
|
* import * as assert from "node:assert"
|
|
462
|
+
* import { pipe, takeWhile } from "@local/eff"
|
|
754
463
|
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
464
|
+
* // data-first
|
|
465
|
+
* assert.deepStrictEqual(takeWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [1, 2])
|
|
757
466
|
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
467
|
+
* // data-last
|
|
468
|
+
* assert.deepStrictEqual(pipe([1, 2, 3, 2, 1], takeWhile((n: number) => n < 3)), [1, 2])
|
|
469
|
+
* ```
|
|
470
|
+
* @category array
|
|
760
471
|
*/
|
|
761
|
-
const
|
|
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
|
+
});
|
|
478
|
+
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/attribute-name.ts
|
|
762
481
|
/**
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
* **When to use**
|
|
482
|
+
* Get the stringified name of a `JSXAttribute` node.
|
|
766
483
|
*
|
|
767
|
-
*
|
|
484
|
+
* Handles both simple identifiers and namespaced names:
|
|
485
|
+
* - `className` -> `"className"`
|
|
486
|
+
* - `aria-label` -> `"aria-label"`
|
|
487
|
+
* - `xml:space` -> `"xml:space"`.
|
|
488
|
+
* @param node A `JSXAttribute` AST node.
|
|
489
|
+
* @returns The attribute name as a plain string.
|
|
490
|
+
*/
|
|
491
|
+
function getAttributeName(node) {
|
|
492
|
+
if (node.name.type === AST_NODE_TYPES.JSXIdentifier) return node.name.name;
|
|
493
|
+
return node.name.namespace.name + ":" + node.name.name.name;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Check whether a node is a `JSXAttribute` with the given name.
|
|
768
497
|
*
|
|
769
|
-
*
|
|
498
|
+
* Only plain identifier names are matched (ex: `className`); namespaced
|
|
499
|
+
* attributes (ex: `xml:space`) do not match.
|
|
770
500
|
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
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
|
+
});
|
|
511
|
+
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/attribute-find.ts
|
|
514
|
+
/**
|
|
515
|
+
* Find a JSX attribute (or spread attribute containing the property) by name on a given element.
|
|
774
516
|
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
517
|
+
* Returns the last matching attribute to mirror React's behavior where later props win,
|
|
518
|
+
* or `undefined` when the attribute is not present.
|
|
777
519
|
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
520
|
+
* Spread attributes are resolved when possible: if the spread argument is an identifier
|
|
521
|
+
* that resolves to an object expression, the object's properties are searched for a matching key.
|
|
522
|
+
* Nested object expressions and nested spread identifiers are also resolved
|
|
523
|
+
* (see {@link findSpreadProperty}).
|
|
524
|
+
* @param context The ESLint rule context (needed for variable resolution in spread attributes).
|
|
525
|
+
* @param element The `JSXElement` node to search.
|
|
526
|
+
* @param name The attribute name to look for (ex: "className").
|
|
527
|
+
* @returns The matching `JSXAttribute` or `JSXSpreadAttribute`, or `undefined` when not found.
|
|
780
528
|
*/
|
|
781
|
-
|
|
529
|
+
function findAttribute(context, element, name) {
|
|
530
|
+
return element.openingElement.attributes.findLast((attr) => {
|
|
531
|
+
if (attr.type === AST_NODE_TYPES.JSXAttribute) return getAttributeName(attr) === name;
|
|
532
|
+
return findSpreadProperty(context, attr.argument, name) != null;
|
|
533
|
+
});
|
|
534
|
+
}
|
|
782
535
|
/**
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
* **When to use**
|
|
536
|
+
* Walk up the AST from `node` to find the nearest ancestor that is a `JSXAttribute`
|
|
537
|
+
* and (optionally) passes a predicate.
|
|
786
538
|
*
|
|
787
|
-
*
|
|
539
|
+
* This is useful when a rule visitor enters a deeply nested node (ex: a `Literal`
|
|
540
|
+
* inside an expression container) and needs to know which JSX attribute it belongs to.
|
|
541
|
+
* @param node The starting node for the upward search.
|
|
542
|
+
* @param test Optional predicate to filter candidate `JSXAttribute` nodes. When omitted every `JSXAttribute` ancestor matches.
|
|
543
|
+
* @returns The first matching `JSXAttribute` ancestor, or `undefined` if none is found before reaching the root.
|
|
544
|
+
*/
|
|
545
|
+
function findParentAttribute(node, test = () => true) {
|
|
546
|
+
const guard = (n) => {
|
|
547
|
+
return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
|
|
548
|
+
};
|
|
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
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/attribute-has.ts
|
|
606
|
+
/**
|
|
607
|
+
* Check whether a JSX element carries a given attribute (prop).
|
|
788
608
|
*
|
|
789
|
-
*
|
|
609
|
+
* This is a thin convenience wrapper around {@link findAttribute} for the
|
|
610
|
+
* common case where you only need a boolean answer.
|
|
790
611
|
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
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.
|
|
794
624
|
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
625
|
+
* This is a batch variant of {@link hasAttribute} for the common pattern of
|
|
626
|
+
* short-circuiting on multiple prop names.
|
|
797
627
|
*
|
|
798
|
-
* @
|
|
799
|
-
* @
|
|
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.
|
|
800
633
|
*/
|
|
801
|
-
|
|
634
|
+
function hasAnyAttribute(context, element, names) {
|
|
635
|
+
return names.some((name) => findAttribute(context, element, name) != null);
|
|
636
|
+
}
|
|
802
637
|
/**
|
|
803
|
-
*
|
|
804
|
-
* The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
|
|
638
|
+
* Check whether a JSX element carries all of the given attributes (props).
|
|
805
639
|
*
|
|
806
|
-
*
|
|
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.
|
|
807
642
|
*
|
|
808
|
-
*
|
|
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.
|
|
809
658
|
*
|
|
810
|
-
*
|
|
659
|
+
* This is the low-level building block; it operates on a single attribute
|
|
660
|
+
* node that the caller has already located. For the higher-level "find by
|
|
661
|
+
* name and resolve" combo, see {@link getAttributeValue}.
|
|
811
662
|
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
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.
|
|
667
|
+
* @param context The ESLint rule context (needed for scope look-ups).
|
|
668
|
+
* @param attribute A `JSXAttribute` or `JSXSpreadAttribute` node.
|
|
669
|
+
* @param name Optional property name used to resolve `toStatic()` for spread attributes.
|
|
670
|
+
* @returns A discriminated-union descriptor of the attribute's value.
|
|
671
|
+
*/
|
|
672
|
+
function resolveAttributeValue(context, attribute, name) {
|
|
673
|
+
if (attribute.type === AST_NODE_TYPES.JSXAttribute) return resolveJsxAttribute(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.
|
|
815
678
|
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
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.
|
|
818
695
|
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
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).
|
|
821
699
|
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
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();
|
|
710
|
+
}
|
|
711
|
+
function resolveJsxAttribute(context, node) {
|
|
712
|
+
const scope = context.sourceCode.getScope(node);
|
|
713
|
+
if (node.value == null) return {
|
|
714
|
+
kind: "boolean",
|
|
715
|
+
node: null,
|
|
716
|
+
toStatic() {
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
switch (node.value.type) {
|
|
721
|
+
case AST_NODE_TYPES.Literal: {
|
|
722
|
+
const staticValue = node.value.value;
|
|
723
|
+
return {
|
|
724
|
+
kind: "literal",
|
|
725
|
+
node: node.value,
|
|
726
|
+
toStatic() {
|
|
727
|
+
return staticValue;
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
case AST_NODE_TYPES.JSXExpressionContainer: {
|
|
732
|
+
const expr = node.value.expression;
|
|
733
|
+
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
|
|
734
|
+
kind: "missing",
|
|
735
|
+
node: expr,
|
|
736
|
+
toStatic() {}
|
|
737
|
+
};
|
|
738
|
+
return {
|
|
739
|
+
kind: "unknown",
|
|
740
|
+
node: expr,
|
|
741
|
+
toStatic() {
|
|
742
|
+
return getStaticValue(expr, scope)?.value;
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
case AST_NODE_TYPES.JSXElement: return {
|
|
747
|
+
kind: "element",
|
|
748
|
+
node: node.value,
|
|
749
|
+
toStatic() {}
|
|
750
|
+
};
|
|
751
|
+
case AST_NODE_TYPES.JSXSpreadChild: return {
|
|
752
|
+
kind: "spreadChild",
|
|
753
|
+
node: node.value,
|
|
754
|
+
toStatic() {}
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
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
|
+
};
|
|
765
|
+
return {
|
|
766
|
+
kind: "spreadProps",
|
|
767
|
+
getProperty,
|
|
768
|
+
node: node.argument,
|
|
769
|
+
toStatic() {
|
|
770
|
+
return name == null ? void 0 : getProperty(name);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/text.ts
|
|
777
|
+
/**
|
|
778
|
+
* Collapse a multiline JSX text string following React's whitespace rules.
|
|
824
779
|
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
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
|
|
827
789
|
*/
|
|
828
|
-
|
|
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;
|
|
809
|
+
}
|
|
829
810
|
/**
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
* **When to use**
|
|
834
|
-
*
|
|
835
|
-
* Use when you need a return value in a branch that exhaustive checks prove
|
|
836
|
-
* cannot be reached.
|
|
837
|
-
*
|
|
838
|
-
* **Gotchas**
|
|
839
|
-
*
|
|
840
|
-
* Calling `absurd` throws, because a value of type `never` should be
|
|
841
|
-
* impossible at runtime.
|
|
842
|
-
*
|
|
843
|
-
* **Example** (Handling impossible values)
|
|
844
|
-
*
|
|
845
|
-
* ```ts
|
|
846
|
-
* import { absurd } from "effect"
|
|
811
|
+
* Check whether a JSX child node is whitespace padding that React would
|
|
812
|
+
* trim away during rendering.
|
|
847
813
|
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
* }
|
|
851
|
-
*
|
|
814
|
+
* A child is considered whitespace padding when it is a `JSXText` node whose
|
|
815
|
+
* content is empty after applying React's whitespace normalization
|
|
816
|
+
* (see {@link collapseMultilineText}, modelled after Babel's
|
|
817
|
+
* `cleanJSXElementLiteralChild`) **and** it contains a newline. This is the
|
|
818
|
+
* whitespace that appears between JSX tags purely for formatting.
|
|
852
819
|
*
|
|
853
|
-
* @
|
|
854
|
-
* @
|
|
820
|
+
* For the looser "any whitespace-only text" check, see {@link isWhitespaceText}.
|
|
821
|
+
* @param node A JSX child node.
|
|
822
|
+
* @returns `true` when the node is purely formatting whitespace.
|
|
855
823
|
*/
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
824
|
+
function isPaddingWhitespace(node) {
|
|
825
|
+
if (node.type !== AST_NODE_TYPES.JSXText) return false;
|
|
826
|
+
return collapseMultilineText(node.value) == null && node.value.includes("\n");
|
|
827
|
+
}
|
|
859
828
|
/**
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
* **When to use**
|
|
863
|
-
*
|
|
864
|
-
* Use as a temporary typed placeholder while developing incomplete code.
|
|
865
|
-
*
|
|
866
|
-
* **Gotchas**
|
|
867
|
-
*
|
|
868
|
-
* `hole` is intended for temporary development use. If the placeholder is
|
|
869
|
-
* evaluated at runtime, it throws.
|
|
870
|
-
*
|
|
871
|
-
* **Example** (Creating a development placeholder)
|
|
872
|
-
*
|
|
873
|
-
* ```ts
|
|
874
|
-
* import { hole } from "effect"
|
|
875
|
-
*
|
|
876
|
-
* // Intentionally not called: `hole` throws if the placeholder is evaluated.
|
|
877
|
-
* const buildUser = (id: number): { readonly id: number; readonly name: string } => ({
|
|
878
|
-
* id,
|
|
879
|
-
* name: hole<string>()
|
|
880
|
-
* })
|
|
881
|
-
*
|
|
882
|
-
* console.log(typeof buildUser) // "function"
|
|
883
|
-
* ```
|
|
829
|
+
* Check whether a JSX child node is any whitespace-only text.
|
|
884
830
|
*
|
|
885
|
-
* @
|
|
886
|
-
*
|
|
831
|
+
* This is a looser variant of {@link isPaddingWhitespace}; it matches every
|
|
832
|
+
* `JSXText` node whose raw content is empty after trimming, regardless of
|
|
833
|
+
* whether it contains a newline.
|
|
834
|
+
* @param node A JSX child node.
|
|
835
|
+
* @returns `true` when the node is a whitespace-only `JSXText`.
|
|
887
836
|
*/
|
|
888
|
-
|
|
837
|
+
function isWhitespaceText(node) {
|
|
838
|
+
if (node.type !== AST_NODE_TYPES.JSXText) return false;
|
|
839
|
+
return node.raw.trim() === "";
|
|
840
|
+
}
|
|
889
841
|
/**
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
* Supports both data-first and data-last (`pipe`-friendly) call styles.
|
|
893
|
-
*
|
|
894
|
-
* @param pred - The predicate to test each element with.
|
|
895
|
-
* @returns A new array without the matching prefix.
|
|
896
|
-
* @example
|
|
897
|
-
* ```ts
|
|
898
|
-
* import * as assert from "node:assert"
|
|
899
|
-
* import { dropWhile, pipe } from "@local/eff"
|
|
900
|
-
*
|
|
901
|
-
* // data-first
|
|
902
|
-
* assert.deepStrictEqual(dropWhile([1, 2, 3, 2, 1], (n: number) => n < 3), [3, 2, 1])
|
|
842
|
+
* Check whether a JSX child node is an empty string expression (`{""}`).
|
|
903
843
|
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
844
|
+
* React's reconciler and SSR renderer explicitly skip empty strings,
|
|
845
|
+
* producing no DOM node (see `ReactChildFiber.js` and `ReactFizzConfigDOM.js`).
|
|
846
|
+
* Such expressions are therefore treated as non-rendered children, in the same
|
|
847
|
+
* way as whitespace padding.
|
|
848
|
+
* @param node A JSX child node.
|
|
849
|
+
* @returns `true` when the node is a `{""}` expression container.
|
|
908
850
|
*/
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
return
|
|
914
|
-
}
|
|
851
|
+
function isEmptyStringExpression(node) {
|
|
852
|
+
if (node.type !== AST_NODE_TYPES.JSXExpressionContainer) return false;
|
|
853
|
+
const expr = node.expression;
|
|
854
|
+
if (expr.type !== AST_NODE_TYPES.Literal) return false;
|
|
855
|
+
return expr.value === "";
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
//#endregion
|
|
859
|
+
//#region src/children.ts
|
|
915
860
|
/**
|
|
916
|
-
*
|
|
861
|
+
* Get the meaningful children of a JSX element or fragment.
|
|
917
862
|
*
|
|
918
|
-
*
|
|
863
|
+
* Mirrors Babel's `buildChildren` helper:
|
|
864
|
+
* 1. Iterate over `element.children`.
|
|
865
|
+
* 2. Skip `JSXText` nodes that clean to nothing (padding whitespace).
|
|
866
|
+
* 3. Skip `JSXExpressionContainer` nodes whose expression is empty.
|
|
867
|
+
* 4. Skip empty string expressions (`{""}`), which produce no DOM node.
|
|
868
|
+
* 5. Collect everything else.
|
|
869
|
+
* @param element A `JSXElement` or `JSXFragment` node.
|
|
870
|
+
* @returns An array of children nodes that contribute to rendered output.
|
|
871
|
+
*/
|
|
872
|
+
function getChildren(element) {
|
|
873
|
+
const children = [];
|
|
874
|
+
for (const child of element.children) {
|
|
875
|
+
if (isPaddingWhitespace(child)) continue;
|
|
876
|
+
if (child.type === AST_NODE_TYPES.JSXExpressionContainer) {
|
|
877
|
+
if (child.expression.type === AST_NODE_TYPES.JSXEmptyExpression) continue;
|
|
878
|
+
if (isEmptyStringExpression(child)) continue;
|
|
879
|
+
}
|
|
880
|
+
children.push(child);
|
|
881
|
+
}
|
|
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.
|
|
919
887
|
*
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
* import * as assert from "node:assert"
|
|
925
|
-
* import { pipe, takeWhile } from "@local/eff"
|
|
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.
|
|
926
892
|
*
|
|
927
|
-
*
|
|
928
|
-
*
|
|
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.
|
|
929
896
|
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
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.
|
|
934
905
|
*/
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
return xs.slice(0, idx);
|
|
940
|
-
});
|
|
906
|
+
function hasChildren(element) {
|
|
907
|
+
if (element.children.length === 0) return false;
|
|
908
|
+
return !element.children.every((child) => isWhitespaceText(child) || isEmptyStringExpression(child));
|
|
909
|
+
}
|
|
941
910
|
|
|
942
911
|
//#endregion
|
|
943
|
-
//#region src/
|
|
912
|
+
//#region src/element-type.ts
|
|
944
913
|
/**
|
|
945
|
-
*
|
|
914
|
+
* Get the string representation of a JSX element's type.
|
|
946
915
|
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
916
|
+
* - `<div>` -> `"div"`
|
|
917
|
+
* - `<Foo.Bar>` -> `"Foo.Bar"`
|
|
918
|
+
* - `<React.Fragment>` -> `"React.Fragment"`
|
|
919
|
+
* - `<xml:space>` -> `"xml:space"`
|
|
920
|
+
* - `<></>` -> `""`.
|
|
921
|
+
* @param node A `JSXElement` or `JSXFragment` node.
|
|
922
|
+
* @returns The fully-qualified element type string.
|
|
923
|
+
*/
|
|
924
|
+
function getElementFullType(node) {
|
|
925
|
+
if (node.type === AST_NODE_TYPES.JSXFragment) return "";
|
|
926
|
+
function getQualifiedName(node) {
|
|
927
|
+
switch (node.type) {
|
|
928
|
+
case AST_NODE_TYPES.JSXIdentifier: return node.name;
|
|
929
|
+
case AST_NODE_TYPES.JSXNamespacedName: return node.namespace.name + ":" + node.name.name;
|
|
930
|
+
default: return getQualifiedName(node.object) + "." + getQualifiedName(node.property);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return getQualifiedName(node.openingElement.name);
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Get the self name (last dot-separated segment) of a JSX element type.
|
|
949
937
|
*
|
|
950
|
-
*
|
|
951
|
-
* - `
|
|
952
|
-
* - `
|
|
953
|
-
* @param node
|
|
954
|
-
* @
|
|
955
|
-
* @returns `true` when the node is a `JSXAttribute` named `name`.
|
|
938
|
+
* - `<Foo.Bar.Baz>` -> `"Baz"`
|
|
939
|
+
* - `<div>` -> `"div"`
|
|
940
|
+
* - `<></>` -> `""`.
|
|
941
|
+
* @param node A `JSXElement` or `JSXFragment` node.
|
|
942
|
+
* @returns The last segment of the element type, or `""` for fragments.
|
|
956
943
|
*/
|
|
957
|
-
|
|
958
|
-
return node.
|
|
959
|
-
}
|
|
944
|
+
function getElementSelfType(node) {
|
|
945
|
+
return getElementFullType(node).split(".").at(-1) ?? "";
|
|
946
|
+
}
|
|
960
947
|
|
|
961
948
|
//#endregion
|
|
962
|
-
//#region src/is
|
|
949
|
+
//#region src/element-is.ts
|
|
963
950
|
/**
|
|
964
951
|
* Check whether a node is a `JSXElement` (or `JSXFragment`) and optionally
|
|
965
952
|
* matches a given test.
|
|
@@ -985,9 +972,6 @@ function isElement(node, test) {
|
|
|
985
972
|
default: return test.includes(elementType);
|
|
986
973
|
}
|
|
987
974
|
}
|
|
988
|
-
|
|
989
|
-
//#endregion
|
|
990
|
-
//#region src/is-fragment-element.ts
|
|
991
975
|
/**
|
|
992
976
|
* Check whether a node is a React Fragment element.
|
|
993
977
|
*
|
|
@@ -1007,9 +991,6 @@ function isFragmentElement(node, jsxFragmentFactory = "React.Fragment") {
|
|
|
1007
991
|
const fragment = jsxFragmentFactory.split(".").at(-1) ?? "Fragment";
|
|
1008
992
|
return getElementFullType(node).split(".").at(-1) === fragment;
|
|
1009
993
|
}
|
|
1010
|
-
|
|
1011
|
-
//#endregion
|
|
1012
|
-
//#region src/is-host-element.ts
|
|
1013
994
|
/**
|
|
1014
995
|
* Check whether a node is a host (intrinsic / DOM) element.
|
|
1015
996
|
*
|
|
@@ -1028,32 +1009,4 @@ function isHostElement(node) {
|
|
|
1028
1009
|
}
|
|
1029
1010
|
|
|
1030
1011
|
//#endregion
|
|
1031
|
-
|
|
1032
|
-
/**
|
|
1033
|
-
* Hints for JSX detection.
|
|
1034
|
-
*/
|
|
1035
|
-
const JsxDetectionHint = {
|
|
1036
|
-
None: 0n,
|
|
1037
|
-
DoNotIncludeJsxWithNullValue: 1n << 0n,
|
|
1038
|
-
DoNotIncludeJsxWithNumberValue: 1n << 1n,
|
|
1039
|
-
DoNotIncludeJsxWithBigIntValue: 1n << 2n,
|
|
1040
|
-
DoNotIncludeJsxWithStringValue: 1n << 3n,
|
|
1041
|
-
DoNotIncludeJsxWithBooleanValue: 1n << 4n,
|
|
1042
|
-
DoNotIncludeJsxWithUndefinedValue: 1n << 5n,
|
|
1043
|
-
DoNotIncludeJsxWithEmptyArrayValue: 1n << 6n,
|
|
1044
|
-
DoNotIncludeJsxWithCreateElementValue: 1n << 7n,
|
|
1045
|
-
RequireAllArrayElementsToBeJsx: 1n << 8n,
|
|
1046
|
-
RequireBothSidesOfLogicalExpressionToBeJsx: 1n << 9n,
|
|
1047
|
-
RequireBothBranchesOfConditionalExpressionToBeJsx: 1n << 10n
|
|
1048
|
-
};
|
|
1049
|
-
/**
|
|
1050
|
-
* Default JSX detection hint.
|
|
1051
|
-
*
|
|
1052
|
-
* Skips number, bigint, boolean, string, and undefined literals,
|
|
1053
|
-
* the value types that are commonly returned alongside JSX in React
|
|
1054
|
-
* components but are not themselves renderable elements.
|
|
1055
|
-
*/
|
|
1056
|
-
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
|
|
1057
|
-
|
|
1058
|
-
//#endregion
|
|
1059
|
-
export { DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, collapseMultilineText, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isAttribute, 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 };
|