@db-ux/core-eslint-plugin 0.0.0 → 4.4.2-eslint-plugin2-696cb23
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 +1 -0
- package/README.md +843 -0
- package/build/index.d.ts +352 -0
- package/build/index.js +82 -0
- package/build/rules/accordion/accordion-item-headline-required.d.ts +16 -0
- package/build/rules/accordion/accordion-item-headline-required.js +61 -0
- package/build/rules/accordion/no-nested-accordion.d.ts +16 -0
- package/build/rules/accordion/no-nested-accordion.js +54 -0
- package/build/rules/badge/badge-corner-placement-rules.d.ts +18 -0
- package/build/rules/badge/badge-corner-placement-rules.js +114 -0
- package/build/rules/badge/badge-no-inline-in-interactive.d.ts +17 -0
- package/build/rules/badge/badge-no-inline-in-interactive.js +129 -0
- package/build/rules/button/button-no-text-requires-tooltip.d.ts +18 -0
- package/build/rules/button/button-no-text-requires-tooltip.js +109 -0
- package/build/rules/button/button-single-icon-attribute.d.ts +16 -0
- package/build/rules/button/button-single-icon-attribute.js +49 -0
- package/build/rules/button/button-type-required.d.ts +17 -0
- package/build/rules/button/button-type-required.js +58 -0
- package/build/rules/close-button/close-button-text-required.d.ts +16 -0
- package/build/rules/close-button/close-button-text-required.js +38 -0
- package/build/rules/content/text-or-children-required.d.ts +16 -0
- package/build/rules/content/text-or-children-required.js +47 -0
- package/build/rules/form/form-label-required.d.ts +16 -0
- package/build/rules/form/form-label-required.js +45 -0
- package/build/rules/form/form-validation-message-required.d.ts +16 -0
- package/build/rules/form/form-validation-message-required.js +91 -0
- package/build/rules/header/header-burger-menu-label-required.d.ts +16 -0
- package/build/rules/header/header-burger-menu-label-required.js +43 -0
- package/build/rules/icon/prefer-icon-attribute.d.ts +17 -0
- package/build/rules/icon/prefer-icon-attribute.js +82 -0
- package/build/rules/input/input-file-type-validation.d.ts +18 -0
- package/build/rules/input/input-file-type-validation.js +50 -0
- package/build/rules/input/input-type-required.d.ts +17 -0
- package/build/rules/input/input-type-required.js +54 -0
- package/build/rules/link/link-external-security.d.ts +19 -0
- package/build/rules/link/link-external-security.js +166 -0
- package/build/rules/navigation/navigation-item-back-button-text-required.d.ts +16 -0
- package/build/rules/navigation/navigation-item-back-button-text-required.js +43 -0
- package/build/rules/select/custom-select-tags-remove-text-required.d.ts +16 -0
- package/build/rules/select/custom-select-tags-remove-text-required.js +33 -0
- package/build/rules/select/select-requires-options.d.ts +16 -0
- package/build/rules/select/select-requires-options.js +45 -0
- package/build/rules/tag/tag-removable-remove-button-required.d.ts +16 -0
- package/build/rules/tag/tag-removable-remove-button-required.js +49 -0
- package/build/rules/tooltip/no-interactive-tooltip-content.d.ts +15 -0
- package/build/rules/tooltip/no-interactive-tooltip-content.js +49 -0
- package/build/rules/tooltip/tooltip-requires-interactive-parent.d.ts +15 -0
- package/build/rules/tooltip/tooltip-requires-interactive-parent.js +47 -0
- package/build/shared/constants.d.ts +58 -0
- package/build/shared/constants.js +98 -0
- package/build/shared/utils.d.ts +54 -0
- package/build/shared/utils.js +178 -0
- package/package.json +37 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularFix, createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
function getTextContent(node) {
|
|
4
|
+
if (node.children) {
|
|
5
|
+
for (const child of node.children) {
|
|
6
|
+
if (child.type === 'JSXText') {
|
|
7
|
+
return child.value.trim();
|
|
8
|
+
}
|
|
9
|
+
if (child.type === 'Text') {
|
|
10
|
+
return child.value?.trim() || null;
|
|
11
|
+
}
|
|
12
|
+
if (child.type === 'VText') {
|
|
13
|
+
return child.value?.trim() || null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export default {
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'problem',
|
|
22
|
+
docs: {
|
|
23
|
+
description: 'Ensure DBBadge with corner placement has max 3 characters and label',
|
|
24
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#badge-corner-placement-rules'
|
|
25
|
+
},
|
|
26
|
+
fixable: 'code',
|
|
27
|
+
messages: {
|
|
28
|
+
[MESSAGE_IDS.BADGE_CORNER_TEXT_TOO_LONG]: MESSAGES.BADGE_CORNER_TEXT_TOO_LONG,
|
|
29
|
+
[MESSAGE_IDS.BADGE_CORNER_MISSING_LABEL]: MESSAGES.BADGE_CORNER_MISSING_LABEL
|
|
30
|
+
},
|
|
31
|
+
schema: []
|
|
32
|
+
},
|
|
33
|
+
create(context) {
|
|
34
|
+
const angularHandler = (node, parserServices) => {
|
|
35
|
+
const placement = getAttributeValue(node, 'placement');
|
|
36
|
+
if (!placement || placement === 'inline')
|
|
37
|
+
return;
|
|
38
|
+
const text = getAttributeValue(node, 'text');
|
|
39
|
+
const children = getTextContent(node);
|
|
40
|
+
const content = (typeof text === 'string' ? text : children) || '';
|
|
41
|
+
const label = getAttributeValue(node, 'label');
|
|
42
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
43
|
+
if (content.length > 3) {
|
|
44
|
+
context.report({
|
|
45
|
+
loc,
|
|
46
|
+
messageId: MESSAGE_IDS.BADGE_CORNER_TEXT_TOO_LONG
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (!label) {
|
|
50
|
+
context.report({
|
|
51
|
+
loc,
|
|
52
|
+
messageId: MESSAGE_IDS.BADGE_CORNER_MISSING_LABEL,
|
|
53
|
+
fix(fixer) {
|
|
54
|
+
const fixData = createAngularFix(context, node, ` label="${content || 'Badge'}"`);
|
|
55
|
+
if (!fixData)
|
|
56
|
+
return null;
|
|
57
|
+
return fixer.insertTextBeforeRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBBadge, angularHandler);
|
|
63
|
+
if (angularVisitors)
|
|
64
|
+
return angularVisitors;
|
|
65
|
+
const checkBadge = (node) => {
|
|
66
|
+
const openingElement = node.openingElement || node;
|
|
67
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBBadge))
|
|
68
|
+
return;
|
|
69
|
+
const placement = getAttributeValue(openingElement, 'placement');
|
|
70
|
+
if (!placement || placement === 'inline')
|
|
71
|
+
return;
|
|
72
|
+
const text = getAttributeValue(openingElement, 'text');
|
|
73
|
+
const children = getTextContent(node);
|
|
74
|
+
const content = (typeof text === 'string' ? text : children) || '';
|
|
75
|
+
const label = getAttributeValue(openingElement, 'label');
|
|
76
|
+
if (content.length > 3) {
|
|
77
|
+
context.report({
|
|
78
|
+
node: openingElement,
|
|
79
|
+
messageId: MESSAGE_IDS.BADGE_CORNER_TEXT_TOO_LONG,
|
|
80
|
+
fix(fixer) {
|
|
81
|
+
const fixes = [];
|
|
82
|
+
const shortText = content.slice(0, 3);
|
|
83
|
+
if (text && typeof text === 'string') {
|
|
84
|
+
const textAttr = openingElement.attributes.find((a) => a.type === 'JSXAttribute' &&
|
|
85
|
+
a.name.name === 'text');
|
|
86
|
+
if (textAttr) {
|
|
87
|
+
fixes.push(fixer.replaceText(textAttr, `text="${shortText}" label="${content}"`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (children) {
|
|
91
|
+
const textChild = node.children.find((c) => c.type === 'JSXText');
|
|
92
|
+
if (textChild) {
|
|
93
|
+
fixes.push(fixer.replaceText(textChild, shortText));
|
|
94
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
95
|
+
const insertPos = lastAttr
|
|
96
|
+
? lastAttr.range[1]
|
|
97
|
+
: openingElement.name.range[1];
|
|
98
|
+
fixes.push(fixer.insertTextAfterRange([insertPos, insertPos], ` label="${content}"`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return fixes;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (!label) {
|
|
106
|
+
context.report({
|
|
107
|
+
node: openingElement,
|
|
108
|
+
messageId: MESSAGE_IDS.BADGE_CORNER_MISSING_LABEL
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return defineTemplateBodyVisitor(context, { VElement: checkBadge, Element: checkBadge }, { JSXElement: checkBadge });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
declare const _default: {
|
|
3
|
+
meta: {
|
|
4
|
+
type: string;
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
fixable: string;
|
|
10
|
+
messages: {
|
|
11
|
+
[MESSAGE_IDS.BADGE_NO_INLINE_IN_INTERACTIVE]: string;
|
|
12
|
+
};
|
|
13
|
+
schema: never[];
|
|
14
|
+
};
|
|
15
|
+
create(context: any): any;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularFix, createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
const INTERACTIVE_PARENTS = ['DBButton', 'DBLink', 'button', 'a'];
|
|
4
|
+
export default {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'problem',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Prevent inline placement for DBBadge inside interactive elements',
|
|
9
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#badge-no-inline-in-interactive'
|
|
10
|
+
},
|
|
11
|
+
fixable: 'code',
|
|
12
|
+
messages: {
|
|
13
|
+
[MESSAGE_IDS.BADGE_NO_INLINE_IN_INTERACTIVE]: MESSAGES.BADGE_NO_INLINE_IN_INTERACTIVE
|
|
14
|
+
},
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const angularHandler = (node, parserServices) => {
|
|
19
|
+
const placement = getAttributeValue(node, 'placement');
|
|
20
|
+
if (placement && placement !== 'inline')
|
|
21
|
+
return;
|
|
22
|
+
let parent = node.parent;
|
|
23
|
+
while (parent) {
|
|
24
|
+
if (parent.type === 'Element') {
|
|
25
|
+
const matchedParent = INTERACTIVE_PARENTS.find((p) => parent.name === p ||
|
|
26
|
+
parent.name === p.toLowerCase().replace('db', 'db-'));
|
|
27
|
+
if (matchedParent) {
|
|
28
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
29
|
+
context.report({
|
|
30
|
+
loc,
|
|
31
|
+
messageId: MESSAGE_IDS.BADGE_NO_INLINE_IN_INTERACTIVE,
|
|
32
|
+
data: { parent: matchedParent },
|
|
33
|
+
fix(fixer) {
|
|
34
|
+
const fixData = createAngularFix(context, node, ' placement="corner-top-right"');
|
|
35
|
+
if (!fixData)
|
|
36
|
+
return null;
|
|
37
|
+
return fixer.insertTextBeforeRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
parent = parent.parent;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBBadge, angularHandler);
|
|
47
|
+
if (angularVisitors)
|
|
48
|
+
return angularVisitors;
|
|
49
|
+
const checkBadge = (node) => {
|
|
50
|
+
const openingElement = node.openingElement || node;
|
|
51
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBBadge))
|
|
52
|
+
return;
|
|
53
|
+
const placement = getAttributeValue(openingElement, 'placement');
|
|
54
|
+
if (placement && placement !== 'inline')
|
|
55
|
+
return;
|
|
56
|
+
let parent = node.parent;
|
|
57
|
+
while (parent) {
|
|
58
|
+
if (parent.type === 'JSXElement' ||
|
|
59
|
+
parent.type === 'VElement') {
|
|
60
|
+
const parentOpening = parent.openingElement || parent;
|
|
61
|
+
const parentName = parentOpening.name || parentOpening.rawName;
|
|
62
|
+
const name = typeof parentName === 'string'
|
|
63
|
+
? parentName
|
|
64
|
+
: parentName.type === 'JSXIdentifier'
|
|
65
|
+
? parentName.name
|
|
66
|
+
: null;
|
|
67
|
+
if (name) {
|
|
68
|
+
const matchedParent = INTERACTIVE_PARENTS.find((p) => name === p ||
|
|
69
|
+
name === p.toLowerCase().replace('db', 'db-'));
|
|
70
|
+
if (matchedParent) {
|
|
71
|
+
context.report({
|
|
72
|
+
node: openingElement,
|
|
73
|
+
messageId: MESSAGE_IDS.BADGE_NO_INLINE_IN_INTERACTIVE,
|
|
74
|
+
data: { parent: matchedParent },
|
|
75
|
+
fix(fixer) {
|
|
76
|
+
if (node.openingElement) {
|
|
77
|
+
// JSX
|
|
78
|
+
const placementAttr = openingElement.attributes.find((a) => a.type === 'JSXAttribute' &&
|
|
79
|
+
a.name.name === 'placement');
|
|
80
|
+
if (placementAttr) {
|
|
81
|
+
return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const lastAttr = openingElement.attributes[openingElement.attributes
|
|
85
|
+
.length - 1];
|
|
86
|
+
const insertPos = lastAttr
|
|
87
|
+
? lastAttr.range[1]
|
|
88
|
+
: openingElement.name.range[1];
|
|
89
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Vue
|
|
94
|
+
const placementAttr = openingElement.startTag.attributes.find((a) => a.key.name === 'placement');
|
|
95
|
+
if (placementAttr) {
|
|
96
|
+
return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const attrs = openingElement.startTag
|
|
100
|
+
.attributes;
|
|
101
|
+
if (attrs.length > 0) {
|
|
102
|
+
const lastAttr = attrs[attrs.length - 1];
|
|
103
|
+
return fixer.insertTextAfterRange([
|
|
104
|
+
lastAttr.range[1],
|
|
105
|
+
lastAttr.range[1]
|
|
106
|
+
], ' placement="corner-top-right"');
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const insertPos = openingElement.startTag
|
|
110
|
+
.range[0] +
|
|
111
|
+
1 +
|
|
112
|
+
openingElement.rawName
|
|
113
|
+
.length;
|
|
114
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
parent = parent.parent;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return defineTemplateBodyVisitor(context, { VElement: checkBadge, Element: checkBadge }, { JSXElement: checkBadge });
|
|
128
|
+
}
|
|
129
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
declare const _default: {
|
|
3
|
+
meta: {
|
|
4
|
+
type: string;
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
fixable: string;
|
|
10
|
+
messages: {
|
|
11
|
+
[MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_ICON]: string;
|
|
12
|
+
[MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_TOOLTIP]: string;
|
|
13
|
+
};
|
|
14
|
+
schema: never[];
|
|
15
|
+
};
|
|
16
|
+
create(context: any): any;
|
|
17
|
+
};
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, hasChildOfType, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Ensure DBButton with noText has icon and DBTooltip child',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#button-no-text-requires-tooltip'
|
|
9
|
+
},
|
|
10
|
+
fixable: 'code',
|
|
11
|
+
messages: {
|
|
12
|
+
[MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_ICON]: MESSAGES.BUTTON_NO_TEXT_MISSING_ICON,
|
|
13
|
+
[MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_TOOLTIP]: MESSAGES.BUTTON_NO_TEXT_MISSING_TOOLTIP
|
|
14
|
+
},
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const angularHandler = (node, parserServices) => {
|
|
19
|
+
const noText = getAttributeValue(node, 'noText');
|
|
20
|
+
if (!noText)
|
|
21
|
+
return;
|
|
22
|
+
const icon = getAttributeValue(node, 'icon') ||
|
|
23
|
+
getAttributeValue(node, 'iconLeading') ||
|
|
24
|
+
getAttributeValue(node, 'iconTrailing');
|
|
25
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
26
|
+
if (!icon) {
|
|
27
|
+
context.report({
|
|
28
|
+
loc,
|
|
29
|
+
messageId: MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_ICON
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const hasTooltip = hasChildOfType(node, COMPONENTS.DBTooltip);
|
|
33
|
+
if (!hasTooltip) {
|
|
34
|
+
context.report({
|
|
35
|
+
loc,
|
|
36
|
+
messageId: MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_TOOLTIP,
|
|
37
|
+
fix(fixer) {
|
|
38
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
39
|
+
const text = sourceCode.getText();
|
|
40
|
+
const startOffset = node.sourceSpan.start.offset;
|
|
41
|
+
const endOffset = node.sourceSpan.end.offset;
|
|
42
|
+
const tagText = text.substring(startOffset, endOffset);
|
|
43
|
+
const closeTagIndex = tagText.lastIndexOf('</db-button>');
|
|
44
|
+
if (closeTagIndex === -1)
|
|
45
|
+
return null;
|
|
46
|
+
const insertPos = startOffset + closeTagIndex;
|
|
47
|
+
return fixer.insertTextBeforeRange([insertPos, insertPos], '\n <db-tooltip>Describe action</db-tooltip>');
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBButton, angularHandler);
|
|
53
|
+
if (angularVisitors)
|
|
54
|
+
return angularVisitors;
|
|
55
|
+
const checkButton = (node) => {
|
|
56
|
+
const openingElement = node.openingElement || node;
|
|
57
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBButton))
|
|
58
|
+
return;
|
|
59
|
+
const noText = getAttributeValue(openingElement, 'noText');
|
|
60
|
+
if (!noText)
|
|
61
|
+
return;
|
|
62
|
+
const icon = getAttributeValue(openingElement, 'icon') ||
|
|
63
|
+
getAttributeValue(openingElement, 'iconLeading') ||
|
|
64
|
+
getAttributeValue(openingElement, 'iconTrailing');
|
|
65
|
+
if (!icon) {
|
|
66
|
+
context.report({
|
|
67
|
+
node: openingElement,
|
|
68
|
+
messageId: MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_ICON
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const hasTooltip = hasChildOfType(node, COMPONENTS.DBTooltip);
|
|
72
|
+
if (!hasTooltip) {
|
|
73
|
+
context.report({
|
|
74
|
+
node: openingElement,
|
|
75
|
+
messageId: MESSAGE_IDS.BUTTON_NO_TEXT_MISSING_TOOLTIP,
|
|
76
|
+
fix(fixer) {
|
|
77
|
+
if (node.openingElement) {
|
|
78
|
+
// JSX
|
|
79
|
+
const closingTag = node.closingElement;
|
|
80
|
+
if (!closingTag)
|
|
81
|
+
return null;
|
|
82
|
+
const componentName = openingElement.name.type === 'JSXIdentifier'
|
|
83
|
+
? openingElement.name.name
|
|
84
|
+
: 'DBButton';
|
|
85
|
+
const tooltipName = componentName.includes('-')
|
|
86
|
+
? 'db-tooltip'
|
|
87
|
+
: 'DBTooltip';
|
|
88
|
+
return fixer.insertTextBefore(closingTag, `\n <${tooltipName}>Describe action</${tooltipName}>`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Vue
|
|
92
|
+
if (!node.endTag || !node.startTag?.range)
|
|
93
|
+
return null;
|
|
94
|
+
const componentName = openingElement.rawName;
|
|
95
|
+
const tooltipName = componentName.includes('-')
|
|
96
|
+
? 'db-tooltip'
|
|
97
|
+
: 'DBTooltip';
|
|
98
|
+
return fixer.insertTextAfterRange([
|
|
99
|
+
node.startTag.range[1],
|
|
100
|
+
node.startTag.range[1]
|
|
101
|
+
], `\n <${tooltipName}>Describe action</${tooltipName}>`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return defineTemplateBodyVisitor(context, { VElement: checkButton, Element: checkButton }, { JSXElement: checkButton });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
declare const _default: {
|
|
3
|
+
meta: {
|
|
4
|
+
type: string;
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
messages: {
|
|
10
|
+
[MESSAGE_IDS.BUTTON_MULTIPLE_ICONS]: string;
|
|
11
|
+
};
|
|
12
|
+
schema: never[];
|
|
13
|
+
};
|
|
14
|
+
create(context: any): any;
|
|
15
|
+
};
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
2
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Ensure DBButton uses only one icon attribute',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#button-single-icon-attribute'
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
[MESSAGE_IDS.BUTTON_MULTIPLE_ICONS]: MESSAGES.BUTTON_MULTIPLE_ICONS
|
|
12
|
+
},
|
|
13
|
+
schema: []
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
const angularHandler = (node, parserServices) => {
|
|
17
|
+
const icon = getAttributeValue(node, 'icon');
|
|
18
|
+
const iconLeading = getAttributeValue(node, 'iconLeading');
|
|
19
|
+
const iconTrailing = getAttributeValue(node, 'iconTrailing');
|
|
20
|
+
const iconCount = [icon, iconLeading, iconTrailing].filter(Boolean).length;
|
|
21
|
+
if (iconCount > 1) {
|
|
22
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
23
|
+
context.report({
|
|
24
|
+
loc,
|
|
25
|
+
messageId: MESSAGE_IDS.BUTTON_MULTIPLE_ICONS
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBButton, angularHandler);
|
|
30
|
+
if (angularVisitors)
|
|
31
|
+
return angularVisitors;
|
|
32
|
+
const checkButton = (node) => {
|
|
33
|
+
const openingElement = node.openingElement || node;
|
|
34
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBButton))
|
|
35
|
+
return;
|
|
36
|
+
const icon = getAttributeValue(openingElement, 'icon');
|
|
37
|
+
const iconLeading = getAttributeValue(openingElement, 'iconLeading');
|
|
38
|
+
const iconTrailing = getAttributeValue(openingElement, 'iconTrailing');
|
|
39
|
+
const iconCount = [icon, iconLeading, iconTrailing].filter(Boolean).length;
|
|
40
|
+
if (iconCount > 1) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: openingElement,
|
|
43
|
+
messageId: MESSAGE_IDS.BUTTON_MULTIPLE_ICONS
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return defineTemplateBodyVisitor(context, { VElement: checkButton, Element: checkButton }, { JSXElement: checkButton });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
declare const _default: {
|
|
3
|
+
meta: {
|
|
4
|
+
type: string;
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
fixable: string;
|
|
10
|
+
messages: {
|
|
11
|
+
[MESSAGE_IDS.BUTTON_TYPE_REQUIRED]: string;
|
|
12
|
+
};
|
|
13
|
+
schema: never[];
|
|
14
|
+
};
|
|
15
|
+
create(context: any): any;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularFix, createAngularVisitors, createJsxVueFix, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Ensure DBButton has explicit type attribute',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#button-type-required'
|
|
9
|
+
},
|
|
10
|
+
fixable: 'code',
|
|
11
|
+
messages: {
|
|
12
|
+
[MESSAGE_IDS.BUTTON_TYPE_REQUIRED]: MESSAGES.BUTTON_TYPE_REQUIRED
|
|
13
|
+
},
|
|
14
|
+
schema: []
|
|
15
|
+
},
|
|
16
|
+
create(context) {
|
|
17
|
+
const angularHandler = (node, parserServices) => {
|
|
18
|
+
const type = getAttributeValue(node, 'type');
|
|
19
|
+
if (!type) {
|
|
20
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
21
|
+
context.report({
|
|
22
|
+
loc,
|
|
23
|
+
messageId: MESSAGE_IDS.BUTTON_TYPE_REQUIRED,
|
|
24
|
+
fix(fixer) {
|
|
25
|
+
const fixData = createAngularFix(context, node, ' type="button"');
|
|
26
|
+
if (!fixData)
|
|
27
|
+
return null;
|
|
28
|
+
return fixer.insertTextBeforeRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBButton, angularHandler);
|
|
34
|
+
if (angularVisitors)
|
|
35
|
+
return angularVisitors;
|
|
36
|
+
const checkButton = (node) => {
|
|
37
|
+
const openingElement = node.openingElement || node;
|
|
38
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBButton))
|
|
39
|
+
return;
|
|
40
|
+
const type = getAttributeValue(openingElement, 'type');
|
|
41
|
+
if (type)
|
|
42
|
+
return;
|
|
43
|
+
const hasClickHandler = getAttributeValue(openingElement, 'onClick') ||
|
|
44
|
+
getAttributeValue(openingElement, '(click)') ||
|
|
45
|
+
getAttributeValue(openingElement, '@click');
|
|
46
|
+
const typeValue = hasClickHandler ? 'button' : 'submit';
|
|
47
|
+
context.report({
|
|
48
|
+
node: openingElement,
|
|
49
|
+
messageId: MESSAGE_IDS.BUTTON_TYPE_REQUIRED,
|
|
50
|
+
fix(fixer) {
|
|
51
|
+
const fixData = createJsxVueFix(node, openingElement, ` type="${typeValue}"`);
|
|
52
|
+
return fixer.insertTextAfterRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
return defineTemplateBodyVisitor(context, { VElement: checkButton, Element: checkButton }, { JSXElement: checkButton });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
declare const _default: {
|
|
3
|
+
meta: {
|
|
4
|
+
type: string;
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
messages: {
|
|
10
|
+
[MESSAGE_IDS.CLOSE_BUTTON_TEXT_REQUIRED]: string;
|
|
11
|
+
};
|
|
12
|
+
schema: never[];
|
|
13
|
+
};
|
|
14
|
+
create(context: any): any;
|
|
15
|
+
};
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
2
|
+
import { MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
3
|
+
const COMPONENTS_WITH_CLOSE_BUTTON = {
|
|
4
|
+
DBNotification: 'closeButtonText',
|
|
5
|
+
DBDrawer: 'closeButtonText',
|
|
6
|
+
DBCustomSelect: 'mobileCloseButtonText'
|
|
7
|
+
};
|
|
8
|
+
export default {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Ensure components have close button text for accessibility',
|
|
13
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#close-button-text-required'
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
[MESSAGE_IDS.CLOSE_BUTTON_TEXT_REQUIRED]: MESSAGES.CLOSE_BUTTON_TEXT_REQUIRED
|
|
17
|
+
},
|
|
18
|
+
schema: []
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const checkComponent = (node) => {
|
|
22
|
+
const openingElement = node.openingElement || node;
|
|
23
|
+
const component = Object.keys(COMPONENTS_WITH_CLOSE_BUTTON).find((comp) => isDBComponent(openingElement, comp));
|
|
24
|
+
if (!component)
|
|
25
|
+
return;
|
|
26
|
+
const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
|
|
27
|
+
const value = getAttributeValue(openingElement, attribute);
|
|
28
|
+
if (!value) {
|
|
29
|
+
context.report({
|
|
30
|
+
node: openingElement,
|
|
31
|
+
messageId: MESSAGE_IDS.CLOSE_BUTTON_TEXT_REQUIRED,
|
|
32
|
+
data: { component, attribute }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return defineTemplateBodyVisitor(context, { VElement: checkComponent, Element: checkComponent }, { JSXElement: checkComponent });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
declare const _default: {
|
|
3
|
+
meta: {
|
|
4
|
+
type: string;
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
messages: {
|
|
10
|
+
[MESSAGE_IDS.TEXT_OR_CHILDREN_REQUIRED]: string;
|
|
11
|
+
};
|
|
12
|
+
schema: never[];
|
|
13
|
+
};
|
|
14
|
+
create(context: any): any;
|
|
15
|
+
};
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
const COMPONENTS_REQUIRING_CONTENT = [
|
|
4
|
+
'DBAccordionItem',
|
|
5
|
+
'DBButton',
|
|
6
|
+
'DBLink',
|
|
7
|
+
'DBIcon',
|
|
8
|
+
'DBInfotext',
|
|
9
|
+
'DBNavigationItem',
|
|
10
|
+
'DBNotification'
|
|
11
|
+
];
|
|
12
|
+
export default {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Ensure components have text property or children content',
|
|
17
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#text-or-children-required'
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
[MESSAGE_IDS.TEXT_OR_CHILDREN_REQUIRED]: MESSAGES.TEXT_OR_CHILDREN_REQUIRED
|
|
21
|
+
},
|
|
22
|
+
schema: []
|
|
23
|
+
},
|
|
24
|
+
create(context) {
|
|
25
|
+
const checkComponent = (node) => {
|
|
26
|
+
const openingElement = node.openingElement || node;
|
|
27
|
+
const component = COMPONENTS_REQUIRING_CONTENT.find((comp) => isDBComponent(openingElement, comp));
|
|
28
|
+
if (!component)
|
|
29
|
+
return;
|
|
30
|
+
const text = getAttributeValue(openingElement, 'text');
|
|
31
|
+
const hasChildren = node.children?.some((child) => (child.type === 'JSXText' && child.value.trim() !== '') ||
|
|
32
|
+
(child.type === 'VText' && child.value.trim() !== '') ||
|
|
33
|
+
child.type === 'JSXElement' ||
|
|
34
|
+
child.type === 'VElement' ||
|
|
35
|
+
child.type === 'JSXExpressionContainer' ||
|
|
36
|
+
child.type === 'VExpressionContainer');
|
|
37
|
+
if (!text && !hasChildren) {
|
|
38
|
+
context.report({
|
|
39
|
+
node: openingElement,
|
|
40
|
+
messageId: MESSAGE_IDS.TEXT_OR_CHILDREN_REQUIRED,
|
|
41
|
+
data: { component }
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return defineTemplateBodyVisitor(context, { VElement: checkComponent, Element: checkComponent }, { JSXElement: checkComponent });
|
|
46
|
+
}
|
|
47
|
+
};
|