@adyen/eslint-plugin-bento 2.13.0 → 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.
Files changed (18) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -1
  3. package/rules/deprecation-bento-timeline-item-variant-prop/README.md +13 -5
  4. package/rules/deprecation-bento-timeline-item-variant-prop/__tests__/deprecation-bento-timeline-item-variant-prop.vue +2 -2
  5. package/rules/deprecation-bento-timeline-item-variant-prop/deprecation-bento-timeline-item-variant-prop.integration.test.ts +6 -2
  6. package/rules/deprecation-bento-timeline-item-variant-prop/deprecation-bento-timeline-item-variant-prop.js +61 -12
  7. package/rules/deprecation-bento-timeline-item-variant-prop/deprecation-bento-timeline-item-variant-prop.test.ts +87 -12
  8. package/rules/deprecation-components-error-prop/README.md +8 -4
  9. package/rules/deprecation-components-error-prop/__tests__/eslint.config.js +1 -1
  10. package/rules/deprecation-components-error-prop/deprecation-components-error-prop.integration.test.ts +14 -10
  11. package/rules/deprecation-components-error-prop/deprecation-components-error-prop.js +20 -30
  12. package/rules/deprecation-components-error-prop/deprecation-components-error-prop.test.ts +69 -36
  13. package/rules/deprecation-components-has-error-prop/README.md +7 -3
  14. package/rules/deprecation-components-has-error-prop/deprecation-components-has-error-prop.integration.test.ts +10 -2
  15. package/rules/deprecation-components-has-error-prop/deprecation-components-has-error-prop.js +18 -24
  16. package/rules/deprecation-components-has-error-prop/deprecation-components-has-error-prop.test.ts +58 -69
  17. package/rules/deprecation-components-input-emit/deprecation-components-input-emit.integration.test.ts +9 -1
  18. package/rules/deprecation-components-input-emit/deprecation-components-input-emit.js +9 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.15.0 (2026-06-03)
