eslint-plugin-primer-react 9.3.2-rc.8eef5f9 → 9.4.0-rc.a59f32a

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/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # eslint-plugin-primer-react
2
2
 
3
- ## 9.3.2
3
+ ## 9.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#705](https://github.com/primer/eslint-plugin-primer-react/pull/705) [`9b282ba`](https://github.com/primer/eslint-plugin-primer-react/commit/9b282babfb4436dee689eaeeec568b1f6b3ce8b4) Thanks [@rickyzhangca](https://github.com/rickyzhangca)! - Expand `no-deprecated-props` to cover known deprecated Primer props, including compound components and aliased imports.
4
8
 
5
9
  ### Patch Changes
6
10
 
@@ -1,48 +1,31 @@
1
1
  ## Rule Details
2
2
 
3
- This rule enforces to use the recommended API (`ActionList.GroupHeading`) component over the deprecated prop (`title` prop on `ActionList.Group`) for ActionList component.
3
+ Reports known deprecated Primer React props. Supports compound components, import
4
+ aliases, and namespace imports without TypeScript type information.
4
5
 
5
- 👎 Examples of **incorrect** code for this rule:
6
+ ### Incorrect
6
7
 
7
8
  ```jsx
8
- /* eslint primer-react/no-deprecated-props: "error" */
9
- import {ActionList} from '@primer/react'
10
-
11
- const App = () => (
12
- <ActionList>
13
- <ActionList.Group title="Group heading">
14
- <ActionList.Item>Item 1</ActionList.Item>
15
- </ActionList.Group>
16
- </ActionList>
17
- )
9
+ import {Autocomplete, Banner} from '@primer/react'
10
+
11
+ const input = <Autocomplete.Input openOnFocus />
12
+ const banner = <Banner icon={InfoIcon} />
18
13
  ```
19
14
 
20
- 👍 Examples of **correct** code for this rule:
15
+ ### Correct
21
16
 
22
17
  ```jsx
23
- /* eslint primer-react/no-deprecated-props: "error" */
24
- import {ActionList} from '@primer/react'
25
-
26
- const App = () => (
27
- <ActionList>
28
- <ActionList.Group>
29
- <ActionList.GroupHeading as="h2">Group heading</ActionList.GroupHeading>
30
- <ActionList.Item>Item 1</ActionList.Item>
31
- </ActionList.Group>
32
- </ActionList>
33
- )
34
- ```
18
+ import {Autocomplete, Banner} from '@primer/react'
35
19
 
36
- ```jsx
37
- /* eslint primer-react/no-deprecated-props: "error" */
38
- import {ActionList} from '@primer/react'
39
-
40
- const App = () => (
41
- <ActionList role="lisbox">
42
- <ActionList.Group>
43
- <ActionList.GroupHeading>Group heading</ActionList.GroupHeading>
44
- <ActionList.Item>Item 1</ActionList.Item>
45
- </ActionList.Group>
46
- </ActionList>
47
- )
20
+ const input = <Autocomplete.Input />
21
+ const banner = <Banner leadingVisual={InfoIcon} />
48
22
  ```
23
+
24
+ Removing `openOnFocus` changes when suggestions open; it has no replacement prop.
25
+
26
+ The [prop list](../../src/rules/no-deprecated-props.js) is maintained manually.
27
+ The rule checks explicit JSX props, not spreads, nested objects, wrappers,
28
+ re-exports, or `React.createElement`.
29
+
30
+ Only `ActionList.Group.title` has an autofix, for paired tags with string or
31
+ expression values. Other migrations require manual changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-primer-react",
3
- "version": "9.3.2-rc.8eef5f9",
3
+ "version": "9.4.0-rc.a59f32a",
4
4
  "description": "ESLint rules for Primer React",
5
5
  "main": "src/index.js",
