@adyen/eslint-plugin-bento 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/configs/vue-recommended.js +3 -0
  3. package/configs/vue-strict.js +3 -0
  4. package/index.js +3 -0
  5. package/package.json +2 -2
  6. package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/README.md +48 -0
  7. package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/__tests__/deprecation-bento-data-grid-cell-action-menu-item-handler.vue +63 -0
  8. package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/__tests__/eslint.config.js +26 -0
  9. package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/deprecation-bento-data-grid-cell-action-menu-item-handler.integration.test.ts +24 -0
  10. package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/deprecation-bento-data-grid-cell-action-menu-item-handler.js +232 -0
  11. package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/deprecation-bento-data-grid-cell-action-menu-item-handler.test.ts +116 -0
  12. package/rules/deprecation-bento-date-range-picker-has-actions-prop/README.md +50 -0
  13. package/rules/deprecation-bento-date-range-picker-has-actions-prop/__tests__/deprecation-bento-date-range-picker-has-actions-prop.vue +10 -0
  14. package/rules/deprecation-bento-date-range-picker-has-actions-prop/__tests__/eslint.config.js +29 -0
  15. package/rules/deprecation-bento-date-range-picker-has-actions-prop/__tests__/remove/deprecation-bento-date-range-picker-has-actions-prop.vue +11 -0
  16. package/rules/deprecation-bento-date-range-picker-has-actions-prop/__tests__/remove/eslint.config.js +29 -0
  17. package/rules/deprecation-bento-date-range-picker-has-actions-prop/deprecation-bento-date-range-picker-has-actions-prop.integration.test.ts +47 -0
  18. package/rules/deprecation-bento-date-range-picker-has-actions-prop/deprecation-bento-date-range-picker-has-actions-prop.js +205 -0
  19. package/rules/deprecation-bento-date-range-picker-has-actions-prop/deprecation-bento-date-range-picker-has-actions-prop.test.ts +176 -0
  20. package/rules/deprecation-country-custom-usage/README.md +151 -0
  21. package/rules/deprecation-country-custom-usage/__tests__/deprecation-country-custom-usage.vue +93 -0
  22. package/rules/deprecation-country-custom-usage/__tests__/eslint.config.js +25 -0
  23. package/rules/deprecation-country-custom-usage/deprecation-country-custom-usage.integration.test.ts +22 -0
  24. package/rules/deprecation-country-custom-usage/deprecation-country-custom-usage.js +569 -0
  25. package/rules/deprecation-country-custom-usage/deprecation-country-custom-usage.test.ts +380 -0
