@elastic/eslint-plugin-eui 2.15.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +125 -0
  2. package/lib/cjs/index.js +10 -1
  3. package/lib/cjs/rules/a11y/no_nested_copy_tooltip.d.ts +3 -0
  4. package/lib/cjs/rules/a11y/no_nested_copy_tooltip.d.ts.map +1 -0
  5. package/lib/cjs/rules/a11y/no_nested_copy_tooltip.js +84 -0
  6. package/lib/cjs/rules/a11y/tooltip_button_icon_wrap.d.ts.map +1 -1
  7. package/lib/cjs/rules/a11y/tooltip_button_icon_wrap.js +29 -1
  8. package/lib/cjs/rules/a11y/tooltip_no_interactive_content.d.ts.map +1 -1
  9. package/lib/cjs/rules/a11y/tooltip_no_interactive_content.js +17 -45
  10. package/lib/cjs/rules/button_group_no_invalid_children.d.ts +3 -0
  11. package/lib/cjs/rules/button_group_no_invalid_children.d.ts.map +1 -0
  12. package/lib/cjs/rules/button_group_no_invalid_children.js +159 -0
  13. package/lib/cjs/rules/callout_prefer_props_for_content.d.ts.map +1 -1
  14. package/lib/cjs/rules/callout_prefer_props_for_content.js +93 -156
  15. package/lib/cjs/rules/no_deprecated_icon_aliases.d.ts +174 -0
  16. package/lib/cjs/rules/no_deprecated_icon_aliases.d.ts.map +1 -0
  17. package/lib/cjs/rules/no_deprecated_icon_aliases.js +302 -0
  18. package/lib/cjs/utils/flat_map.d.ts +2 -0
  19. package/lib/cjs/utils/flat_map.d.ts.map +1 -0
  20. package/lib/cjs/utils/flat_map.js +24 -0
  21. package/lib/cjs/utils/has_meaningful_attr.d.ts +20 -0
  22. package/lib/cjs/utils/has_meaningful_attr.d.ts.map +1 -0
  23. package/lib/cjs/utils/has_meaningful_attr.js +61 -0
  24. package/lib/cjs/utils/is_fragment.d.ts +8 -0
  25. package/lib/cjs/utils/is_fragment.d.ts.map +1 -0
  26. package/lib/cjs/utils/is_fragment.js +26 -0
  27. package/lib/cjs/utils/walk_jsx_children.d.ts +30 -0
  28. package/lib/cjs/utils/walk_jsx_children.d.ts.map +1 -0
  29. package/lib/cjs/utils/walk_jsx_children.js +210 -0
  30. package/lib/esm/index.js +9 -0
  31. package/lib/esm/index.js.map +1 -1
  32. package/lib/esm/rules/a11y/no_nested_copy_tooltip.d.ts +2 -0
  33. package/lib/esm/rules/a11y/no_nested_copy_tooltip.js +84 -0
  34. package/lib/esm/rules/a11y/no_nested_copy_tooltip.js.map +1 -0
  35. package/lib/esm/rules/a11y/tooltip_button_icon_wrap.js +31 -0
  36. package/lib/esm/rules/a11y/tooltip_button_icon_wrap.js.map +1 -1
  37. package/lib/esm/rules/a11y/tooltip_no_interactive_content.js +19 -42
  38. package/lib/esm/rules/a11y/tooltip_no_interactive_content.js.map +1 -1
  39. package/lib/esm/rules/button_group_no_invalid_children.d.ts +2 -0
  40. package/lib/esm/rules/button_group_no_invalid_children.js +198 -0
  41. package/lib/esm/rules/button_group_no_invalid_children.js.map +1 -0
  42. package/lib/esm/rules/callout_prefer_props_for_content.js +75 -112
  43. package/lib/esm/rules/callout_prefer_props_for_content.js.map +1 -1
  44. package/lib/esm/rules/no_deprecated_icon_aliases.d.ts +173 -0
  45. package/lib/esm/rules/no_deprecated_icon_aliases.js +307 -0
  46. package/lib/esm/rules/no_deprecated_icon_aliases.js.map +1 -0
  47. package/lib/esm/utils/flat_map.d.ts +1 -0
  48. package/lib/esm/utils/flat_map.js +22 -0
  49. package/lib/esm/utils/flat_map.js.map +1 -0
  50. package/lib/esm/utils/has_meaningful_attr.d.ts +19 -0
  51. package/lib/esm/utils/has_meaningful_attr.js +55 -0
  52. package/lib/esm/utils/has_meaningful_attr.js.map +1 -0
  53. package/lib/esm/utils/is_fragment.d.ts +7 -0
  54. package/lib/esm/utils/is_fragment.js +26 -0
  55. package/lib/esm/utils/is_fragment.js.map +1 -0
  56. package/lib/esm/utils/walk_jsx_children.d.ts +29 -0
  57. package/lib/esm/utils/walk_jsx_children.js +213 -0
  58. package/lib/esm/utils/walk_jsx_children.js.map +1 -0
  59. package/package.json +1 -1
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NoDeprecatedIconAliases = exports.DEPRECATED_ICON_ALIASES = exports.DEPRECATED_ICONS_WITHOUT_REPLACEMENT = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ /*
9
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
10
+ * or more contributor license agreements. Licensed under the Elastic License
11
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
12
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
13
+ * Side Public License, v 1.
14
+ */
15
+
16
+ const DEPRECATED_ICON_ALIASES = exports.DEPRECATED_ICON_ALIASES = {
17
+ alert: 'warning',
18
+ anomalyChart: 'chartAnomaly',
19
+ apmTrace: 'chartWaterfall',
20
+ arrowDown: 'chevronSingleDown',
21
+ arrowEnd: 'chevronLimitRight',
22
+ arrowLeft: 'chevronSingleLeft',
23
+ arrowRight: 'chevronSingleRight',
24
+ arrowStart: 'chevronLimitLeft',
25
+ arrowUp: 'chevronSingleUp',
26
+ beaker: 'flask',
27
+ boxesHorizontal: 'ellipsis',
28
+ boxesVertical: 'ellipsis',
29
+ changePointDetection: 'chartChangePoint',
30
+ checkInCircleFilled: 'checkCircleFill',
31
+ cheer: 'popper',
32
+ color: 'paintBucket',
33
+ compute: 'processor',
34
+ console: 'commandLine',
35
+ contrastHigh: 'contrastFill',
36
+ controlsHorizontal: 'controls',
37
+ controlsVertical: 'controls',
38
+ copyClipboard: 'copy',
39
+ crossInCircle: 'crossCircle',
40
+ crosshairs: 'crosshair',
41
+ currency: 'money',
42
+ cut: 'scissors',
43
+ desktop: 'display',
44
+ diff: 'compare',
45
+ discuss: 'comment',
46
+ documentEdit: 'pencil',
47
+ documents: 'document',
48
+ doubleArrowLeft: 'chevronDoubleLeft',
49
+ doubleArrowRight: 'chevronDoubleRight',
50
+ editorAlignCenter: 'textAlignCenter',
51
+ editorAlignLeft: 'textAlignLeft',
52
+ editorAlignRight: 'textAlignRight',
53
+ editorBold: 'textBold',
54
+ editorChecklist: 'listCheck',
55
+ editorCodeBlock: 'code',
56
+ editorComment: 'comment',
57
+ editorDistributeHorizontal: 'distributeHorizontal',
58
+ editorDistributeVertical: 'distributeVertical',
59
+ editorHeading: 'textHeading',
60
+ editorItalic: 'textItalic',
61
+ editorItemAlignBottom: 'alignBottom',
62
+ editorItemAlignCenter: 'alignCenterHorizontal',
63
+ editorItemAlignLeft: 'alignLeft',
64
+ editorItemAlignMiddle: 'alignCenterVertical',
65
+ editorItemAlignRight: 'alignRight',
66
+ editorItemAlignTop: 'alignTop',
67
+ editorLink: 'link',
68
+ editorOrderedList: 'listNumber',
69
+ editorPositionBottomLeft: 'alignBottomLeft',
70
+ editorPositionBottomRight: 'alignBottomRight',
71
+ editorPositionTopLeft: 'alignTopLeft',
72
+ editorPositionTopRight: 'alignTopRight',
73
+ editorRedo: 'redo',
74
+ editorStrike: 'textStrike',
75
+ editorTable: 'table',
76
+ editorUnderline: 'textUnderline',
77
+ editorUndo: 'undo',
78
+ editorUnorderedList: 'listBullet',
79
+ email: 'mail',
80
+ eql: 'query',
81
+ errorFilled: 'errorFill',
82
+ exit: 'logOut',
83
+ expand: 'maximize',
84
+ expandMini: 'maximize',
85
+ export: 'upload',
86
+ exportAction: 'upload',
87
+ eyeClosed: 'eyeSlash',
88
+ fieldStatistics: 'tableInfo',
89
+ filterInCircle: 'filter',
90
+ folderCheck: 'check',
91
+ folderOpened: 'folderOpen',
92
+ glasses: 'readOnly',
93
+ grab: 'dragVertical',
94
+ grabHorizontal: 'dragHorizontal',
95
+ grabOmnidirectional: 'drag',
96
+ heatmap: 'chartHeatmap',
97
+ importAction: 'download',
98
+ indexClose: 'tableCross',
99
+ indexEdit: 'tablePencil',
100
+ indexFlush: 'chartThreshold',
101
+ indexMapping: 'mapping',
102
+ indexOpen: 'tablePlus',
103
+ indexRuntime: 'tablePlay',
104
+ indexSettings: 'tableGear',
105
+ indexTemporary: 'tableTime',
106
+ invert: 'contrast',
107
+ kqlField: 'queryField',
108
+ kqlOperand: 'queryOperand',
109
+ kqlSelector: 'querySelector',
110
+ kqlValue: 'queryValue',
111
+ kubernetesPod: 'cube',
112
+ launch: 'rocket',
113
+ lettering: 'text',
114
+ lineDashed: 'lineDash',
115
+ lineDotted: 'lineDot',
116
+ list: 'listBullet',
117
+ listAdd: 'plusCircle',
118
+ logPatternAnalysis: 'pattern',
119
+ logRateAnalysis: 'chartBarVertical',
120
+ logstashIf: 'if',
121
+ logstashQueue: 'queue',
122
+ magnifyWithExclamation: 'magnifyExclamation',
123
+ magnifyWithMinus: 'magnifyMinus',
124
+ magnifyWithPlus: 'magnifyPlus',
125
+ mapMarker: 'waypoint',
126
+ minusInCircle: 'minusCircle',
127
+ minusInCircleFilled: 'minusCircle',
128
+ minusInSquare: 'minusSquare',
129
+ newChat: 'plusCircle',
130
+ node: 'vectorTriangle',
131
+ offline: 'wifiSlash',
132
+ online: 'wifi',
133
+ pagesSelect: 'documentsCheck',
134
+ pinFilled: 'pinFill',
135
+ pipeBreaks: 'lineBreak',
136
+ pipeNoBreaks: 'lineBreakSlash',
137
+ playFilled: 'play',
138
+ plusInCircle: 'plusCircle',
139
+ plusInCircleFilled: 'plusCircle',
140
+ plusInSquare: 'plusSquare',
141
+ popout: 'external',
142
+ productRobot: 'productAgent',
143
+ push: 'send',
144
+ returnKey: 'return',
145
+ search: 'magnify',
146
+ securitySignal: 'radar',
147
+ starEmpty: 'star',
148
+ starFilled: 'starFill',
149
+ starFilledSpace: 'starFillSpace',
150
+ starMinusFilled: 'starMinusFill',
151
+ starPlusFilled: 'starPlusFill',
152
+ stopFilled: 'stopFill',
153
+ streamsClassic: 'productStreamsClassic',
154
+ streamsWired: 'productStreamsWired',
155
+ submodule: 'merge',
156
+ tableDensityCompact: 'tableDensityHigh',
157
+ tableDensityExpanded: 'tableDensityLow',
158
+ tableDensityNormal: 'table',
159
+ temperature: 'thermometer',
160
+ timeRefresh: 'refreshTime',
161
+ timelineWithArrow: 'timelinePointer',
162
+ timeslider: 'clockControl',
163
+ tokenDenseVector: 'tokenVectorDense',
164
+ training: 'presentation',
165
+ unlink: 'linkSlash',
166
+ userAvatar: 'user',
167
+ vector: 'vectorSquare',
168
+ visArea: 'chartArea',
169
+ visAreaStacked: 'chartAreaStack',
170
+ visBarHorizontal: 'chartBarHorizontal',
171
+ visBarHorizontalStacked: 'chartBarHorizontalStack',
172
+ visBarVertical: 'chartBarVertical',
173
+ visBarVerticalStacked: 'chartBarVerticalStack',
174
+ visGauge: 'chartGauge',
175
+ visLine: 'chartLine',
176
+ visMapCoordinate: 'waypoint',
177
+ visMapRegion: 'map',
178
+ visMetric: 'chartMetric',
179
+ visPie: 'chartPie',
180
+ visTable: 'table',
181
+ visTagCloud: 'chartTagCloud',
182
+ visText: 'text',
183
+ visVega: 'code',
184
+ warningFilled: 'warningFill'
185
+ };
186
+ const DEPRECATED_ICONS_WITHOUT_REPLACEMENT = exports.DEPRECATED_ICONS_WITHOUT_REPLACEMENT = new Set(['mobile']);
187
+ const getComponentName = node => node.name.type === 'JSXIdentifier' ? node.name.name : undefined;
188
+ const COMPONENT_ICON_ATTRIBUTES = {
189
+ EuiCommentTimeline: new Set(['timelineAvatar']),
190
+ EuiFormAppend: new Set(['iconLeft', 'iconRight']),
191
+ EuiFormAppendPrepend: new Set(['iconLeft', 'iconRight']),
192
+ EuiFormPrepend: new Set(['iconLeft', 'iconRight']),
193
+ EuiLoadingLogo: new Set(['logo']),
194
+ EuiTimelineItem: new Set(['icon'])
195
+ };
196
+ const isIconAttribute = (attributeName, importedComponentName) => {
197
+ const normalizedName = attributeName.toLowerCase();
198
+ return normalizedName.endsWith('icontype') || COMPONENT_ICON_ATTRIBUTES[importedComponentName]?.has(attributeName) === true || normalizedName === 'type' && (importedComponentName === 'EuiIcon' || importedComponentName === 'EuiIconTip');
199
+ };
200
+ const getStaticStringNode = node => {
201
+ if (node.value?.type === 'Literal' && typeof node.value.value === 'string') {
202
+ return node.value;
203
+ }
204
+ if (node.value?.type !== 'JSXExpressionContainer') {
205
+ return;
206
+ }
207
+ const {
208
+ expression
209
+ } = node.value;
210
+ if (expression.type === 'Literal' && typeof expression.value === 'string') {
211
+ return expression;
212
+ }
213
+ if (expression.type === 'TemplateLiteral' && expression.expressions.length === 0) {
214
+ return expression;
215
+ }
216
+ };
217
+ const getStaticStringValue = node => {
218
+ if (node.type === 'Literal') {
219
+ return typeof node.value === 'string' ? node.value : undefined;
220
+ }
221
+ return node.quasis[0]?.value.cooked ?? undefined;
222
+ };
223
+ const isDeprecatedIconAlias = value => value in DEPRECATED_ICON_ALIASES;
224
+ const NoDeprecatedIconAliases = exports.NoDeprecatedIconAliases = _utils.ESLintUtils.RuleCreator.withoutDocs({
225
+ create(context) {
226
+ const euiComponentImports = new Map();
227
+ return {
228
+ ImportDeclaration(node) {
229
+ if (typeof node.source.value !== 'string' || node.source.value !== '@elastic/eui' && !node.source.value.startsWith('@elastic/eui/')) {
230
+ return;
231
+ }
232
+ for (const specifier of node.specifiers) {
233
+ if (specifier.type !== 'ImportSpecifier') {
234
+ continue;
235
+ }
236
+ const importedName = specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value;
237
+ if (importedName.startsWith('Eui')) {
238
+ euiComponentImports.set(specifier.local.name, importedName);
239
+ }
240
+ }
241
+ },
242
+ JSXAttribute(node) {
243
+ if (node.name.type !== 'JSXIdentifier') {
244
+ return;
245
+ }
246
+ const openingElement = node.parent;
247
+ const localComponentName = openingElement.type === 'JSXOpeningElement' ? getComponentName(openingElement) : undefined;
248
+ const importedComponentName = localComponentName ? euiComponentImports.get(localComponentName) : undefined;
249
+ if (!importedComponentName || !isIconAttribute(node.name.name, importedComponentName)) {
250
+ return;
251
+ }
252
+ const staticStringNode = getStaticStringNode(node);
253
+ if (!staticStringNode) {
254
+ return;
255
+ }
256
+ const iconType = getStaticStringValue(staticStringNode);
257
+ if (!iconType) {
258
+ return;
259
+ }
260
+ if (!isDeprecatedIconAlias(iconType)) {
261
+ if (DEPRECATED_ICONS_WITHOUT_REPLACEMENT.has(iconType)) {
262
+ context.report({
263
+ node: staticStringNode,
264
+ messageId: 'deprecatedIcon',
265
+ data: {
266
+ iconType
267
+ }
268
+ });
269
+ }
270
+ return;
271
+ }
272
+ const replacement = DEPRECATED_ICON_ALIASES[iconType];
273
+ context.report({
274
+ node: staticStringNode,
275
+ messageId: 'deprecatedIconAlias',
276
+ data: {
277
+ alias: iconType,
278
+ replacement
279
+ },
280
+ fix(fixer) {
281
+ const sourceText = context.sourceCode.getText(staticStringNode);
282
+ const quote = sourceText[0];
283
+ return fixer.replaceText(staticStringNode, `${quote}${replacement}${quote}`);
284
+ }
285
+ });
286
+ }
287
+ };
288
+ },
289
+ meta: {
290
+ type: 'suggestion',
291
+ docs: {
292
+ description: 'Disallow deprecated EUI icons in JSX icon props.'
293
+ },
294
+ fixable: 'code',
295
+ schema: [],
296
+ messages: {
297
+ deprecatedIcon: 'Icon `{{iconType}}` is deprecated and has no direct replacement.',
298
+ deprecatedIconAlias: 'Icon alias `{{alias}}` is deprecated. Use `{{replacement}}` instead.'
299
+ }
300
+ },
301
+ defaultOptions: []
302
+ });
@@ -0,0 +1,2 @@
1
+ export declare function flatMap<T, U>(arr: readonly T[], fn: (item: T) => U[]): U[];
2
+ //# sourceMappingURL=flat_map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flat_map.d.ts","sourceRoot":"","sources":["../../../src/utils/flat_map.ts"],"names":[],"mappings":"AAUA,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,CAO1E"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.flatMap = flatMap;
7
+ /*
8
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
9
+ * or more contributor license agreements. Licensed under the Elastic License
10
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
11
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
12
+ * Side Public License, v 1.
13
+ */
14
+
15
+ // `Array.prototype.flatMap` requires ES2019 lib; this shim keeps rules that
16
+ // need it within the package's es5 lib setting without widening it for others.
17
+ function flatMap(arr, fn) {
18
+ const out = [];
19
+ for (const item of arr) {
20
+ const items = fn(item);
21
+ for (const i of items) out.push(i);
22
+ }
23
+ return out;
24
+ }
@@ -0,0 +1,20 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * Checks whether a JSX opening element has an attribute whose value is
4
+ * statically "meaningful" — present and not a statically-known empty/falsy
5
+ * value (`""`, `{''}`, `{0}`, `{false}`, `{null}`, `{undefined}`).
6
+ *
7
+ * Dynamic or otherwise non-statically-analyzable values (variables, JSX,
8
+ * template literals, calls, …) are treated as meaningful, since their runtime
9
+ * value is unknown. Boolean shorthand (`<El attr />`) is also meaningful.
10
+ *
11
+ * Useful for rules that must distinguish an attribute that actually renders
12
+ * something from one that is present but effectively empty.
13
+ *
14
+ * @param openingElement - The `JSXOpeningElement` node (ESTree).
15
+ * @param attrName - The attribute name to look up.
16
+ * @returns `true` if the attribute is present with a non-empty/non-falsy or
17
+ * dynamic value; otherwise `false`.
18
+ */
19
+ export declare function hasMeaningfulAttr(openingElement: TSESTree.JSXOpeningElement, attrName: string): boolean;
20
+ //# sourceMappingURL=has_meaningful_attr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"has_meaningful_attr.d.ts","sourceRoot":"","sources":["../../../src/utils/has_meaningful_attr.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,QAAQ,CAAC,iBAAiB,EAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAkCT"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.hasMeaningfulAttr = hasMeaningfulAttr;
7
+ /*
8
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
9
+ * or more contributor license agreements. Licensed under the Elastic License
10
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
11
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
12
+ * Side Public License, v 1.
13
+ */
14
+
15
+ /**
16
+ * Checks whether a JSX opening element has an attribute whose value is
17
+ * statically "meaningful" — present and not a statically-known empty/falsy
18
+ * value (`""`, `{''}`, `{0}`, `{false}`, `{null}`, `{undefined}`).
19
+ *
20
+ * Dynamic or otherwise non-statically-analyzable values (variables, JSX,
21
+ * template literals, calls, …) are treated as meaningful, since their runtime
22
+ * value is unknown. Boolean shorthand (`<El attr />`) is also meaningful.
23
+ *
24
+ * Useful for rules that must distinguish an attribute that actually renders
25
+ * something from one that is present but effectively empty.
26
+ *
27
+ * @param openingElement - The `JSXOpeningElement` node (ESTree).
28
+ * @param attrName - The attribute name to look up.
29
+ * @returns `true` if the attribute is present with a non-empty/non-falsy or
30
+ * dynamic value; otherwise `false`.
31
+ */
32
+ function hasMeaningfulAttr(openingElement, attrName) {
33
+ const attr = openingElement.attributes.find(a => a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && a.name.name === attrName);
34
+ if (!attr) return false;
35
+
36
+ // Boolean shorthand: `<El attr />` → present and truthy.
37
+ if (attr.value == null) return true;
38
+
39
+ // String literal: `attr="…"`.
40
+ if (attr.value.type === 'Literal') {
41
+ return Boolean(attr.value.value);
42
+ }
43
+ if (attr.value.type === 'JSXExpressionContainer') {
44
+ const {
45
+ expression
46
+ } = attr.value;
47
+
48
+ // Statically-known literals: `{''}`, `{0}`, `{false}`, `{null}`.
49
+ if (expression.type === 'Literal') {
50
+ return Boolean(expression.value);
51
+ }
52
+
53
+ // `{undefined}` (an identifier, not a literal).
54
+ if (expression.type === 'Identifier' && expression.name === 'undefined') {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ // Dynamic / unknown value → treat as present.
60
+ return true;
61
+ }
@@ -0,0 +1,8 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * Returns true if the opening element is a React fragment: `<Fragment>` or
4
+ * `<React.Fragment>`. The `<>` shorthand is represented as a `JSXFragment`
5
+ * node (not a `JSXOpeningElement`) and is handled separately by callers.
6
+ */
7
+ export declare function isFragment(opening: TSESTree.JSXOpeningElement): boolean;
8
+ //# sourceMappingURL=is_fragment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is_fragment.d.ts","sourceRoot":"","sources":["../../../src/utils/is_fragment.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,iBAAiB,GAAG,OAAO,CAUvE"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isFragment = isFragment;
7
+ /*
8
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
9
+ * or more contributor license agreements. Licensed under the Elastic License
10
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
11
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
12
+ * Side Public License, v 1.
13
+ */
14
+
15
+ /**
16
+ * Returns true if the opening element is a React fragment: `<Fragment>` or
17
+ * `<React.Fragment>`. The `<>` shorthand is represented as a `JSXFragment`
18
+ * node (not a `JSXOpeningElement`) and is handled separately by callers.
19
+ */
20
+ function isFragment(opening) {
21
+ const {
22
+ name
23
+ } = opening;
24
+ if (name.type === 'JSXIdentifier' && name.name === 'Fragment') return true;
25
+ return name.type === 'JSXMemberExpression' && name.object.type === 'JSXIdentifier' && name.object.name === 'React' && name.property.type === 'JSXIdentifier' && name.property.name === 'Fragment';
26
+ }
@@ -0,0 +1,30 @@
1
+ import { type TSESTree, type TSESLint } from '@typescript-eslint/utils';
2
+ /**
3
+ * Recursively walks JSX children, expanding structural nodes transparently
4
+ * and calling `visit` for each content node.
5
+ *
6
+ * Always expanded (structural nodes):
7
+ * - `<>...</>`, `<Fragment>`, `<React.Fragment>` (transparent grouping)
8
+ * - `{expr}` (JSXExpressionContainer)
9
+ * - `{a && b}`, `{a || b}`, `{a ?? b}` (LogicalExpression → both sides)
10
+ * - `{c ? a : b}` (ConditionalExpression → both branches)
11
+ * - `{[a, b]}` (ArrayExpression → each element)
12
+ * - `{(arg) => <El />}` (ArrowFunctionExpression)
13
+ * - `{arr.map(fn)}` (CallExpression `.map()` with arrow-fn callback)
14
+ *
15
+ * With `options.sourceCode`: Identifiers and local arrow-fn components are
16
+ * also expanded via scope lookup when resolvable.
17
+ *
18
+ * `options.shouldSkip` - when provided, called for each JSXElement reached
19
+ * (after fragment and local-component checks). Returning true skips the
20
+ * element itself and recurses into its children instead. Use this to make
21
+ * layout wrappers or non-interactive shells transparent to the visitor.
22
+ *
23
+ * `visit` is called for leaf nodes: JSXElement, JSXText, Literal,
24
+ * TemplateLiteral, MemberExpression.
25
+ */
26
+ export declare function walkJsxChildren(node: TSESTree.Node, visit: (node: TSESTree.Node) => void, options?: {
27
+ sourceCode?: TSESLint.SourceCode;
28
+ shouldSkip?: (el: TSESTree.JSXElement) => boolean;
29
+ }): void;
30
+ //# sourceMappingURL=walk_jsx_children.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk_jsx_children.d.ts","sourceRoot":"","sources":["../../../src/utils/walk_jsx_children.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAmMxE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,KAAK,IAAI,EACpC,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,UAAU,KAAK,OAAO,CAAC;CAC9C,GACL,IAAI,CAQN"}