6
6
  "engines": {
@@ -65,59 +65,154 @@ ruleTester.run('no-deprecated-props', rule, {
65
65
  </ActionList>`,
66
66
  ],
67
67
  invalid: [
68
+ [
69
+ `<ActionList.Group title="Group heading 1"></ActionList.Group>`,
70
+ `<ActionList.Group><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
71
+ ],
72
+ [
73
+ `<ActionList.Group title="Group heading 1" sx={{padding: 2}}></ActionList.Group>`,
74
+ `<ActionList.Group sx={{padding: 2}}><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
75
+ ],
76
+ [
77
+ `<ActionList.Group variant="filled" title="Group heading 1"></ActionList.Group>`,
78
+ `<ActionList.Group variant="filled"><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
79
+ ],
80
+ ...['titleVariable', "'Title'", "condition ? 'Title' : undefined"].map(title => [
81
+ `<ActionList.Group title={${title}}></ActionList.Group>`,
82
+ `<ActionList.Group><ActionList.GroupHeading>{${title}}</ActionList.GroupHeading></ActionList.Group>`,
83
+ ]),
84
+ ].map(([code, output]) => ({
85
+ code: `import {ActionList} from '@primer/react';\n${code}`,
86
+ output: `import {ActionList} from '@primer/react';\n${output}`,
87
+ errors: [{messageId: 'titlePropDeprecated'}],
88
+ })),
89
+ })
90
+
91
+ const deprecatedProps = [
92
+ ['ActionList.Item', 'as'],
93
+ ['Autocomplete.Input', 'openOnFocus'],
94
+ ['Autocomplete.Overlay', 'overlayProps'],
95
+ ['Banner', 'icon'],
96
+ ['CounterLabel', 'scheme'],
97
+ ['IconButton', 'keyshortcuts'],
98
+ ['Link', 'hoverColor'],
99
+ ['PageLayout.Header', 'dividerWhenNarrow'],
100
+ ['PageLayout.Pane', 'dividerWhenNarrow'],
101
+ ['PageLayout.Pane', 'positionWhenNarrow'],
102
+ ['PageLayout.Footer', 'dividerWhenNarrow'],
103
+ ['SegmentedControl.Button', 'icon'],
104
+ ['SegmentedControl.Button', 'leadingIcon'],
105
+ ['SelectPanel', 'footer'],
106
+ ['Spinner', 'aria-label'],
107
+ ['StateLabel', 'variant'],
108
+ ['TextInput.Action', 'children'],
109
+ ['TextInput.Action', 'variant'],
110
+ ['Textarea', 'contrast'],
111
+ ['ThemeProvider', 'preventSSRMismatch'],
112
+ ['UnderlineNav.Item', 'icon'],
113
+ ...['TextInput', 'TextInputWithTokens', 'Autocomplete.Input'].flatMap(component =>
114
+ ['icon', 'contrast', 'variant', 'width', 'minWidth', 'maxWidth'].map(prop => [component, prop]),
115
+ ),
116
+ ...['Button', 'LinkButton', 'ActionMenu.Button'].flatMap(component =>
117
+ ['leadingIcon', 'trailingIcon'].map(prop => [component, prop]),
118
+ ),
119
+ ]
120
+
121
+ ruleTester.run('no-deprecated-props catalogue', rule, {
122
+ valid: [
123
+ `import {ActionList} from '@primer/react'; <ActionList.LinkItem as="a" />`,
124
+ `import {Autocomplete} from '@primer/react'; <Autocomplete.Input onFocus={onFocus} />`,
125
+ `import {Autocomplete} from '@primer/react'; <Autocomplete.Overlay width="small" />`,
126
+ `import {Banner} from '@primer/react'; <Banner leadingVisual={Icon} />`,
127
+ `import {CounterLabel} from '@primer/react'; <CounterLabel variant="secondary" />`,
128
+ `import {IconButton} from '@primer/react'; <IconButton icon={Icon} keybindingHint="g" />`,
129
+ `import {Link} from '@primer/react'; <Link className="hover" />`,
130
+ `import {PageLayout} from '@primer/react'; <PageLayout.Pane divider={{narrow: 'line'}} position={{narrow: 'end'}} />`,
131
+ `import {SegmentedControl} from '@primer/react'; <SegmentedControl.Button leadingVisual={Icon} />`,
132
+ `import {SelectPanel} from '@primer/react'; <SelectPanel secondaryAction={action} />`,
133
+ `import {Spinner} from '@primer/react'; <Spinner srText="Loading" />`,
134
+ `import {StateLabel} from '@primer/react'; <StateLabel size="small" />`,
135
+ `import {TextInput} from '@primer/react'; <TextInput leadingVisual={Icon} size="small" style={{width: 100}} />`,
136
+ `import {TextInput} from '@primer/react'; <TextInput.Action icon={Icon}>{/* no children */}</TextInput.Action>`,
137
+ `import {UnderlineNav} from '@primer/react'; <UnderlineNav.Item leadingVisual={Icon} />`,
138
+ `import {Banner} from '@primer/react-brand'; <Banner icon={Icon} />`,
139
+ `import * as Primer from '@primer/react-brand'; <Primer.Autocomplete.Input openOnFocus />`,
140
+ `import {SelectPanel} from '@primer/react/experimental'; <SelectPanel footer={footer} />`,
141
+ `import {ActionList} from '@primer/react/deprecated'; <ActionList.Item as="a" />`,
142
+ `import {ActionList} from './local'; <ActionList.Group title="Local title" />`,
143
+ `const Autocomplete = local; <Autocomplete.Input openOnFocus />`,
144
+ `import {Banner} from '@primer/react'; function Example(Banner) { return <Banner icon={Icon} /> }`,
145
+ `import * as Primer from '@primer/react'; function Example(Primer) { return <Primer.Banner icon={Icon} /> }`,
146
+ `import {Autocomplete} from '@primer/react'; function Example(Autocomplete) { return <Autocomplete.Input openOnFocus /> }`,
147
+ `import {Banner} from '@primer/react'; <Banner {...props} />`,
148
+ `import * as Primer from '@primer/react'; <Primer.toString icon={Icon} />`,
149
+ `import {Banner} from '@primer/react'; <Banner toString={value} />`,
150
+ `<ActionList.Group title="Unresolved component" />`,
151
+ `<svg:path title="Not a component" />`,
152
+ ],
153
+ invalid: [
154
+ ...deprecatedProps.flatMap(([component, prop]) => {
155
+ const [root, ...members] = component.split('.')
156
+ const alias = ['Alias', ...members].join('.')
157
+ return [
158
+ `import {${root}} from '@primer/react'; <${component} ${prop}={value} />`,
159
+ `import {${root} as Alias} from '@primer/react'; <${alias} ${prop}={value} />`,
160
+ `import * as Primer from '@primer/react'; <Primer.${component} ${prop}={value} />`,
161
+ ].map(code => ({
162
+ code,
163
+ output: null,
164
+ errors: [{messageId: 'deprecatedProp'}],
165
+ }))
166
+ }),
68
167
  {
69
- code: `<ActionList.Group title="Group heading 1"></ActionList.Group>`,
70
- output: `<ActionList.Group><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
71
- errors: [
72
- {
73
- messageId: 'titlePropDeprecated',
74
- },
75
- ],
76
- },
77
- {
78
- code: `<ActionList.Group title="Group heading 1" sx={{padding: 2}}></ActionList.Group>`,
79
- output: `<ActionList.Group sx={{padding: 2}}><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
80
- errors: [
81
- {
82
- messageId: 'titlePropDeprecated',
83
- },
84
- ],
168
+ code: `import {Autocomplete as A} from '@primer/react'; <A.Input openOnFocus={false} />`,
169
+ output: null,
170
+ errors: [{messageId: 'deprecatedProp'}],
85
171
  },
86
172
  {
87
- code: `<ActionList.Group variant="filled" title="Group heading 1"></ActionList.Group>`,
88
- output: `<ActionList.Group variant="filled"><ActionList.GroupHeading>Group heading 1</ActionList.GroupHeading></ActionList.Group>`,
89
- errors: [
90
- {
91
- messageId: 'titlePropDeprecated',
92
- },
93
- ],
173
+ code: `import {TextInput} from '@primer/react'; <TextInput icon={Icon} contrast />`,
174
+ output: null,
175
+ errors: [{messageId: 'deprecatedProp'}, {messageId: 'deprecatedProp'}],
94
176
  },
177
+ ...['trailingIcon', 'trailingText'].map(prop => ({
178
+ code: `import {ActionList as Legacy} from '@primer/react/deprecated'; <Legacy.Item ${prop}={value} />`,
179
+ output: null,
180
+ errors: [{messageId: 'deprecatedProp'}],
181
+ })),
182
+ ...['icon', 'contrast', 'variant', 'width', 'minWidth', 'maxWidth'].map(prop => ({
183
+ code: `import {FilteredActionList} from '@primer/react/experimental'; <FilteredActionList.Input ${prop}={value} />`,
184
+ output: null,
185
+ errors: [{messageId: 'deprecatedProp'}],
186
+ })),
95
187
  {
96
- code: `<ActionList.Group title={titleVariable}></ActionList.Group>`,
97
- output: `<ActionList.Group><ActionList.GroupHeading>{titleVariable}</ActionList.GroupHeading></ActionList.Group>`,
98
- errors: [
99
- {
100
- messageId: 'titlePropDeprecated',
101
- },
102
- ],
188
+ code: `import {ThemeProvider} from '@primer/react/next'; <ThemeProvider preventSSRMismatch />`,
189
+ output: null,
190
+ errors: [{messageId: 'deprecatedProp'}],
103
191
  },
192
+ ...['Text', '{value}', '<span />', '<>Text</>'].map(children => ({
193
+ code: `import {TextInput} from '@primer/react'; <TextInput.Action>${children}</TextInput.Action>`,
194
+ output: null,
195
+ errors: [{messageId: 'deprecatedProp'}],
196
+ })),
104
197
  {
105
- code: `<ActionList.Group title={'Title'}></ActionList.Group>`,
106
- output: `<ActionList.Group><ActionList.GroupHeading>{'Title'}</ActionList.GroupHeading></ActionList.Group>`,
107
- errors: [
108
- {
109
- messageId: 'titlePropDeprecated',
110
- },
111
- ],
198
+ code: `import {ActionList as List} from '@primer/react'; <List.Group title="Title"></List.Group>`,
199
+ output: `import {ActionList as List} from '@primer/react'; <List.Group><List.GroupHeading>Title</List.GroupHeading></List.Group>`,
200
+ errors: [{messageId: 'titlePropDeprecated'}],
112
201
  },
113
202
  {
114
- code: `<ActionList.Group title={condition ? 'Title' : undefined}></ActionList.Group>`,
115
- output: `<ActionList.Group><ActionList.GroupHeading>{condition ? 'Title' : undefined}</ActionList.GroupHeading></ActionList.Group>`,
116
- errors: [
117
- {
118
- messageId: 'titlePropDeprecated',
119
- },
120
- ],
203
+ code: `import * as Primer from '@primer/react'; <Primer.ActionList.Group title="A &amp; B"></Primer.ActionList.Group>`,
204
+ output: `import * as Primer from '@primer/react'; <Primer.ActionList.Group><Primer.ActionList.GroupHeading>A &amp; B</Primer.ActionList.GroupHeading></Primer.ActionList.Group>`,
205
+ errors: [{messageId: 'titlePropDeprecated'}],
121
206
  },
207
+ ...['title', 'title="Title"', 'title={title}'].map(attribute => ({
208
+ code: `import {ActionList} from '@primer/react'; <ActionList.Group ${attribute} />`,
209
+ output: null,
210
+ errors: [{messageId: 'titlePropDeprecated'}],
211
+ })),
212
+ ...['title', 'title="{literal}"'].map(attribute => ({
213
+ code: `import {ActionList} from '@primer/react'; <ActionList.Group ${attribute}></ActionList.Group>`,
214
+ output: null,
215
+ errors: [{messageId: 'titlePropDeprecated'}],
216
+ })),
122
217
  ],
123
218
  })
