@wordpress/eslint-plugin 24.0.1-next.v.0 → 24.1.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
@@ -82,8 +82,10 @@ The granular rulesets will not define any environment globals. As such, if they
82
82
  | [i18n-text-domain](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-text-domain.md) | Enforce passing valid text domains. | ✓ |
83
83
  | [i18n-translator-comments](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-translator-comments.md) | Enforce adding translator comments. | ✓ |
84
84
  | [no-base-control-with-label-without-id](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md) | Disallow the usage of BaseControl component with a label prop set but omitting the id property. | ✓ |
85
+ | [components-no-missing-40px-size-prop](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/components-no-missing-40px-size-prop.md) | Disallow missing `__next40pxDefaultSize` prop on `@wordpress/components` components. | ✓ |
86
+ | [components-no-unsafe-button-disabled](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/components-no-unsafe-button-disabled.md) | Disallow using `disabled` on Button without `accessibleWhenDisabled`. | ✓ |
85
87
  | [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls. | ✓ |
86
- | [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packagesl | ✓ |
88
+ | [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packages | ✓ |
87
89
  | [no-unused-vars-before-return](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) | Disallow assigning variable values if unused before a return. | ✓ |
88
90
  | [no-wp-process-env](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-wp-process-env.md) | Disallow legacy usage of WordPress variables via `process.env` like `process.env.SCRIPT_DEBUG`. | ✓ |
89
91
  | [react-no-unsafe-timeout](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md) | Disallow unsafe `setTimeout` in component. | |
package/configs/custom.js CHANGED
@@ -6,6 +6,8 @@ module.exports = {
6
6
  '@wordpress/no-unguarded-get-range-at': 'error',
7
7
  '@wordpress/no-global-active-element': 'error',
8
8
  '@wordpress/no-global-get-selection': 'error',
9
+ '@wordpress/no-setting-ds-tokens': 'error',
10
+ '@wordpress/no-unknown-ds-tokens': 'error',
9
11
  '@wordpress/no-unsafe-wp-apis': 'error',
10
12
  '@wordpress/no-wp-process-env': 'error',
11
13
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/eslint-plugin",
3
- "version": "24.0.1-next.v.0+500f87dd8",
3
+ "version": "24.1.0",
4
4
  "description": "ESLint plugin for WordPress development.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -40,8 +40,9 @@
40
40
  "@babel/eslint-parser": "7.25.7",
41
41
  "@typescript-eslint/eslint-plugin": "^6.4.1",
42
42
  "@typescript-eslint/parser": "^6.4.1",
43
- "@wordpress/babel-preset-default": "^8.37.1-next.v.0+500f87dd8",
44
- "@wordpress/prettier-config": "^4.37.1-next.v.0+500f87dd8",
43
+ "@wordpress/babel-preset-default": "^8.39.0",
44
+ "@wordpress/prettier-config": "^4.39.0",
45
+ "@wordpress/theme": "^0.6.0",
45
46
  "cosmiconfig": "^7.0.0",
46
47
  "eslint-config-prettier": "^8.3.0",
47
48
  "eslint-import-resolver-typescript": "^4.4.4",
@@ -77,5 +78,5 @@
77
78
  "publishConfig": {
78
79
  "access": "public"
79
80
  },
80
- "gitHead": "ca0db0ee8ac2116cd307650136027d26d0cdd9bd"
81
+ "gitHead": "eee1cfb1472f11183e40fb77465a5f13145df7ad"
81
82
  }
@@ -0,0 +1,353 @@
1
+ import { RuleTester } from 'eslint';
2
+ import rule from '../components-no-missing-40px-size-prop';
3
+
4
+ const ruleTester = new RuleTester( {
5
+ parserOptions: {
6
+ sourceType: 'module',
7
+ ecmaVersion: 6,
8
+ ecmaFeatures: {
9
+ jsx: true,
10
+ },
11
+ },
12
+ } );
13
+
14
+ ruleTester.run( 'components-no-missing-40px-size-prop', rule, {
15
+ valid: [
16
+ // Component with __next40pxDefaultSize (boolean attribute)
17
+ {
18
+ code: `
19
+ import { Button } from '@wordpress/components';
20
+ <Button __next40pxDefaultSize />
21
+ `,
22
+ },
23
+ // Component with __next40pxDefaultSize={true}
24
+ {
25
+ code: `
26
+ import { InputControl } from '@wordpress/components';
27
+ <InputControl __next40pxDefaultSize={true} />
28
+ `,
29
+ },
30
+ // Component with non-default size prop
31
+ {
32
+ code: `
33
+ import { Button } from '@wordpress/components';
34
+ <Button size="small" />
35
+ `,
36
+ },
37
+ // Component with size="compact"
38
+ {
39
+ code: `
40
+ import { SelectControl } from '@wordpress/components';
41
+ <SelectControl size="compact" />
42
+ `,
43
+ },
44
+ // Component from @wordpress/ui (should not be checked)
45
+ {
46
+ code: `
47
+ import { Button } from '@wordpress/ui';
48
+ <Button />
49
+ `,
50
+ },
51
+ // Local component (should not be checked)
52
+ {
53
+ code: `
54
+ const Button = () => <button />;
55
+ <Button />
56
+ `,
57
+ },
58
+ // Component from another package (should not be checked)
59
+ {
60
+ code: `
61
+ import { Button } from 'some-other-package';
62
+ <Button />
63
+ `,
64
+ },
65
+ // Aliased import with correct prop
66
+ {
67
+ code: `
68
+ import { Button as WPButton } from '@wordpress/components';
69
+ <WPButton __next40pxDefaultSize />
70
+ `,
71
+ },
72
+ // FormFileUpload with render prop (special case)
73
+ {
74
+ code: `
75
+ import { FormFileUpload } from '@wordpress/components';
76
+ <FormFileUpload render={({ open }) => <button onClick={open}>Upload</button>} />
77
+ `,
78
+ },
79
+ // FormFileUpload with __next40pxDefaultSize
80
+ {
81
+ code: `
82
+ import { FormFileUpload } from '@wordpress/components';
83
+ <FormFileUpload __next40pxDefaultSize />
84
+ `,
85
+ },
86
+ // Component with dynamic size prop (assumes it could be non-default)
87
+ {
88
+ code: `
89
+ import { Button } from '@wordpress/components';
90
+ <Button size={buttonSize} />
91
+ `,
92
+ },
93
+ // Button with variant="link" (doesn't need __next40pxDefaultSize)
94
+ {
95
+ code: `
96
+ import { Button } from '@wordpress/components';
97
+ <Button variant="link" />
98
+ `,
99
+ },
100
+ // Non-targeted component (should not be checked)
101
+ {
102
+ code: `
103
+ import { Modal } from '@wordpress/components';
104
+ <Modal />
105
+ `,
106
+ },
107
+ // All targeted components with __next40pxDefaultSize
108
+ {
109
+ code: `
110
+ import {
111
+ BorderBoxControl,
112
+ BorderControl,
113
+ BoxControl,
114
+ ComboboxControl,
115
+ CustomSelectControl,
116
+ FontAppearanceControl,
117
+ FontFamilyControl,
118
+ FontSizePicker,
119
+ FormTokenField,
120
+ InputControl,
121
+ LetterSpacingControl,
122
+ LineHeightControl,
123
+ NumberControl,
124
+ RangeControl,
125
+ SelectControl,
126
+ TextControl,
127
+ ToggleGroupControl,
128
+ UnitControl,
129
+ } from '@wordpress/components';
130
+ <>
131
+ <BorderBoxControl __next40pxDefaultSize />
132
+ <BorderControl __next40pxDefaultSize />
133
+ <BoxControl __next40pxDefaultSize />
134
+ <ComboboxControl __next40pxDefaultSize />
135
+ <CustomSelectControl __next40pxDefaultSize />
136
+ <FontAppearanceControl __next40pxDefaultSize />
137
+ <FontFamilyControl __next40pxDefaultSize />
138
+ <FontSizePicker __next40pxDefaultSize />
139
+ <FormTokenField __next40pxDefaultSize />
140
+ <InputControl __next40pxDefaultSize />
141
+ <LetterSpacingControl __next40pxDefaultSize />
142
+ <LineHeightControl __next40pxDefaultSize />
143
+ <NumberControl __next40pxDefaultSize />
144
+ <RangeControl __next40pxDefaultSize />
145
+ <SelectControl __next40pxDefaultSize />
146
+ <TextControl __next40pxDefaultSize />
147
+ <ToggleGroupControl __next40pxDefaultSize />
148
+ <UnitControl __next40pxDefaultSize />
149
+ </>
150
+ `,
151
+ },
152
+ ],
153
+ invalid: [
154
+ // Button without __next40pxDefaultSize
155
+ {
156
+ code: `
157
+ import { Button } from '@wordpress/components';
158
+ <Button />
159
+ `,
160
+ errors: [
161
+ {
162
+ messageId: 'missingProp',
163
+ data: { component: 'Button' },
164
+ },
165
+ ],
166
+ },
167
+ // InputControl without __next40pxDefaultSize
168
+ {
169
+ code: `
170
+ import { InputControl } from '@wordpress/components';
171
+ <InputControl value={value} onChange={onChange} />
172
+ `,
173
+ errors: [
174
+ {
175
+ messageId: 'missingProp',
176
+ data: { component: 'InputControl' },
177
+ },
178
+ ],
179
+ },
180
+ // Component with __next40pxDefaultSize={false}
181
+ {
182
+ code: `
183
+ import { SelectControl } from '@wordpress/components';
184
+ <SelectControl __next40pxDefaultSize={false} />
185
+ `,
186
+ errors: [
187
+ {
188
+ messageId: 'missingProp',
189
+ data: { component: 'SelectControl' },
190
+ },
191
+ ],
192
+ },
193
+ // Component with size="default" (should still require __next40pxDefaultSize)
194
+ {
195
+ code: `
196
+ import { Button } from '@wordpress/components';
197
+ <Button size="default" />
198
+ `,
199
+ errors: [
200
+ {
201
+ messageId: 'missingProp',
202
+ data: { component: 'Button' },
203
+ },
204
+ ],
205
+ },
206
+ // Aliased import without __next40pxDefaultSize
207
+ {
208
+ code: `
209
+ import { TextControl as MyTextControl } from '@wordpress/components';
210
+ <MyTextControl />
211
+ `,
212
+ errors: [
213
+ {
214
+ messageId: 'missingProp',
215
+ data: { component: 'TextControl' },
216
+ },
217
+ ],
218
+ },
219
+ // FormFileUpload without __next40pxDefaultSize or render
220
+ {
221
+ code: `
222
+ import { FormFileUpload } from '@wordpress/components';
223
+ <FormFileUpload onChange={handleChange} />
224
+ `,
225
+ errors: [
226
+ {
227
+ messageId: 'missingPropFormFileUpload',
228
+ },
229
+ ],
230
+ },
231
+ // Multiple components, some invalid
232
+ {
233
+ code: `
234
+ import { Button, InputControl } from '@wordpress/components';
235
+ <>
236
+ <Button __next40pxDefaultSize />
237
+ <InputControl />
238
+ </>
239
+ `,
240
+ errors: [
241
+ {
242
+ messageId: 'missingProp',
243
+ data: { component: 'InputControl' },
244
+ },
245
+ ],
246
+ },
247
+ // Multiple invalid components
248
+ {
249
+ code: `
250
+ import { Button, SelectControl } from '@wordpress/components';
251
+ <>
252
+ <Button />
253
+ <SelectControl />
254
+ </>
255
+ `,
256
+ errors: [
257
+ {
258
+ messageId: 'missingProp',
259
+ data: { component: 'Button' },
260
+ },
261
+ {
262
+ messageId: 'missingProp',
263
+ data: { component: 'SelectControl' },
264
+ },
265
+ ],
266
+ },
267
+ // Relative import with checkLocalImports enabled
268
+ {
269
+ code: `
270
+ import { Button } from '../button';
271
+ <Button />
272
+ `,
273
+ options: [ { checkLocalImports: true } ],
274
+ errors: [
275
+ {
276
+ messageId: 'missingProp',
277
+ data: { component: 'Button' },
278
+ },
279
+ ],
280
+ },
281
+ // Default import from input-control path with checkLocalImports enabled
282
+ {
283
+ code: `
284
+ import InputControl from '../input-control';
285
+ <InputControl />
286
+ `,
287
+ options: [ { checkLocalImports: true } ],
288
+ errors: [
289
+ {
290
+ messageId: 'missingProp',
291
+ data: { component: 'InputControl' },
292
+ },
293
+ ],
294
+ },
295
+ ],
296
+ } );
297
+
298
+ // Additional tests for checkLocalImports option
299
+ ruleTester.run(
300
+ 'components-no-missing-40px-size-prop (checkLocalImports)',
301
+ rule,
302
+ {
303
+ valid: [
304
+ // Relative import with correct props
305
+ {
306
+ code: `
307
+ import { Button } from '../button';
308
+ <Button __next40pxDefaultSize />
309
+ `,
310
+ options: [ { checkLocalImports: true } ],
311
+ },
312
+ // Default import with correct props
313
+ {
314
+ code: `
315
+ import InputControl from './input-control';
316
+ <InputControl __next40pxDefaultSize />
317
+ `,
318
+ options: [ { checkLocalImports: true } ],
319
+ },
320
+ // Relative import with non-default size
321
+ {
322
+ code: `
323
+ import { Button } from '../button';
324
+ <Button size="small" />
325
+ `,
326
+ options: [ { checkLocalImports: true } ],
327
+ },
328
+ // Relative import without checkLocalImports (should not be checked)
329
+ {
330
+ code: `
331
+ import { Button } from '../button';
332
+ <Button />
333
+ `,
334
+ },
335
+ // Default import without checkLocalImports (should not be checked)
336
+ {
337
+ code: `
338
+ import InputControl from '../input-control';
339
+ <InputControl />
340
+ `,
341
+ },
342
+ // FormFileUpload relative import with render prop
343
+ {
344
+ code: `
345
+ import { FormFileUpload } from '../form-file-upload';
346
+ <FormFileUpload render={({ open }) => <button onClick={open}>Upload</button>} />
347
+ `,
348
+ options: [ { checkLocalImports: true } ],
349
+ },
350
+ ],
351
+ invalid: [],
352
+ }
353
+ );
@@ -0,0 +1,235 @@
1
+ import { RuleTester } from 'eslint';
2
+ import rule from '../components-no-unsafe-button-disabled';
3
+
4
+ const ruleTester = new RuleTester( {
5
+ parserOptions: {
6
+ sourceType: 'module',
7
+ ecmaVersion: 6,
8
+ ecmaFeatures: {
9
+ jsx: true,
10
+ },
11
+ },
12
+ } );
13
+
14
+ ruleTester.run( 'components-no-unsafe-button-disabled', rule, {
15
+ valid: [
16
+ // Button with both disabled and accessibleWhenDisabled
17
+ {
18
+ code: `
19
+ import { Button } from '@wordpress/components';
20
+ <Button disabled accessibleWhenDisabled />
21
+ `,
22
+ },
23
+ // Button with accessibleWhenDisabled={true}
24
+ {
25
+ code: `
26
+ import { Button } from '@wordpress/components';
27
+ <Button disabled accessibleWhenDisabled={true} />
28
+ `,
29
+ },
30
+ // Button with accessibleWhenDisabled={false}
31
+ {
32
+ code: `
33
+ import { Button } from '@wordpress/components';
34
+ <Button disabled accessibleWhenDisabled={false} />
35
+ `,
36
+ },
37
+ // Button with accessibleWhenDisabled={someVar}
38
+ {
39
+ code: `
40
+ import { Button } from '@wordpress/components';
41
+ <Button disabled accessibleWhenDisabled={someVar} />
42
+ `,
43
+ },
44
+ // Button with accessibleWhenDisabled={false} should error (handled in invalid)
45
+ // Button with disabled={false} should not require accessibleWhenDisabled
46
+ {
47
+ code: `
48
+ import { Button } from '@wordpress/components';
49
+ <Button disabled={false} />
50
+ `,
51
+ },
52
+ // Button with disabled={someVar} and accessibleWhenDisabled={someVar}
53
+ {
54
+ code: `
55
+ import { Button } from '@wordpress/components';
56
+ <Button disabled={isDisabled} accessibleWhenDisabled={someVar} />
57
+ `,
58
+ },
59
+ // Button without disabled prop
60
+ {
61
+ code: `
62
+ import { Button } from '@wordpress/components';
63
+ <Button onClick={handleClick} />
64
+ `,
65
+ },
66
+ // Button from @wordpress/ui (should not be checked)
67
+ {
68
+ code: `
69
+ import { Button } from '@wordpress/ui';
70
+ <Button disabled />
71
+ `,
72
+ },
73
+ // Local Button component (should not be checked)
74
+ {
75
+ code: `
76
+ const Button = () => <button />;
77
+ <Button disabled />
78
+ `,
79
+ },
80
+ // Button from another package (should not be checked)
81
+ {
82
+ code: `
83
+ import { Button } from 'some-other-package';
84
+ <Button disabled />
85
+ `,
86
+ },
87
+ // Aliased import with correct props
88
+ {
89
+ code: `
90
+ import { Button as WPButton } from '@wordpress/components';
91
+ <WPButton disabled accessibleWhenDisabled />
92
+ `,
93
+ },
94
+ // Non-Button component with disabled (should not be checked)
95
+ {
96
+ code: `
97
+ import { TextControl } from '@wordpress/components';
98
+ <TextControl disabled />
99
+ `,
100
+ },
101
+ ],
102
+ invalid: [
103
+ // Button with disabled but no accessibleWhenDisabled
104
+ {
105
+ code: `
106
+ import { Button } from '@wordpress/components';
107
+ <Button disabled />
108
+ `,
109
+ errors: [
110
+ {
111
+ messageId: 'missingAccessibleWhenDisabled',
112
+ },
113
+ ],
114
+ },
115
+ // Button with disabled={someVar} but no accessibleWhenDisabled
116
+ {
117
+ code: `
118
+ import { Button } from '@wordpress/components';
119
+ <Button disabled={isDisabled} />
120
+ `,
121
+ errors: [
122
+ {
123
+ messageId: 'missingAccessibleWhenDisabled',
124
+ },
125
+ ],
126
+ },
127
+ // Button with disabled={true} but no accessibleWhenDisabled
128
+ {
129
+ code: `
130
+ import { Button } from '@wordpress/components';
131
+ <Button disabled={true} />
132
+ `,
133
+ errors: [
134
+ {
135
+ messageId: 'missingAccessibleWhenDisabled',
136
+ },
137
+ ],
138
+ },
139
+ // Aliased import without accessibleWhenDisabled
140
+ {
141
+ code: `
142
+ import { Button as MyButton } from '@wordpress/components';
143
+ <MyButton disabled />
144
+ `,
145
+ errors: [
146
+ {
147
+ messageId: 'missingAccessibleWhenDisabled',
148
+ },
149
+ ],
150
+ },
151
+ // Multiple Buttons, one invalid
152
+ {
153
+ code: `
154
+ import { Button } from '@wordpress/components';
155
+ <>
156
+ <Button disabled accessibleWhenDisabled />
157
+ <Button disabled />
158
+ </>
159
+ `,
160
+ errors: [
161
+ {
162
+ messageId: 'missingAccessibleWhenDisabled',
163
+ },
164
+ ],
165
+ },
166
+ ],
167
+ } );
168
+
169
+ // Additional tests for checkLocalImports option
170
+ ruleTester.run(
171
+ 'components-no-unsafe-button-disabled (checkLocalImports)',
172
+ rule,
173
+ {
174
+ valid: [
175
+ // Relative import with correct props
176
+ {
177
+ code: `
178
+ import { Button } from '../button';
179
+ <Button disabled accessibleWhenDisabled />
180
+ `,
181
+ options: [ { checkLocalImports: true } ],
182
+ },
183
+ // Default import with correct props
184
+ {
185
+ code: `
186
+ import Button from './button';
187
+ <Button disabled accessibleWhenDisabled />
188
+ `,
189
+ options: [ { checkLocalImports: true } ],
190
+ },
191
+ // Relative import without checkLocalImports (should not be checked)
192
+ {
193
+ code: `
194
+ import { Button } from '../button';
195
+ <Button disabled />
196
+ `,
197
+ },
198
+ // Default import without checkLocalImports (should not be checked)
199
+ {
200
+ code: `
201
+ import Button from '../button';
202
+ <Button disabled />
203
+ `,
204
+ },
205
+ ],
206
+ invalid: [
207
+ // Relative import with checkLocalImports enabled
208
+ {
209
+ code: `
210
+ import { Button } from '../button';
211
+ <Button disabled />
212
+ `,
213
+ options: [ { checkLocalImports: true } ],
214
+ errors: [
215
+ {
216
+ messageId: 'missingAccessibleWhenDisabled',
217
+ },
218
+ ],
219
+ },
220
+ // Default import from button path with checkLocalImports enabled
221
+ {
222
+ code: `
223
+ import Button from '../button';
224
+ <Button disabled />
225
+ `,
226
+ options: [ { checkLocalImports: true } ],
227
+ errors: [
228
+ {
229
+ messageId: 'missingAccessibleWhenDisabled',
230
+ },
231
+ ],
232
+ },
233
+ ],
234
+ }
235
+ );
@@ -53,6 +53,14 @@ const { Component } = require( '@wordpress/element' );
53
53
  */
54
54
  const edit = require( './edit' );`,
55
55
  },
56
+ {
57
+ code: `
58
+ import { camelCase } from 'change-case';
59
+ import clsx from 'clsx';
60
+ import { Component } from '@wordpress/element';
61
+ import edit from './edit';`,
62
+ options: [ 'never' ],
63
+ },
56
64
  ],
57
65
  invalid: [
58
66
  {
@@ -131,5 +139,30 @@ const { Component } = require( '@wordpress/element' );
131
139
  */
132
140
  const edit = require( './edit' );`,
133
141
  },
142
+ {
143
+ code: `
144
+ /**
145
+ * External dependencies
146
+ */
147
+ import { camelCase } from 'change-case';
148
+
149
+ /**
150
+ * WordPress dependencies
151
+ */
152
+
153
+ import { Component } from '@wordpress/element';`,
154
+ options: [ 'never' ],
155
+ errors: [
156
+ {
157
+ message: 'Unexpected dependency group comment block',
158
+ },
159
+ {
160
+ message: 'Unexpected dependency group comment block',
161
+ },
162
+ ],
163
+ output: `
164
+ import { camelCase } from 'change-case';
165
+ import { Component } from '@wordpress/element';`,
166
+ },
134
167
  ],
135
168
  } );