@db-ux/core-eslint-plugin 4.12.0 → 4.13.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 +14 -0
- package/build/rules/accordion/no-nested-accordion.js +2 -2
- package/build/rules/badge/badge-corner-placement-rules.js +4 -4
- package/build/rules/badge/badge-no-inline-in-interactive.js +23 -35
- package/build/rules/button/button-no-text-requires-tooltip.js +12 -17
- package/build/rules/button/button-type-required.js +8 -4
- package/build/rules/close-button/close-button-text-required.js +6 -5
- package/build/rules/content/text-or-children-required.js +2 -2
- package/build/rules/form/form-label-required.js +9 -5
- package/build/rules/form/form-validation-message-required.js +14 -14
- package/build/rules/header/header-burger-menu-label-required.js +2 -2
- package/build/rules/icon/prefer-icon-attribute.js +2 -2
- package/build/rules/input/input-file-type-validation.js +6 -6
- package/build/rules/input/input-type-required.js +2 -2
- package/build/rules/link/link-external-security.js +30 -45
- package/build/rules/navigation/navigation-item-back-button-text-required.js +2 -2
- package/build/rules/select/custom-select-tags-remove-text-required.js +2 -2
- package/build/rules/select/select-requires-options.js +3 -3
- package/build/rules/tag/tag-removable-remove-button-required.js +2 -2
- package/build/rules/tooltip/no-interactive-tooltip-content.js +2 -2
- package/build/rules/tooltip/tooltip-requires-interactive-parent.js +12 -13
- package/build/shared/utils.d.ts +2 -2
- package/build/shared/utils.js +24 -29
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @db-ux/core-eslint-plugin
|
|
2
2
|
|
|
3
|
+
## 4.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat: enable Invoker Commands (`command`- and `commandfor`-HTML-attributes) for DBButton - [see commit 00f50c3](https://github.com/db-ux-design-system/core-web/commit/00f50c3fc4508e62f2e30589c00148c54e2fc852)
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- fix: return `undefined` instead of `null` from `getAttributeValue` when attribute is missing - [see commit 5dca414](https://github.com/db-ux-design-system/core-web/commit/5dca414656a7f1a9f33b0ddd2ce4f82ec93e1028)
|
|
12
|
+
|
|
13
|
+
## 4.12.1
|
|
14
|
+
|
|
15
|
+
_version bump_
|
|
16
|
+
|
|
3
17
|
## 4.12.0
|
|
4
18
|
|
|
5
19
|
_version bump_
|
|
@@ -14,7 +14,7 @@ export default {
|
|
|
14
14
|
},
|
|
15
15
|
create(context) {
|
|
16
16
|
const angularHandler = (node, parserServices) => {
|
|
17
|
-
let parent = node
|
|
17
|
+
let { parent } = node;
|
|
18
18
|
while (parent) {
|
|
19
19
|
if ((parent.type === 'Element' ||
|
|
20
20
|
parent.type === 'Element$1') &&
|
|
@@ -36,7 +36,7 @@ export default {
|
|
|
36
36
|
const openingElement = node.openingElement || node;
|
|
37
37
|
if (!isDBComponent(openingElement, COMPONENTS.DBAccordion))
|
|
38
38
|
return;
|
|
39
|
-
let parent = node
|
|
39
|
+
let { parent } = node;
|
|
40
40
|
while (parent) {
|
|
41
41
|
const parentOpening = parent.openingElement || parent;
|
|
42
42
|
if ((parent.type === 'JSXElement' ||
|
|
@@ -7,14 +7,14 @@ function getTextContent(node) {
|
|
|
7
7
|
return child.value.trim();
|
|
8
8
|
}
|
|
9
9
|
if (child.type === 'Text') {
|
|
10
|
-
return child.value?.trim() ||
|
|
10
|
+
return child.value?.trim() || undefined;
|
|
11
11
|
}
|
|
12
12
|
if (child.type === 'VText') {
|
|
13
|
-
return child.value?.trim() ||
|
|
13
|
+
return child.value?.trim() || undefined;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
return
|
|
17
|
+
return undefined;
|
|
18
18
|
}
|
|
19
19
|
export default {
|
|
20
20
|
meta: {
|
|
@@ -95,7 +95,7 @@ export default {
|
|
|
95
95
|
if (textChild) {
|
|
96
96
|
fixes.push(fixer.replaceText(textChild, shortText));
|
|
97
97
|
if (!label) {
|
|
98
|
-
const lastAttr = openingElement.attributes
|
|
98
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
99
99
|
const insertPos = lastAttr
|
|
100
100
|
? lastAttr.range[1]
|
|
101
101
|
: openingElement.name.range[1];
|
|
@@ -19,7 +19,7 @@ export default {
|
|
|
19
19
|
const placement = getAttributeValue(node, 'placement');
|
|
20
20
|
if (placement && placement !== 'inline')
|
|
21
21
|
return;
|
|
22
|
-
let parent = node
|
|
22
|
+
let { parent } = node;
|
|
23
23
|
while (parent) {
|
|
24
24
|
if (parent.type === 'Element' || parent.type === 'Element$1') {
|
|
25
25
|
const parentName = parent.name;
|
|
@@ -54,7 +54,7 @@ export default {
|
|
|
54
54
|
const placement = getAttributeValue(openingElement, 'placement');
|
|
55
55
|
if (placement && placement !== 'inline')
|
|
56
56
|
return;
|
|
57
|
-
let parent = node
|
|
57
|
+
let { parent } = node;
|
|
58
58
|
while (parent) {
|
|
59
59
|
if (parent.type === 'JSXElement' ||
|
|
60
60
|
parent.type === 'VElement' ||
|
|
@@ -83,41 +83,29 @@ export default {
|
|
|
83
83
|
if (placementAttr) {
|
|
84
84
|
return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
: openingElement.name.range[1];
|
|
92
|
-
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
93
|
-
}
|
|
86
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
87
|
+
const insertPos = lastAttr
|
|
88
|
+
? lastAttr.range[1]
|
|
89
|
+
: openingElement.name.range[1];
|
|
90
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
94
91
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
lastAttr.range[1],
|
|
108
|
-
lastAttr.range[1]
|
|
109
|
-
], ' placement="corner-top-right"');
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const insertPos = openingElement.startTag
|
|
113
|
-
.range[0] +
|
|
114
|
-
1 +
|
|
115
|
-
openingElement.rawName
|
|
116
|
-
.length;
|
|
117
|
-
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
92
|
+
// Vue
|
|
93
|
+
const placementAttr = openingElement.startTag.attributes.find((a) => a.key.name === 'placement');
|
|
94
|
+
if (placementAttr) {
|
|
95
|
+
return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
|
|
96
|
+
}
|
|
97
|
+
const attrs = openingElement.startTag.attributes;
|
|
98
|
+
if (attrs.length > 0) {
|
|
99
|
+
const lastAttr = attrs.at(-1);
|
|
100
|
+
return fixer.insertTextAfterRange([
|
|
101
|
+
lastAttr.range[1],
|
|
102
|
+
lastAttr.range[1]
|
|
103
|
+
], ' placement="corner-top-right"');
|
|
120
104
|
}
|
|
105
|
+
const insertPos = openingElement.startTag.range[0] +
|
|
106
|
+
1 +
|
|
107
|
+
openingElement.rawName.length;
|
|
108
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
121
109
|
}
|
|
122
110
|
});
|
|
123
111
|
return;
|
|
@@ -17,7 +17,7 @@ export default {
|
|
|
17
17
|
create(context) {
|
|
18
18
|
const angularHandler = (node, parserServices) => {
|
|
19
19
|
const noText = getAttributeValue(node, 'noText');
|
|
20
|
-
if (noText ===
|
|
20
|
+
if (noText === undefined)
|
|
21
21
|
return;
|
|
22
22
|
const icon = getAttributeValue(node, 'icon') ||
|
|
23
23
|
getAttributeValue(node, 'iconLeading') ||
|
|
@@ -60,7 +60,7 @@ export default {
|
|
|
60
60
|
if (!isDBComponent(openingElement, COMPONENTS.DBButton))
|
|
61
61
|
return;
|
|
62
62
|
const noText = getAttributeValue(openingElement, 'noText');
|
|
63
|
-
if (noText ===
|
|
63
|
+
if (noText === undefined)
|
|
64
64
|
return;
|
|
65
65
|
const icon = getAttributeValue(openingElement, 'icon') ||
|
|
66
66
|
getAttributeValue(openingElement, 'iconLeading') ||
|
|
@@ -90,21 +90,16 @@ export default {
|
|
|
90
90
|
: 'DBTooltip';
|
|
91
91
|
return fixer.insertTextBefore(closingTag, `\n <${tooltipName}>Describe action</${tooltipName}>`);
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return fixer.insertTextAfterRange([
|
|
104
|
-
node.startTag.range[1],
|
|
105
|
-
node.startTag.range[1]
|
|
106
|
-
], `\n <${tooltipName}>Describe action</${tooltipName}>`);
|
|
107
|
-
}
|
|
93
|
+
// Vue
|
|
94
|
+
if (!node.endTag)
|
|
95
|
+
return null;
|
|
96
|
+
if (!node.startTag?.range)
|
|
97
|
+
return null;
|
|
98
|
+
const componentName = openingElement.rawName;
|
|
99
|
+
const tooltipName = componentName.includes('-')
|
|
100
|
+
? 'db-tooltip'
|
|
101
|
+
: 'DBTooltip';
|
|
102
|
+
return fixer.insertTextAfterRange([node.startTag.range[1], node.startTag.range[1]], `\n <${tooltipName}>Describe action</${tooltipName}>`);
|
|
108
103
|
}
|
|
109
104
|
});
|
|
110
105
|
}
|
|
@@ -16,9 +16,10 @@ export default {
|
|
|
16
16
|
create(context) {
|
|
17
17
|
const angularHandler = (node, parserServices) => {
|
|
18
18
|
const type = getAttributeValue(node, 'type');
|
|
19
|
-
if (type ===
|
|
19
|
+
if (type === undefined) {
|
|
20
20
|
const hasClickHandler = getAttributeValue(node, '(click)');
|
|
21
|
-
const
|
|
21
|
+
const hasCommandFor = getAttributeValue(node, 'commandfor');
|
|
22
|
+
const typeValue = hasClickHandler || hasCommandFor ? 'button' : 'submit';
|
|
22
23
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
23
24
|
context.report({
|
|
24
25
|
loc,
|
|
@@ -40,12 +41,15 @@ export default {
|
|
|
40
41
|
if (!isDBComponent(openingElement, COMPONENTS.DBButton))
|
|
41
42
|
return;
|
|
42
43
|
const type = getAttributeValue(openingElement, 'type');
|
|
43
|
-
if (type !==
|
|
44
|
+
if (type !== undefined)
|
|
44
45
|
return;
|
|
45
46
|
const hasClickHandler = getAttributeValue(openingElement, 'onClick') ||
|
|
46
47
|
getAttributeValue(openingElement, '(click)') ||
|
|
47
48
|
getAttributeValue(openingElement, '@click');
|
|
48
|
-
const typeValue = hasClickHandler
|
|
49
|
+
const typeValue = hasClickHandler ||
|
|
50
|
+
getAttributeValue(openingElement, 'commandfor')
|
|
51
|
+
? 'button'
|
|
52
|
+
: 'submit';
|
|
49
53
|
context.report({
|
|
50
54
|
node: openingElement,
|
|
51
55
|
messageId: MESSAGE_IDS.BUTTON_TYPE_REQUIRED,
|
|
@@ -26,10 +26,11 @@ export default {
|
|
|
26
26
|
const input = node.inputs?.find((i) => i.name === 'closeable');
|
|
27
27
|
// Check for [closeable]="false" - Angular AST structure
|
|
28
28
|
if (input) {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
29
|
+
const value_ = input.value;
|
|
30
|
+
if (value_?.type === 'LiteralPrimitive' &&
|
|
31
|
+
value_.value === false)
|
|
31
32
|
return;
|
|
32
|
-
if (
|
|
33
|
+
if (value_?.source === 'false')
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
36
|
else {
|
|
@@ -41,7 +42,7 @@ export default {
|
|
|
41
42
|
}
|
|
42
43
|
const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
|
|
43
44
|
const value = getAttributeValue(node, attribute);
|
|
44
|
-
if (value ===
|
|
45
|
+
if (value === undefined || value === '') {
|
|
45
46
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
46
47
|
context.report({
|
|
47
48
|
loc,
|
|
@@ -100,7 +101,7 @@ export default {
|
|
|
100
101
|
const componentName = openingElement.name?.name || openingElement.rawName;
|
|
101
102
|
const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
|
|
102
103
|
const value = getAttributeValue(openingElement, attribute);
|
|
103
|
-
if (value ===
|
|
104
|
+
if (value === undefined || value === '') {
|
|
104
105
|
context.report({
|
|
105
106
|
node: openingElement,
|
|
106
107
|
messageId: MESSAGE_IDS.CLOSE_BUTTON_TEXT_REQUIRED,
|
|
@@ -32,7 +32,7 @@ export default {
|
|
|
32
32
|
const hasChildren = node.children?.some((child) => (child.type === 'Text' && child.value.trim() !== '') ||
|
|
33
33
|
child.type === 'Element' ||
|
|
34
34
|
child.type === 'Element$1');
|
|
35
|
-
if (text ===
|
|
35
|
+
if (text === undefined && !hasChildren) {
|
|
36
36
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
37
37
|
context.report({
|
|
38
38
|
loc,
|
|
@@ -63,7 +63,7 @@ export default {
|
|
|
63
63
|
child.type === 'VElement' ||
|
|
64
64
|
child.type === 'JSXExpressionContainer' ||
|
|
65
65
|
child.type === 'VExpressionContainer');
|
|
66
|
-
if (text ===
|
|
66
|
+
if (text === undefined && !hasChildren) {
|
|
67
67
|
context.report({
|
|
68
68
|
node: openingElement,
|
|
69
69
|
messageId: MESSAGE_IDS.TEXT_OR_CHILDREN_REQUIRED,
|
|
@@ -9,7 +9,11 @@ const FORM_COMPONENTS = [
|
|
|
9
9
|
'DBRadio',
|
|
10
10
|
'DBSwitch'
|
|
11
11
|
];
|
|
12
|
-
const COMPONENTS_WITH_CHILDREN_LABEL = [
|
|
12
|
+
const COMPONENTS_WITH_CHILDREN_LABEL = new Set([
|
|
13
|
+
'DBCheckbox',
|
|
14
|
+
'DBRadio',
|
|
15
|
+
'DBSwitch'
|
|
16
|
+
]);
|
|
13
17
|
export default {
|
|
14
18
|
meta: {
|
|
15
19
|
type: 'problem',
|
|
@@ -30,8 +34,8 @@ export default {
|
|
|
30
34
|
return;
|
|
31
35
|
const label = getAttributeValue(node, 'label');
|
|
32
36
|
const hasChildren = node.children?.some((child) => child.type === 'Text' && child.value.trim() !== '');
|
|
33
|
-
const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.
|
|
34
|
-
if ((label ===
|
|
37
|
+
const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.has(component);
|
|
38
|
+
if ((label === undefined || label === '') &&
|
|
35
39
|
!(canUseChildren && hasChildren)) {
|
|
36
40
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
37
41
|
context.report({
|
|
@@ -60,8 +64,8 @@ export default {
|
|
|
60
64
|
const hasChildren = node.children?.some((child) => (child.type === 'JSXText' && child.value.trim() !== '') ||
|
|
61
65
|
(child.type === 'VText' && child.value.trim() !== '') ||
|
|
62
66
|
(child.type === 'Text' && child.value.trim() !== ''));
|
|
63
|
-
const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.
|
|
64
|
-
if ((label ===
|
|
67
|
+
const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.has(component);
|
|
68
|
+
if ((label === undefined || label === '') &&
|
|
65
69
|
!(canUseChildren && hasChildren)) {
|
|
66
70
|
context.report({
|
|
67
71
|
node: openingElement,
|
|
@@ -26,10 +26,10 @@ export default {
|
|
|
26
26
|
if (!component)
|
|
27
27
|
return;
|
|
28
28
|
const invalidMessage = getAttributeValue(node, 'invalidMessage');
|
|
29
|
-
if (invalidMessage !==
|
|
29
|
+
if (invalidMessage !== undefined)
|
|
30
30
|
return;
|
|
31
31
|
const required = getAttributeValue(node, 'required');
|
|
32
|
-
if (required !==
|
|
32
|
+
if (required !== undefined) {
|
|
33
33
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
34
34
|
context.report({
|
|
35
35
|
loc,
|
|
@@ -41,7 +41,7 @@ export default {
|
|
|
41
41
|
if (component === 'DBInput' || component === 'DBTextarea') {
|
|
42
42
|
const maxLength = getAttributeValue(node, 'maxLength');
|
|
43
43
|
const minLength = getAttributeValue(node, 'minLength');
|
|
44
|
-
if (maxLength !==
|
|
44
|
+
if (maxLength !== undefined) {
|
|
45
45
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
46
46
|
context.report({
|
|
47
47
|
loc,
|
|
@@ -53,7 +53,7 @@ export default {
|
|
|
53
53
|
});
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
-
if (minLength !==
|
|
56
|
+
if (minLength !== undefined) {
|
|
57
57
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
58
58
|
context.report({
|
|
59
59
|
loc,
|
|
@@ -70,7 +70,7 @@ export default {
|
|
|
70
70
|
const min = getAttributeValue(node, 'min');
|
|
71
71
|
const max = getAttributeValue(node, 'max');
|
|
72
72
|
const pattern = getAttributeValue(node, 'pattern');
|
|
73
|
-
if (min !==
|
|
73
|
+
if (min !== undefined) {
|
|
74
74
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
75
75
|
context.report({
|
|
76
76
|
loc,
|
|
@@ -79,7 +79,7 @@ export default {
|
|
|
79
79
|
});
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
|
-
if (max !==
|
|
82
|
+
if (max !== undefined) {
|
|
83
83
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
84
84
|
context.report({
|
|
85
85
|
loc,
|
|
@@ -88,7 +88,7 @@ export default {
|
|
|
88
88
|
});
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
|
-
if (pattern !==
|
|
91
|
+
if (pattern !== undefined) {
|
|
92
92
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
93
93
|
context.report({
|
|
94
94
|
loc,
|
|
@@ -110,10 +110,10 @@ export default {
|
|
|
110
110
|
return;
|
|
111
111
|
const componentName = openingElement.name?.name || openingElement.rawName;
|
|
112
112
|
const invalidMessage = getAttributeValue(openingElement, 'invalidMessage');
|
|
113
|
-
if (invalidMessage !==
|
|
113
|
+
if (invalidMessage !== undefined)
|
|
114
114
|
return;
|
|
115
115
|
const required = getAttributeValue(openingElement, 'required');
|
|
116
|
-
if (required !==
|
|
116
|
+
if (required !== undefined) {
|
|
117
117
|
context.report({
|
|
118
118
|
node: openingElement,
|
|
119
119
|
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
@@ -124,7 +124,7 @@ export default {
|
|
|
124
124
|
if (component === 'DBInput' || component === 'DBTextarea') {
|
|
125
125
|
const maxLength = getAttributeValue(openingElement, 'maxLength');
|
|
126
126
|
const minLength = getAttributeValue(openingElement, 'minLength');
|
|
127
|
-
if (maxLength !==
|
|
127
|
+
if (maxLength !== undefined) {
|
|
128
128
|
context.report({
|
|
129
129
|
node: openingElement,
|
|
130
130
|
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
@@ -135,7 +135,7 @@ export default {
|
|
|
135
135
|
});
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
|
-
if (minLength !==
|
|
138
|
+
if (minLength !== undefined) {
|
|
139
139
|
context.report({
|
|
140
140
|
node: openingElement,
|
|
141
141
|
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
@@ -151,7 +151,7 @@ export default {
|
|
|
151
151
|
const min = getAttributeValue(openingElement, 'min');
|
|
152
152
|
const max = getAttributeValue(openingElement, 'max');
|
|
153
153
|
const pattern = getAttributeValue(openingElement, 'pattern');
|
|
154
|
-
if (min !==
|
|
154
|
+
if (min !== undefined) {
|
|
155
155
|
context.report({
|
|
156
156
|
node: openingElement,
|
|
157
157
|
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
@@ -159,7 +159,7 @@ export default {
|
|
|
159
159
|
});
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
|
-
if (max !==
|
|
162
|
+
if (max !== undefined) {
|
|
163
163
|
context.report({
|
|
164
164
|
node: openingElement,
|
|
165
165
|
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
@@ -167,7 +167,7 @@ export default {
|
|
|
167
167
|
});
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
|
-
if (pattern !==
|
|
170
|
+
if (pattern !== undefined) {
|
|
171
171
|
context.report({
|
|
172
172
|
node: openingElement,
|
|
173
173
|
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
@@ -15,7 +15,7 @@ export default {
|
|
|
15
15
|
create(context) {
|
|
16
16
|
const angularHandler = (node, parserServices) => {
|
|
17
17
|
const burgerMenuLabel = getAttributeValue(node, 'burgerMenuLabel');
|
|
18
|
-
if (burgerMenuLabel ===
|
|
18
|
+
if (burgerMenuLabel === undefined || burgerMenuLabel === '') {
|
|
19
19
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
20
20
|
context.report({
|
|
21
21
|
loc,
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
if (!isDBComponent(openingElement, COMPONENTS.DBHeader))
|
|
32
32
|
return;
|
|
33
33
|
const burgerMenuLabel = getAttributeValue(openingElement, 'burgerMenuLabel');
|
|
34
|
-
if (burgerMenuLabel ===
|
|
34
|
+
if (burgerMenuLabel === undefined || burgerMenuLabel === '') {
|
|
35
35
|
context.report({
|
|
36
36
|
node: openingElement,
|
|
37
37
|
messageId: MESSAGE_IDS.HEADER_MISSING_BURGER_MENU_LABEL
|
|
@@ -79,7 +79,7 @@ export default {
|
|
|
79
79
|
fixes.push(fixer.remove(iconChild));
|
|
80
80
|
if (node.openingElement) {
|
|
81
81
|
// JSX
|
|
82
|
-
const lastAttr = openingElement.attributes
|
|
82
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
83
83
|
const insertPos = lastAttr
|
|
84
84
|
? lastAttr.range[1]
|
|
85
85
|
: openingElement.name.range[1];
|
|
@@ -89,7 +89,7 @@ export default {
|
|
|
89
89
|
// Vue
|
|
90
90
|
const attrs = openingElement.startTag.attributes;
|
|
91
91
|
if (attrs.length > 0) {
|
|
92
|
-
const lastAttr = attrs
|
|
92
|
+
const lastAttr = attrs.at(-1);
|
|
93
93
|
const insertPos = lastAttr.range[1];
|
|
94
94
|
fixes.push(fixer.insertTextAfterRange([insertPos, insertPos], ` icon="${iconValue}"`));
|
|
95
95
|
}
|
|
@@ -20,7 +20,7 @@ export default {
|
|
|
20
20
|
const accept = getAttributeValue(node, 'accept');
|
|
21
21
|
const multiple = getAttributeValue(node, 'multiple');
|
|
22
22
|
if (type === 'file') {
|
|
23
|
-
if (accept ===
|
|
23
|
+
if (accept === undefined) {
|
|
24
24
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
25
25
|
context.report({
|
|
26
26
|
loc,
|
|
@@ -29,14 +29,14 @@ export default {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
|
-
if (multiple !==
|
|
32
|
+
if (multiple !== undefined) {
|
|
33
33
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
34
34
|
context.report({
|
|
35
35
|
loc,
|
|
36
36
|
messageId: MESSAGE_IDS.INPUT_INVALID_MULTIPLE
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
if (accept !==
|
|
39
|
+
if (accept !== undefined) {
|
|
40
40
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
41
41
|
context.report({
|
|
42
42
|
loc,
|
|
@@ -56,7 +56,7 @@ export default {
|
|
|
56
56
|
const accept = getAttributeValue(openingElement, 'accept');
|
|
57
57
|
const multiple = getAttributeValue(openingElement, 'multiple');
|
|
58
58
|
if (type === 'file') {
|
|
59
|
-
if (accept ===
|
|
59
|
+
if (accept === undefined) {
|
|
60
60
|
context.report({
|
|
61
61
|
node: openingElement,
|
|
62
62
|
messageId: MESSAGE_IDS.INPUT_FILE_MISSING_ACCEPT
|
|
@@ -64,13 +64,13 @@ export default {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
else {
|
|
67
|
-
if (multiple !==
|
|
67
|
+
if (multiple !== undefined) {
|
|
68
68
|
context.report({
|
|
69
69
|
node: openingElement,
|
|
70
70
|
messageId: MESSAGE_IDS.INPUT_INVALID_MULTIPLE
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
|
-
if (accept !==
|
|
73
|
+
if (accept !== undefined) {
|
|
74
74
|
context.report({
|
|
75
75
|
node: openingElement,
|
|
76
76
|
messageId: MESSAGE_IDS.INPUT_INVALID_ACCEPT
|
|
@@ -16,7 +16,7 @@ export default {
|
|
|
16
16
|
create(context) {
|
|
17
17
|
const angularHandler = (node, parserServices) => {
|
|
18
18
|
const type = getAttributeValue(node, 'type');
|
|
19
|
-
if (type ===
|
|
19
|
+
if (type === undefined) {
|
|
20
20
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
21
21
|
context.report({
|
|
22
22
|
loc,
|
|
@@ -38,7 +38,7 @@ export default {
|
|
|
38
38
|
if (!isDBComponent(openingElement, COMPONENTS.DBInput))
|
|
39
39
|
return;
|
|
40
40
|
const type = getAttributeValue(openingElement, 'type');
|
|
41
|
-
if (type ===
|
|
41
|
+
if (type === undefined) {
|
|
42
42
|
context.report({
|
|
43
43
|
node: openingElement,
|
|
44
44
|
messageId: MESSAGE_IDS.INPUT_TYPE_REQUIRED,
|
|
@@ -75,27 +75,23 @@ export default {
|
|
|
75
75
|
messageId: MESSAGE_IDS.LINK_MISSING_TARGET_BLANK,
|
|
76
76
|
fix(fixer) {
|
|
77
77
|
if (node.openingElement) {
|
|
78
|
-
const lastAttr = openingElement.attributes
|
|
78
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
79
79
|
const insertPos = lastAttr
|
|
80
80
|
? lastAttr.range[1]
|
|
81
81
|
: openingElement.name.range[1];
|
|
82
82
|
return fixer.insertTextAfterRange([insertPos, insertPos], ' target="_blank"');
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
], ' target="_blank"');
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
const insertPos = openingElement.startTag.range[0] +
|
|
94
|
-
1 +
|
|
95
|
-
openingElement.rawName.length;
|
|
96
|
-
return fixer.insertTextAfterRange([insertPos, insertPos], ' target="_blank"');
|
|
97
|
-
}
|
|
84
|
+
const attrs = openingElement.startTag.attributes;
|
|
85
|
+
if (attrs.length > 0) {
|
|
86
|
+
return fixer.insertTextAfterRange([
|
|
87
|
+
attrs.at(-1).range[1],
|
|
88
|
+
attrs.at(-1).range[1]
|
|
89
|
+
], ' target="_blank"');
|
|
98
90
|
}
|
|
91
|
+
const insertPos = openingElement.startTag.range[0] +
|
|
92
|
+
1 +
|
|
93
|
+
openingElement.rawName.length;
|
|
94
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' target="_blank"');
|
|
99
95
|
}
|
|
100
96
|
});
|
|
101
97
|
}
|
|
@@ -105,27 +101,23 @@ export default {
|
|
|
105
101
|
messageId: MESSAGE_IDS.LINK_MISSING_REFERRER_POLICY,
|
|
106
102
|
fix(fixer) {
|
|
107
103
|
if (node.openingElement) {
|
|
108
|
-
const lastAttr = openingElement.attributes
|
|
104
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
109
105
|
const insertPos = lastAttr
|
|
110
106
|
? lastAttr.range[1]
|
|
111
107
|
: openingElement.name.range[1];
|
|
112
108
|
return fixer.insertTextAfterRange([insertPos, insertPos], ' referrerPolicy="no-referrer"');
|
|
113
109
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
], ' referrerPolicy="no-referrer"');
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
const insertPos = openingElement.startTag.range[0] +
|
|
124
|
-
1 +
|
|
125
|
-
openingElement.rawName.length;
|
|
126
|
-
return fixer.insertTextAfterRange([insertPos, insertPos], ' referrerPolicy="no-referrer"');
|
|
127
|
-
}
|
|
110
|
+
const attrs = openingElement.startTag.attributes;
|
|
111
|
+
if (attrs.length > 0) {
|
|
112
|
+
return fixer.insertTextAfterRange([
|
|
113
|
+
attrs.at(-1).range[1],
|
|
114
|
+
attrs.at(-1).range[1]
|
|
115
|
+
], ' referrerPolicy="no-referrer"');
|
|
128
116
|
}
|
|
117
|
+
const insertPos = openingElement.startTag.range[0] +
|
|
118
|
+
1 +
|
|
119
|
+
openingElement.rawName.length;
|
|
120
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' referrerPolicy="no-referrer"');
|
|
129
121
|
}
|
|
130
122
|
});
|
|
131
123
|
}
|
|
@@ -136,27 +128,20 @@ export default {
|
|
|
136
128
|
messageId: MESSAGE_IDS.LINK_MISSING_CONTENT_EXTERNAL,
|
|
137
129
|
fix(fixer) {
|
|
138
130
|
if (node.openingElement) {
|
|
139
|
-
const lastAttr = openingElement.attributes
|
|
131
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
140
132
|
const insertPos = lastAttr
|
|
141
133
|
? lastAttr.range[1]
|
|
142
134
|
: openingElement.name.range[1];
|
|
143
135
|
return fixer.insertTextAfterRange([insertPos, insertPos], ' content="external"');
|
|
144
136
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return fixer.insertTextAfterRange([
|
|
149
|
-
attrs[attrs.length - 1].range[1],
|
|
150
|
-
attrs[attrs.length - 1].range[1]
|
|
151
|
-
], ' content="external"');
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
const insertPos = openingElement.startTag.range[0] +
|
|
155
|
-
1 +
|
|
156
|
-
openingElement.rawName.length;
|
|
157
|
-
return fixer.insertTextAfterRange([insertPos, insertPos], ' content="external"');
|
|
158
|
-
}
|
|
137
|
+
const attrs = openingElement.startTag.attributes;
|
|
138
|
+
if (attrs.length > 0) {
|
|
139
|
+
return fixer.insertTextAfterRange([attrs.at(-1).range[1], attrs.at(-1).range[1]], ' content="external"');
|
|
159
140
|
}
|
|
141
|
+
const insertPos = openingElement.startTag.range[0] +
|
|
142
|
+
1 +
|
|
143
|
+
openingElement.rawName.length;
|
|
144
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' content="external"');
|
|
160
145
|
}
|
|
161
146
|
});
|
|
162
147
|
}
|
|
@@ -15,7 +15,7 @@ export default {
|
|
|
15
15
|
create(context) {
|
|
16
16
|
const angularHandler = (node, parserServices) => {
|
|
17
17
|
const backButtonText = getAttributeValue(node, 'backButtonText');
|
|
18
|
-
if (backButtonText ===
|
|
18
|
+
if (backButtonText === undefined || backButtonText === '') {
|
|
19
19
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
20
20
|
context.report({
|
|
21
21
|
loc,
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
if (!isDBComponent(openingElement, COMPONENTS.DBNavigationItem))
|
|
32
32
|
return;
|
|
33
33
|
const backButtonText = getAttributeValue(openingElement, 'backButtonText');
|
|
34
|
-
if (backButtonText ===
|
|
34
|
+
if (backButtonText === undefined || backButtonText === '') {
|
|
35
35
|
context.report({
|
|
36
36
|
node: openingElement,
|
|
37
37
|
messageId: MESSAGE_IDS.NAVIGATION_ITEM_MISSING_BACK_BUTTON_TEXT
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
if (selectedType !== 'tag')
|
|
19
19
|
return;
|
|
20
20
|
const removeTagsTexts = getAttributeValue(node, 'removeTagsTexts');
|
|
21
|
-
if (removeTagsTexts ===
|
|
21
|
+
if (removeTagsTexts === undefined || removeTagsTexts === '') {
|
|
22
22
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
23
23
|
context.report({
|
|
24
24
|
loc,
|
|
@@ -37,7 +37,7 @@ export default {
|
|
|
37
37
|
if (selectedType !== 'tag')
|
|
38
38
|
return;
|
|
39
39
|
const removeTagsTexts = getAttributeValue(openingElement, 'removeTagsTexts');
|
|
40
|
-
if (removeTagsTexts ===
|
|
40
|
+
if (removeTagsTexts === undefined || removeTagsTexts === '') {
|
|
41
41
|
context.report({
|
|
42
42
|
node: openingElement,
|
|
43
43
|
messageId: MESSAGE_IDS.CUSTOM_SELECT_MISSING_REMOVE_TAGS_TEXTS
|
|
@@ -3,7 +3,7 @@ import { createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, is
|
|
|
3
3
|
function hasOptionChildren(node) {
|
|
4
4
|
return node.children?.some((child) => {
|
|
5
5
|
if (child.type === 'JSXElement') {
|
|
6
|
-
const name = child.openingElement
|
|
6
|
+
const { name } = child.openingElement;
|
|
7
7
|
if (name.type === 'JSXIdentifier') {
|
|
8
8
|
return name.name === 'option';
|
|
9
9
|
}
|
|
@@ -30,7 +30,7 @@ export default {
|
|
|
30
30
|
const angularHandler = (node, parserServices) => {
|
|
31
31
|
const options = getAttributeValue(node, 'options');
|
|
32
32
|
const hasChildren = hasOptionChildren(node);
|
|
33
|
-
if (options ===
|
|
33
|
+
if (options === undefined && !hasChildren) {
|
|
34
34
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
35
35
|
context.report({
|
|
36
36
|
loc,
|
|
@@ -47,7 +47,7 @@ export default {
|
|
|
47
47
|
return;
|
|
48
48
|
const options = getAttributeValue(openingElement, 'options');
|
|
49
49
|
const hasChildren = hasOptionChildren(node);
|
|
50
|
-
if (options ===
|
|
50
|
+
if (options === undefined && !hasChildren) {
|
|
51
51
|
context.report({
|
|
52
52
|
node: openingElement,
|
|
53
53
|
messageId: MESSAGE_IDS.SELECT_MISSING_OPTIONS
|
|
@@ -18,7 +18,7 @@ export default {
|
|
|
18
18
|
if (behavior !== 'removable')
|
|
19
19
|
return;
|
|
20
20
|
const removeButton = getAttributeValue(node, 'removeButton');
|
|
21
|
-
if (removeButton ===
|
|
21
|
+
if (removeButton === undefined || removeButton === '') {
|
|
22
22
|
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
23
23
|
context.report({
|
|
24
24
|
loc,
|
|
@@ -37,7 +37,7 @@ export default {
|
|
|
37
37
|
if (behavior !== 'removable')
|
|
38
38
|
return;
|
|
39
39
|
const removeButton = getAttributeValue(openingElement, 'removeButton');
|
|
40
|
-
if (removeButton ===
|
|
40
|
+
if (removeButton === undefined || removeButton === '') {
|
|
41
41
|
context.report({
|
|
42
42
|
node: openingElement,
|
|
43
43
|
messageId: MESSAGE_IDS.TAG_REMOVABLE_REMOVE_BUTTON_REQUIRED
|
|
@@ -14,8 +14,8 @@ function hasInteractiveChild(node) {
|
|
|
14
14
|
? name.name
|
|
15
15
|
: null;
|
|
16
16
|
if (tagName &&
|
|
17
|
-
INTERACTIVE_ELEMENTS.some((
|
|
18
|
-
tagName ===
|
|
17
|
+
INTERACTIVE_ELEMENTS.some((element) => tagName === element ||
|
|
18
|
+
tagName === element.toLowerCase().replace('db', 'db-'))) {
|
|
19
19
|
return true;
|
|
20
20
|
}
|
|
21
21
|
return hasInteractiveChild(child);
|
|
@@ -9,12 +9,12 @@ function isInteractiveElement(node) {
|
|
|
9
9
|
if (!tagName)
|
|
10
10
|
return false;
|
|
11
11
|
const normalizedTag = tagName.toLowerCase();
|
|
12
|
-
return INTERACTIVE_ELEMENTS.some((
|
|
13
|
-
const
|
|
14
|
-
const kebabCase =
|
|
15
|
-
?
|
|
16
|
-
:
|
|
17
|
-
return normalizedTag ===
|
|
12
|
+
return INTERACTIVE_ELEMENTS.some((element) => {
|
|
13
|
+
const elementLower = element.toLowerCase();
|
|
14
|
+
const kebabCase = elementLower.startsWith('db')
|
|
15
|
+
? elementLower.replace('db', 'db-')
|
|
16
|
+
: elementLower;
|
|
17
|
+
return normalizedTag === elementLower || normalizedTag === kebabCase;
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
export default {
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
},
|
|
32
32
|
create(context) {
|
|
33
33
|
const angularHandler = (node, parserServices) => {
|
|
34
|
-
let parent = node
|
|
34
|
+
let { parent } = node;
|
|
35
35
|
while (parent) {
|
|
36
36
|
if ((parent.type === 'Element' ||
|
|
37
37
|
parent.type === 'Element$1') &&
|
|
@@ -53,14 +53,13 @@ export default {
|
|
|
53
53
|
const openingElement = node.openingElement || node;
|
|
54
54
|
if (!isDBComponent(openingElement, 'DBTooltip'))
|
|
55
55
|
return;
|
|
56
|
-
let parent = node
|
|
56
|
+
let { parent } = node;
|
|
57
57
|
while (parent) {
|
|
58
|
-
if (parent.type === 'JSXElement' ||
|
|
58
|
+
if ((parent.type === 'JSXElement' ||
|
|
59
59
|
parent.type === 'VElement' ||
|
|
60
|
-
parent.type === 'Element')
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
60
|
+
parent.type === 'Element') &&
|
|
61
|
+
isInteractiveElement(parent)) {
|
|
62
|
+
return;
|
|
64
63
|
}
|
|
65
64
|
parent = parent.parent;
|
|
66
65
|
}
|
package/build/shared/utils.d.ts
CHANGED
|
@@ -40,14 +40,14 @@ type AngularElement = {
|
|
|
40
40
|
children?: AngularElement[];
|
|
41
41
|
};
|
|
42
42
|
type ElementNode = TSESTree.JSXOpeningElement | VElement | AngularElement;
|
|
43
|
-
export declare function getAttributeValue(node: ElementNode, attrName: string): string | boolean |
|
|
43
|
+
export declare function getAttributeValue(node: ElementNode, attrName: string): string | boolean | undefined;
|
|
44
44
|
export declare function hasChildOfType(node: TSESTree.JSXElement | VElement | AngularElement, componentName: string): boolean;
|
|
45
45
|
export declare function isDBComponent(node: ElementNode, componentName: string): boolean;
|
|
46
46
|
export declare function defineTemplateBodyVisitor(context: any, templateVisitor: any, scriptVisitor?: any): any;
|
|
47
47
|
export declare function createAngularVisitors(context: any, componentName: string, handler: (node: any, parserServices: any) => void): {
|
|
48
48
|
[x: string]: (node: any) => void;
|
|
49
49
|
} | null;
|
|
50
|
-
export declare function toKebabCase(
|
|
50
|
+
export declare function toKebabCase(string_: string): string;
|
|
51
51
|
export declare function getAngularComponentName(componentName: string): string;
|
|
52
52
|
export declare function createAngularFix(context: any, node: any, attributeText: string): {
|
|
53
53
|
insertPos: any;
|
package/build/shared/utils.js
CHANGED
|
@@ -18,7 +18,7 @@ export function getAttributeValue(node, attrName) {
|
|
|
18
18
|
const input = node.inputs.find((i) => i.name === attrName || i.name === kebabAttrName);
|
|
19
19
|
if (input)
|
|
20
20
|
return true;
|
|
21
|
-
return
|
|
21
|
+
return undefined;
|
|
22
22
|
}
|
|
23
23
|
if (isVElement(node)) {
|
|
24
24
|
const attr = node.startTag.attributes.find((a) => {
|
|
@@ -42,23 +42,22 @@ export function getAttributeValue(node, attrName) {
|
|
|
42
42
|
keyName === `:${kebabAttrName}`);
|
|
43
43
|
});
|
|
44
44
|
if (!attr)
|
|
45
|
-
return
|
|
45
|
+
return undefined;
|
|
46
46
|
if (!attr.value)
|
|
47
47
|
return true;
|
|
48
|
-
return attr.value.value;
|
|
48
|
+
return attr.value.value ?? true;
|
|
49
49
|
}
|
|
50
|
-
const variants = [attrName, `[${attrName}]`, `:${attrName}`];
|
|
51
|
-
const attr = node.attributes.find((a) => a.type === 'JSXAttribute' &&
|
|
52
|
-
variants.includes(a.name.name));
|
|
50
|
+
const variants = new Set([attrName, `[${attrName}]`, `:${attrName}`]);
|
|
51
|
+
const attr = node.attributes.find((a) => a.type === 'JSXAttribute' && variants.has(a.name.name));
|
|
53
52
|
if (!attr)
|
|
54
|
-
return
|
|
53
|
+
return undefined;
|
|
55
54
|
if (!attr.value)
|
|
56
55
|
return true;
|
|
57
56
|
if (attr.value.type === 'Literal')
|
|
58
57
|
return attr.value.value;
|
|
59
58
|
if (attr.value.type === 'JSXExpressionContainer')
|
|
60
59
|
return true;
|
|
61
|
-
return
|
|
60
|
+
return undefined;
|
|
62
61
|
}
|
|
63
62
|
export function hasChildOfType(node, componentName) {
|
|
64
63
|
const kebabName = getAngularComponentName(componentName);
|
|
@@ -81,9 +80,9 @@ export function hasChildOfType(node, componentName) {
|
|
|
81
80
|
}
|
|
82
81
|
return node.children.some((child) => {
|
|
83
82
|
if (child.type === 'JSXElement') {
|
|
84
|
-
const openingElement = child
|
|
83
|
+
const { openingElement } = child;
|
|
85
84
|
if (openingElement.name.type === 'JSXIdentifier') {
|
|
86
|
-
const name = openingElement.name
|
|
85
|
+
const { name } = openingElement.name;
|
|
87
86
|
return name === componentName || name === kebabName;
|
|
88
87
|
}
|
|
89
88
|
}
|
|
@@ -100,7 +99,7 @@ export function isDBComponent(node, componentName) {
|
|
|
100
99
|
}
|
|
101
100
|
if (node.name.type !== 'JSXIdentifier')
|
|
102
101
|
return false;
|
|
103
|
-
const name = node.name
|
|
102
|
+
const { name } = node.name;
|
|
104
103
|
return name === componentName || name === kebabName;
|
|
105
104
|
}
|
|
106
105
|
export function defineTemplateBodyVisitor(context, templateVisitor, scriptVisitor) {
|
|
@@ -114,7 +113,7 @@ export function defineTemplateBodyVisitor(context, templateVisitor, scriptVisito
|
|
|
114
113
|
const angularVisitors = {};
|
|
115
114
|
for (const [key, handler] of Object.entries(templateVisitor)) {
|
|
116
115
|
if (key === 'VElement' || key === 'Element') {
|
|
117
|
-
angularVisitors
|
|
116
|
+
angularVisitors.Element = handler;
|
|
118
117
|
}
|
|
119
118
|
else {
|
|
120
119
|
angularVisitors[key] = handler;
|
|
@@ -133,14 +132,16 @@ export function createAngularVisitors(context, componentName, handler) {
|
|
|
133
132
|
return null;
|
|
134
133
|
}
|
|
135
134
|
const kebabName = getAngularComponentName(componentName);
|
|
136
|
-
const wrappedHandler = (node) =>
|
|
135
|
+
const wrappedHandler = (node) => {
|
|
136
|
+
handler(node, parserServices);
|
|
137
|
+
};
|
|
137
138
|
return {
|
|
138
139
|
[`Element[name="${kebabName}"]`]: wrappedHandler,
|
|
139
140
|
[`Element[name="${componentName}"]`]: wrappedHandler
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
|
-
export function toKebabCase(
|
|
143
|
-
return
|
|
143
|
+
export function toKebabCase(string_) {
|
|
144
|
+
return string_.replaceAll(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
144
145
|
}
|
|
145
146
|
export function getAngularComponentName(componentName) {
|
|
146
147
|
// For DB components, convert DBComponentName -> db-component-name
|
|
@@ -163,24 +164,18 @@ export function createAngularFix(context, node, attributeText) {
|
|
|
163
164
|
export function createJsxVueFix(node, openingElement, attributeText) {
|
|
164
165
|
if (node.openingElement) {
|
|
165
166
|
// JSX
|
|
166
|
-
const lastAttr = openingElement.attributes
|
|
167
|
+
const lastAttr = openingElement.attributes.at(-1);
|
|
167
168
|
const insertPos = lastAttr
|
|
168
169
|
? lastAttr.range[1]
|
|
169
170
|
: openingElement.name.range[1];
|
|
170
171
|
return { insertPos, attributeText };
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return { insertPos: lastAttr.range[1], attributeText };
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
const insertPos = openingElement.startTag.range[0] +
|
|
181
|
-
1 +
|
|
182
|
-
openingElement.rawName.length;
|
|
183
|
-
return { insertPos, attributeText };
|
|
184
|
-
}
|
|
173
|
+
// Vue
|
|
174
|
+
const attrs = openingElement.startTag.attributes;
|
|
175
|
+
if (attrs.length > 0) {
|
|
176
|
+
const lastAttr = attrs.at(-1);
|
|
177
|
+
return { insertPos: lastAttr.range[1], attributeText };
|
|
185
178
|
}
|
|
179
|
+
const insertPos = openingElement.startTag.range[0] + 1 + openingElement.rawName.length;
|
|
180
|
+
return { insertPos, attributeText };
|
|
186
181
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@db-ux/core-eslint-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ESLint plugin for DB UX Design System components",
|
|
6
6
|
"repository": {
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@angular-eslint/utils": "21.4.0",
|
|
31
|
-
"@typescript-eslint/utils": "^8.61.
|
|
31
|
+
"@typescript-eslint/utils": "^8.61.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@angular-eslint/bundled-angular-compiler": "21.4.0",
|
|
35
35
|
"@angular-eslint/template-parser": "21.4.0",
|
|
36
36
|
"@angular-eslint/test-utils": "21.4.0",
|
|
37
|
-
"@typescript-eslint/parser": "8.61.
|
|
38
|
-
"@typescript-eslint/rule-tester": "8.61.
|
|
37
|
+
"@typescript-eslint/parser": "8.61.1",
|
|
38
|
+
"@typescript-eslint/rule-tester": "8.61.1",
|
|
39
39
|
"cpr": "3.0.1",
|
|
40
40
|
"npm-run-all2": "9.0.2",
|
|
41
41
|
"typescript": "5.9.3",
|
|
42
|
-
"vitest": "4.1.
|
|
42
|
+
"vitest": "4.1.9",
|
|
43
43
|
"vue-eslint-parser": "10.4.1"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|