@@ -1,6 +1,128 @@
1
1
  'use strict'
2
- const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
3
- const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
2
+ const {getVariableDeclaration} = require('../utils/get-variable-declaration')
3
+ const url = require('../url')
4
+
5
+ const textInputProps = {
6
+ icon: 'Use `leadingVisual` or `trailingVisual` instead.',
7
+ contrast: 'Omit this prop to use the default background.',
8
+ variant: 'Use `size` instead.',
9
+ width: 'Set `width` using CSS or `style` instead.',
10
+ minWidth: 'Set `min-width` using CSS or `style` instead.',
11
+ maxWidth: 'Set `max-width` using CSS or `style` instead.',
12
+ }
13
+ const buttonProps = {
14
+ leadingIcon: 'Use `leadingVisual` instead.',
15
+ trailingIcon: 'Use `trailingVisual` instead.',
16
+ }
17
+ const narrowDivider = 'Use `divider` with a responsive value instead.'
18
+ const deprecations = {
19
+ 'ActionList.Group': {title: 'Use `ActionList.GroupHeading` instead.'},
20
+ 'ActionList.Item': {as: 'Use `ActionList.LinkItem` for links; `as` has no effect on `ActionList.Item`.'},
21
+ 'ActionMenu.Button': buttonProps,
22
+ 'Autocomplete.Input': {
23
+ ...textInputProps,
24
+ openOnFocus: 'Remove this prop. Review whether opening the menu on focus is still needed.',
25
+ },
26
+ 'Autocomplete.Overlay': {overlayProps: 'Pass overlay props directly to `Autocomplete.Overlay` instead.'},
27
+ Banner: {icon: 'Use `leadingVisual` instead.'},
28
+ Button: buttonProps,
29
+ CounterLabel: {scheme: 'Use `variant` instead.'},
30
+ IconButton: {keyshortcuts: 'Use `keybindingHint` instead.'},
31
+ Link: {hoverColor: 'Style hover color using CSS instead.'},
32
+ LinkButton: buttonProps,
33
+ 'PageLayout.Header': {dividerWhenNarrow: narrowDivider},
34
+ 'PageLayout.Pane': {
35
+ dividerWhenNarrow: narrowDivider,
36
+ positionWhenNarrow: 'Use `position` with a responsive value instead.',
37
+ },
38
+ 'PageLayout.Footer': {dividerWhenNarrow: narrowDivider},
39
+ 'SegmentedControl.Button': {
40
+ icon: 'Use `leadingVisual` instead.',
41
+ leadingIcon: 'Use `leadingVisual` instead.',
42
+ },
43
+ SelectPanel: {footer: 'Use `secondaryAction` instead.'},
44
+ Spinner: {'aria-label': 'Use `srText` instead.'},
45
+ StateLabel: {variant: 'Use `size` with `small` or `medium` instead.'},
46
+ TextInput: textInputProps,
47
+ 'TextInput.Action': {
48
+ children: 'Use an icon-only action with the `icon` prop instead.',
49
+ variant: 'Omit this prop to use the invisible button variant.',
50
+ },
51
+ TextInputWithTokens: textInputProps,
52
+ Textarea: {contrast: 'Omit this prop to use the default background.'},
53
+ ThemeProvider: {preventSSRMismatch: 'Remove this prop; it no longer has any effect.'},
54
+ 'UnderlineNav.Item': {icon: 'Use `leadingVisual` instead.'},
55
+ }
56
+
57
+ const entrypoints = new Map([
58
+ ['@primer/react', deprecations],
59
+ [
60
+ '@primer/react/deprecated',
61
+ {
62
+ 'ActionList.Item': {
63
+ trailingIcon: 'Use `trailingVisual` instead.',
64
+ trailingText: 'Use `trailingVisual` instead.',
65
+ },
66
+ },
67
+ ],
68
+ [
69
+ '@primer/react/experimental',
70
+ {
71
+ 'FilteredActionList.Input': textInputProps,
72
+ },
73
+ ],
74
+ [
75
+ '@primer/react/next',
76
+ {
77
+ ThemeProvider: deprecations.ThemeProvider,
78
+ },
79
+ ],
80
+ ])
81
+
82
+ function getPrimerComponent(node, scope) {
83
+ const members = []
84
+ let root = node.name
85
+ while (root.type === 'JSXMemberExpression') {
86
+ members.unshift(root.property.name)
87
+ root = root.object
88
+ }
89
+ if (root.type !== 'JSXIdentifier') return null
90
+
91
+ const definition = getVariableDeclaration(scope, {name: root.name})
92
+ if (definition?.type !== 'ImportBinding') return null
93
+ const entrypoint = definition.parent.source.value
94
+ const specifier = definition.node
95
+ if (specifier.type === 'ImportSpecifier') {
96
+ members.unshift(specifier.imported.name ?? specifier.imported.value)
97
+ } else if (specifier.type !== 'ImportNamespaceSpecifier') {
98
+ return null
99
+ }
100
+
101
+ const component = members.join('.')
102
+ const catalogue = entrypoints.get(entrypoint)
103
+ if (!catalogue || !Object.hasOwn(catalogue, component)) return null
104
+ return {component, props: catalogue[component]}
105
+ }
106
+
107
+ function fixGroupTitle(fixer, node, title, sourceCode) {
108
+ if (!title.value || node.selfClosing) return null
109
+ let groupTitle
110
+ if (title.value.type === 'Literal') {
111
+ groupTitle = sourceCode.getText(title.value).slice(1, -1)
112
+ if (/[{}<>]/.test(groupTitle)) return null
113
+ } else if (title.value.type === 'JSXExpressionContainer' && title.value.expression.type !== 'JSXEmptyExpression') {
114
+ groupTitle = sourceCode.getText(title.value)
115
+ } else {
116
+ return null
117
+ }
118
+ const heading = `${sourceCode.getText(node.name).slice(0, -'Group'.length)}GroupHeading`
119
+ const start = title.range[0]
120
+ const whitespace = sourceCode.text[start - 1]
121
+ return [
122
+ fixer.removeRange([whitespace === ' ' ? start - 1 : start, title.range[1]]),
123
+ fixer.insertTextAfter(node, `<${heading}>${groupTitle}</${heading}>`),
124
+ ]
125
+ }
4
126
 