4
+
5
+ ### Bug Fixes
6
+
7
+ - fixed `deprecation-components-input-emit` error message wording ([5a22428fa](https://github.com/Adyen/bento/commit/5a22428fa))
8
+ - updated `deprecation-components-error-prop` rule from fix to suggest ([edcf472fd](https://github.com/Adyen/bento/commit/edcf472fd))
9
+ - updated `deprecation-components-has-error-prop` rule from fix to suggest ([f573b3643](https://github.com/Adyen/bento/commit/f573b3643))
10
+ - updated `deprecation-bento-timeline-item-variant-prop` to replace values correctly ([989c92838](https://github.com/Adyen/bento/commit/989c92838))
11
+
12
+ ### Code Refactoring
13
+
14
+ - extended `deprecation-components-input-emit` error message with race conditions example ([4030de20f](https://github.com/Adyen/bento/commit/4030de20f))
15
+
16
+ ### ❤️ Thank You
17
+
18
+ - daver
19
+ - erice
20
+ - gerald
21
+
22
+
23
+ ## 2.14.0 (2026-05-27)
24
+
25
+ This was a version bump only for eslint to align it with other projects, there were no code changes.
26
+
27
+
3
28
  ## 2.13.0 (2026-05-20)
4
29
 
5
30
  This was a version bump only for eslint to align it with other projects, there were no code changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adyen/eslint-plugin-bento",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "Adyen Bento ESLint rules",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -2,20 +2,28 @@
2
2
 
3
3
  ## Description
4
4
 
5
- The `variant` prop in the following components is deprecated:
5
+ The `variant` prop in `bento-timeline-item` is deprecated. Use `status` instead.
6
6
 
7
- - `bento-timeline-item`
7
+ The `variant` prop accepted `BentoTimelineItemVariant` enum values (icon names), while `status` accepts color strings.
8
+ The autofix remaps values automatically:
8
9
 
9
- Please use `status` instead.
10
+ | `variant` value | `status` value |
11
+ | ------------------------------- | -------------- |
12
+ | `SquareSmallIcon` / `DEFAULT` | `black` |
13
+ | `CheckedCircleIcon` / `SUCCESS` | `green` |
14
+ | `CrossCircleIcon` / `CRITICAL` | `red` |
15
+
16
+ > **Note:** If the variant value cannot be statically resolved (e.g. a variable reference), the autofix is skipped and
17
+ > only the deprecation warning is reported.
10
18
 
11
19
  ## Rule Details
12
20
 
13
- This rule reports usages of the deprecated item.
21
+ This rule reports usages of the deprecated `variant` prop and autofixes it to `status` with the correct color value.
14
22
 
15
23
  ### ❌ Incorrect
16
24
 
17
25
  ```html
18
- <bento-timeline-item variant="SUCCESS" /> <bento-timeline-item :variant="BentoTimelineItemVariant.CRITICAL" />
26
+ <bento-timeline-item variant="CheckedCircleIcon" /> <bento-timeline-item :variant="BentoTimelineItemVariant.CRITICAL" />
19
27
  ```
20
28
 
21
29
  ### ✅ Correct
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div>
3
- <bento-timeline-item variant="SUCCESS" />
3
+ <bento-timeline-item variant="CheckedCircleIcon" />
4
4
  <bento-timeline-item :variant="BentoTimelineItemVariant.CRITICAL" />
5
- <bento-timeline-item :variant="BentoTimelineItemVariant.SUCCESS" />
5
+ <bento-timeline-item :variant="BentoTimelineItemVariant.DEFAULT" />
6
6
  </div>
7
7
  </template>
8
8
 
@@ -8,8 +8,12 @@ describe('@adyen/bento/deprecation-bento-timeline-item-variant-prop', () => {
8
8
  // Load the test file generated alongside this one
9
9
  const vueFile = path.resolve(__dirname, '__tests__', 'deprecation-bento-timeline-item-variant-prop.vue');
10
10
 
11
+ // Strip any non-JSON preamble (e.g. pnpm warnings)
12
+ const rawOutput = execFileSyncEslintVueFiles(eslintrc, vueFile);
13
+ const jsonStart = rawOutput.indexOf('[');
14
+
11
15
  // Execute the linter
12
- const result = JSON.parse(execFileSyncEslintVueFiles(eslintrc, vueFile));
16
+ const result = JSON.parse(jsonStart >= 0 ? rawOutput.slice(jsonStart) : rawOutput);
13
17
 
14
18
  // 3 examples: static, shorthand, longhand
15
19
  expect(result[0].warningCount).toBe(3);
@@ -24,7 +28,7 @@ describe('@adyen/bento/deprecation-bento-timeline-item-variant-prop', () => {
24
28
  message: `The "variant" prop is deprecated. Please use "status" instead.`,
25
29
 
26
30
  fix: expect.objectContaining({
27
- text: expect.stringContaining('status'),
31
+ text: expect.stringContaining('status='),
28
32
  }),
29
33
  });
30
34
  });
@@ -7,6 +7,47 @@
7
7
 
8
8
  const VueUtils = require('eslint-plugin-vue/lib/utils');
9
9
 
10
+ // Maps BentoTimelineItemVariant enum values (runtime strings) to status colors
11
+ const VARIANT_VALUE_TO_STATUS = {
12
+ SquareSmallIcon: 'black',
13
+ CheckedCircleIcon: 'green',
14
+ CrossCircleIcon: 'red',
15
+ };
16
+
17
+ // Maps BentoTimelineItemVariant enum member names to status colors
18
+ const VARIANT_ENUM_KEY_TO_STATUS = {
19
+ DEFAULT: 'black',
20
+ SUCCESS: 'green',
21
+ CRITICAL: 'red',
22
+ };
23
+
24
+ /**
25
+ * Resolves a dynamic expression to a status color string.
26
+ * Returns null if the expression cannot be statically resolved.
27
+ */
28
+ function resolveExpressionToStatus(expression) {
29
+ if (!expression) {
30
+ return null;
31
+ }
32
+
33
+ // String literal: :variant="'CheckedCircleIcon'"
34
+ if (expression.type === 'Literal' && typeof expression.value === 'string') {
35
+ return VARIANT_VALUE_TO_STATUS[expression.value] ?? null;
36
+ }
37
+
38
+ // Enum member: :variant="BentoTimelineItemVariant.SUCCESS"
39
+ if (
40
+ expression.type === 'MemberExpression' &&
41
+ expression.object?.type === 'Identifier' &&
42
+ expression.object.name === 'BentoTimelineItemVariant' &&
43
+ expression.property?.type === 'Identifier'
44
+ ) {
45
+ return VARIANT_ENUM_KEY_TO_STATUS[expression.property.name] ?? null;
46
+ }
47
+
48
+ return null;
49
+ }
50
+
10
51
  /** @type {RuleModule} */
11
52
  module.exports = {
12
53
  meta: {
@@ -44,33 +85,41 @@ module.exports = {
44
85
  /* PROP DEPRECATION */
45
86
  /* ------------------------------------------------ */
46
87
  return VueUtils.defineTemplateBodyVisitor(context, {
47
- // 1. STATIC: value="foo"
88
+ // 1. STATIC: variant="CheckedCircleIcon"
48
89
  [`VElement:matches([name="bento-timeline-item"], [name="BentoTimelineItem"]) > VStartTag > VAttribute[directive=false][key.name="variant"]`](
49
90
  node
50
91
  ) {
92
+ const staticValue = node.value?.value;
93
+ const mappedStatus = staticValue ? VARIANT_VALUE_TO_STATUS[staticValue] : null;
94
+
51
95
  context.report({
52
96
  node,
53
97
  messageId: 'deprecation',
54
- ...(autofix && {
55
- fix(fixer) {
56
- return fixer.replaceText(node.key, 'status');
57
- },
58
- }),
98
+ ...(autofix &&
99
+ mappedStatus && {
100
+ fix(fixer) {
101
+ return fixer.replaceText(node, `status="${mappedStatus}"`);
102
+ },
103
+ }),
59
104
  });
60
105
  },
61
106
 
62
- // 2. DYNAMIC: :value="foo" or v-bind:value="foo"
107
+ // 2. DYNAMIC: :variant="BentoTimelineItemVariant.SUCCESS" or v-bind:variant="..."
63
108
  [`VElement:matches([name="bento-timeline-item"], [name="BentoTimelineItem"]) > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="variant"]`](
64
109
  node
65
110
  ) {
111
+ const expression = node.value?.expression;
112
+ const mappedStatus = resolveExpressionToStatus(expression);
113
+
66
114
  context.report({
67
115
  node,
68
116
  messageId: 'deprecation',
69
- ...(autofix && {
70
- fix(fixer) {
71
- return fixer.replaceText(node.key.argument, 'status');
72
- },
73
- }),
117
+ ...(autofix &&
118
+ mappedStatus && {
119
+ fix(fixer) {
120
+ return fixer.replaceText(node, `status="${mappedStatus}"`);
121
+ },
122
+ }),
74
123
  });
75
124
  },
76
125
  });
@@ -18,20 +18,21 @@ describe('@adyen/bento/deprecation-bento-timeline-item-variant-prop', () => {
18
18
  ruleTester.run('deprecation-bento-timeline-item-variant-prop', deprecationBentoTimelineItemVariantProp, {
19
19
  valid: [
20
20
  {
21
- name: 'Valid usage for bento-timeline-item',
21
+ name: 'Valid usage with status prop',
22
22
  filename: 'test.vue',
23
23
  code: `
24
24
  <template>
25
- <bento-timeline-item status="val" />
25
+ <bento-timeline-item status="green" />
26
26
  </template>
27
27
  `,
28
28
  },
29
29
  ],
30
30
  invalid: [
31
+ // No autofix when disabled
31
32
  {
32
33
  name: 'Invalid: autofix disabled (explicit false)',
33
34
  filename: 'test.vue',
34
- code: `<template><bento-timeline-item variant="val" /></template>`,
35
+ code: `<template><bento-timeline-item variant="CheckedCircleIcon" /></template>`,
35
36
  output: null,
36
37
  options: [{ autofix: false }],
37
38
  errors: [{ messageId: 'deprecation' }],
@@ -39,25 +40,99 @@ describe('@adyen/bento/deprecation-bento-timeline-item-variant-prop', () => {
39
40
  {
40
41
  name: 'Invalid: autofix disabled (no options)',
41
42
  filename: 'test.vue',
42
- code: `<template><bento-timeline-item :variant="val" /></template>`,
43
+ code: `<template><bento-timeline-item :variant="BentoTimelineItemVariant.SUCCESS" /></template>`,
43
44
  output: null,
44
45
  errors: [{ messageId: 'deprecation' }],
45
46
  },
46
- // bento-timeline-item: Static
47
+
48
+ // Static values — remaps enum value strings to colors
47
49
  {
48
- name: 'Invalid: Static prop on bento-timeline-item',
50
+ name: 'Invalid: Static variant="SquareSmallIcon" status="black"',
49
51
  filename: 'test.vue',
50
- code: `<template><bento-timeline-item variant="val" /></template>`,
51
- output: `<template><bento-timeline-item status="val" /></template>`,
52
+ code: `<template><bento-timeline-item variant="SquareSmallIcon" /></template>`,
53
+ output: `<template><bento-timeline-item status="black" /></template>`,
52
54
  options: [{ autofix: true }],
53
55
  errors: [{ messageId: 'deprecation' }],
54
56
  },
55
- // bento-timeline-item: Shorthand
56
57
  {
57
- name: 'Invalid: Dynamic prop on bento-timeline-item',
58
+ name: 'Invalid: Static variant="CheckedCircleIcon" status="green"',
58
59
  filename: 'test.vue',
59
- code: `<template><bento-timeline-item :variant="val" /></template>`,
60
- output: `<template><bento-timeline-item :status="val" /></template>`,
60
+ code: `<template><bento-timeline-item variant="CheckedCircleIcon" /></template>`,
61
+ output: `<template><bento-timeline-item status="green" /></template>`,
62
+ options: [{ autofix: true }],
63
+ errors: [{ messageId: 'deprecation' }],
64
+ },
65
+ {
66
+ name: 'Invalid: Static variant="CrossCircleIcon" → status="red"',
67
+ filename: 'test.vue',
68
+ code: `<template><bento-timeline-item variant="CrossCircleIcon" /></template>`,
69
+ output: `<template><bento-timeline-item status="red" /></template>`,
70
+ options: [{ autofix: true }],
71
+ errors: [{ messageId: 'deprecation' }],
72
+ },
73
+
74
+ // Static with unknown value — no fix applied
75
+ {
76
+ name: 'Invalid: Static variant with unknown value does not autofix',
77
+ filename: 'test.vue',
78
+ code: `<template><bento-timeline-item variant="unknownValue" /></template>`,
79
+ output: null,
80
+ options: [{ autofix: true }],
81
+ errors: [{ messageId: 'deprecation' }],
82
+ },
83
+
84
+ // Dynamic with enum member expression — remaps enum key to color
85
+ {
86
+ name: 'Invalid: Dynamic :variant="BentoTimelineItemVariant.DEFAULT" → status="black"',
87
+ filename: 'test.vue',
88
+ code: `<template><bento-timeline-item :variant="BentoTimelineItemVariant.DEFAULT" /></template>`,
89
+ output: `<template><bento-timeline-item status="black" /></template>`,
90
+ options: [{ autofix: true }],
91
+ errors: [{ messageId: 'deprecation' }],
92
+ },
93
+ {
94
+ name: 'Invalid: Dynamic :variant="BentoTimelineItemVariant.SUCCESS" → status="green"',
95
+ filename: 'test.vue',
96
+ code: `<template><bento-timeline-item :variant="BentoTimelineItemVariant.SUCCESS" /></template>`,
97
+ output: `<template><bento-timeline-item status="green" /></template>`,
98
+ options: [{ autofix: true }],
99
+ errors: [{ messageId: 'deprecation' }],
100
+ },
101
+ {
102
+ name: 'Invalid: Dynamic :variant="BentoTimelineItemVariant.CRITICAL" → status="red"',
103
+ filename: 'test.vue',
104
+ code: `<template><bento-timeline-item :variant="BentoTimelineItemVariant.CRITICAL" /></template>`,
105
+ output: `<template><bento-timeline-item status="red" /></template>`,
106
+ options: [{ autofix: true }],
107
+ errors: [{ messageId: 'deprecation' }],
108
+ },
109
+
110
+ // Dynamic with string literal — remaps value to color
111
+ {
112
+ name: 'Invalid: Dynamic :variant="\'CheckedCircleIcon\'" → status="green"',
113
+ filename: 'test.vue',
114
+ code: `<template><bento-timeline-item :variant="'CheckedCircleIcon'" /></template>`,
115
+ output: `<template><bento-timeline-item status="green" /></template>`,
116
+ options: [{ autofix: true }],
117
+ errors: [{ messageId: 'deprecation' }],
118
+ },
119
+
120
+ // Dynamic with unknown expression — no fix applied
121
+ {
122
+ name: 'Invalid: Dynamic :variant with unknown expression does not autofix',
123
+ filename: 'test.vue',
124
+ code: `<template><bento-timeline-item :variant="computedVariant" /></template>`,
125
+ output: null,
126
+ options: [{ autofix: true }],
127
+ errors: [{ messageId: 'deprecation' }],
128
+ },
129
+
130
+ // Dynamic with non-BentoTimelineItemVariant enum — no fix applied
131
+ {
132
+ name: 'Invalid: Dynamic :variant with unrelated enum does not autofix',
133
+ filename: 'test.vue',
134
+ code: `<template><bento-timeline-item :variant="customStatus.SUCCESS" /></template>`,
135
+ output: null,
61
136
  options: [{ autofix: true }],
62
137
  errors: [{ messageId: 'deprecation' }],
63
138
  },
@@ -10,9 +10,13 @@ The `error` prop in the following components is deprecated:
10
10
 
11
11
  Please use `error-message` instead.
12
12
 
13
+ > **Note:** The `error` prop is a `boolean` that only toggles error styling, while `error-message` is a `string` that
14
+ > both displays a message and applies error styling. A direct rename is not type-safe — you must also update the bound
15
+ > value from a boolean to an error message string.
16
+
13
17
  ## Rule Details
14
18
 
15
- This rule reports usages of the deprecated item.
19
+ This rule reports usages of the deprecated `error` prop and provides an IDE suggestion (not an autofix) to rename it.
16
20
 
17
21
  ### ❌ Incorrect
18
22
 
@@ -31,13 +35,13 @@ This rule reports usages of the deprecated item.
31
35
  ### ✅ Correct
32
36
 
33
37
  ```html
34
- <bento-dropdown error-message="value" />
38
+ <bento-dropdown error-message="Error message text" />
35
39
  ```
36
40
 
37
41
  ```html
38
- <bento-input-field error-message="value" />
42
+ <bento-input-field error-message="Error message text" />
39
43
  ```
40
44
 
41
45
  ```html
42
- <bento-input-field-phone-number error-message="value" />
46
+ <bento-input-field-phone-number error-message="Error message text" />
43
47
  ```
@@ -19,7 +19,7 @@ module.exports = defineConfig([
19
19
  },
20
20
  rules: {
21
21
  // Register the specific rule we are testing
22
- '@adyen/bento/deprecation-components-error-prop': ['warn', { autofix: true }],
22
+ '@adyen/bento/deprecation-components-error-prop': 'warn',
23
23
  },
24
24
  files: ['**/*.vue'],
25
25
  },
@@ -8,8 +8,10 @@ describe('@adyen/bento/deprecation-components-error-prop', () => {
8
8
  // Load the test file generated alongside this one
9
9
  const vueFile = path.resolve(__dirname, '__tests__', 'deprecation-components-error-prop.vue');
10
10
 
11
- // Execute the linter
12
- const result = JSON.parse(execFileSyncEslintVueFiles(eslintrc, vueFile));
11
+ // Execute the linter — strip any non-JSON preamble (e.g. pnpm warnings)
12
+ const rawOutput = execFileSyncEslintVueFiles(eslintrc, vueFile);
13
+ const jsonStart = rawOutput.indexOf('[{"');
14
+ const result = JSON.parse(jsonStart >= 0 ? rawOutput.slice(jsonStart) : rawOutput);
13
15
 
14
16
  /* ------------------------------------------------ */
15
17
  /* CASE 2: SINGLE REPLACEMENT */
@@ -20,18 +22,20 @@ describe('@adyen/bento/deprecation-components-error-prop', () => {
20
22
  const numberOfComponents = 3;
21
23
  expect(result[0].warningCount).toBe(numberOfComponents * 3);
22
24
 
23
- // Verify the message structure on the first error found.
24
- // Since the rule is the same for all components, checking the first one confirms the message is correct.
25
+ // Verify the message structure on the first warning found.
25
26
  expect(result[0].messages[0]).toMatchObject({
26
27
  ruleId: '@adyen/bento/deprecation-components-error-prop',
27
28
  severity: 1,
28
-
29
29
  messageId: 'deprecation',
30
- message: `The "error" prop is deprecated. Please use "error-message" instead.`,
31
-
32
- fix: expect.objectContaining({
33
- text: expect.stringContaining('error-message'),
34
- }),
30
+ message: `The "error" prop is deprecated. Use "error-message" instead. Note that "error" accepted a boolean, but "error-message" expects a string, so update the value to an error message.`,
31
+ suggestions: expect.arrayContaining([
32
+ expect.objectContaining({
33
+ messageId: 'suggestReplace',
34
+ fix: expect.objectContaining({
35
+ text: expect.stringContaining('error-message'),
36
+ }),
37
+ }),
38
+ ]),
35
39
  });
36
40
  });
37
41
  });
@@ -19,59 +19,49 @@ module.exports = {
19
19
  recommended: true,
20
20
  },
21
21
  messages: {
22
- deprecation: `The "error" prop is deprecated. Please use "error-message" instead.`,
22
+ deprecation: `The "error" prop is deprecated. Use "error-message" instead. Note that "error" accepted a boolean, but "error-message" expects a string, so update the value to an error message.`,
23
+ suggestReplace: `Replace "error" with "error-message". Remember to update the value from a boolean to a string error message.`,
23
24
  },
24
- fixable: 'code',
25
- schema: [
26
- {
27
- type: 'object',
28
- properties: {
29
- autofix: {
30
- type: 'boolean',
31
- default: false,
32
- },
33
- },
34
- additionalProperties: false,
35
- },
36
- ],
25
+ hasSuggestions: true,
26
+ schema: [],
37
27
  },
38
28
 
39
29
  /** @param {RuleContext} context */
40
30
  create(context) {
41
- const options = context.options[0] || {};
42
- const autofix = options.autofix === true;
43
-
44
- /* ------------------------------------------------ */
45
- /* CASE 2: PROP DEPRECATION (Multiple Components) */
46
- /* ------------------------------------------------ */
47
31
  return VueUtils.defineTemplateBodyVisitor(context, {
48
- // 1. STATIC: value="foo"
32
+ // 1. STATIC: error="foo"
49
33
  [`VElement:matches([name="bento-dropdown"], [name="BentoDropdown"], [name="bento-input-field"], [name="BentoInputField"], [name="bento-input-field-phone-number"], [name="BentoInputFieldPhoneNumber"]) > VStartTag > VAttribute[directive=false][key.name="error"]`](
50
34
  node
51
35
  ) {
52
36
  context.report({
53
37
  node,
54
38
  messageId: 'deprecation',
55
- ...(autofix && {
56
- fix(fixer) {
57
- return fixer.replaceText(node.key, 'error-message');
39
+ suggest: [
40
+ {
41
+ messageId: 'suggestReplace',
42
+ fix(fixer) {
43
+ return fixer.replaceText(node.key, 'error-message');
44
+ },
58
45
  },
59
- }),
46
+ ],
60
47
  });
61
48
  },
62
49
 
63
- // 2. DYNAMIC: :value="foo" or v-bind:value="foo"
50
+ // 2. DYNAMIC: :error="foo" or v-bind:error="foo"
64
51
  [`VElement:matches([name="bento-dropdown"], [name="BentoDropdown"], [name="bento-input-field"], [name="BentoInputField"], [name="bento-input-field-phone-number"], [name="BentoInputFieldPhoneNumber"]) > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="error"]`](
65
52
  node
66
53
  ) {
67
54
  context.report({
68
55
  node,
69
56
  messageId: 'deprecation',
70
- ...(autofix && {
71
- fix(fixer) {
72
- return fixer.replaceText(node.key.argument, 'error-message');
57
+ suggest: [
58
+ {
59
+ messageId: 'suggestReplace',
60
+ fix(fixer) {
61
+ return fixer.replaceText(node.key.argument, 'error-message');
62
+ },
73
63
  },
74
- }),
64
+ ],
75
65
  });
76
66
  },
77
67
  });
@@ -46,74 +46,107 @@ describe('@adyen/bento/deprecation-components-error-prop', () => {
46
46
  },
47
47
  ],
48
48
  invalid: [
49
- {
50
- name: 'Invalid: autofix disabled (explicit false)',
51
- filename: 'test.vue',
52
- code: `<template><bento-dropdown error="val" /></template>`,
53
- output: null,
54
- options: [{ autofix: false }],
55
- errors: [{ messageId: 'deprecation' }],
56
- },
57
- {
58
- name: 'Invalid: autofix disabled (no options)',
59
- filename: 'test.vue',
60
- code: `<template><bento-dropdown :error="val" /></template>`,
61
- output: null,
62
- errors: [{ messageId: 'deprecation' }],
63
- },
64
49
  // bento-dropdown: Static
65
50
  {
66
51
  name: 'Invalid: Static prop on bento-dropdown',
67
52
  filename: 'test.vue',
68
53
  code: `<template><bento-dropdown error="val" /></template>`,
69
- output: `<template><bento-dropdown error-message="val" /></template>`,
70
- options: [{ autofix: true }],
71
- errors: [{ messageId: 'deprecation' }],
54
+ errors: [
55
+ {
56
+ messageId: 'deprecation',
57
+ suggestions: [
58
+ {
59
+ messageId: 'suggestReplace',
60
+ output: `<template><bento-dropdown error-message="val" /></template>`,
61
+ },
62
+ ],
63
+ },
64
+ ],
72
65
  },
73
- // bento-dropdown: Shorthand
66
+ // bento-dropdown: Dynamic
74
67
  {
75
68
  name: 'Invalid: Dynamic prop on bento-dropdown',
76
69
  filename: 'test.vue',
77
70
  code: `<template><bento-dropdown :error="val" /></template>`,
78
- output: `<template><bento-dropdown :error-message="val" /></template>`,
79
- options: [{ autofix: true }],
80
- errors: [{ messageId: 'deprecation' }],
71
+ errors: [
72
+ {
73
+ messageId: 'deprecation',
74
+ suggestions: [
75
+ {
76
+ messageId: 'suggestReplace',
77
+ output: `<template><bento-dropdown :error-message="val" /></template>`,
78
+ },
79
+ ],
80
+ },
81
+ ],
81
82
  },
82
83
  // bento-input-field: Static
83
84
  {
84
85
  name: 'Invalid: Static prop on bento-input-field',
85
86
  filename: 'test.vue',
86
87
  code: `<template><bento-input-field error="val" /></template>`,
87
- output: `<template><bento-input-field error-message="val" /></template>`,
88
- options: [{ autofix: true }],
89
- errors: [{ messageId: 'deprecation' }],
88
+ errors: [
89
+ {
90
+ messageId: 'deprecation',
91
+ suggestions: [
92
+ {
93
+ messageId: 'suggestReplace',
94
+ output: `<template><bento-input-field error-message="val" /></template>`,
95
+ },
96
+ ],
97
+ },
98
+ ],
90
99
  },
91
- // bento-input-field: Shorthand
100
+ // bento-input-field: Dynamic
92
101
  {
93
102
  name: 'Invalid: Dynamic prop on bento-input-field',
94
103
  filename: 'test.vue',
95
104
  code: `<template><bento-input-field :error="val" /></template>`,
96
- output: `<template><bento-input-field :error-message="val" /></template>`,
97
- options: [{ autofix: true }],
98
- errors: [{ messageId: 'deprecation' }],
105
+ errors: [
106
+ {
107
+ messageId: 'deprecation',
108
+ suggestions: [
109
+ {
110
+ messageId: 'suggestReplace',
111
+ output: `<template><bento-input-field :error-message="val" /></template>`,
112
+ },
113
+ ],
114
+ },
115
+ ],
99
116
  },
100
117
  // bento-input-field-phone-number: Static
101
118
  {
102
119
  name: 'Invalid: Static prop on bento-input-field-phone-number',
103
120
  filename: 'test.vue',
104
121
  code: `<template><bento-input-field-phone-number error="val" /></template>`,
105
- output: `<template><bento-input-field-phone-number error-message="val" /></template>`,
106
- options: [{ autofix: true }],
107
- errors: [{ messageId: 'deprecation' }],
122
+ errors: [
123
+ {
124
+ messageId: 'deprecation',
125
+ suggestions: [
126
+ {
127
+ messageId: 'suggestReplace',
128
+ output: `<template><bento-input-field-phone-number error-message="val" /></template>`,
129
+ },
130
+ ],
131
+ },
132
+ ],
108
133
  },
109
- // bento-input-field-phone-number: Shorthand
134
+ // bento-input-field-phone-number: Dynamic
110
135
  {
111
136
  name: 'Invalid: Dynamic prop on bento-input-field-phone-number',
112
137
  filename: 'test.vue',
113
138
  code: `<template><bento-input-field-phone-number :error="val" /></template>`,
114
- output: `<template><bento-input-field-phone-number :error-message="val" /></template>`,
115
- options: [{ autofix: true }],
116
- errors: [{ messageId: 'deprecation' }],
139
+ errors: [
140
+ {
141
+ messageId: 'deprecation',
142
+ suggestions: [
143
+ {
144
+ messageId: 'suggestReplace',
145
+ output: `<template><bento-input-field-phone-number :error-message="val" /></template>`,
146
+ },
147
+ ],
148
+ },
149
+ ],
117
150
  },
118
151
  ],
119
152
  });
@@ -9,9 +9,13 @@ The `hasError` prop in the following components is deprecated:
9
9
 
10
10
  Please use `errorMessage` instead.
11
11
 
12
+ > **Note:** The `hasError` prop is a `boolean` that only toggles error styling, while `errorMessage` is a `string` that
13
+ > both displays a message and applies error styling. A direct rename is not type-safe — you must also update the bound
14
+ > value from a boolean to an error message string.
15
+
12
16
  ## Rule Details
13
17
 
14
- This rule reports usages of the deprecated item.
18
+ This rule reports usages of the deprecated `hasError` prop and provides an IDE suggestion (not an autofix) to rename it.
15
19
 
16
20
  ### ❌ Incorrect
17
21
 
@@ -26,9 +30,9 @@ This rule reports usages of the deprecated item.
26
30
  ### ✅ Correct
27
31
 
28
32
  ```html
29
- <bento-checkbox-group errorMessage="value" />
33
+ <bento-checkbox-group errorMessage="Error message text" />
30
34
  ```
31
35
 
32
36
  ```html
33
- <bento-radio-group errorMessage="value" />
37
+ <bento-radio-group errorMessage="Error message text" />
34
38
  ```
@@ -20,10 +20,18 @@ describe('@adyen/bento/deprecation-components-has-error-prop', () => {
20
20
  severity: 1,
21
21
 
22
22
  messageId: 'deprecation',
23
- message: `The "has-error" prop is deprecated. Please use "error-message" instead.`,
23
+ message: `The "has-error" prop is deprecated. Use "error-message" instead. Note that "has-error" accepted a boolean, but "error-message" expects a string, so update the value to an error message.`,
24
+ suggestions: expect.arrayContaining([
25
+ expect.objectContaining({
26
+ messageId: 'suggestReplace',
27
+ fix: expect.objectContaining({
28
+ text: expect.stringContaining('error-message'),
29
+ }),
30
+ }),
31
+ ]),
24
32
  });
25
33
 
26
- // Without autofix option, no fix should be present
34
+ // The rule should provide a suggestion, not an autofix, because the replacement changes the expected value type.
27
35
  expect(result[0].messages[0].fix).toBeUndefined();
28
36
  });
29
37
  });
@@ -18,27 +18,15 @@ module.exports = {
18
18
  recommended: true,
19
19
  },
20
20
  messages: {
21
- deprecation: `The "has-error" prop is deprecated. Please use "error-message" instead.`,
21
+ deprecation: `The "has-error" prop is deprecated. Use "error-message" instead. Note that "has-error" accepted a boolean, but "error-message" expects a string, so update the value to an error message.`,
22
+ suggestReplace: `Replace "has-error" with "error-message". Remember to update the value from a boolean to a string error message.`,
22
23
  },
23
- fixable: 'code',
24
- schema: [
25
- {
26
- type: 'object',
27
- properties: {
28
- autofix: {
29
- type: 'boolean',
30
- default: false,
31
- },
32
- },
33
- additionalProperties: false,
34
- },
35
- ],
24
+ hasSuggestions: true,
25
+ schema: [],
36
26
  },
37
27
 
38
28
  /** @param {RuleContext} context */
39
29
  create(context) {
40
- const options = context.options[0] || {};
41
- const autofix = options.autofix === true;
42
30
  /* ------------------------------------------------ */
43
31
  /* CASE 2: PROP DEPRECATION (Multiple Components) */
44
32
  /* ------------------------------------------------ */
@@ -50,11 +38,14 @@ module.exports = {
50
38
  context.report({
51
39
  node,
52
40
  messageId: 'deprecation',
53
- ...(autofix && {
54
- fix(fixer) {
55
- return fixer.replaceText(node.key, 'error-message');
41
+ suggest: [
42
+ {
43
+ messageId: 'suggestReplace',
44
+ fix(fixer) {
45
+ return fixer.replaceText(node.key, 'error-message');
46
+ },
56
47
  },
57
- }),
48
+ ],
58
49
  });
59
50
  },
60
51
 
@@ -65,11 +56,14 @@ module.exports = {
65
56
  context.report({
66
57
  node,
67
58
  messageId: 'deprecation',
68
- ...(autofix && {
69
- fix(fixer) {
70
- return fixer.replaceText(node.key.argument, 'error-message');
59
+ suggest: [
60
+ {
61
+ messageId: 'suggestReplace',
62
+ fix(fixer) {
63
+ return fixer.replaceText(node.key.argument, 'error-message');
64
+ },
71
65
  },
72
- }),
66
+ ],
73
67
  });
74
68
  },
75
69
  });
@@ -37,96 +37,85 @@ describe('@adyen/bento/deprecation-components-has-error-prop', () => {
37
37
  },
38
38
  ],
39
39
  invalid: [
40
- // bento-checkbox-group: Static
41
40
  {
42
41
  name: 'Invalid: Static prop on bento-checkbox-group',
43
42
  filename: 'test.vue',
44
43
  code: `<template><bento-checkbox-group has-error="val" /></template>`,
45
- errors: [{ messageId: 'deprecation' }],
44
+ errors: [
45
+ {
46
+ messageId: 'deprecation',
47
+ suggestions: [
48
+ {
49
+ messageId: 'suggestReplace',
50
+ output: `<template><bento-checkbox-group error-message="val" /></template>`,
51
+ },
52
+ ],
53
+ },
54
+ ],
46
55
  },
47
- // bento-checkbox-group: Shorthand
48
56
  {
49
- name: 'Invalid: Dynamic prop on bento-checkbox-group',
57
+ name: 'Invalid: Dynamic shorthand prop on bento-checkbox-group',
50
58
  filename: 'test.vue',
51
59
  code: `<template><bento-checkbox-group :has-error="val" /></template>`,
52
- errors: [{ messageId: 'deprecation' }],
60
+ errors: [
61
+ {
62
+ messageId: 'deprecation',
63
+ suggestions: [
64
+ {
65
+ messageId: 'suggestReplace',
66
+ output: `<template><bento-checkbox-group :error-message="val" /></template>`,
67
+ },
68
+ ],
69
+ },
70
+ ],
53
71
  },
54
- // bento-radio-group: Static
55
72
  {
56
73
  name: 'Invalid: Static prop on bento-radio-group',
57
74
  filename: 'test.vue',
58
75
  code: `<template><bento-radio-group has-error="val" /></template>`,
59
- errors: [{ messageId: 'deprecation' }],
76
+ errors: [
77
+ {
78
+ messageId: 'deprecation',
79
+ suggestions: [
80
+ {
81
+ messageId: 'suggestReplace',
82
+ output: `<template><bento-radio-group error-message="val" /></template>`,
83
+ },
84
+ ],
85
+ },
86
+ ],
60
87
  },
61
- // bento-radio-group: Shorthand
62
88
  {
63
- name: 'Invalid: Dynamic prop on bento-radio-group',
89
+ name: 'Invalid: Dynamic shorthand prop on bento-radio-group',
64
90
  filename: 'test.vue',
65
91
  code: `<template><bento-radio-group :has-error="val" /></template>`,
66
- errors: [{ messageId: 'deprecation' }],
92
+ errors: [
93
+ {
94
+ messageId: 'deprecation',
95
+ suggestions: [
96
+ {
97
+ messageId: 'suggestReplace',
98
+ output: `<template><bento-radio-group :error-message="val" /></template>`,
99
+ },
100
+ ],
101
+ },
102
+ ],
67
103
  },
68
- ],
69
- });
70
-
71
- /* ------------------------------------------------ */
72
- /* AUTOFIX TESTS */
73
- /* ------------------------------------------------ */
74
- ruleTester.run('deprecation-components-has-error-prop (autofix enabled)', deprecationComponentsHasErrorProp, {
75
- valid: [
76
104
  {
77
- name: 'Valid: error-message prop on bento-checkbox-group (autofix enabled)',
105
+ name: 'Invalid: Dynamic longhand prop on bento-radio-group',
78
106
  filename: 'test.vue',
79
- options: [{ autofix: true }],
80
- code: `<template><bento-checkbox-group error-message="val" /></template>`,
81
- },
82
- ],
83
- invalid: [
84
- {
85
- name: 'Autofix: static prop on bento-checkbox-group',
86
- filename: 'test.vue',
87
- options: [{ autofix: true }],
88
- code: `<template><bento-checkbox-group has-error="val" /></template>`,
89
- output: `<template><bento-checkbox-group error-message="val" /></template>`,
90
- errors: [{ messageId: 'deprecation' }],
91
- },
92
- {
93
- name: 'Autofix: dynamic prop on bento-checkbox-group',
94
- filename: 'test.vue',
95
- options: [{ autofix: true }],
96
- code: `<template><bento-checkbox-group :has-error="val" /></template>`,
97
- output: `<template><bento-checkbox-group :error-message="val" /></template>`,
98
- errors: [{ messageId: 'deprecation' }],
99
- },
100
- {
101
- name: 'Autofix: static prop on bento-radio-group',
102
- filename: 'test.vue',
103
- options: [{ autofix: true }],
104
- code: `<template><bento-radio-group has-error="val" /></template>`,
105
- output: `<template><bento-radio-group error-message="val" /></template>`,
106
- errors: [{ messageId: 'deprecation' }],
107
- },
108
- {
109
- name: 'Autofix: dynamic prop on bento-radio-group',
110
- filename: 'test.vue',
111
- options: [{ autofix: true }],
112
- code: `<template><bento-radio-group :has-error="val" /></template>`,
113
- output: `<template><bento-radio-group :error-message="val" /></template>`,
114
- errors: [{ messageId: 'deprecation' }],
115
- },
116
- {
117
- name: 'Autofix: v-bind longhand on bento-radio-group',
118
- filename: 'test.vue',
119
- options: [{ autofix: true }],
120
107
  code: `<template><bento-radio-group v-bind:has-error="val" /></template>`,
121
- output: `<template><bento-radio-group v-bind:error-message="val" /></template>`,
122
- errors: [{ messageId: 'deprecation' }],
123
- },
124
- {
125
- name: 'Autofix: does not fix when autofix is false',
126
- filename: 'test.vue',
127
- options: [{ autofix: false }],
128
- code: `<template><bento-checkbox-group has-error="val" /></template>`,
129
- errors: [{ messageId: 'deprecation' }],
108
+ errors: [
109
+ {
110
+ messageId: 'deprecation',
111
+ suggestions: [
112
+ {
113
+ messageId: 'suggestReplace',
114
+ output: `<template><bento-radio-group v-bind:error-message="val" /></template>`,
115
+ },
116
+ ],
117
+ },
118
+ ],
130
119
  },
131
120
  ],
132
121
  });
@@ -25,7 +25,15 @@ describe('@adyen/bento/deprecation-components-input-emit', () => {
25
25
  severity: 1,
26
26
 
27
27
  messageId: 'deprecation',
28
- message: `The "input" emit is deprecated. Please use "update:model-value" instead.`,
28
+ message: `The \`input\` emit is deprecated. Please use \`update:model-value\` instead.
29
+
30
+ Be wary of undesired side effects and race conditions if \`v-model\` is used in conjunction with \`@input\`.
31
+
32
+ Incorrect:
33
+ <bento-component v-model="text" @input="onInput" />
34
+
35
+ Correct:
36
+ <bento-component :model-value="text" @update:model-value="onInput" />`,
29
37
 
30
38
  fix: expect.objectContaining({
31
39
  text: expect.stringContaining('update:model-value'),
@@ -19,7 +19,15 @@ module.exports = {
19
19
  recommended: true,
20
20
  },
21
21
  messages: {
22
- deprecation: `The "input" emit is deprecated. Please use "update:model-value" instead.`,
22
+ deprecation: `The \`input\` emit is deprecated. Please use \`update:model-value\` instead.
23
+
24
+ Be wary of undesired side effects and race conditions if \`v-model\` is used in conjunction with \`@input\`.
25
+
26
+ Incorrect:
27
+ <bento-component v-model="text" @input="onInput" />
28
+
29
+ Correct:
30
+ <bento-component :model-value="text" @update:model-value="onInput" />`,
23
31
  },
24
32
  fixable: 'code',
25
33
  schema: [