@@ -0,0 +1,29 @@
1
+ // eslint.config.js
2
+ const { defineConfig } = require('eslint/config');
3
+ const vueParser = require('vue-eslint-parser');
4
+ const tsParser = require('@typescript-eslint/parser');
5
+ const adyenBento = require('@adyen/eslint-plugin-bento');
6
+
7
+ module.exports = defineConfig([
8
+ {
9
+ languageOptions: {
10
+ ecmaVersion: 'latest',
11
+ sourceType: 'module',
12
+ parser: vueParser,
13
+ parserOptions: {
14
+ parser: tsParser,
15
+ },
16
+ },
17
+ plugins: {
18
+ '@adyen/bento': adyenBento,
19
+ },
20
+ rules: {
21
+ // Register the specific rule we are testing
22
+ '@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop': [
23
+ 'warn',
24
+ { action: 'add', autofix: true },
25
+ ],
26
+ },
27
+ files: ['**/*.vue'],
28
+ },
29
+ ]);
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <div>
3
+ <!-- These should trigger the "remove" action (has-actions present) -->
4
+ <bento-date-range-picker has-actions />
5
+ <bento-date-range-picker has-actions label="Pick a date" />
6
+ <bento-date-range-picker :has-actions="true" />
7
+ <bento-date-range-picker :has-actions="false" />
8
+ </div>
9
+ </template>
10
+
11
+ <script setup></script>
@@ -0,0 +1,29 @@
1
+ // eslint.config.js
2
+ const { defineConfig } = require('eslint/config');
3
+ const vueParser = require('vue-eslint-parser');
4
+ const tsParser = require('@typescript-eslint/parser');
5
+ const adyenBento = require('@adyen/eslint-plugin-bento');
6
+
7
+ module.exports = defineConfig([
8
+ {
9
+ languageOptions: {
10
+ ecmaVersion: 'latest',
11
+ sourceType: 'module',
12
+ parser: vueParser,
13
+ parserOptions: {
14
+ parser: tsParser,
15
+ },
16
+ },
17
+ plugins: {
18
+ '@adyen/bento': adyenBento,
19
+ },
20
+ rules: {
21
+ // Register the specific rule we are testing
22
+ '@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop': [
23
+ 'warn',
24
+ { action: 'remove', autofix: true },
25
+ ],
26
+ },
27
+ files: ['**/*.vue'],
28
+ },
29
+ ]);
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+ import { execFileSyncEslintVueFiles } from '../../utils/exec-file-sync-eslint-vue-files';
3
+
4
+ describe('@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop', () => {
5
+ it('should lint with warning when has-actions is missing (add action)', () => {
6
+ const eslintrc = path.resolve(__dirname, '__tests__', 'eslint.config.js');
7
+ const vueFile = path.resolve(
8
+ __dirname,
9
+ '__tests__',
10
+ 'deprecation-bento-date-range-picker-has-actions-prop.vue'
11
+ );
12
+
13
+ const result = JSON.parse(execFileSyncEslintVueFiles(eslintrc, vueFile));
14
+
15
+ // The test .vue file has 3 bento-date-range-picker instances without has-actions
16
+ expect(result[0].warningCount).toBe(3);
17
+
18
+ expect(result[0].messages[0]).toMatchObject({
19
+ ruleId: '@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop',
20
+ severity: 1,
21
+ messageId: 'missingProp',
22
+ message: `The temporary "has-actions" prop is missing on bento-date-range-picker. Add it to opt in to the new actions behavior.`,
23
+ });
24
+ });
25
+
26
+ it('should lint with warning when has-actions is present (remove action)', () => {
27
+ const eslintrc = path.resolve(__dirname, '__tests__', 'remove', 'eslint.config.js');
28
+ const vueFile = path.resolve(
29
+ __dirname,
30
+ '__tests__',
31
+ 'remove',
32
+ 'deprecation-bento-date-range-picker-has-actions-prop.vue'
33
+ );
34
+
35
+ const result = JSON.parse(execFileSyncEslintVueFiles(eslintrc, vueFile));
36
+
37
+ // The test .vue file has 4 bento-date-range-picker instances with has-actions
38
+ expect(result[0].warningCount).toBe(4);
39
+
40
+ expect(result[0].messages[0]).toMatchObject({
41
+ ruleId: '@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop',
42
+ severity: 1,
43
+ messageId: 'removeProp',
44
+ message: `The temporary "has-actions" prop should be removed from bento-date-range-picker.`,
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @typedef {import('eslint').Rule.RuleContext} RuleContext
3
+ */
4
+ /**
5
+ * @typedef {import('eslint-plugin-vue/lib/utils').RuleModule} RuleModule
6
+ */
7
+
8
+ const VueUtils = require('eslint-plugin-vue/lib/utils');
9
+
10
+ const COMPONENT_SELECTOR = `VElement:matches([name="bento-date-range-picker"], [name="BentoDateRangePicker"])`;
11
+ const STATIC_ATTR = `${COMPONENT_SELECTOR} > VStartTag > VAttribute[directive=false][key.name="has-actions"]`;
12
+ const DYNAMIC_ATTR = `${COMPONENT_SELECTOR} > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="has-actions"]`;
13
+
14
+ /**
15
+ * Checks whether a VElement already has the `has-actions` attribute set to a truthy value.
16
+ * Returns false if the prop is missing or explicitly set to false.
17
+ */
18
+ function hasHasActionsTruthy(startTag) {
19
+ return startTag.attributes.some(attr => {
20
+ if (!attr.directive) {
21
+ // Static: has-actions (boolean) or has-actions="something"
22
+ // has-actions="false" is the string "false", still counts as explicitly false
23
+ if (attr.key.name !== 'has-actions') {
24
+ return false;
25
+ }
26
+ return !attr.value || attr.value.value !== 'false';
27
+ }
28
+ // Dynamic: :has-actions="expr" or v-bind:has-actions="expr"
29
+ if (
30
+ attr.key.name &&
31
+ attr.key.name.name === 'bind' &&
32
+ attr.key.argument &&
33
+ attr.key.argument.name === 'has-actions'
34
+ ) {
35
+ // :has-actions="false" → expression is a Literal with value false
36
+ const expr = attr.value && attr.value.expression;
37
+ if (expr && expr.type === 'Literal' && expr.value === false) {
38
+ return false;
39
+ }
40
+ return true;
41
+ }
42
+ return false;
43
+ });
44
+ }
45
+
46
+ /**
47
+ * Removes an attribute node. If the attribute is alone on its line,
48
+ * removes the entire line (including leading whitespace and trailing newline)
49
+ * to avoid leaving a blank line.
50
+ */
51
+ function removeAttribute(fixer, node, sourceCode) {
52
+ const fullText = sourceCode.getText();
53
+ const lineStart = fullText.lastIndexOf('\n', node.range[0] - 1) + 1;
54
+ const lineEnd = node.range[1];
55
+
56
+ // Check if the attribute is the only non-whitespace content on its line
57
+ const textBefore = fullText.slice(lineStart, node.range[0]);
58
+ const textAfter = fullText.slice(node.range[1], fullText.indexOf('\n', node.range[1]));
59
+
60
+ if (textBefore.trim() === '' && textAfter.trim() === '') {
61
+ // Remove from end of previous line to end of this line
62
+ const removeEnd = fullText.indexOf('\n', lineEnd);
63
+ return fixer.removeRange([lineStart - 1, removeEnd >= 0 ? removeEnd : lineEnd]);
64
+ }
65
+
66
+ return fixer.remove(node);
67
+ }
68
+
69
+ /** @type {RuleModule} */
70
+ module.exports = {
71
+ meta: {
72
+ type: 'suggestion',
73
+ docs: {
74
+ description:
75
+ 'Manages the temporary "has-actions" prop on bento-date-range-picker. Use action "add" to add it or "remove" to remove it.',
76
+ category: 'Deprecations',
77
+ url: 'https://github.com/Adyen/bento/blob/main/packages/eslint/rules/deprecation-bento-date-range-picker-has-actions-prop/README.md',
78
+ recommended: true,
79
+ },
80
+ messages: {
81
+ missingProp: `The temporary "has-actions" prop is missing on bento-date-range-picker. Add it to opt in to the new actions behavior.`,
82
+ removeProp: `The temporary "has-actions" prop should be removed from bento-date-range-picker.`,
83
+ },
84
+ fixable: 'code',
85
+ schema: [
86
+ {
87
+ type: 'object',
88
+ properties: {
89
+ action: {
90
+ type: 'string',
91
+ enum: ['add', 'remove'],
92
+ default: 'add',
93
+ },
94
+ autofix: {
95
+ type: 'boolean',
96
+ default: false,
97
+ },
98
+ },
99
+ additionalProperties: false,
100
+ },
101
+ ],
102
+ },
103
+
104
+ /** @param {RuleContext} context */
105
+ create(context) {
106
+ const options = context.options[0] || {};
107
+ const action = options.action || 'add';
108
+ const autofix = options.autofix === true;
109
+
110
+ if (action === 'remove') {
111
+ /* ------------------------------------------------ */
112
+ /* REMOVE: report and optionally remove has-actions */
113
+ /* ------------------------------------------------ */
114
+ return VueUtils.defineTemplateBodyVisitor(context, {
115
+ [STATIC_ATTR](node) {
116
+ context.report({
117
+ node,
118
+ messageId: 'removeProp',
119
+ ...(autofix && {
120
+ fix(fixer) {
121
+ return removeAttribute(fixer, node, context.sourceCode);
122
+ },
123
+ }),
124
+ });
125
+ },
126
+ [DYNAMIC_ATTR](node) {
127
+ context.report({
128
+ node,
129
+ messageId: 'removeProp',
130
+ ...(autofix && {
131
+ fix(fixer) {
132
+ return removeAttribute(fixer, node, context.sourceCode);
133
+ },
134
+ }),
135
+ });
136
+ },
137
+ });
138
+ }
139
+
140
+ /* ------------------------------------------------ */
141
+ /* ADD: report components missing has-actions */
142
+ /* ------------------------------------------------ */
143
+ return VueUtils.defineTemplateBodyVisitor(context, {
144
+ [COMPONENT_SELECTOR](node) {
145
+ if (hasHasActionsTruthy(node.startTag)) {
146
+ return;
147
+ }
148
+
149
+ // Find if has-actions exists but is set to false
150
+ const falseAttr = node.startTag.attributes.find(attr => {
151
+ if (!attr.directive) {
152
+ return attr.key.name === 'has-actions' && attr.value && attr.value.value === 'false';
153
+ }
154
+ if (
155
+ attr.key.name &&
156
+ attr.key.name.name === 'bind' &&
157
+ attr.key.argument &&
158
+ attr.key.argument.name === 'has-actions'
159
+ ) {
160
+ const expr = attr.value && attr.value.expression;
161
+ return expr && expr.type === 'Literal' && expr.value === false;
162
+ }
163
+ return false;
164
+ });
165
+
166
+ context.report({
167
+ node: falseAttr || node.startTag,
168
+ messageId: 'missingProp',
169
+ ...(autofix && {
170
+ fix(fixer) {
171
+ // If has-actions="false" or :has-actions="false", replace with boolean has-actions
172
+ if (falseAttr) {
173
+ return fixer.replaceText(falseAttr, 'has-actions');
174
+ }
175
+
176
+ // Prop is completely missing – insert it
177
+ const sourceCode = context.sourceCode;
178
+ const fullText = sourceCode.getText();
179
+ const firstAttr = node.startTag.attributes[0];
180
+ if (firstAttr) {
181
+ const isMultiLine = firstAttr.loc.start.line !== node.startTag.loc.start.line;
182
+ if (isMultiLine) {
183
+ // Derive indentation from the actual whitespace before the first attribute
184
+ const lineStart = fullText.lastIndexOf('\n', firstAttr.range[0] - 1) + 1;
185
+ const indent = fullText.slice(lineStart, firstAttr.range[0]);
186
+ return fixer.insertTextBefore(firstAttr, `has-actions\n${indent}`);
187
+ }
188
+ return fixer.insertTextBefore(firstAttr, 'has-actions ');
189
+ }
190
+
191
+ // No attributes – insert before the closing > or />
192
+ const startTagText = sourceCode.getText(node.startTag);
193
+ const selfClosing = startTagText.endsWith('/>');
194
+ const insertPos = node.startTag.range[1] - (selfClosing ? 2 : 1);
195
+ // Only add a leading space if there isn't already whitespace before the insert position
196
+ const charBefore = fullText[insertPos - 1];
197
+ const leadingSpace = charBefore && /\s/.test(charBefore) ? '' : ' ';
198
+ return fixer.insertTextBeforeRange([insertPos, insertPos], `${leadingSpace}has-actions `);
199
+ },
200
+ }),
201
+ });
202
+ },
203
+ });
204
+ },
205
+ };
@@ -0,0 +1,176 @@
1
+ import { RuleTester } from 'eslint';
2
+ import vueParser from 'vue-eslint-parser';
3
+ import tsParser from '@typescript-eslint/parser';
4
+ import deprecationBentoDateRangePickerHasActionsProp from './deprecation-bento-date-range-picker-has-actions-prop';
5
+
6
+ const ruleTester = new RuleTester({
7
+ languageOptions: {
8
+ ecmaVersion: 'latest',
9
+ sourceType: 'module',
10
+ parser: vueParser,
11
+ parserOptions: {
12
+ parser: tsParser,
13
+ },
14
+ },
15
+ });
16
+
17
+ describe('@adyen/bento/deprecation-bento-date-range-picker-has-actions-prop', () => {
18
+ describe('action: add (default)', () => {
19
+ ruleTester.run(
20
+ 'deprecation-bento-date-range-picker-has-actions-prop (add)',
21
+ deprecationBentoDateRangePickerHasActionsProp,
22
+ {
23
+ valid: [
24
+ {
25
+ name: 'Valid: component already has has-actions (static boolean)',
26
+ filename: 'test.vue',
27
+ code: `<template><bento-date-range-picker has-actions /></template>`,
28
+ },
29
+ {
30
+ name: 'Valid: component already has has-actions (dynamic)',
31
+ filename: 'test.vue',
32
+ code: `<template><bento-date-range-picker :has-actions="true" /></template>`,
33
+ },
34
+ {
35
+ name: 'Valid: unrelated component without has-actions',
36
+ filename: 'test.vue',
37
+ code: `<template><bento-button label="click" /></template>`,
38
+ },
39
+ ],
40
+ invalid: [
41
+ {
42
+ name: 'Invalid: missing has-actions (no autofix)',
43
+ filename: 'test.vue',
44
+ code: `<template><bento-date-range-picker label="Pick" /></template>`,
45
+ output: null,
46
+ errors: [{ messageId: 'missingProp' }],
47
+ },
48
+ {
49
+ name: 'Invalid: missing has-actions (autofix adds prop)',
50
+ filename: 'test.vue',
51
+ code: `<template><bento-date-range-picker label="Pick" /></template>`,
52
+ output: `<template><bento-date-range-picker has-actions label="Pick" /></template>`,
53
+ options: [{ action: 'add', autofix: true }],
54
+ errors: [{ messageId: 'missingProp' }],
55
+ },
56
+ {
57
+ name: 'Invalid: missing has-actions on self-closing tag without attrs (autofix)',
58
+ filename: 'test.vue',
59
+ code: `<template><bento-date-range-picker /></template>`,
60
+ output: `<template><bento-date-range-picker has-actions /></template>`,
61
+ options: [{ autofix: true }],
62
+ errors: [{ messageId: 'missingProp' }],
63
+ },
64
+ {
65
+ name: 'Invalid: missing has-actions on self-closing tag without space before /> (autofix)',
66
+ filename: 'test.vue',
67
+ code: `<template><bento-date-range-picker/></template>`,
68
+ output: `<template><bento-date-range-picker has-actions /></template>`,
69
+ options: [{ autofix: true }],
70
+ errors: [{ messageId: 'missingProp' }],
71
+ },
72
+ {
73
+ name: 'Invalid: has-actions="false" (static string false, no autofix)',
74
+ filename: 'test.vue',
75
+ code: `<template><bento-date-range-picker has-actions="false" /></template>`,
76
+ output: null,
77
+ errors: [{ messageId: 'missingProp' }],
78
+ },
79
+ {
80
+ name: 'Invalid: has-actions="false" (static string false, autofix replaces)',
81
+ filename: 'test.vue',
82
+ code: `<template><bento-date-range-picker has-actions="false" label="Pick" /></template>`,
83
+ output: `<template><bento-date-range-picker has-actions label="Pick" /></template>`,
84
+ options: [{ autofix: true }],
85
+ errors: [{ messageId: 'missingProp' }],
86
+ },
87
+ {
88
+ name: 'Invalid: :has-actions="false" (dynamic false, autofix replaces)',
89
+ filename: 'test.vue',
90
+ code: `<template><bento-date-range-picker :has-actions="false" label="Pick" /></template>`,
91
+ output: `<template><bento-date-range-picker has-actions label="Pick" /></template>`,
92
+ options: [{ autofix: true }],
93
+ errors: [{ messageId: 'missingProp' }],
94
+ },
95
+ {
96
+ name: 'Invalid: multi-line component missing has-actions (autofix preserves indentation)',
97
+ filename: 'test.vue',
98
+ code: `<template>
99
+ <bento-date-range-picker
100
+ label="Pick"
101
+ />
102
+ </template>`,
103
+ output: `<template>
104
+ <bento-date-range-picker
105
+ has-actions
106
+ label="Pick"
107
+ />
108
+ </template>`,
109
+ options: [{ autofix: true }],
110
+ errors: [{ messageId: 'missingProp' }],
111
+ },
112
+ ],
113
+ }
114
+ );
115
+ });
116
+
117
+ describe('action: remove', () => {
118
+ ruleTester.run(
119
+ 'deprecation-bento-date-range-picker-has-actions-prop (remove)',
120
+ deprecationBentoDateRangePickerHasActionsProp,
121
+ {
122
+ valid: [
123
+ {
124
+ name: 'Valid: component without has-actions',
125
+ filename: 'test.vue',
126
+ code: `<template><bento-date-range-picker label="Pick" /></template>`,
127
+ options: [{ action: 'remove' }],
128
+ },
129
+ ],
130
+ invalid: [
131
+ {
132
+ name: 'Invalid: has-actions present (static boolean, no autofix)',
133
+ filename: 'test.vue',
134
+ code: `<template><bento-date-range-picker has-actions /></template>`,
135
+ output: null,
136
+ options: [{ action: 'remove' }],
137
+ errors: [{ messageId: 'removeProp' }],
138
+ },
139
+ {
140
+ name: 'Invalid: has-actions present (static boolean, autofix removes)',
141
+ filename: 'test.vue',
142
+ code: `<template><bento-date-range-picker has-actions label="Pick" /></template>`,
143
+ output: `<template><bento-date-range-picker label="Pick" /></template>`,
144
+ options: [{ action: 'remove', autofix: true }],
145
+ errors: [{ messageId: 'removeProp' }],
146
+ },
147
+ {
148
+ name: 'Invalid: has-actions present (dynamic, autofix removes)',
149
+ filename: 'test.vue',
150
+ code: `<template><bento-date-range-picker :has-actions="true" /></template>`,
151
+ output: `<template><bento-date-range-picker /></template>`,
152
+ options: [{ action: 'remove', autofix: true }],
153
+ errors: [{ messageId: 'removeProp' }],
154
+ },
155
+ {
156
+ name: 'Invalid: multi-line has-actions on its own line (autofix removes entire line)',
157
+ filename: 'test.vue',
158
+ code: `<template>
159
+ <bento-date-range-picker
160
+ has-actions
161
+ label="Pick"
162
+ />
163
+ </template>`,
164
+ output: `<template>
165
+ <bento-date-range-picker
166
+ label="Pick"
167
+ />
168
+ </template>`,
169
+ options: [{ action: 'remove', autofix: true }],
170
+ errors: [{ messageId: 'removeProp' }],
171
+ },
172
+ ],
173
+ }
174
+ );
175
+ });
176
+ });
@@ -0,0 +1,151 @@
1
+ # deprecation-country-custom-usage
2
+
3
+ ## Description
4
+
5
+ This rule deprecates the `variant="custom"` prop and custom `label` usage across country-related components.
6
+ These features are being removed because country components should use built-in variants (`name`, `iso`, `capital`, `phone_code`) rather than custom labels.
7
+
8
+ The rule covers three components:
9
+
10
+ 1. **`bento-country`** — the `variant="custom"` prop and the `label` prop
11
+ 2. **`bento-dropdown-country`** — the `variant="custom"` prop and `label` keys in the `:items` array
12
+ 3. **`BentoFilterItemType.SELECT_COUNTRY`** — the `variant: 'custom'` option and `label` keys in `listboxItems`
13
+
14
+ ## Rule Details
15
+
16
+ ### `bento-country`
17
+
18
+ The `label` prop only takes effect when `variant="custom"` is set. Using either one alone is dead code.
19
+
20
+ #### ❌ Incorrect
21
+
22
+ ```html
23
+ <!-- variant="custom" only — variant does nothing without a label -->
24
+ <bento-country code="NL" variant="custom" />
25
+
26
+ <!-- label only — label is ignored without variant="custom" -->
27
+ <bento-country code="NL" label="Custom label" />
28
+
29
+ <!-- both — deprecated, use bento-typography instead -->
30
+ <bento-country code="NL" variant="custom" label="Custom label" />
31
+ <bento-country code="NL" :variant="BentoCountryVariant.CUSTOM" :label="customLabel" />
32
+ ```
33
+
34
+ #### ✅ Correct
35
+
36
+ ```html
37
+ <!-- Use a built-in variant -->
38
+ <bento-country code="NL" variant="name" />
39
+
40
+ <!-- Use bento-typography for custom text -->
41
+ <bento-typography el="span">Custom label</bento-typography>
42
+ ```
43
+
44
+ ### `bento-dropdown-country`
45
+
46
+ The `label` key in items is only used when `variant="custom"` is set. Using either one alone is dead code.
47
+
48
+ #### ❌ Incorrect
49
+
50
+ ```html
51
+ <!-- label in items only — labels are ignored without variant="custom" -->
52
+ <bento-dropdown-country :items="[{ value: 'NL', label: 'Custom label' }]" />
53
+
54
+ <!-- variant="custom" only — variant does nothing without labels in items -->
55
+ <bento-dropdown-country :items="[{ value: 'NL' }]" variant="custom" />
56
+
57
+ <!-- both — deprecated, use bento-dropdown instead -->
58
+ <bento-dropdown-country :items="[{ value: 'NL', label: 'Custom label' }]" variant="custom" />
59
+ ```
60
+
61
+ #### ✅ Correct
62
+
63
+ ```html
64
+ <!-- Use a built-in variant -->
65
+ <bento-dropdown-country :items="[{ value: 'NL' }]" variant="name" />
66
+
67
+ <!-- Use bento-dropdown for custom labels -->
68
+ <bento-dropdown :items="[{ value: 'NL', label: 'Custom label' }]" />
69
+ ```
70
+
71
+ ### `SELECT_COUNTRY` filter config (`bento-filter-bar` / `bento-data-grid`)
72
+
73
+ The `label` key in `listboxItems` is only used when `variant: 'custom'` is set. Using either one alone is dead code.
74
+
75
+ #### ❌ Incorrect
76
+
77
+ ```html
78
+ <!-- variant: 'custom' only -->
79
+ <bento-filter-bar
80
+ :config="[{
81
+ field: 'country', label: 'Country',
82
+ type: BentoFilterItemType.SELECT_COUNTRY,
83
+ options: { listboxItems: [{ value: 'NL' }], variant: 'custom' }
84
+ }]"
85
+ />
86
+
87
+ <!-- label in listboxItems only -->
88
+ <bento-filter-bar
89
+ :config="[{
90
+ field: 'country', label: 'Country',
91
+ type: BentoFilterItemType.SELECT_COUNTRY,
92
+ options: { listboxItems: [{ value: 'NL', label: 'Custom label' }] }
93
+ }]"
94
+ />
95
+
96
+ <!-- both -->
97
+ <bento-filter-bar
98
+ :config="[{
99
+ field: 'country', label: 'Country',
100
+ type: BentoFilterItemType.SELECT_COUNTRY,
101
+ options: {
102
+ listboxItems: [{ value: 'NL', label: 'Custom label' }],
103
+ variant: 'custom'
104
+ }
105
+ }]"
106
+ />
107
+ ```
108
+
109
+ #### ✅ Correct
110
+
111
+ ```html
112
+ <!-- Use a built-in variant -->
113
+ <bento-filter-bar
114
+ :config="[{
115
+ field: 'country', label: 'Country',
116
+ type: BentoFilterItemType.SELECT_COUNTRY,
117
+ options: { listboxItems: [{ value: 'NL' }], variant: 'name' }
118
+ }]"
119
+ />
120
+
121
+ <!-- Use SELECT type for custom labels -->
122
+ <bento-filter-bar
123
+ :config="[{
124
+ field: 'country', label: 'Country',
125
+ type: BentoFilterItemType.SELECT,
126
+ options: { listboxItems: [{ value: 'NL', label: 'Custom label' }] }
127
+ }]"
128
+ />
129
+ ```
130
+
131
+ ## Autofix
132
+
133
+ Enable opt-in autofix via ESLint config:
134
+
135
+ ```js
136
+ '@adyen/bento/deprecation-country-custom-usage': ['warn', { autofix: true }]
137
+ ```
138
+
139
+ The autofix behavior depends on which deprecated props are present:
140
+
141
+ | Scenario | Autofix |
142
+ |---|---|
143
+ | `variant="custom"` only | Removes the `variant` prop/property |
144
+ | `label` only | Removes the `label` prop/property |
145
+ | Both `variant="custom"` + `label` | Full replacement (see below) |
146
+
147
+ ### Full replacement (both present)
148
+
149
+ - **`bento-country`** — replaces the entire element with `<bento-typography el="span">{{ label }}</bento-typography>`
150
+ - **`bento-dropdown-country`** — renames the tag to `bento-dropdown` and removes the `variant` attribute
151
+ - **`SELECT_COUNTRY` filter** — changes `type` to `BentoFilterItemType.SELECT` and removes `variant` from options