5
127
  /**
6
128
  * @type {import('eslint').Rule.RuleModule}
@@ -9,51 +131,56 @@ module.exports = {
9
131
  meta: {
10
132
  type: 'suggestion',
11
133
  docs: {
12
- description:
13
- 'Avoid using deprecated `title` prop on `ActionList.Group` component. Use `ActionList.GroupHeading` instead.',
134
+ description: 'Avoid deprecated Primer React component props.',
14
135
  recommended: true,
15
- url: 'https://primer.style/components/action-list/react/beta#actionlistgroupheading',
136
+ url: url(module),
16
137
  },
17
138
  fixable: 'code',
18
139
  schema: [],
19
140
  messages: {
20
141
  titlePropDeprecated: 'The `title` prop is deprecated. Please use `ActionList.GroupHeading` instead.',
142
+ deprecatedProp: 'The `{{prop}}` prop on `{{component}}` is deprecated. {{hint}}',
21
143
  },
22
144
  },
23
145
  create(context) {
24
146
  return {
25
147
  JSXOpeningElement(node) {
26
- const openingElName = getJSXOpeningElementName(node)
27
- if (openingElName !== 'ActionList.Group') {
28
- return
148
+ const primer = getPrimerComponent(node, context.sourceCode.getScope(node))
149
+ if (!primer) return
150
+ for (const attribute of node.attributes) {
151
+ if (attribute.type !== 'JSXAttribute' || attribute.name.type !== 'JSXIdentifier') continue
152
+ const prop = attribute.name.name
153
+ if (!Object.hasOwn(primer.props, prop)) continue
154
+ if (primer.component === 'ActionList.Group' && prop === 'title') {
155
+ context.report({
156
+ node: attribute,
157
+ messageId: 'titlePropDeprecated',
158
+ fix: fixer => fixGroupTitle(fixer, node, attribute, context.sourceCode),
159
+ })
160
+ } else {
161
+ context.report({
162
+ node: attribute,
163
+ messageId: 'deprecatedProp',
164
+ data: {component: primer.component, prop, hint: primer.props[prop]},
165
+ })
166
+ }
29
167
  }
30
- const title = getJSXOpeningElementAttribute(node, 'title')
31
- let groupTitle = ''
32
- if (title !== undefined) {
168
+ if (
169
+ Object.hasOwn(primer.props, 'children') &&
170
+ !node.selfClosing &&
171
+ node.parent.children.some(
172
+ child =>
173
+ (child.type === 'JSXText' && child.value.trim() !== '') ||
174
+ (child.type === 'JSXExpressionContainer' && child.expression.type !== 'JSXEmptyExpression') ||
175
+ child.type === 'JSXElement' ||
176
+ child.type === 'JSXFragment' ||
177
+ child.type === 'JSXSpreadChild',
178
+ )
179
+ ) {
33
180
  context.report({
34
181
  node,
35
- messageId: 'titlePropDeprecated',
36
- fix(fixer) {
37
- // Group title is a string literal i.e. title="title"
38
- if (title.value.type === 'Literal') {
39
- groupTitle = title.value.value
40
- // Group title is a JSX expression i.e. title={title}
41
- } else if (title.value.type === 'JSXExpressionContainer') {
42
- groupTitle = context.sourceCode.getText(title.value)
43
- } else {
44
- // we don't provide fix for cases where the title prop is not a string literal or JSX expression
45
- return []
46
- }
47
- const start = title.range[0]
48
- const end = title.range[1]
49
- return [
50
- fixer.removeRange([start - 1, end]), // remove the space before the title as well
51
- fixer.insertTextAfterRange(
52
- [node.range[1], node.range[1]],
53
- `<ActionList.GroupHeading>${groupTitle}</ActionList.GroupHeading>`,
54
- ),
55
- ]
56
- },
182
+ messageId: 'deprecatedProp',
183
+ data: {component: primer.component, prop: 'children', hint: primer.props.children},
57
184
  })
58
185
  }
59
186
  },