@adyen/eslint-plugin-bento 1.1.0 → 1.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adyen/eslint-plugin-bento",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Adyen Bento ESLint rules",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -19,9 +19,10 @@
19
19
  },
20
20
  "license": "MIT",
21
21
  "devDependencies": {
22
- "@vitest/coverage-v8": "2.1.4",
22
+ "@vitest/coverage-v8": "2.1.9",
23
23
  "eslint": "8.57.1",
24
- "vitest": "2.1.4",
24
+ "jscodeshift": "17.1.2",
25
+ "vitest": "2.1.9",
25
26
  "vue-eslint-parser": "9.3.2"
26
27
  },
27
28
  "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="`${thisIsMyVariableWithCurrencyValue}`" />' },
33
+ fix: { range: [30, 101], text: ' :value="thisIsMyVariableWithCurrencyValue" />' },
34
34
  });
35
35
  });
36
36
  });
@@ -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="\`\${thisIsMyVariableWithCurrencyValue}\`" />
120
+ <bento-currency :value="thisIsMyVariableWithCurrencyValue" />
88
121
  </template>
89
122
  `,
90
123
  errors: [{ message: '"default" slot is deprecated in favor of the "value" property' }],
@@ -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="\`\${thisIsMyVariableWithText}\`" />
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,61 +1,90 @@
1
- /**
2
- * Generates a report with a fix to those VElements containing a deprecated default slot.
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
7
- */
8
- export function componentSlotMigration({ propertyName, context, template, alwaysExpression = false }) {
9
- return node => {
10
- if (!node.children.length) {
11
- return;
12
- }
13
-
14
- // Turned true if one of the children is an expression
15
- let hasExpression = false;
16
-
17
- const transformNodeToText = ({ value, type, expression }) => {
18
- if (type === 'VText') {
19
- return value;
1
+ const j = require('jscodeshift');
2
+
3
+ module.exports = {
4
+ /**
5
+ * Generates a report with a fix to those VElements containing a deprecated default slot.
6
+ *
7
+ * It moves the content of the "default" slot into a prop with the "propertyName" value.
8
+ *
9
+ * @param props Contains the name of the property by which it will be replace and the context
10
+ */
11
+ componentSlotMigration({ propertyName, context, template, alwaysExpression = false }) {
12
+ return node => {
13
+ if (!node.children.length) {
14
+ return;
20
15
  }
21
16
 
22
- if (type === 'VExpressionContainer') {
23
- hasExpression = true;
17
+ // Turned true if one of the children is an expression
18
+ let hasExpression = false;
19
+
20
+ function transformNodeToText(node, wrapStringLiteral = false) {
21
+ if (node.type === 'VText') {
22
+ return node.value;
23
+ }
24
24
 
25
- return `\${${expression.name}}`;
25
+ if (node.type === 'VExpressionContainer') {
26
+ hasExpression = true;
27
+ if (['CallExpression', 'MemberExpression', 'Identifier'].includes(node.expression?.type || '')) {
28
+ const expression = j(node.expression).toSource();
29
+ return wrapStringLiteral ? `\${${expression}}` : expression;
30
+ }
31
+ }
26
32
  }
27
- };
28
33
 
29
- // Opening tag closing brace ">"
30
- const closingBrace = template.getLastToken(node.startTag);
31
- // Get all content and concat
32
- const slotContent = node.children
33
- // .map(({ value }) => value)
34
- .map(transformNodeToText)
35
- .join('')
36
- .trim();
37
-
38
- const attributeFormat = () => {
39
- if (alwaysExpression) {
40
- return `:${propertyName}`;
34
+ function removeEmptyVTextNodes(node) {
35
+ if (node.type === 'VText') {
36
+ return !!node.value.trim().length;
37
+ }
38
+
39
+ return true;
41
40
  }
42
41
 
43
- return hasExpression ? `:${propertyName}` : propertyName;
44
- };
42
+ // Opening tag closing brace ">"
43
+ const closingBrace = template.getLastToken(node.startTag);
44
+
45
+ // Remove empty nodes
46
+ const filteredNodes = node.children.filter(removeEmptyVTextNodes);
47
+
48
+ // Get all content and concat
49
+ const slotContent = (() => {
50
+ if (filteredNodes.length === 1) {
51
+ return transformNodeToText(filteredNodes[0])?.trim();
52
+ }
45
53
 
46
- context.report({
47
- node,
48
- loc: node.loc,
49
- messageId: 'migrateDefaultSlot',
50
- *fix(fixer) {
51
- const attribute = attributeFormat();
52
- // Add attribute at the end of opening tag
53
- const attributeValue = hasExpression ? `\`${slotContent}\`` : slotContent;
54
- yield fixer.insertTextBefore(closingBrace, ` ${attribute}="${attributeValue}" /`);
55
-
56
- // Remove everything after the closing brace
57
- yield fixer.removeRange([closingBrace.range[0] + 1, node.range[1]]);
58
- },
59
- });
60
- };
61
- }
54
+ const content = node.children
55
+ .map(node => transformNodeToText(node, true))
56
+ .join('')
57
+ .trim();
58
+
59
+ return `\`${content}\``;
60
+ })();
61
+
62
+ /**
63
+ * Formats an attribute name into an expression (:attribute) or a value (attribute)
64
+ * for the Vue template.
65
+ * @returns formatted attribute name
66
+ */
67
+ function attributeFormat() {
68
+ if (alwaysExpression) {
69
+ return `:${propertyName}`;
70
+ }
71
+
72
+ return hasExpression ? `:${propertyName}` : propertyName;
73
+ }
74
+
75
+ context.report({
76
+ node,
77
+ loc: node.loc,
78
+ messageId: 'migrateDefaultSlot',
79
+ *fix(fixer) {
80
+ const attribute = attributeFormat();
81
+ // Add attribute at the end of opening tag
82
+ yield fixer.insertTextBefore(closingBrace, ` ${attribute}="${slotContent}" /`);
83
+
84
+ // Remove everything after the closing brace
85
+ yield fixer.removeRange([closingBrace.range[0] + 1, node.range[1]]);
86
+ },
87
+ });
88
+ };
89
+ },
90
+ };