@adyen/eslint-plugin-bento 2.10.0 → 2.11.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 +13 -0
- package/configs/vue-recommended.js +2 -0
- package/configs/vue-strict.js +2 -0
- package/index.js +2 -0
- package/package.json +2 -2
- package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/README.md +48 -0
- package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/__tests__/deprecation-bento-data-grid-cell-action-menu-item-handler.vue +63 -0
- package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/__tests__/eslint.config.js +26 -0
- 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
- package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/deprecation-bento-data-grid-cell-action-menu-item-handler.js +232 -0
- package/rules/deprecation-bento-data-grid-cell-action-menu-item-handler/deprecation-bento-data-grid-cell-action-menu-item-handler.test.ts +116 -0
- package/rules/deprecation-country-custom-usage/README.md +151 -0
- package/rules/deprecation-country-custom-usage/__tests__/deprecation-country-custom-usage.vue +93 -0
- package/rules/deprecation-country-custom-usage/__tests__/eslint.config.js +25 -0
- package/rules/deprecation-country-custom-usage/deprecation-country-custom-usage.integration.test.ts +22 -0
- package/rules/deprecation-country-custom-usage/deprecation-country-custom-usage.js +569 -0
- package/rules/deprecation-country-custom-usage/deprecation-country-custom-usage.test.ts +380 -0
|
@@ -0,0 +1,569 @@
|
|
|
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
|
+
/** @type {RuleModule} */
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description:
|
|
16
|
+
'Deprecates "custom" variant / custom label usage across bento-country, bento-dropdown-country, and SELECT_COUNTRY filter config.',
|
|
17
|
+
category: 'Deprecations',
|
|
18
|
+
url: 'https://github.com/Adyen/bento/blob/main/packages/eslint/rules/deprecation-country-custom-usage/README.md',
|
|
19
|
+
recommended: true,
|
|
20
|
+
},
|
|
21
|
+
messages: {
|
|
22
|
+
countryCustomVariant:
|
|
23
|
+
'The "bento-country" component with variant "custom" is deprecated. Use <bento-typography el="span"> instead.',
|
|
24
|
+
countryUnusedLabel:
|
|
25
|
+
'The "label" prop on "bento-country" is only used with variant "custom" which is deprecated. Remove the "label" prop.',
|
|
26
|
+
dropdownCountryCustomLabel:
|
|
27
|
+
'The "bento-dropdown-country" component with custom "label" in items is deprecated. Use "bento-dropdown" instead.',
|
|
28
|
+
dropdownCountryCustomVariant:
|
|
29
|
+
'The "variant" prop with value "custom" on "bento-dropdown-country" is deprecated. Remove the "variant" prop.',
|
|
30
|
+
selectCountryCustom:
|
|
31
|
+
'The "{{ key }}" property in SELECT_COUNTRY filter options indicates custom usage. Use "BentoFilterItemType.SELECT" instead of "SELECT_COUNTRY".',
|
|
32
|
+
},
|
|
33
|
+
fixable: 'code',
|
|
34
|
+
schema: [
|
|
35
|
+
{
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
autofix: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
default: false,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/** @param {RuleContext} context */
|
|
49
|
+
create(context) {
|
|
50
|
+
const options = context.options[0] || {};
|
|
51
|
+
const autofix = options.autofix === true;
|
|
52
|
+
|
|
53
|
+
function getStaticPropertyName(prop) {
|
|
54
|
+
if (prop.type !== 'Property' || prop.computed) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return prop.key.type === 'Identifier'
|
|
58
|
+
? prop.key.name
|
|
59
|
+
: prop.key.type === 'Literal'
|
|
60
|
+
? String(prop.key.value)
|
|
61
|
+
: null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function removeAttribute(fixer, attr) {
|
|
65
|
+
const sourceCode = context.getSourceCode();
|
|
66
|
+
const src = sourceCode.getText();
|
|
67
|
+
// Walk backwards from the attribute to consume preceding whitespace
|
|
68
|
+
let start = attr.range[0];
|
|
69
|
+
while (start > 0 && (src[start - 1] === ' ' || src[start - 1] === '\t')) {
|
|
70
|
+
start--;
|
|
71
|
+
}
|
|
72
|
+
return fixer.removeRange([start, attr.range[1]]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function removeProperty(fixer, node, prop) {
|
|
76
|
+
const properties = node.properties;
|
|
77
|
+
const index = properties.indexOf(prop);
|
|
78
|
+
const isLast = index === properties.length - 1;
|
|
79
|
+
|
|
80
|
+
if (properties.length === 1) {
|
|
81
|
+
return fixer.replaceText(node, '{}');
|
|
82
|
+
}
|
|
83
|
+
if (isLast) {
|
|
84
|
+
const prev = properties[index - 1];
|
|
85
|
+
return fixer.removeRange([prev.range[1], prop.range[1]]);
|
|
86
|
+
}
|
|
87
|
+
const next = properties[index + 1];
|
|
88
|
+
return fixer.removeRange([prop.range[0], next.range[0]]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function removeLabelsFromArray(fixer, arrayNode) {
|
|
92
|
+
const fixes = [];
|
|
93
|
+
for (const element of arrayNode.elements) {
|
|
94
|
+
if (!element || element.type !== 'ObjectExpression') {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const prop of element.properties) {
|
|
98
|
+
if (getStaticPropertyName(prop) === 'label') {
|
|
99
|
+
fixes.push(removeProperty(fixer, element, prop));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return fixes;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ------------------------------------------------ */
|
|
107
|
+
/* CASE 1: <bento-country variant="custom"> */
|
|
108
|
+
/* ------------------------------------------------ */
|
|
109
|
+
|
|
110
|
+
function isCustomVariantValue(expression) {
|
|
111
|
+
if (!expression) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (expression.type === 'Literal' && expression.value === 'custom') {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (
|
|
118
|
+
expression.type === 'MemberExpression' &&
|
|
119
|
+
expression.object?.type === 'Identifier' &&
|
|
120
|
+
expression.object.name === 'BentoCountryVariant' &&
|
|
121
|
+
expression.property?.type === 'Identifier' &&
|
|
122
|
+
expression.property.name === 'CUSTOM'
|
|
123
|
+
) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function replaceWithTypography(fixer, element) {
|
|
130
|
+
const sourceCode = context.getSourceCode();
|
|
131
|
+
const startTag = element.startTag;
|
|
132
|
+
const endTag = element.endTag;
|
|
133
|
+
|
|
134
|
+
const labelAttr = findLabelAttribute(element);
|
|
135
|
+
|
|
136
|
+
let content = '';
|
|
137
|
+
if (labelAttr) {
|
|
138
|
+
if (labelAttr.directive) {
|
|
139
|
+
content = `{{ ${sourceCode.getText(labelAttr.value.expression)} }}`;
|
|
140
|
+
} else {
|
|
141
|
+
content = labelAttr.value.value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!endTag) {
|
|
146
|
+
return fixer.replaceTextRange(
|
|
147
|
+
element.range,
|
|
148
|
+
`<bento-typography el="span">${content}</bento-typography>`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const childrenSource = sourceCode.text.slice(startTag.range[1], endTag.range[0]);
|
|
153
|
+
const body = content || childrenSource;
|
|
154
|
+
return fixer.replaceTextRange(element.range, `<bento-typography el="span">${body}</bento-typography>`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ------------------------------------------------ */
|
|
158
|
+
/* CASE 2: <bento-dropdown-country :items="[...]"> */
|
|
159
|
+
/* ------------------------------------------------ */
|
|
160
|
+
|
|
161
|
+
function checkItemsForCustomLabel(node) {
|
|
162
|
+
if (node.type !== 'ArrayExpression') {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return node.elements.some(element => {
|
|
166
|
+
if (!element || element.type !== 'ObjectExpression') {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
return element.properties.some(prop => {
|
|
170
|
+
const keyName = getStaticPropertyName(prop);
|
|
171
|
+
return keyName === 'label';
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getItemsExpression(element) {
|
|
177
|
+
const itemsAttr = element.startTag.attributes.find(
|
|
178
|
+
attr => attr.directive && attr.key?.name?.name === 'bind' && attr.key?.argument?.name === 'items'
|
|
179
|
+
);
|
|
180
|
+
return itemsAttr?.value?.expression;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function replaceDropdownCountryTag(fixer, element) {
|
|
184
|
+
const startTag = element.startTag;
|
|
185
|
+
const openTagName = element.rawName;
|
|
186
|
+
const endTag = element.endTag;
|
|
187
|
+
const sourceCode = context.getSourceCode();
|
|
188
|
+
|
|
189
|
+
const fixes = [
|
|
190
|
+
fixer.replaceTextRange(
|
|
191
|
+
[startTag.range[0] + 1, startTag.range[0] + 1 + openTagName.length],
|
|
192
|
+
'bento-dropdown'
|
|
193
|
+
),
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
// Remove the variant="custom" attribute since it's meaningless on bento-dropdown
|
|
197
|
+
const variantAttr = findVariantAttribute(element);
|
|
198
|
+
if (variantAttr) {
|
|
199
|
+
fixes.push(removeAttribute(fixer, variantAttr));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (endTag) {
|
|
203
|
+
const endTagSource = sourceCode.getText(endTag);
|
|
204
|
+
const closingTagName = endTagSource.match(/<\/\s*([^\s>]+)/)?.[1];
|
|
205
|
+
if (closingTagName) {
|
|
206
|
+
fixes.push(
|
|
207
|
+
fixer.replaceTextRange(
|
|
208
|
+
[endTag.range[0] + 2, endTag.range[0] + 2 + closingTagName.length],
|
|
209
|
+
'bento-dropdown'
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return fixes;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ------------------------------------------------ */
|
|
219
|
+
/* CASE 3: Filter config SELECT_COUNTRY with custom */
|
|
220
|
+
/* ------------------------------------------------ */
|
|
221
|
+
|
|
222
|
+
function isSelectCountryFilterType(valueNode) {
|
|
223
|
+
if (
|
|
224
|
+
valueNode.type === 'MemberExpression' &&
|
|
225
|
+
valueNode.object?.type === 'Identifier' &&
|
|
226
|
+
valueNode.object.name === 'BentoFilterItemType' &&
|
|
227
|
+
valueNode.property?.type === 'Identifier' &&
|
|
228
|
+
valueNode.property.name === 'SELECT_COUNTRY'
|
|
229
|
+
) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (valueNode.type === 'Literal' && valueNode.value === 'BentoSelectCountryFilter') {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function hasCustomUsageInOptions(optionsNode) {
|
|
239
|
+
if (!optionsNode || optionsNode.type !== 'ObjectExpression') {
|
|
240
|
+
return { hasCustomLabel: false, hasCustomVariant: false, variantProp: null, listboxItemsNode: null };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let hasCustomLabel = false;
|
|
244
|
+
let hasCustomVariant = false;
|
|
245
|
+
let variantProp = null;
|
|
246
|
+
let listboxItemsNode = null;
|
|
247
|
+
|
|
248
|
+
for (const prop of optionsNode.properties) {
|
|
249
|
+
const keyName = getStaticPropertyName(prop);
|
|
250
|
+
|
|
251
|
+
if (keyName === 'variant') {
|
|
252
|
+
if (prop.value.type === 'Literal' && prop.value.value === 'custom') {
|
|
253
|
+
hasCustomVariant = true;
|
|
254
|
+
variantProp = prop;
|
|
255
|
+
} else if (
|
|
256
|
+
prop.value.type === 'MemberExpression' &&
|
|
257
|
+
prop.value.property?.type === 'Identifier' &&
|
|
258
|
+
prop.value.property.name === 'CUSTOM'
|
|
259
|
+
) {
|
|
260
|
+
hasCustomVariant = true;
|
|
261
|
+
variantProp = prop;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (keyName === 'listboxItems' && prop.value.type === 'ArrayExpression') {
|
|
266
|
+
listboxItemsNode = prop.value;
|
|
267
|
+
hasCustomLabel = prop.value.elements.some(el => {
|
|
268
|
+
if (!el || el.type !== 'ObjectExpression') {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return el.properties.some(p => getStaticPropertyName(p) === 'label');
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { hasCustomLabel, hasCustomVariant, variantProp, listboxItemsNode };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function checkFilterConfigObject(node) {
|
|
280
|
+
if (node.type !== 'ObjectExpression') {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let typeValueNode = null;
|
|
285
|
+
let optionsNode = null;
|
|
286
|
+
|
|
287
|
+
for (const prop of node.properties) {
|
|
288
|
+
const keyName = getStaticPropertyName(prop);
|
|
289
|
+
|
|
290
|
+
if (keyName === 'type' && isSelectCountryFilterType(prop.value)) {
|
|
291
|
+
typeValueNode = prop.value;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (keyName === 'options' && prop.value.type === 'ObjectExpression') {
|
|
295
|
+
optionsNode = prop.value;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!typeValueNode || !optionsNode) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const { hasCustomLabel, hasCustomVariant, variantProp, listboxItemsNode } =
|
|
304
|
+
hasCustomUsageInOptions(optionsNode);
|
|
305
|
+
|
|
306
|
+
if (hasCustomVariant) {
|
|
307
|
+
context.report({
|
|
308
|
+
node: optionsNode,
|
|
309
|
+
messageId: 'selectCountryCustom',
|
|
310
|
+
data: { key: 'variant' },
|
|
311
|
+
...(autofix && {
|
|
312
|
+
fix(fixer) {
|
|
313
|
+
if (hasCustomLabel) {
|
|
314
|
+
// Both present: change type to SELECT and remove variant
|
|
315
|
+
const fixes = [removeProperty(fixer, optionsNode, variantProp)];
|
|
316
|
+
if (typeValueNode.type === 'MemberExpression') {
|
|
317
|
+
fixes.push(fixer.replaceText(typeValueNode.property, 'SELECT'));
|
|
318
|
+
} else {
|
|
319
|
+
fixes.push(fixer.replaceText(typeValueNode, "'BentoSelectFilter'"));
|
|
320
|
+
}
|
|
321
|
+
return fixes;
|
|
322
|
+
}
|
|
323
|
+
// Variant only: remove the variant property
|
|
324
|
+
return removeProperty(fixer, optionsNode, variantProp);
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (hasCustomLabel) {
|
|
331
|
+
context.report({
|
|
332
|
+
node: optionsNode,
|
|
333
|
+
messageId: 'selectCountryCustom',
|
|
334
|
+
data: { key: 'listboxItems with label' },
|
|
335
|
+
...(autofix && {
|
|
336
|
+
fix(fixer) {
|
|
337
|
+
if (hasCustomVariant) {
|
|
338
|
+
// Both present: change type to SELECT and remove variant
|
|
339
|
+
const fixes = [removeProperty(fixer, optionsNode, variantProp)];
|
|
340
|
+
if (typeValueNode.type === 'MemberExpression') {
|
|
341
|
+
fixes.push(fixer.replaceText(typeValueNode.property, 'SELECT'));
|
|
342
|
+
} else {
|
|
343
|
+
fixes.push(fixer.replaceText(typeValueNode, "'BentoSelectFilter'"));
|
|
344
|
+
}
|
|
345
|
+
return fixes;
|
|
346
|
+
}
|
|
347
|
+
// Label only: remove label properties from listboxItems
|
|
348
|
+
return removeLabelsFromArray(fixer, listboxItemsNode);
|
|
349
|
+
},
|
|
350
|
+
}),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* ------------------------------------------------ */
|
|
356
|
+
/* Visitors */
|
|
357
|
+
/* ------------------------------------------------ */
|
|
358
|
+
|
|
359
|
+
function hasCustomVariantAttribute(element) {
|
|
360
|
+
return element.startTag.attributes.some(attr => {
|
|
361
|
+
if (!attr.directive && attr.key.name === 'variant' && attr.value?.value === 'custom') {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
if (attr.directive && attr.key?.name?.name === 'bind' && attr.key?.argument?.name === 'variant') {
|
|
365
|
+
return isCustomVariantValue(attr.value?.expression);
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function findVariantAttribute(element) {
|
|
372
|
+
return element.startTag.attributes.find(attr => {
|
|
373
|
+
if (!attr.directive && attr.key.name === 'variant') {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (attr.directive && attr.key?.name?.name === 'bind' && attr.key?.argument?.name === 'variant') {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function findLabelAttribute(element) {
|
|
384
|
+
return element.startTag.attributes.find(attr => {
|
|
385
|
+
if (!attr.directive && attr.key.name === 'label') {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
if (attr.directive && attr.key?.name?.name === 'bind' && attr.key?.argument?.name === 'label') {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
return false;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function hasLabelAttribute(element) {
|
|
396
|
+
return !!findLabelAttribute(element);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const countryComponentSelector = `VElement:matches([name="bento-country"], [name="BentoCountry"])`;
|
|
400
|
+
const dropdownCountrySelector = `VElement:matches([name="bento-dropdown-country"], [name="BentoDropdownCountry"], [name="b-dropdown-country"])`;
|
|
401
|
+
|
|
402
|
+
const filterConfigVisitor = {
|
|
403
|
+
ObjectExpression(node) {
|
|
404
|
+
checkFilterConfigObject(node);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return VueUtils.defineTemplateBodyVisitor(
|
|
409
|
+
context,
|
|
410
|
+
{
|
|
411
|
+
// CASE 1a: static variant="custom" on bento-country
|
|
412
|
+
[`${countryComponentSelector} > VStartTag > VAttribute[directive=false][key.name="variant"][value.value="custom"]`](
|
|
413
|
+
node
|
|
414
|
+
) {
|
|
415
|
+
const element = node.parent.parent;
|
|
416
|
+
const hasLabel = hasLabelAttribute(element);
|
|
417
|
+
context.report({
|
|
418
|
+
node: element,
|
|
419
|
+
messageId: 'countryCustomVariant',
|
|
420
|
+
...(autofix && {
|
|
421
|
+
fix(fixer) {
|
|
422
|
+
if (hasLabel) {
|
|
423
|
+
return replaceWithTypography(fixer, element);
|
|
424
|
+
}
|
|
425
|
+
// variant="custom" only: remove the variant attribute
|
|
426
|
+
return removeAttribute(fixer, node);
|
|
427
|
+
},
|
|
428
|
+
}),
|
|
429
|
+
});
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
// CASE 1b: dynamic :variant="'custom'" or :variant="BentoCountryVariant.CUSTOM" on bento-country
|
|
433
|
+
[`${countryComponentSelector} > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="variant"]`](
|
|
434
|
+
node
|
|
435
|
+
) {
|
|
436
|
+
const expression = node.value?.expression;
|
|
437
|
+
if (isCustomVariantValue(expression)) {
|
|
438
|
+
const element = node.parent.parent;
|
|
439
|
+
const hasLabel = hasLabelAttribute(element);
|
|
440
|
+
context.report({
|
|
441
|
+
node: element,
|
|
442
|
+
messageId: 'countryCustomVariant',
|
|
443
|
+
...(autofix && {
|
|
444
|
+
fix(fixer) {
|
|
445
|
+
if (hasLabel) {
|
|
446
|
+
return replaceWithTypography(fixer, element);
|
|
447
|
+
}
|
|
448
|
+
// variant="custom" only: remove the variant attribute
|
|
449
|
+
return removeAttribute(fixer, node);
|
|
450
|
+
},
|
|
451
|
+
}),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
// CASE 1c: label prop on bento-country without variant="custom"
|
|
457
|
+
[`${countryComponentSelector} > VStartTag > VAttribute[directive=false][key.name="label"]`](node) {
|
|
458
|
+
const element = node.parent.parent;
|
|
459
|
+
if (!hasCustomVariantAttribute(element)) {
|
|
460
|
+
context.report({
|
|
461
|
+
node: element,
|
|
462
|
+
messageId: 'countryUnusedLabel',
|
|
463
|
+
...(autofix && {
|
|
464
|
+
fix(fixer) {
|
|
465
|
+
return removeAttribute(fixer, node);
|
|
466
|
+
},
|
|
467
|
+
}),
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
// CASE 1d: dynamic :label on bento-country without variant="custom"
|
|
473
|
+
[`${countryComponentSelector} > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="label"]`](
|
|
474
|
+
node
|
|
475
|
+
) {
|
|
476
|
+
const element = node.parent.parent;
|
|
477
|
+
if (!hasCustomVariantAttribute(element)) {
|
|
478
|
+
context.report({
|
|
479
|
+
node: element,
|
|
480
|
+
messageId: 'countryUnusedLabel',
|
|
481
|
+
...(autofix && {
|
|
482
|
+
fix(fixer) {
|
|
483
|
+
return removeAttribute(fixer, node);
|
|
484
|
+
},
|
|
485
|
+
}),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
// CASE 2a: :items with label on bento-dropdown-country
|
|
491
|
+
[`${dropdownCountrySelector} > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="items"]`](
|
|
492
|
+
node
|
|
493
|
+
) {
|
|
494
|
+
const valueNode = node.value?.expression;
|
|
495
|
+
if (!valueNode) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (checkItemsForCustomLabel(valueNode)) {
|
|
500
|
+
const element = node.parent.parent;
|
|
501
|
+
const hasCustomVariant = hasCustomVariantAttribute(element);
|
|
502
|
+
context.report({
|
|
503
|
+
node: element,
|
|
504
|
+
messageId: 'dropdownCountryCustomLabel',
|
|
505
|
+
...(autofix && {
|
|
506
|
+
fix(fixer) {
|
|
507
|
+
if (hasCustomVariant) {
|
|
508
|
+
return replaceDropdownCountryTag(fixer, element);
|
|
509
|
+
}
|
|
510
|
+
// Label only: remove labels from items
|
|
511
|
+
return removeLabelsFromArray(fixer, valueNode);
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
// CASE 2b: variant="custom" on bento-dropdown-country
|
|
519
|
+
[`${dropdownCountrySelector} > VStartTag > VAttribute[directive=false][key.name="variant"][value.value="custom"]`](
|
|
520
|
+
node
|
|
521
|
+
) {
|
|
522
|
+
const element = node.parent.parent;
|
|
523
|
+
const itemsExpr = getItemsExpression(element);
|
|
524
|
+
const hasLabel = itemsExpr ? checkItemsForCustomLabel(itemsExpr) : false;
|
|
525
|
+
if (!hasLabel) {
|
|
526
|
+
context.report({
|
|
527
|
+
node: element,
|
|
528
|
+
messageId: 'dropdownCountryCustomVariant',
|
|
529
|
+
...(autofix && {
|
|
530
|
+
fix(fixer) {
|
|
531
|
+
return removeAttribute(fixer, node);
|
|
532
|
+
},
|
|
533
|
+
}),
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
// CASE 2c: dynamic :variant="'custom'" on bento-dropdown-country
|
|
539
|
+
[`${dropdownCountrySelector} > VStartTag > VAttribute[directive=true][key.name.name="bind"][key.argument.name="variant"]`](
|
|
540
|
+
node
|
|
541
|
+
) {
|
|
542
|
+
const expression = node.value?.expression;
|
|
543
|
+
if (!isCustomVariantValue(expression)) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const element = node.parent.parent;
|
|
547
|
+
const itemsExpr = getItemsExpression(element);
|
|
548
|
+
const hasLabel = itemsExpr ? checkItemsForCustomLabel(itemsExpr) : false;
|
|
549
|
+
if (!hasLabel) {
|
|
550
|
+
context.report({
|
|
551
|
+
node: element,
|
|
552
|
+
messageId: 'dropdownCountryCustomVariant',
|
|
553
|
+
...(autofix && {
|
|
554
|
+
fix(fixer) {
|
|
555
|
+
return removeAttribute(fixer, node);
|
|
556
|
+
},
|
|
557
|
+
}),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
// CASE 3: inline filter config in template
|
|
563
|
+
...filterConfigVisitor,
|
|
564
|
+
},
|
|
565
|
+
// CASE 3: script-defined filter config
|
|
566
|
+
filterConfigVisitor
|
|
567
|
+
);
|
|
568
|
+
},
|
|
569
|
+
};
|