@adyen/eslint-plugin-bento 2.21.0 → 2.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.23.0 (2026-08-17)
4
+
5
+ ### Features
6
+
7
+ - updated `deprecation-filter-bar-deprecated-props` rule to check also for configs with enums ([5259ab283](https://github.com/Adyen/bento/commit/5259ab283))
8
+
9
+ ### Miscellaneous Chores
10
+
11
+ - enabled strict `@adyen/eslint-plugin-bento` plugin ESLint rules to enforce deprecations ([974a8e49e](https://github.com/Adyen/bento/commit/974a8e49e))
12
+
13
+ ### ❤️ Thank You
14
+
15
+ - daver
16
+
17
+
18
+ ## 2.22.0 (2026-07-29)
19
+
20
+ ### Bug Fixes
21
+
22
+ - removed autofix from `deprecation-filter-bar-values` eslint ([b1a22af27](https://github.com/Adyen/bento/commit/b1a22af27))
23
+
24
+ ### ❤️ Thank You
25
+
26
+ - erice
27
+
28
+
3
29
  ## 2.21.0 (2026-07-21)
4
30
 
5
31
  This was a version bump only for eslint to align it with other projects, there were no code changes.
@@ -1,3 +1,4 @@
1
+ /** @type {import('eslint').Linter.Config[]} */
1
2
  module.exports = [
2
3
  {
3
4
  name: '@bento/adyen/recommended',
@@ -1,3 +1,4 @@
1
+ /** @type {import('eslint').Linter.Config[]} */
1
2
  module.exports = [
2
3
  {
3
4
  name: '@bento/adyen/strict',
@@ -12,7 +13,7 @@ module.exports = [
12
13
  },
13
14
  rules: {
14
15
  '@adyen/bento/deprecation-bento-data-grid-cell-action-menu-item-handler': 'error',
15
- '@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop': 'error',
16
+ '@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop': ['error', { action: 'remove' }],
16
17
  '@adyen/bento/deprecation-input-filter-dropdown-options': 'error',
17
18
  '@adyen/bento/deprecation-bento-dropdown-size-prop': 'error',
18
19
  '@adyen/bento/deprecation-bento-timeline-item-variant-prop': 'error',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adyen/eslint-plugin-bento",
3
- "version": "2.21.0",
3
+ "version": "2.23.0",
4
4
  "description": "Adyen Bento ESLint rules",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -30,8 +30,8 @@
30
30
  "@types/eslint": "9.6.1",
31
31
  "@types/estree": "1.0.6",
32
32
  "@types/node": "24.9.0",
33
- "@typescript-eslint/eslint-plugin": "8.52.0",
34
- "@typescript-eslint/parser": "8.52.0",
33
+ "@typescript-eslint/eslint-plugin": "8.61.0",
34
+ "@typescript-eslint/parser": "8.61.0",
35
35
  "@vitest/eslint-plugin": "1.1.25",
36
36
  "@vitest/coverage-v8": "3.2.6",
37
37
  "eslint": "9.39.0",
@@ -61,4 +61,11 @@ const config = [
61
61
  options: { listboxItems: [] },
62
62
  },
63
63
  ];
64
+
65
+ const values = [
66
+ {
67
+ field: 'status',
68
+ value: 'active',
69
+ },
70
+ ];
64
71
  ```
@@ -14,6 +14,7 @@
14
14
  :config="[
15
15
  {
16
16
  field: 'inline',
17
+ label: 'Inline',
17
18
  type: 'BentoSelectFilter',
18
19
  options: { messages: {} },
19
20
  },
@@ -27,6 +28,7 @@
27
28
  const configWithValue = [
28
29
  {
29
30
  field: 'name',
31
+ label: 'Name',
30
32
  type: 'BentoInputFilter',
31
33
  value: 'test',
32
34
  },
@@ -36,6 +38,7 @@
36
38
  const configWithOptions = [
37
39
  {
38
40
  field: 'status',
41
+ label: 'Status',
39
42
  type: 'BentoSelectFilter',
40
43
  options: {
41
44
  messages: {},
@@ -46,3 +49,14 @@
46
49
  },
47
50
  ];
48
51
  </script>
52
+
53
+ <script>
54
+ export const parseBinaryOperation = operation => ({
55
+ field: operation.left.name,
56
+ operator: operation.type,
57
+ category: operation.left.type,
58
+ type: parseOperationValueType(operation.right.type),
59
+ value: parseOperationValue(operation),
60
+ key: getUniqueId(),
61
+ });
62
+ </script>
@@ -20,6 +20,16 @@ function getStaticPropertyName(prop) {
20
20
  return prop.key.type === 'Identifier' ? prop.key.name : prop.key.type === 'Literal' ? String(prop.key.value) : null;
21
21
  }
22
22
 
23
+ function isBentoFilterItemType(node) {
24
+ return (
25
+ node?.type === 'MemberExpression' &&
26
+ !node.computed &&
27
+ node.object.type === 'Identifier' &&
28
+ node.object.name === 'BentoFilterItemType' &&
29
+ node.property.type === 'Identifier'
30
+ );
31
+ }
32
+
23
33
  /** @type {RuleModule} */
24
34
  module.exports = {
25
35
  meta: {
@@ -85,7 +95,13 @@ module.exports = {
85
95
  }
86
96
 
87
97
  // Only check objects that look like filter config items (BentoFilterModel)
88
- const isFilterConfig = propertyMap.field && propertyMap.type;
98
+ const filterType = propertyMap.type?.value;
99
+ const isFilterConfig =
100
+ propertyMap.field &&
101
+ ((filterType?.type === 'Literal' &&
102
+ typeof filterType.value === 'string' &&
103
+ filterType.value.endsWith('Filter')) ||
104
+ isBentoFilterItemType(filterType));
89
105
  if (!isFilterConfig) {
90
106
  return;
91
107
  }
@@ -95,11 +111,6 @@ module.exports = {
95
111
  context.report({
96
112
  node: propertyMap.value.key,
97
113
  messageId: 'deprecationConfigValue',
98
- ...(autofix && {
99
- fix(fixer) {
100
- return removeProperty(fixer, node, propertyMap.value);
101
- },
102
- }),
103
114
  });
104
115
  }
105
116
 
@@ -144,11 +155,6 @@ module.exports = {
144
155
  context.report({
145
156
  node,
146
157
  messageId: 'deprecationValue',
147
- ...(autofix && {
148
- fix(fixer) {
149
- return fixer.remove(node);
150
- },
151
- }),
152
158
  });
153
159
  },
154
160
  // Direct "value" prop on bento-filter-bar (dynamic)
@@ -158,25 +164,19 @@ module.exports = {
158
164
  context.report({
159
165
  node,
160
166
  messageId: 'deprecationValue',
161
- ...(autofix && {
162
- fix(fixer) {
163
- return fixer.remove(node);
164
- },
165
- }),
166
167
  });
167
168
  },
168
- // v-model on bento-filter-bar (v-model binds to "value" in Vue 2)
169
+ // Bare v-model binds to "value" in Vue 2; v-model:value does the same in Vue 3
169
170
  [`VElement[name="bento-filter-bar"] > VStartTag > VAttribute[directive=true][key.name.name="model"]`](
170
171
  node
171
172
  ) {
173
+ if (node.key.argument && node.key.argument.name !== 'value') {
174
+ return;
175
+ }
176
+
172
177
  context.report({
173
178
  node,
174
179
  messageId: 'deprecationValue',
175
- ...(autofix && {
176
- fix(fixer) {
177
- return fixer.remove(node);
178
- },
179
- }),
180
180
  });
181
181
  },
182
182
  ...objectVisitor,
@@ -26,6 +26,15 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
26
26
  </template>
27
27
  `,
28
28
  },
29
+ {
30
+ name: 'Valid: Vue 3 filter-values v-model on bento-filter-bar',
31
+ filename: 'test.vue',
32
+ code: `
33
+ <template>
34
+ <bento-filter-bar :config="config" v-model:filter-values="filterValues" />
35
+ </template>
36
+ `,
37
+ },
29
38
  {
30
39
  name: 'Valid: filter config item without deprecated value property',
31
40
  filename: 'test.vue',
@@ -97,6 +106,43 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
97
106
  <template><div /></template>
98
107
  `,
99
108
  },
109
+ {
110
+ name: 'Valid: unrelated binary operation object with field, type, and value properties',
111
+ filename: 'test.vue',
112
+ code: `
113
+ <script>
114
+ export const parseBinaryOperation = operation => ({
115
+ field: operation.left.name,
116
+ operator: operation.type,
117
+ category: operation.left.type,
118
+ type: parseOperationValueType(operation.right.type),
119
+ value: parseOperationValue(operation),
120
+ key: getUniqueId(),
121
+ });
122
+ </script>
123
+ <template><div /></template>
124
+ `,
125
+ },
126
+ {
127
+ name: 'Valid: unrelated object with a non-filter string type',
128
+ filename: 'test.vue',
129
+ code: `
130
+ <script setup>
131
+ const operation = { field: 'name', type: 'BinaryOperation', value: 'test' };
132
+ </script>
133
+ <template><div /></template>
134
+ `,
135
+ },
136
+ {
137
+ name: 'Valid: unrelated enum member with a similar name',
138
+ filename: 'test.vue',
139
+ code: `
140
+ <script setup>
141
+ const config = [{ field: 'name', type: OtherFilterItemType.INPUT, value: 'test' }];
142
+ </script>
143
+ <template><bento-filter-bar :config="config" /></template>
144
+ `,
145
+ },
100
146
  {
101
147
  name: 'Valid: unrelated object with value property (no false positive)',
102
148
  filename: 'test.vue',
@@ -128,9 +174,15 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
128
174
  code: `<template><bento-filter-bar v-model="filters" /></template>`,
129
175
  errors: [{ messageId: 'deprecationValue' }],
130
176
  },
177
+ {
178
+ name: 'Invalid: Vue 3 value v-model on bento-filter-bar',
179
+ filename: 'test.vue',
180
+ code: `<template><bento-filter-bar v-model:value="filters" /></template>`,
181
+ errors: [{ messageId: 'deprecationValue' }],
182
+ },
131
183
  // "value" inside filter config items (script)
132
184
  {
133
- name: 'Invalid: value property in filter config item (script)',
185
+ name: 'Invalid: value property in filter config item without a label',
134
186
  filename: 'test.vue',
135
187
  code: `
136
188
  <script setup>
@@ -144,13 +196,28 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
144
196
  `,
145
197
  errors: [{ messageId: 'deprecationConfigValue' }],
146
198
  },
199
+ {
200
+ name: 'Invalid: value property in BentoFilterItemType enum config item',
201
+ filename: 'test.vue',
202
+ code: `
203
+ <script setup>
204
+ const config = [{
205
+ field: 'name',
206
+ type: BentoFilterItemType.INPUT,
207
+ value: 'test',
208
+ }];
209
+ </script>
210
+ <template><bento-filter-bar :config="config" /></template>
211
+ `,
212
+ errors: [{ messageId: 'deprecationConfigValue' }],
213
+ },
147
214
  // "value" inside filter config items (inline template)
148
215
  {
149
216
  name: 'Invalid: value property in filter config item (inline template)',
150
217
  filename: 'test.vue',
151
218
  code: `
152
219
  <template>
153
- <bento-filter-bar :config="[{ field: 'name', type: 'BentoInputFilter', value: 'test' }]" />
220
+ <bento-filter-bar :config="[{ field: 'name', label: 'Name', type: 'BentoInputFilter', value: 'test' }]" />
154
221
  </template>
155
222
  `,
156
223
  errors: [{ messageId: 'deprecationConfigValue' }],
@@ -163,6 +230,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
163
230
  <script setup>
164
231
  const config = [{
165
232
  field: 'status',
233
+ label: 'Status',
166
234
  type: 'BentoSelectFilter',
167
235
  options: { messages: { en: { persistentButtonLabelPartiallySelected: 'x' } }, listboxItems: [] },
168
236
  }];
@@ -179,6 +247,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
179
247
  <script setup>
180
248
  const config = [{
181
249
  field: 'status',
250
+ label: 'Status',
182
251
  type: 'BentoSelectFilter',
183
252
  options: { messagesPersistentButtonLabelPartiallySelectedKey: 'customKey', listboxItems: [] },
184
253
  }];
@@ -200,6 +269,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
200
269
  <script setup>
201
270
  const config = [{
202
271
  field: 'status',
272
+ label: 'Status',
203
273
  type: 'BentoSelectFilter',
204
274
  options: { messagesPersistentButtonLabelAllSelectedKey: 'customKey', listboxItems: [] },
205
275
  }];
@@ -218,6 +288,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
218
288
  <script setup>
219
289
  const config = [{
220
290
  field: 'status',
291
+ label: 'Status',
221
292
  type: 'BentoSelectFilter',
222
293
  options: {
223
294
  messages: {},
@@ -246,6 +317,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
246
317
  <script setup>
247
318
  const config = [{
248
319
  field: 'status',
320
+ label: 'Status',
249
321
  type: 'BentoSelectFilter',
250
322
  options: { 'messages': {} },
251
323
  }];
@@ -262,6 +334,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
262
334
  <script setup>
263
335
  const filters = [{
264
336
  field: 'name',
337
+ label: 'Name',
265
338
  type: 'BentoInputFilter',
266
339
  value: 'test',
267
340
  }];
@@ -278,6 +351,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
278
351
  <script setup>
279
352
  const filters = [{
280
353
  field: 'status',
354
+ label: 'Status',
281
355
  type: 'BentoSelectFilter',
282
356
  options: { messages: {}, listboxItems: [] },
283
357
  }];
@@ -294,6 +368,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
294
368
  <template>
295
369
  <bento-filter-bar :config="[{
296
370
  field: 'status',
371
+ label: 'Status',
297
372
  type: 'BentoSelectFilter',
298
373
  options: { messages: {}, listboxItems: [] },
299
374
  }]" />
@@ -310,6 +385,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
310
385
  const defaults = {};
311
386
  const config = [{
312
387
  field: 'status',
388
+ label: 'Status',
313
389
  type: 'BentoSelectFilter',
314
390
  options: { ...defaults, messages: {} },
315
391
  }];
@@ -335,52 +411,45 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
335
411
  ],
336
412
  invalid: [
337
413
  {
338
- name: 'Autofix: removes static value prop from bento-filter-bar',
414
+ name: 'Autofix: preserves static value prop on bento-filter-bar',
339
415
  filename: 'test.vue',
340
416
  options: [{ autofix: true }],
341
417
  code: `<template><bento-filter-bar value="val" /></template>`,
342
- output: `<template><bento-filter-bar /></template>`,
418
+ output: null,
343
419
  errors: [{ messageId: 'deprecationValue' }],
344
420
  },
345
421
  {
346
- name: 'Autofix: removes dynamic value prop from bento-filter-bar',
422
+ name: 'Autofix: preserves dynamic value prop on bento-filter-bar',
347
423
  filename: 'test.vue',
348
424
  options: [{ autofix: true }],
349
425
  code: `<template><bento-filter-bar :value="filters" /></template>`,
350
- output: `<template><bento-filter-bar /></template>`,
426
+ output: null,
351
427
  errors: [{ messageId: 'deprecationValue' }],
352
428
  },
353
429
  {
354
- name: 'Autofix: removes v-model from bento-filter-bar',
430
+ name: 'Autofix: preserves v-model on bento-filter-bar',
355
431
  filename: 'test.vue',
356
432
  options: [{ autofix: true }],
357
433
  code: `<template><bento-filter-bar v-model="filters" /></template>`,
358
- output: `<template><bento-filter-bar /></template>`,
434
+ output: null,
359
435
  errors: [{ messageId: 'deprecationValue' }],
360
436
  },
361
437
  {
362
- name: 'Autofix: removes value from filter config item',
438
+ name: 'Autofix: preserves value on filter config item',
363
439
  filename: 'test.vue',
364
440
  options: [{ autofix: true }],
365
441
  code: `
366
442
  <script setup>
367
443
  const config = [{
368
444
  field: 'name',
445
+ label: 'Name',
369
446
  type: 'BentoInputFilter',
370
447
  value: 'test',
371
448
  }];
372
449
  </script>
373
450
  <template><bento-filter-bar :config="config" /></template>
374
451
  `,
375
- output: `
376
- <script setup>
377
- const config = [{
378
- field: 'name',
379
- type: 'BentoInputFilter',
380
- }];
381
- </script>
382
- <template><bento-filter-bar :config="config" /></template>
383
- `,
452
+ output: null,
384
453
  errors: [{ messageId: 'deprecationConfigValue' }],
385
454
  },
386
455
  {
@@ -391,6 +460,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
391
460
  <script setup>
392
461
  const config = [{
393
462
  field: 'status',
463
+ label: 'Status',
394
464
  type: 'BentoSelectFilter',
395
465
  options: { messages: {}, listboxItems: [] },
396
466
  }];
@@ -401,6 +471,7 @@ describe('@adyen/bento/deprecation-filter-bar-deprecated-props', () => {
401
471
  <script setup>
402
472
  const config = [{
403
473
  field: 'status',
474
+ label: 'Status',
404
475
  type: 'BentoSelectFilter',
405
476
  options: { listboxItems: [] },
406
477
  }];
@@ -42,6 +42,24 @@ describe('@adyen/bento/deprecation-tag-default-slot should suggest the usage of
42
42
  <bento-tag :label="thisIsMyVariableWithText" variant="blue" />
43
43
  `,
44
44
  },
45
+ {
46
+ name: 'BentoTag with named "icon" slot',
47
+ filename: 'test.vue',
48
+ code: `
49
+ <template>
50
+ <bento-tag
51
+ v-if="errorCounter != null"
52
+ class="b-tabs__tab-error-counter"
53
+ variant="red"
54
+ :label="errorCounter"
55
+ >
56
+ <template #icon>
57
+ <cross-circle-fill-icon aria-hidden="true" />
58
+ </template>
59
+ </bento-tag>
60
+ </template>
61
+ `,
62
+ },
45
63
  ],
46
64
  invalid: [
47
65
  {
@@ -6,6 +6,7 @@ const j = require('jscodeshift');
6
6
  */
7
7
 
8
8
  /**
9
+ * @typedef {import('vue-eslint-parser').AST.VElement} VElement
9
10
  * @typedef {import('vue-eslint-parser').AST.VText} VText
10
11
  * @typedef {import('vue-eslint-parser').AST.VExpressionContainer} VExpressionContainer
11
12
  */
@@ -26,10 +27,23 @@ module.exports = {
26
27
  */
27
28
  componentSlotMigration({ propertyName, context, template, alwaysExpression = false }) {
28
29
  return node => {
29
- if (!node.children.length) {
30
- return;
30
+ /**
31
+ * @param {VElement | VText | VExpressionContainer} child
32
+ * @returns {boolean}
33
+ */
34
+ function isNamedSlot(child) {
35
+ return (
36
+ child.type === 'VElement' &&
37
+ child.name === 'template' &&
38
+ child.startTag.attributes.some(
39
+ attribute => attribute.directive && attribute.key.name.name === 'slot'
40
+ )
41
+ );
31
42
  }
32
43
 
44
+ // Named slots are not default slot content and should not trigger migration.
45
+ const contentNodes = node.children.filter(child => !isNamedSlot(child));
46
+
33
47
  // Turned true if one of the children is an expression
34
48
  let hasExpression = false;
35
49
 
@@ -72,7 +86,11 @@ module.exports = {
72
86
 
73
87
  // Remove empty nodes
74
88
  /** @type { Array<VText | VExpressionContainer> } */
75
- const filteredNodes = node.children.filter(removeEmptyVTextNodes);
89
+ const filteredNodes = contentNodes.filter(removeEmptyVTextNodes);
90
+
91
+ if (!filteredNodes.length) {
92
+ return;
93
+ }
76
94
 
77
95
  // Get all content and concat
78
96
  const slotContent = (() => {
@@ -80,7 +98,7 @@ module.exports = {
80
98
  return transformNodeToText(filteredNodes[0])?.trim();
81
99
  }
82
100
 
83
- const content = node.children
101
+ const content = contentNodes
84
102
  // eslint-disable-next-line @typescript-eslint/no-shadow
85
103
  .map((/** @type {VText | VExpressionContainer} */ node) => transformNodeToText(node, true))
86
104
  .join('')