@adyen/eslint-plugin-bento 1.1.0 → 1.3.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/package.json +10 -3
- package/rules/deprecation-currency-default-slot/deprecation-currency-default-slot.integration.test.ts +1 -1
- package/rules/deprecation-currency-default-slot/deprecation-currency-default-slot.js +12 -0
- package/rules/deprecation-currency-default-slot/deprecation-currency-default-slot.test.ts +38 -5
- package/rules/deprecation-tag-default-slot/deprecation-tag-default-slot.js +11 -0
- package/rules/deprecation-tag-default-slot/deprecation-tag-default-slot.test.ts +33 -2
- package/rules/vue-old-spacer-tokens/vue-old-spacer-tokens.js +18 -3
- package/utils/component-slot-migration.js +107 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adyen/eslint-plugin-bento",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Adyen Bento ESLint rules",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -18,10 +18,17 @@
|
|
|
18
18
|
"url": "git@github.com:Adyen/bento.git"
|
|
19
19
|
},
|
|
20
20
|
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"jscodeshift": "17.1.2"
|
|
23
|
+
},
|
|
21
24
|
"devDependencies": {
|
|
22
|
-
"@
|
|
25
|
+
"@types/estree": "1.0.6",
|
|
26
|
+
"@types/node": "20.17.24",
|
|
27
|
+
"@types/eslint": "8.56.12",
|
|
28
|
+
"@vitest/coverage-v8": "2.1.9",
|
|
23
29
|
"eslint": "8.57.1",
|
|
24
|
-
"vitest": "2.1.
|
|
30
|
+
"vitest": "2.1.9",
|
|
31
|
+
"eslint-plugin-vue": "9.25.0",
|
|
25
32
|
"vue-eslint-parser": "9.3.2"
|
|
26
33
|
},
|
|
27
34
|
"peerDependencies": {
|
|
@@ -30,7 +30,7 @@ describe('@adyen/bento/deprecation-currency-default-slot', () => {
|
|
|
30
30
|
nodeType: 'VElement',
|
|
31
31
|
endLine: 4,
|
|
32
32
|
endColumn: 22,
|
|
33
|
-
fix: { range: [30, 101], text: ' :value="
|
|
33
|
+
fix: { range: [30, 101], text: ' :value="thisIsMyVariableWithCurrencyValue" />' },
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
});
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('eslint-plugin-vue/lib/utils').RuleModule} RuleModule
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
const VueUtils = require('eslint-plugin-vue/lib/utils');
|
|
2
10
|
const { componentSlotMigration } = require('../../utils/component-slot-migration');
|
|
3
11
|
|
|
12
|
+
/** @type {RuleModule} */
|
|
4
13
|
module.exports = {
|
|
5
14
|
meta: {
|
|
6
15
|
type: 'suggestion',
|
|
@@ -16,8 +25,11 @@ module.exports = {
|
|
|
16
25
|
fixable: 'code',
|
|
17
26
|
schema: [], // No options are needed for this rule
|
|
18
27
|
},
|
|
28
|
+
|
|
29
|
+
/** @param {RuleContext} context */
|
|
19
30
|
create(context) {
|
|
20
31
|
const sourceCode = context.getSourceCode();
|
|
32
|
+
|
|
21
33
|
const template =
|
|
22
34
|
sourceCode.parserServices.getTemplateBodyTokenStore &&
|
|
23
35
|
sourceCode.parserServices.getTemplateBodyTokenStore();
|
|
@@ -31,10 +31,10 @@ describe('@adyen/bento/deprecation-currency-default-slot should suggest the usag
|
|
|
31
31
|
filename: 'test.vue',
|
|
32
32
|
code: `
|
|
33
33
|
<template>
|
|
34
|
-
<bento-currency
|
|
35
|
-
:value="thisIsMyVariableWithCurrencyValue"
|
|
36
|
-
:currency="BentoCurrencyISOCode.USD"
|
|
37
|
-
major-units
|
|
34
|
+
<bento-currency
|
|
35
|
+
:value="thisIsMyVariableWithCurrencyValue"
|
|
36
|
+
:currency="BentoCurrencyISOCode.USD"
|
|
37
|
+
major-units
|
|
38
38
|
/>
|
|
39
39
|
</template>
|
|
40
40
|
`,
|
|
@@ -57,6 +57,23 @@ describe('@adyen/bento/deprecation-currency-default-slot should suggest the usag
|
|
|
57
57
|
`,
|
|
58
58
|
errors: [{ message: '"default" slot is deprecated in favor of the "value" property' }],
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
name: 'BentoCurrency with CallExpression child',
|
|
62
|
+
code: `
|
|
63
|
+
<template>
|
|
64
|
+
<bento-currency :currency="BentoCurrencyISOCode.EUR" major-units
|
|
65
|
+
>{{ myFunction(valueRefs, EnumValues.EURO_AMOUNT) }}
|
|
66
|
+
</bento-currency>
|
|
67
|
+
</template>
|
|
68
|
+
`,
|
|
69
|
+
output: `
|
|
70
|
+
<template>
|
|
71
|
+
<bento-currency :currency="BentoCurrencyISOCode.EUR" major-units
|
|
72
|
+
:value="myFunction(valueRefs, EnumValues.EURO_AMOUNT)" />
|
|
73
|
+
</template>
|
|
74
|
+
`,
|
|
75
|
+
errors: [{ message: '"default" slot is deprecated in favor of the "value" property' }],
|
|
76
|
+
},
|
|
60
77
|
{
|
|
61
78
|
name: 'BentoCurrency with VText child and no attributes',
|
|
62
79
|
code: `
|
|
@@ -75,6 +92,22 @@ describe('@adyen/bento/deprecation-currency-default-slot should suggest the usag
|
|
|
75
92
|
},
|
|
76
93
|
{
|
|
77
94
|
name: 'BentoCurrency with property as child',
|
|
95
|
+
code: `
|
|
96
|
+
<template>
|
|
97
|
+
<bento-currency :key="item.thisIsMyVariableWithCurrencyValue" :currency="BentoCurrencyISOCode.EUR" major-units>
|
|
98
|
+
{{ item.thisIsMyVariableWithCurrencyValue }}
|
|
99
|
+
</bento-currency>
|
|
100
|
+
</template>
|
|
101
|
+
`,
|
|
102
|
+
output: `
|
|
103
|
+
<template>
|
|
104
|
+
<bento-currency :key="item.thisIsMyVariableWithCurrencyValue" :currency="BentoCurrencyISOCode.EUR" major-units :value="item.thisIsMyVariableWithCurrencyValue" />
|
|
105
|
+
</template>
|
|
106
|
+
`,
|
|
107
|
+
errors: [{ message: '"default" slot is deprecated in favor of the "value" property' }],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'BentoCurrency with simple property as child',
|
|
78
111
|
code: `
|
|
79
112
|
<template>
|
|
80
113
|
<bento-currency>
|
|
@@ -84,7 +117,7 @@ describe('@adyen/bento/deprecation-currency-default-slot should suggest the usag
|
|
|
84
117
|
`,
|
|
85
118
|
output: `
|
|
86
119
|
<template>
|
|
87
|
-
<bento-currency :value="
|
|
120
|
+
<bento-currency :value="thisIsMyVariableWithCurrencyValue" />
|
|
88
121
|
</template>
|
|
89
122
|
`,
|
|
90
123
|
errors: [{ message: '"default" slot is deprecated in favor of the "value" property' }],
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('eslint-plugin-vue/lib/utils').RuleModule} RuleModule
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
const VueUtils = require('eslint-plugin-vue/lib/utils');
|
|
2
10
|
const { componentSlotMigration } = require('../../utils/component-slot-migration');
|
|
3
11
|
|
|
12
|
+
/** @type {RuleModule} */
|
|
4
13
|
module.exports = {
|
|
5
14
|
meta: {
|
|
6
15
|
type: 'suggestion',
|
|
@@ -16,6 +25,8 @@ module.exports = {
|
|
|
16
25
|
fixable: 'code',
|
|
17
26
|
schema: [], // No options are needed for this rule
|
|
18
27
|
},
|
|
28
|
+
|
|
29
|
+
/** @param {RuleContext} context */
|
|
19
30
|
create(context) {
|
|
20
31
|
const sourceCode = context.getSourceCode();
|
|
21
32
|
const template =
|
|
@@ -78,12 +78,11 @@ describe('@adyen/bento/deprecation-tag-default-slot should suggest the usage of
|
|
|
78
78
|
`,
|
|
79
79
|
output: `
|
|
80
80
|
<template>
|
|
81
|
-
<bento-tag :label="
|
|
81
|
+
<bento-tag :label="thisIsMyVariableWithText" />
|
|
82
82
|
</template>
|
|
83
83
|
`,
|
|
84
84
|
errors: [{ message: '"default" slot is deprecated in favor of the "label" property' }],
|
|
85
85
|
},
|
|
86
|
-
|
|
87
86
|
{
|
|
88
87
|
name: 'BentoTag with text and property as child',
|
|
89
88
|
code: `
|
|
@@ -100,6 +99,38 @@ describe('@adyen/bento/deprecation-tag-default-slot should suggest the usage of
|
|
|
100
99
|
`,
|
|
101
100
|
errors: [{ message: '"default" slot is deprecated in favor of the "label" property' }],
|
|
102
101
|
},
|
|
102
|
+
{
|
|
103
|
+
name: 'BentoTag with text and expression with a call',
|
|
104
|
+
code: `
|
|
105
|
+
<template>
|
|
106
|
+
<bento-tag>
|
|
107
|
+
Value: {{ thisIsAValue() }}
|
|
108
|
+
</bento-tag>
|
|
109
|
+
</template>
|
|
110
|
+
`,
|
|
111
|
+
output: `
|
|
112
|
+
<template>
|
|
113
|
+
<bento-tag :label="\`Value: \${thisIsAValue()}\`" />
|
|
114
|
+
</template>
|
|
115
|
+
`,
|
|
116
|
+
errors: [{ message: '"default" slot is deprecated in favor of the "label" property' }],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'BentoTag with multiple expressions',
|
|
120
|
+
code: `
|
|
121
|
+
<template>
|
|
122
|
+
<bento-tag>
|
|
123
|
+
{{ expression }} {{ thisIsAValue() }}
|
|
124
|
+
</bento-tag>
|
|
125
|
+
</template>
|
|
126
|
+
`,
|
|
127
|
+
output: `
|
|
128
|
+
<template>
|
|
129
|
+
<bento-tag :label="\`\${expression} \${thisIsAValue()}\`" />
|
|
130
|
+
</template>
|
|
131
|
+
`,
|
|
132
|
+
errors: [{ message: '"default" slot is deprecated in favor of the "label" property' }],
|
|
133
|
+
},
|
|
103
134
|
],
|
|
104
135
|
});
|
|
105
136
|
});
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('estree').Node} Node
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('vue-eslint-parser/ast').VLiteral} VLiteral
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('eslint-plugin-vue/lib/utils').RuleModule} RuleModule
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
const VueUtils = require('eslint-plugin-vue/lib/utils');
|
|
2
15
|
|
|
3
16
|
// Mappings for the classes that can to be replaced
|
|
@@ -15,6 +28,7 @@ const CLASS_MAP = {
|
|
|
15
28
|
'64': '140',
|
|
16
29
|
};
|
|
17
30
|
|
|
31
|
+
/** @type {RuleModule} */
|
|
18
32
|
module.exports = {
|
|
19
33
|
meta: {
|
|
20
34
|
type: 'suggestion', // The type can be "problem", "suggestion", or "layout"
|
|
@@ -30,16 +44,17 @@ module.exports = {
|
|
|
30
44
|
fixable: 'code',
|
|
31
45
|
schema: [], // No options are needed for this rule
|
|
32
46
|
},
|
|
47
|
+
/** @param {RuleContext} context */
|
|
33
48
|
create(context) {
|
|
34
49
|
return VueUtils.defineTemplateBodyVisitor(context, {
|
|
35
|
-
VLiteral: node => {
|
|
50
|
+
VLiteral: (/** @type {any} */ node) => {
|
|
36
51
|
if (typeof node.value === 'string') {
|
|
37
52
|
let hasReplacements = false;
|
|
38
|
-
const classList = node.value;
|
|
53
|
+
const classList = /** @type {VLiteral['value']} */ node.value;
|
|
39
54
|
|
|
40
55
|
const fixed = classList
|
|
41
56
|
.split(' ')
|
|
42
|
-
.map(cssClass => {
|
|
57
|
+
.map((/** @type {VLiteral['value']} */ cssClass) => {
|
|
43
58
|
// Check if the class list has "padding" or "margin" utilities
|
|
44
59
|
const regexPaddingMargin = /\bu-(padding|margin)\b/g;
|
|
45
60
|
if (regexPaddingMargin.test(cssClass)) {
|
|
@@ -1,61 +1,117 @@
|
|
|
1
|
+
const j = require('jscodeshift');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('eslint').Rule.RuleContext} RuleContext
|
|
5
|
+
* @typedef {import('eslint').AST.Token} Token
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* It moves the content of the "default" slot into a prop with the "propertyName" value.
|
|
5
|
-
*
|
|
6
|
-
* @param props Contains the name of the property by which it will be replace and the context
|
|
9
|
+
* @typedef {import('vue-eslint-parser/ast').VText} VText
|
|
10
|
+
* @typedef {import('vue-eslint-parser/ast').VExpressionContainer} VExpressionContainer
|
|
7
11
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef { { propertyName: string, context: RuleContext, template: any, alwaysExpression?: boolean } } ComponentSlotMigrationProps
|
|
15
|
+
* @typedef { (node: any) => void } ComponentSlotMigrationReturn
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
/**
|
|
20
|
+
* Generates a report with a fix to those VElements containing a deprecated default slot.
|
|
21
|
+
*
|
|
22
|
+
* It moves the content of the "default" slot into a prop with the "propertyName" value.
|
|
23
|
+
*
|
|
24
|
+
* @param {ComponentSlotMigrationProps} props Contains the name of the property by which it will be replace and the context
|
|
25
|
+
* @returns {ComponentSlotMigrationReturn}
|
|
26
|
+
*/
|
|
27
|
+
componentSlotMigration({ propertyName, context, template, alwaysExpression = false }) {
|
|
28
|
+
return node => {
|
|
29
|
+
if (!node.children.length) {
|
|
30
|
+
return;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
if
|
|
23
|
-
|
|
33
|
+
// Turned true if one of the children is an expression
|
|
34
|
+
let hasExpression = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {VText | VExpressionContainer } node
|
|
38
|
+
* @param {boolean} wrapStringLiteral
|
|
39
|
+
* @returns {string | undefined}
|
|
40
|
+
*/
|
|
41
|
+
function transformNodeToText(node, wrapStringLiteral = false) {
|
|
42
|
+
if (node.type === 'VText') {
|
|
43
|
+
return node.value;
|
|
44
|
+
}
|
|
24
45
|
|
|
25
|
-
|
|
46
|
+
if (node.type === 'VExpressionContainer') {
|
|
47
|
+
hasExpression = true;
|
|
48
|
+
if (['CallExpression', 'MemberExpression', 'Identifier'].includes(node.expression?.type || '')) {
|
|
49
|
+
const expression = j(node.expression).toSource();
|
|
50
|
+
return wrapStringLiteral ? `\${${expression}}` : expression;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
26
53
|
}
|
|
27
|
-
};
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (alwaysExpression) {
|
|
40
|
-
return `:${propertyName}`;
|
|
55
|
+
/**
|
|
56
|
+
* @param { VText | VExpressionContainer } node
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
function removeEmptyVTextNodes(node) {
|
|
60
|
+
if (node.type === 'VText') {
|
|
61
|
+
return !!node.value.trim().length;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true;
|
|
41
65
|
}
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
|
|
67
|
+
// Opening tag closing brace ">"
|
|
68
|
+
/** @type {Token} */
|
|
69
|
+
const closingBrace = template.getLastToken(node.startTag);
|
|
70
|
+
|
|
71
|
+
// Remove empty nodes
|
|
72
|
+
/** @type { Array<VText | VExpressionContainer> } */
|
|
73
|
+
const filteredNodes = node.children.filter(removeEmptyVTextNodes);
|
|
74
|
+
|
|
75
|
+
// Get all content and concat
|
|
76
|
+
const slotContent = (() => {
|
|
77
|
+
if (filteredNodes.length === 1) {
|
|
78
|
+
return transformNodeToText(filteredNodes[0])?.trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const content = node.children
|
|
82
|
+
.map((/** @type {VText | VExpressionContainer} */ node) => transformNodeToText(node, true))
|
|
83
|
+
.join('')
|
|
84
|
+
.trim();
|
|
85
|
+
|
|
86
|
+
return `\`${content}\``;
|
|
87
|
+
})();
|
|
45
88
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Formats an attribute name into an expression (:attribute) or a value (attribute)
|
|
91
|
+
* for the Vue template.
|
|
92
|
+
* @returns formatted attribute name
|
|
93
|
+
*/
|
|
94
|
+
function attributeFormat() {
|
|
95
|
+
if (alwaysExpression) {
|
|
96
|
+
return `:${propertyName}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return hasExpression ? `:${propertyName}` : propertyName;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
context.report({
|
|
103
|
+
node,
|
|
104
|
+
loc: node.loc,
|
|
105
|
+
messageId: 'migrateDefaultSlot',
|
|
106
|
+
*fix(fixer) {
|
|
107
|
+
const attribute = attributeFormat();
|
|
108
|
+
// Add attribute at the end of opening tag
|
|
109
|
+
yield fixer.insertTextBefore(closingBrace, ` ${attribute}="${slotContent}" /`);
|
|
110
|
+
|
|
111
|
+
// Remove everything after the closing brace
|
|
112
|
+
yield fixer.removeRange([closingBrace.range[0] + 1, node.range[1]]);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|