@elastic/eslint-plugin-eui 2.14.1 → 2.15.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.
package/README.md CHANGED
@@ -157,6 +157,57 @@ Ensure that all radio input components (`EuiRadio`, `EuiRadioGroup`) have a `nam
157
157
 
158
158
  Ensure that `EuiCallOut` components rendered conditionally have the `announceOnMount` prop for better accessibility. When callouts appear dynamically (e.g., after user interactions, form validation errors, or status changes), screen readers may not announce their content to users. The `announceOnMount` prop ensures these messages are properly announced to users with assistive technologies.
159
159
 
160
+ ### `@elastic/eui/callout-prefer-props-for-content`
161
+
162
+ Enforce correct usage of `EuiCallOut` `text` and `actionProps` props and discourage using `children` for content.
163
+
164
+ `EuiCallOut` accepts dedicated `text` and `actionProps` props introduced to standardize callout content:
165
+
166
+ - **Text content** (plain text, `<p>`, `<span>`, `<strong>`, etc. or EUI text components like `EuiText`) should be passed via the `text` prop.
167
+ - **Action elements** (buttons and non-inline links) should be passed via `actionProps`, which renders standardized primary and secondary action buttons.
168
+
169
+ The rule warns (rather than errors) to allow incremental migration from the `children` pattern.
170
+
171
+ #### Examples
172
+
173
+ ```tsx
174
+ // ✗ Flagged — text content via children
175
+ <EuiCallOut title="Title">
176
+ <p>Callout body text.</p>
177
+ </EuiCallOut>
178
+
179
+ // ✓ Good
180
+ <EuiCallOut title="Title" text={<p>Callout body text.</p>} />
181
+ ```
182
+
183
+ ```tsx
184
+ // ✗ Flagged — action button via children
185
+ <EuiCallOut title="Title">
186
+ <EuiButton onClick={onClick}>Take action</EuiButton>
187
+ </EuiCallOut>
188
+
189
+ // ✓ Good
190
+ <EuiCallOut
191
+ title="Title"
192
+ actionProps={{ primary: { children: 'Take action', onClick: onClick } }}
193
+ />
194
+ ```
195
+
196
+ The rule traverses fragments (`<>`, `<React.Fragment>`, `<Fragment>`), layout containers (`EuiFlexGroup`, `EuiFlexGrid`, `EuiFlexItem`, `div`), and conditional/logical expressions. It does not traverse custom component children, since their content cannot be statically analyzed.
197
+
198
+ #### Options
199
+
200
+ | Option | Type | Default | Description |
201
+ |---|---|---|---|
202
+ | `components` | `string[]` | `['EuiCallOut']` | Component names to check. Replaces the default — include `'EuiCallOut'` explicitly if you also want to keep checking it. |
203
+
204
+ ```js
205
+ // .eslintrc.js
206
+ '@elastic/eui/callout-prefer-props-for-content': ['warn', {
207
+ components: ['EuiCallOut', 'KbnWarningCallout'],
208
+ }]
209
+ ```
210
+
160
211
  ### `@elastic/eui/no-unnamed-interactive-element`
161
212
 
162
213
  Ensure that appropriate aria-attributes are set for `EuiBetaBadge`, `EuiButtonIcon`, `EuiComboBox`, `EuiSelect`, `EuiSelectWithWidth`,`EuiSuperSelect`,`EuiPagination`, `EuiTreeView`, `EuiBreadcrumbs`. Without this rule, screen reader users lose context, keyboard navigation can be confusing.
package/lib/cjs/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var _accessible_interactive_element = require("./rules/a11y/accessible_interactive_element");
4
4
  var _callout_announce_on_mount = require("./rules/a11y/callout_announce_on_mount");
5
+ var _callout_prefer_props_for_content = require("./rules/callout_prefer_props_for_content");
5
6
  var _consistent_is_invalid_props = require("./rules/a11y/consistent_is_invalid_props");
6
7
  var _href_or_on_click = require("./rules/href_or_on_click");
7
8
  var _require_href_for_link = require("./rules/require_href_for_link");
@@ -32,6 +33,7 @@ const config = {
32
33
  rules: {
33
34
  'accessible-interactive-element': _accessible_interactive_element.AccessibleInteractiveElements,
34
35
  'callout-announce-on-mount': _callout_announce_on_mount.CallOutAnnounceOnMount,
36
+ 'callout-prefer-props-for-content': _callout_prefer_props_for_content.CallOutPreferPropsForContent,
35
37
  'consistent-is-invalid-props': _consistent_is_invalid_props.ConsistentIsInvalidProps,
36
38
  'href-or-on-click': _href_or_on_click.HrefOnClick,
37
39
  'no-css-color': _no_css_color.NoCssColor,
@@ -57,6 +59,7 @@ const config = {
57
59
  rules: {
58
60
  '@elastic/eui/accessible-interactive-element': 'warn',
59
61
  '@elastic/eui/callout-announce-on-mount': 'warn',
62
+ '@elastic/eui/callout-prefer-props-for-content': 'warn',
60
63
  '@elastic/eui/consistent-is-invalid-props': 'warn',
61
64
  '@elastic/eui/href-or-on-click': 'warn',
62
65
  '@elastic/eui/no-css-color': 'warn',
@@ -0,0 +1,8 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ type MessageIds = 'childrenHavePlainText' | 'childrenHaveText' | 'childrenHaveActions';
3
+ type Options = [{
4
+ components?: string[];
5
+ }];
6
+ export declare const CallOutPreferPropsForContent: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
7
+ export {};
8
+ //# sourceMappingURL=callout_prefer_props_for_content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callout_prefer_props_for_content.d.ts","sourceRoot":"","sources":["../../../src/rules/callout_prefer_props_for_content.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAqBtE,KAAK,UAAU,GACX,uBAAuB,GACvB,kBAAkB,GAClB,qBAAqB,CAAC;AAC1B,KAAK,OAAO,GAAG,CAAC;IAAE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAsI3C,eAAO,MAAM,4BAA4B,gFAyExC,CAAC"}
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CallOutPreferPropsForContent = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _constants = require("../utils/constants");
9
+ var _get_element_name = require("../utils/get_element_name");
10
+ /*
11
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
12
+ * or more contributor license agreements. Licensed under the Elastic License
13
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
14
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
15
+ * Side Public License, v 1.
16
+ */
17
+
18
+ const DEFAULT_COMPONENTS = ['EuiCallOut'];
19
+ const EUI_ACTION_COMPONENTS = new Set(_constants.INTERACTIVE_EUI_COMPONENTS.filter(c => c.startsWith('EuiButton') || c === 'EuiLink'));
20
+ /**
21
+ * Recursively checks a node for text or action elements that should not be in callout children.
22
+ * It unwraps fragments, layout containers, conditional expressions, and logical expressions.
23
+ * It does NOT traverse into custom/complex component children.
24
+ */
25
+ function checkNode(node, context, componentName) {
26
+ switch (node.type) {
27
+ case 'JSXText':
28
+ {
29
+ // Plain string content
30
+ if (node.value.trim().length > 0) {
31
+ context.report({
32
+ node,
33
+ messageId: 'childrenHavePlainText',
34
+ data: {
35
+ componentName
36
+ }
37
+ });
38
+ }
39
+ break;
40
+ }
41
+ case 'Literal':
42
+ {
43
+ const {
44
+ value
45
+ } = node;
46
+ if (typeof value === 'string' && value.trim().length > 0) {
47
+ context.report({
48
+ node,
49
+ messageId: 'childrenHavePlainText',
50
+ data: {
51
+ componentName
52
+ }
53
+ });
54
+ }
55
+ break;
56
+ }
57
+ case 'JSXElement':
58
+ {
59
+ const el = node;
60
+ const {
61
+ name
62
+ } = el.openingElement;
63
+ if (name.type === 'JSXMemberExpression' && name.object.type === 'JSXIdentifier' && name.object.name === 'React' && name.property.type === 'JSXIdentifier' && name.property.name === 'Fragment') {
64
+ for (const child of el.children) {
65
+ checkNode(child, context, componentName);
66
+ }
67
+ break;
68
+ }
69
+ const elementName = (0, _get_element_name.getElementName)(el.openingElement);
70
+ if (!elementName) return;
71
+ if (_constants.HTML_TEXT_ELEMENTS.has(elementName) || _constants.EUI_TEXT_COMPONENTS.has(elementName) || _constants.I18N_TEXT_COMPONENTS.has(elementName)) {
72
+ context.report({
73
+ node,
74
+ messageId: 'childrenHaveText',
75
+ data: {
76
+ elementName,
77
+ componentName
78
+ }
79
+ });
80
+ } else if (_constants.HTML_ACTION_ELEMENTS.has(elementName) || EUI_ACTION_COMPONENTS.has(elementName)) {
81
+ context.report({
82
+ node,
83
+ messageId: 'childrenHaveActions',
84
+ data: {
85
+ elementName,
86
+ componentName
87
+ }
88
+ });
89
+ } else if (_constants.CALLOUT_LAYOUT_CONTAINERS.has(elementName)) {
90
+ // Transparent layout wrapper, traverse its children
91
+ for (const child of el.children) {
92
+ checkNode(child, context, componentName);
93
+ }
94
+ }
95
+
96
+ // Custom/complex component, don't traverse its children
97
+ break;
98
+ }
99
+ case 'JSXFragment':
100
+ {
101
+ for (const child of node.children) {
102
+ checkNode(child, context, componentName);
103
+ }
104
+ break;
105
+ }
106
+ case 'MemberExpression':
107
+ {
108
+ const {
109
+ object
110
+ } = node;
111
+ if (object.type === 'Identifier' && object.name === 'i18n') {
112
+ context.report({
113
+ node,
114
+ messageId: 'childrenHavePlainText',
115
+ data: {
116
+ componentName
117
+ }
118
+ });
119
+ }
120
+ break;
121
+ }
122
+ case 'TemplateLiteral':
123
+ {
124
+ context.report({
125
+ node,
126
+ messageId: 'childrenHavePlainText',
127
+ data: {
128
+ componentName
129
+ }
130
+ });
131
+ break;
132
+ }
133
+ case 'ArrayExpression':
134
+ {
135
+ for (const element of node.elements) {
136
+ if (element) checkNode(element, context, componentName);
137
+ }
138
+ break;
139
+ }
140
+ case 'JSXExpressionContainer':
141
+ {
142
+ const {
143
+ expression
144
+ } = node;
145
+ if (expression.type !== 'JSXEmptyExpression') {
146
+ checkNode(expression, context, componentName);
147
+ }
148
+ break;
149
+ }
150
+ case 'LogicalExpression':
151
+ {
152
+ const {
153
+ left,
154
+ right
155
+ } = node;
156
+ checkNode(left, context, componentName);
157
+ checkNode(right, context, componentName);
158
+ break;
159
+ }
160
+ case 'ConditionalExpression':
161
+ {
162
+ // e.g. {condition ? <p>text</p> : null}
163
+ const {
164
+ consequent,
165
+ alternate
166
+ } = node;
167
+ checkNode(consequent, context, componentName);
168
+ checkNode(alternate, context, componentName);
169
+ break;
170
+ }
171
+ default:
172
+ break;
173
+ }
174
+ }
175
+ const CallOutPreferPropsForContent = exports.CallOutPreferPropsForContent = _utils.ESLintUtils.RuleCreator.withoutDocs({
176
+ create(context) {
177
+ const components = new Set(context.options[0]?.components ?? DEFAULT_COMPONENTS);
178
+ return {
179
+ JSXElement(node) {
180
+ const {
181
+ openingElement,
182
+ children
183
+ } = node;
184
+ if (openingElement.name.type !== 'JSXIdentifier' || !components.has(openingElement.name.name)) {
185
+ return;
186
+ }
187
+ for (const child of children) {
188
+ checkNode(child, context, openingElement.name.name);
189
+ }
190
+ }
191
+ };
192
+ },
193
+ meta: {
194
+ type: 'suggestion',
195
+ docs: {
196
+ description: ['Enforce correct usage of `text` and `actionProps` props and discourage using `children` for content.', 'Text elements should be passed via the `text` prop.', 'Action elements (buttons, links) should be passed via `actionProps` instead.'].join(' ')
197
+ },
198
+ schema: [{
199
+ type: 'object',
200
+ properties: {
201
+ components: {
202
+ type: 'array',
203
+ items: {
204
+ type: 'string',
205
+ minLength: 1
206
+ },
207
+ minItems: 1
208
+ }
209
+ },
210
+ additionalProperties: false
211
+ }],
212
+ messages: {
213
+ childrenHavePlainText: ['Plain text passed as `children` of `{{componentName}}` should be moved to the `text` prop instead.', 'Example:', ' <{{componentName}} title="Callout title" text="Callout text content" />'].join('\n'),
214
+ childrenHaveText: ['`<{{elementName}}>` passed as `children` of `{{componentName}}` should be moved to the `text` prop instead.', 'The `text` prop accepts string text, block text elements (e.g. `<p>`) or inline elements (e.g. `<span>`, `<strong>`, `<em>`).', 'Use `children` only for complex non-text content that cannot be expressed via `text`.', 'Example:', ' <{{componentName}} title="Callout title" text={<p>Callout text content</p>} />'].join('\n'),
215
+ childrenHaveActions: ['`<{{elementName}}>` passed as `children` of `{{componentName}}` should be moved to the `actionProps` prop instead.', 'Use `actionProps` to render standardized primary or secondary action buttons, including buttons-as-links via `href`.', 'If a link is part of inline copy, move the surrounding content to the `text` prop instead.', 'Example:', ' <{{componentName}}', ' title="Callout title"', ' actionProps={{ primary: { children: "Primary action", onClick: onClick }, secondary: { children: "Secondary action", href: "/"} }}', ' />'].join('\n')
216
+ }
217
+ },
218
+ defaultOptions: [{}]
219
+ });
@@ -31,4 +31,18 @@ export declare const INTERACTIVE_EUI_COMPONENTS: string[];
31
31
  * exclude these.
32
32
  */
33
33
  export declare const CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS: string[];
34
+ export declare const HTML_TEXT_ELEMENTS: Set<string>;
35
+ export declare const EUI_TEXT_COMPONENTS: Set<string>;
36
+ export declare const HTML_ACTION_ELEMENTS: Set<string>;
37
+ /**
38
+ * Transparent layout wrappers inside `EuiCallOut` children that the rule should
39
+ * traverse rather than treat as opaque custom components.
40
+ */
41
+ export declare const CALLOUT_LAYOUT_CONTAINERS: Set<string>;
42
+ /**
43
+ * Third-party i18n components that render plain text and are common in EUI consumers
44
+ * (e.g. `FormattedMessage` from react-intl used extensively in Kibana).
45
+ * Rules treat these the same as `HTML_TEXT_ELEMENTS` / `EUI_TEXT_COMPONENTS`.
46
+ */
47
+ export declare const I18N_TEXT_COMPONENTS: Set<string>;
34
48
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/utils/constants.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,UAyCrC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,UA8CtC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,UAIpD,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/utils/constants.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,UAyCrC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,UA8CtC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,UAIpD,CAAC;AAEF,eAAO,MAAM,kBAAkB,aAS7B,CAAC;AAEH,eAAO,MAAM,mBAAmB,aAO9B,CAAC;AAEH,eAAO,MAAM,oBAAoB,aAA2B,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,yBAAyB,aAMpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,aAAgC,CAAC"}
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.NON_INTERACTIVE_HTML_TAGS = exports.INTERACTIVE_EUI_COMPONENTS = exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = void 0;
6
+ exports.NON_INTERACTIVE_HTML_TAGS = exports.INTERACTIVE_EUI_COMPONENTS = exports.I18N_TEXT_COMPONENTS = exports.HTML_TEXT_ELEMENTS = exports.HTML_ACTION_ELEMENTS = exports.EUI_TEXT_COMPONENTS = exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = exports.CALLOUT_LAYOUT_CONTAINERS = void 0;
7
7
  /*
8
8
  * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
9
9
  * or more contributor license agreements. Licensed under the Elastic License
@@ -46,4 +46,20 @@ const INTERACTIVE_EUI_COMPONENTS = exports.INTERACTIVE_EUI_COMPONENTS = ['EuiBad
46
46
  * so rules that need to distinguish unconditionally-interactive components should
47
47
  * exclude these.
48
48
  */
49
- const CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = ['EuiBadge', 'EuiBetaBadge', 'EuiCard'];
49
+ const CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = ['EuiBadge', 'EuiBetaBadge', 'EuiCard'];
50
+ const HTML_TEXT_ELEMENTS = exports.HTML_TEXT_ELEMENTS = new Set(['p', 'span', 'strong', 'em', 'b', 'i', 'small', 'code']);
51
+ const EUI_TEXT_COMPONENTS = exports.EUI_TEXT_COMPONENTS = new Set(['EuiText', 'EuiTextColor', 'EuiTextAlign', 'EuiCode', 'EuiMark', 'EuiHighlight']);
52
+ const HTML_ACTION_ELEMENTS = exports.HTML_ACTION_ELEMENTS = new Set(['button', 'a']);
53
+
54
+ /**
55
+ * Transparent layout wrappers inside `EuiCallOut` children that the rule should
56
+ * traverse rather than treat as opaque custom components.
57
+ */
58
+ const CALLOUT_LAYOUT_CONTAINERS = exports.CALLOUT_LAYOUT_CONTAINERS = new Set(['Fragment', 'EuiFlexGroup', 'EuiFlexGrid', 'EuiFlexItem', 'div']);
59
+
60
+ /**
61
+ * Third-party i18n components that render plain text and are common in EUI consumers
62
+ * (e.g. `FormattedMessage` from react-intl used extensively in Kibana).
63
+ * Rules treat these the same as `HTML_TEXT_ELEMENTS` / `EUI_TEXT_COMPONENTS`.
64
+ */
65
+ const I18N_TEXT_COMPONENTS = exports.I18N_TEXT_COMPONENTS = new Set(['FormattedMessage']);
@@ -0,0 +1,3 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ export declare function getElementName(openingElement: TSESTree.JSXOpeningElement): string | null;
3
+ //# sourceMappingURL=get_element_name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get_element_name.d.ts","sourceRoot":"","sources":["../../../src/utils/get_element_name.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,wBAAgB,cAAc,CAC5B,cAAc,EAAE,QAAQ,CAAC,iBAAiB,GACzC,MAAM,GAAG,IAAI,CAMf"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getElementName = getElementName;
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
+ function getElementName(openingElement) {
16
+ const {
17
+ name
18
+ } = openingElement;
19
+ if (name.type === 'JSXIdentifier') return name.name;
20
+ return null;
21
+ }
package/lib/esm/index.js CHANGED
@@ -9,6 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  const accessible_interactive_element_1 = require("./rules/a11y/accessible_interactive_element");
11
11
  const callout_announce_on_mount_1 = require("./rules/a11y/callout_announce_on_mount");
12
+ const callout_prefer_props_for_content_1 = require("./rules/callout_prefer_props_for_content");
12
13
  const consistent_is_invalid_props_1 = require("./rules/a11y/consistent_is_invalid_props");
13
14
  const href_or_on_click_1 = require("./rules/href_or_on_click");
14
15
  const require_href_for_link_1 = require("./rules/require_href_for_link");
@@ -31,6 +32,7 @@ const config = {
31
32
  rules: {
32
33
  'accessible-interactive-element': accessible_interactive_element_1.AccessibleInteractiveElements,
33
34
  'callout-announce-on-mount': callout_announce_on_mount_1.CallOutAnnounceOnMount,
35
+ 'callout-prefer-props-for-content': callout_prefer_props_for_content_1.CallOutPreferPropsForContent,
34
36
  'consistent-is-invalid-props': consistent_is_invalid_props_1.ConsistentIsInvalidProps,
35
37
  'href-or-on-click': href_or_on_click_1.HrefOnClick,
36
38
  'no-css-color': no_css_color_1.NoCssColor,
@@ -56,6 +58,7 @@ const config = {
56
58
  rules: {
57
59
  '@elastic/eui/accessible-interactive-element': 'warn',
58
60
  '@elastic/eui/callout-announce-on-mount': 'warn',
61
+ '@elastic/eui/callout-prefer-props-for-content': 'warn',
59
62
  '@elastic/eui/consistent-is-invalid-props': 'warn',
60
63
  '@elastic/eui/href-or-on-click': 'warn',
61
64
  '@elastic/eui/no-css-color': 'warn',
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAEH,gGAA4F;AAC5F,sFAAgF;AAChF,0FAAoF;AACpF,+DAAuD;AACvD,yEAAmE;AACnE,uDAAkD;AAClD,iFAA2E;AAC3E,iEAA2D;AAC3D,gGAA0F;AAC1F,gFAA0E;AAC1E,0EAAoE;AACpE,8FAAuF;AACvF,8EAAyE;AACzE,wFAA4F;AAC5F,oFAA+E;AAC/E,iHAAyG;AACzG,sFAAoF;AACpF,oFAAkF;AAClF,gGAA0F;AAC1F,oFAA8E;AAE9E,MAAM,MAAM,GAAG;IACb,KAAK,EAAE;QACL,gCAAgC,EAAE,8DAA6B;QAC/D,2BAA2B,EAAE,kDAAsB;QACnD,6BAA6B,EAAE,sDAAwB;QACvD,kBAAkB,EAAE,8BAAW;QAC/B,cAAc,EAAE,yBAAU;QAC1B,2BAA2B,EAAE,kDAAsB;QACnD,mBAAmB,EAAE,kCAAc;QACnC,gCAAgC,EAAE,4DAA2B;QAC7D,wBAAwB,EAAE,4CAAmB;QAC7C,qBAAqB,EAAE,sCAAgB;QACvC,+BAA+B,EAAE,yDAAyB;QAC1D,uBAAuB,EAAE,2CAAmB;QAC5C,4BAA4B,EAAE,8DAAiC;QAC/D,0BAA0B,EAAE,iDAAsB;QAClD,2CAA2C,EACzC,gFAAoC;QACtC,2BAA2B,EAAE,sDAA0B;QACvD,0BAA0B,EAAE,oDAAyB;QACrD,uBAAuB,EAAE,0CAAkB;QAC3C,gCAAgC,EAAE,4DAA2B;QAC7D,0BAA0B,EAAE,gDAAqB;KAClD;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,4BAA4B,CAAC;YACvC,KAAK,EAAE;gBACL,6CAA6C,EAAE,MAAM;gBACrD,wCAAwC,EAAE,MAAM;gBAChD,0CAA0C,EAAE,MAAM;gBAClD,+BAA+B,EAAE,MAAM;gBACvC,2BAA2B,EAAE,MAAM;gBACnC,wCAAwC,EAAE,MAAM;gBAChD,gCAAgC,EAAE,MAAM;gBACxC,6CAA6C,EAAE,MAAM;gBACrD,qCAAqC,EAAE,MAAM;gBAC7C,kCAAkC,EAAE,MAAM;gBAC1C,4CAA4C,EAAE,MAAM;gBACpD,oCAAoC,EAAE,MAAM;gBAC5C,yCAAyC,EAAE,MAAM;gBACjD,uCAAuC,EAAE,MAAM;gBAC/C,wDAAwD,EAAE,MAAM;gBAChE,wCAAwC,EAAE,MAAM;gBAChD,uCAAuC,EAAE,MAAM;gBAC/C,oCAAoC,EAAE,MAAM;gBAC5C,6CAA6C,EAAE,MAAM;gBACrD,uCAAuC,EAAE,MAAM;aAChD;SACF;KACF;CACF,CAAC;AAEF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAEH,gGAA4F;AAC5F,sFAAgF;AAChF,+FAAwF;AACxF,0FAAoF;AACpF,+DAAuD;AACvD,yEAAmE;AACnE,uDAAkD;AAClD,iFAA2E;AAC3E,iEAA2D;AAC3D,gGAA0F;AAC1F,gFAA0E;AAC1E,0EAAoE;AACpE,8FAAuF;AACvF,8EAAyE;AACzE,wFAA4F;AAC5F,oFAA+E;AAC/E,iHAAyG;AACzG,sFAAoF;AACpF,oFAAkF;AAClF,gGAA0F;AAC1F,oFAA8E;AAE9E,MAAM,MAAM,GAAG;IACb,KAAK,EAAE;QACL,gCAAgC,EAAE,8DAA6B;QAC/D,2BAA2B,EAAE,kDAAsB;QACnD,kCAAkC,EAAE,+DAA4B;QAChE,6BAA6B,EAAE,sDAAwB;QACvD,kBAAkB,EAAE,8BAAW;QAC/B,cAAc,EAAE,yBAAU;QAC1B,2BAA2B,EAAE,kDAAsB;QACnD,mBAAmB,EAAE,kCAAc;QACnC,gCAAgC,EAAE,4DAA2B;QAC7D,wBAAwB,EAAE,4CAAmB;QAC7C,qBAAqB,EAAE,sCAAgB;QACvC,+BAA+B,EAAE,yDAAyB;QAC1D,uBAAuB,EAAE,2CAAmB;QAC5C,4BAA4B,EAAE,8DAAiC;QAC/D,0BAA0B,EAAE,iDAAsB;QAClD,2CAA2C,EACzC,gFAAoC;QACtC,2BAA2B,EAAE,sDAA0B;QACvD,0BAA0B,EAAE,oDAAyB;QACrD,uBAAuB,EAAE,0CAAkB;QAC3C,gCAAgC,EAAE,4DAA2B;QAC7D,0BAA0B,EAAE,gDAAqB;KAClD;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,4BAA4B,CAAC;YACvC,KAAK,EAAE;gBACL,6CAA6C,EAAE,MAAM;gBACrD,wCAAwC,EAAE,MAAM;gBAChD,+CAA+C,EAAE,MAAM;gBACvD,0CAA0C,EAAE,MAAM;gBAClD,+BAA+B,EAAE,MAAM;gBACvC,2BAA2B,EAAE,MAAM;gBACnC,wCAAwC,EAAE,MAAM;gBAChD,gCAAgC,EAAE,MAAM;gBACxC,6CAA6C,EAAE,MAAM;gBACrD,qCAAqC,EAAE,MAAM;gBAC7C,kCAAkC,EAAE,MAAM;gBAC1C,4CAA4C,EAAE,MAAM;gBACpD,oCAAoC,EAAE,MAAM;gBAC5C,yCAAyC,EAAE,MAAM;gBACjD,uCAAuC,EAAE,MAAM;gBAC/C,wDAAwD,EAAE,MAAM;gBAChE,wCAAwC,EAAE,MAAM;gBAChD,uCAAuC,EAAE,MAAM;gBAC/C,oCAAoC,EAAE,MAAM;gBAC5C,6CAA6C,EAAE,MAAM;gBACrD,uCAAuC,EAAE,MAAM;aAChD;SACF;KACF;CACF,CAAC;AAEF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ type MessageIds = 'childrenHavePlainText' | 'childrenHaveText' | 'childrenHaveActions';
3
+ type Options = [{
4
+ components?: string[];
5
+ }];
6
+ export declare const CallOutPreferPropsForContent: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
7
+ export {};
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
4
+ * or more contributor license agreements. Licensed under the Elastic License
5
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
6
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
7
+ * Side Public License, v 1.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.CallOutPreferPropsForContent = void 0;
11
+ const utils_1 = require("@typescript-eslint/utils");
12
+ const constants_1 = require("../utils/constants");
13
+ const get_element_name_1 = require("../utils/get_element_name");
14
+ const DEFAULT_COMPONENTS = ['EuiCallOut'];
15
+ const EUI_ACTION_COMPONENTS = new Set(constants_1.INTERACTIVE_EUI_COMPONENTS.filter((c) => c.startsWith('EuiButton') || c === 'EuiLink'));
16
+ /**
17
+ * Recursively checks a node for text or action elements that should not be in callout children.
18
+ * It unwraps fragments, layout containers, conditional expressions, and logical expressions.
19
+ * It does NOT traverse into custom/complex component children.
20
+ */
21
+ function checkNode(node, context, componentName) {
22
+ switch (node.type) {
23
+ case 'JSXText': {
24
+ // Plain string content
25
+ if (node.value.trim().length > 0) {
26
+ context.report({ node, messageId: 'childrenHavePlainText', data: { componentName } });
27
+ }
28
+ break;
29
+ }
30
+ case 'Literal': {
31
+ const { value } = node;
32
+ if (typeof value === 'string' && value.trim().length > 0) {
33
+ context.report({ node, messageId: 'childrenHavePlainText', data: { componentName } });
34
+ }
35
+ break;
36
+ }
37
+ case 'JSXElement': {
38
+ const el = node;
39
+ const { name } = el.openingElement;
40
+ if (name.type === 'JSXMemberExpression' &&
41
+ name.object.type === 'JSXIdentifier' &&
42
+ name.object.name === 'React' &&
43
+ name.property.type === 'JSXIdentifier' &&
44
+ name.property.name === 'Fragment') {
45
+ for (const child of el.children) {
46
+ checkNode(child, context, componentName);
47
+ }
48
+ break;
49
+ }
50
+ const elementName = (0, get_element_name_1.getElementName)(el.openingElement);
51
+ if (!elementName)
52
+ return;
53
+ if (constants_1.HTML_TEXT_ELEMENTS.has(elementName) ||
54
+ constants_1.EUI_TEXT_COMPONENTS.has(elementName) ||
55
+ constants_1.I18N_TEXT_COMPONENTS.has(elementName)) {
56
+ context.report({
57
+ node,
58
+ messageId: 'childrenHaveText',
59
+ data: { elementName, componentName },
60
+ });
61
+ }
62
+ else if (constants_1.HTML_ACTION_ELEMENTS.has(elementName) ||
63
+ EUI_ACTION_COMPONENTS.has(elementName)) {
64
+ context.report({
65
+ node,
66
+ messageId: 'childrenHaveActions',
67
+ data: { elementName, componentName },
68
+ });
69
+ }
70
+ else if (constants_1.CALLOUT_LAYOUT_CONTAINERS.has(elementName)) {
71
+ // Transparent layout wrapper, traverse its children
72
+ for (const child of el.children) {
73
+ checkNode(child, context, componentName);
74
+ }
75
+ }
76
+ // Custom/complex component, don't traverse its children
77
+ break;
78
+ }
79
+ case 'JSXFragment': {
80
+ for (const child of node.children) {
81
+ checkNode(child, context, componentName);
82
+ }
83
+ break;
84
+ }
85
+ case 'MemberExpression': {
86
+ const { object } = node;
87
+ if (object.type === 'Identifier' && object.name === 'i18n') {
88
+ context.report({ node, messageId: 'childrenHavePlainText', data: { componentName } });
89
+ }
90
+ break;
91
+ }
92
+ case 'TemplateLiteral': {
93
+ context.report({ node, messageId: 'childrenHavePlainText', data: { componentName } });
94
+ break;
95
+ }
96
+ case 'ArrayExpression': {
97
+ for (const element of node.elements) {
98
+ if (element)
99
+ checkNode(element, context, componentName);
100
+ }
101
+ break;
102
+ }
103
+ case 'JSXExpressionContainer': {
104
+ const { expression } = node;
105
+ if (expression.type !== 'JSXEmptyExpression') {
106
+ checkNode(expression, context, componentName);
107
+ }
108
+ break;
109
+ }
110
+ case 'LogicalExpression': {
111
+ const { left, right } = node;
112
+ checkNode(left, context, componentName);
113
+ checkNode(right, context, componentName);
114
+ break;
115
+ }
116
+ case 'ConditionalExpression': {
117
+ // e.g. {condition ? <p>text</p> : null}
118
+ const { consequent, alternate } = node;
119
+ checkNode(consequent, context, componentName);
120
+ checkNode(alternate, context, componentName);
121
+ break;
122
+ }
123
+ default:
124
+ break;
125
+ }
126
+ }
127
+ exports.CallOutPreferPropsForContent = utils_1.ESLintUtils.RuleCreator.withoutDocs({
128
+ create(context) {
129
+ const components = new Set(context.options[0]?.components ?? DEFAULT_COMPONENTS);
130
+ return {
131
+ JSXElement(node) {
132
+ const { openingElement, children } = node;
133
+ if (openingElement.name.type !== 'JSXIdentifier' ||
134
+ !components.has(openingElement.name.name)) {
135
+ return;
136
+ }
137
+ for (const child of children) {
138
+ checkNode(child, context, openingElement.name.name);
139
+ }
140
+ },
141
+ };
142
+ },
143
+ meta: {
144
+ type: 'suggestion',
145
+ docs: {
146
+ description: [
147
+ 'Enforce correct usage of `text` and `actionProps` props and discourage using `children` for content.',
148
+ 'Text elements should be passed via the `text` prop.',
149
+ 'Action elements (buttons, links) should be passed via `actionProps` instead.',
150
+ ].join(' '),
151
+ },
152
+ schema: [
153
+ {
154
+ type: 'object',
155
+ properties: {
156
+ components: {
157
+ type: 'array',
158
+ items: { type: 'string', minLength: 1 },
159
+ minItems: 1,
160
+ },
161
+ },
162
+ additionalProperties: false,
163
+ },
164
+ ],
165
+ messages: {
166
+ childrenHavePlainText: [
167
+ 'Plain text passed as `children` of `{{componentName}}` should be moved to the `text` prop instead.',
168
+ 'Example:',
169
+ ' <{{componentName}} title="Callout title" text="Callout text content" />',
170
+ ].join('\n'),
171
+ childrenHaveText: [
172
+ '`<{{elementName}}>` passed as `children` of `{{componentName}}` should be moved to the `text` prop instead.',
173
+ 'The `text` prop accepts string text, block text elements (e.g. `<p>`) or inline elements (e.g. `<span>`, `<strong>`, `<em>`).',
174
+ 'Use `children` only for complex non-text content that cannot be expressed via `text`.',
175
+ 'Example:',
176
+ ' <{{componentName}} title="Callout title" text={<p>Callout text content</p>} />',
177
+ ].join('\n'),
178
+ childrenHaveActions: [
179
+ '`<{{elementName}}>` passed as `children` of `{{componentName}}` should be moved to the `actionProps` prop instead.',
180
+ 'Use `actionProps` to render standardized primary or secondary action buttons, including buttons-as-links via `href`.',
181
+ 'If a link is part of inline copy, move the surrounding content to the `text` prop instead.',
182
+ 'Example:',
183
+ ' <{{componentName}}',
184
+ ' title="Callout title"',
185
+ ' actionProps={{ primary: { children: "Primary action", onClick: onClick }, secondary: { children: "Secondary action", href: "/"} }}',
186
+ ' />',
187
+ ].join('\n'),
188
+ },
189
+ },
190
+ defaultOptions: [{}],
191
+ });
192
+ //# sourceMappingURL=callout_prefer_props_for_content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callout_prefer_props_for_content.js","sourceRoot":"","sources":["../../../src/rules/callout_prefer_props_for_content.ts"],"names":[],"mappings":"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"}
@@ -31,3 +31,17 @@ export declare const INTERACTIVE_EUI_COMPONENTS: string[];
31
31
  * exclude these.
32
32
  */
33
33
  export declare const CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS: string[];
34
+ export declare const HTML_TEXT_ELEMENTS: Set<string>;
35
+ export declare const EUI_TEXT_COMPONENTS: Set<string>;
36
+ export declare const HTML_ACTION_ELEMENTS: Set<string>;
37
+ /**
38
+ * Transparent layout wrappers inside `EuiCallOut` children that the rule should
39
+ * traverse rather than treat as opaque custom components.
40
+ */
41
+ export declare const CALLOUT_LAYOUT_CONTAINERS: Set<string>;
42
+ /**
43
+ * Third-party i18n components that render plain text and are common in EUI consumers
44
+ * (e.g. `FormattedMessage` from react-intl used extensively in Kibana).
45
+ * Rules treat these the same as `HTML_TEXT_ELEMENTS` / `EUI_TEXT_COMPONENTS`.
46
+ */
47
+ export declare const I18N_TEXT_COMPONENTS: Set<string>;
@@ -7,7 +7,7 @@
7
7
  * Side Public License, v 1.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = exports.INTERACTIVE_EUI_COMPONENTS = exports.NON_INTERACTIVE_HTML_TAGS = void 0;
10
+ exports.I18N_TEXT_COMPONENTS = exports.CALLOUT_LAYOUT_CONTAINERS = exports.HTML_ACTION_ELEMENTS = exports.EUI_TEXT_COMPONENTS = exports.HTML_TEXT_ELEMENTS = exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = exports.INTERACTIVE_EUI_COMPONENTS = exports.NON_INTERACTIVE_HTML_TAGS = void 0;
11
11
  /**
12
12
  * A list of standard HTML tags that are considered **non-interactive** elements.
13
13
  *
@@ -132,4 +132,40 @@ exports.CONDITIONALLY_INTERACTIVE_EUI_COMPONENTS = [
132
132
  'EuiBetaBadge',
133
133
  'EuiCard',
134
134
  ];
135
+ exports.HTML_TEXT_ELEMENTS = new Set([
136
+ 'p',
137
+ 'span',
138
+ 'strong',
139
+ 'em',
140
+ 'b',
141
+ 'i',
142
+ 'small',
143
+ 'code',
144
+ ]);
145
+ exports.EUI_TEXT_COMPONENTS = new Set([
146
+ 'EuiText',
147
+ 'EuiTextColor',
148
+ 'EuiTextAlign',
149
+ 'EuiCode',
150
+ 'EuiMark',
151
+ 'EuiHighlight',
152
+ ]);
153
+ exports.HTML_ACTION_ELEMENTS = new Set(['button', 'a']);
154
+ /**
155
+ * Transparent layout wrappers inside `EuiCallOut` children that the rule should
156
+ * traverse rather than treat as opaque custom components.
157
+ */
158
+ exports.CALLOUT_LAYOUT_CONTAINERS = new Set([
159
+ 'Fragment',
160
+ 'EuiFlexGroup',
161
+ 'EuiFlexGrid',
162
+ 'EuiFlexItem',
163
+ 'div',
164
+ ]);
165
+ /**
166
+ * Third-party i18n components that render plain text and are common in EUI consumers
167
+ * (e.g. `FormattedMessage` from react-intl used extensively in Kibana).
168
+ * Rules treat these the same as `HTML_TEXT_ELEMENTS` / `EUI_TEXT_COMPONENTS`.
169
+ */
170
+ exports.I18N_TEXT_COMPONENTS = new Set(['FormattedMessage']);
135
171
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/utils/constants.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH;;;;;;;;;;;;GAYG;AACU,QAAA,yBAAyB,GAAG;IACvC,KAAK;IACL,MAAM;IACN,GAAG;IACH,SAAS;IACT,OAAO;IACP,YAAY;IACZ,IAAI;IACJ,SAAS;IACT,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,YAAY;IACZ,QAAQ;IACR,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,MAAM;IACN,KAAK;IACL,IAAI;IACJ,KAAK;IACL,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,KAAK;IACL,OAAO;IACP,OAAO;IACP,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,IAAI;CACL,CAAC;AAEF;;;;;;;;;;GAUG;AACU,QAAA,0BAA0B,GAAG;IACxC,UAAU;IACV,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,gBAAgB;IAChB,eAAe;IACf,SAAS;IACT,kBAAkB;IAClB,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,oBAAoB;IACpB,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB;IAClB,gBAAgB;IAChB,cAAc;IACd,iBAAiB;IACjB,qBAAqB;IACrB,qBAAqB;IACrB,eAAe;IACf,eAAe;IACf,4BAA4B;IAC5B,kBAAkB;IAClB,mBAAmB;IACnB,SAAS;IACT,kBAAkB;IAClB,eAAe;IACf,sBAAsB;IACtB,UAAU;IACV,UAAU;IACV,WAAW;IACX,eAAe;IACf,YAAY;IACZ,mBAAmB;IACnB,oBAAoB;IACpB,gBAAgB;IAChB,WAAW;IACX,QAAQ;IACR,aAAa;IACb,aAAa;CACd,CAAC;AAEF;;;;;GAKG;AACU,QAAA,wCAAwC,GAAG;IACtD,UAAU;IACV,cAAc;IACd,SAAS;CACV,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/utils/constants.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH;;;;;;;;;;;;GAYG;AACU,QAAA,yBAAyB,GAAG;IACvC,KAAK;IACL,MAAM;IACN,GAAG;IACH,SAAS;IACT,OAAO;IACP,YAAY;IACZ,IAAI;IACJ,SAAS;IACT,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,YAAY;IACZ,QAAQ;IACR,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,MAAM;IACN,KAAK;IACL,IAAI;IACJ,KAAK;IACL,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,KAAK;IACL,OAAO;IACP,OAAO;IACP,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,IAAI;CACL,CAAC;AAEF;;;;;;;;;;GAUG;AACU,QAAA,0BAA0B,GAAG;IACxC,UAAU;IACV,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,gBAAgB;IAChB,eAAe;IACf,SAAS;IACT,kBAAkB;IAClB,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,oBAAoB;IACpB,eAAe;IACf,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB;IAClB,gBAAgB;IAChB,cAAc;IACd,iBAAiB;IACjB,qBAAqB;IACrB,qBAAqB;IACrB,eAAe;IACf,eAAe;IACf,4BAA4B;IAC5B,kBAAkB;IAClB,mBAAmB;IACnB,SAAS;IACT,kBAAkB;IAClB,eAAe;IACf,sBAAsB;IACtB,UAAU;IACV,UAAU;IACV,WAAW;IACX,eAAe;IACf,YAAY;IACZ,mBAAmB;IACnB,oBAAoB;IACpB,gBAAgB;IAChB,WAAW;IACX,QAAQ;IACR,aAAa;IACb,aAAa;CACd,CAAC;AAEF;;;;;GAKG;AACU,QAAA,wCAAwC,GAAG;IACtD,UAAU;IACV,cAAc;IACd,SAAS;CACV,CAAC;AAEW,QAAA,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACxC,GAAG;IACH,MAAM;IACN,QAAQ;IACR,IAAI;IACJ,GAAG;IACH,GAAG;IACH,OAAO;IACP,MAAM;CACP,CAAC,CAAC;AAEU,QAAA,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACzC,SAAS;IACT,cAAc;IACd,cAAc;IACd,SAAS;IACT,SAAS;IACT,cAAc;CACf,CAAC,CAAC;AAEU,QAAA,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;AAE7D;;;GAGG;AACU,QAAA,yBAAyB,GAAG,IAAI,GAAG,CAAC;IAC/C,UAAU;IACV,cAAc;IACd,aAAa;IACb,aAAa;IACb,KAAK;CACN,CAAC,CAAC;AAEH;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type TSESTree } from '@typescript-eslint/utils';
2
+ export declare function getElementName(openingElement: TSESTree.JSXOpeningElement): string | null;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
4
+ * or more contributor license agreements. Licensed under the Elastic License
5
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
6
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
7
+ * Side Public License, v 1.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.getElementName = getElementName;
11
+ function getElementName(openingElement) {
12
+ const { name } = openingElement;
13
+ if (name.type === 'JSXIdentifier')
14
+ return name.name;
15
+ return null;
16
+ }
17
+ //# sourceMappingURL=get_element_name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get_element_name.js","sourceRoot":"","sources":["../../../src/utils/get_element_name.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAIH,wCAQC;AARD,SAAgB,cAAc,CAC5B,cAA0C;IAE1C,MAAM,EAAE,IAAI,EAAE,GAAG,cAAc,CAAC;IAEhC,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IAEpD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elastic/eslint-plugin-eui",
3
- "version": "2.14.1",
3
+ "version": "2.15.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",