@db-ux/core-eslint-plugin 4.4.2-eslint-plugin-28ea614 → 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/README.md +89 -0
- package/build/index.d.ts +308 -55
- package/build/index.js +17 -14
- 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 +14 -3
- package/build/rules/accordion/no-nested-accordion.js +40 -23
- package/build/rules/badge/badge-corner-placement-rules.d.ts +16 -3
- package/build/rules/badge/badge-corner-placement-rules.js +96 -58
- package/build/rules/badge/badge-no-inline-in-interactive.d.ts +15 -3
- package/build/rules/badge/badge-no-inline-in-interactive.js +102 -40
- package/build/rules/button/button-no-text-requires-tooltip.d.ts +16 -3
- package/build/rules/button/button-no-text-requires-tooltip.js +85 -35
- package/build/rules/button/button-single-icon-attribute.d.ts +14 -3
- package/build/rules/button/button-single-icon-attribute.js +37 -23
- package/build/rules/button/button-type-required.d.ts +15 -3
- package/build/rules/button/button-type-required.js +45 -32
- package/build/rules/close-button/close-button-text-required.d.ts +14 -3
- package/build/rules/close-button/close-button-text-required.js +21 -23
- package/build/rules/content/text-or-children-required.d.ts +14 -3
- package/build/rules/content/text-or-children-required.js +26 -28
- package/build/rules/form/form-label-required.d.ts +14 -3
- package/build/rules/form/form-label-required.js +23 -25
- package/build/rules/form/form-validation-message-required.d.ts +14 -3
- package/build/rules/form/form-validation-message-required.js +65 -67
- package/build/rules/header/header-burger-menu-label-required.d.ts +14 -3
- package/build/rules/header/header-burger-menu-label-required.js +31 -20
- package/build/rules/icon/prefer-icon-attribute.d.ts +15 -3
- package/build/rules/icon/prefer-icon-attribute.js +48 -33
- package/build/rules/input/input-file-type-validation.d.ts +16 -3
- package/build/rules/input/input-file-type-validation.js +36 -38
- package/build/rules/input/input-type-required.d.ts +15 -3
- package/build/rules/input/input-type-required.js +41 -27
- package/build/rules/link/link-external-security.d.ts +17 -3
- package/build/rules/link/link-external-security.js +148 -32
- package/build/rules/navigation/navigation-item-back-button-text-required.d.ts +14 -3
- package/build/rules/navigation/navigation-item-back-button-text-required.js +31 -20
- package/build/rules/select/custom-select-tags-remove-text-required.d.ts +14 -3
- package/build/rules/select/custom-select-tags-remove-text-required.js +21 -23
- package/build/rules/select/select-requires-options.d.ts +14 -3
- package/build/rules/select/select-requires-options.js +23 -22
- package/build/rules/tag/tag-removable-remove-button-required.d.ts +14 -3
- package/build/rules/tag/tag-removable-remove-button-required.js +37 -23
- package/build/rules/tooltip/no-interactive-tooltip-content.d.ts +13 -3
- package/build/rules/tooltip/no-interactive-tooltip-content.js +28 -26
- package/build/rules/tooltip/tooltip-requires-interactive-parent.d.ts +13 -3
- package/build/rules/tooltip/tooltip-requires-interactive-parent.js +30 -30
- package/build/shared/constants.d.ts +58 -0
- package/build/shared/constants.js +98 -0
- package/build/shared/utils.d.ts +53 -4
- package/build/shared/utils.js +145 -28
- package/package.json +8 -3
|
@@ -1,37 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export default createRule({
|
|
5
|
-
name: 'no-nested-accordion',
|
|
1
|
+
import { createAngularVisitors, defineTemplateBodyVisitor, isDBComponent } from '../../shared/utils.js';
|
|
2
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
3
|
+
export default {
|
|
6
4
|
meta: {
|
|
7
5
|
type: 'problem',
|
|
8
6
|
docs: {
|
|
9
|
-
description: 'Prevent nesting DBAccordion components'
|
|
7
|
+
description: 'Prevent nesting DBAccordion components',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#no-nested-accordion'
|
|
10
9
|
},
|
|
11
10
|
messages: {
|
|
12
|
-
|
|
11
|
+
[MESSAGE_IDS.ACCORDION_NO_NESTED]: MESSAGES.ACCORDION_NO_NESTED
|
|
13
12
|
},
|
|
14
13
|
schema: []
|
|
15
14
|
},
|
|
16
|
-
defaultOptions: [],
|
|
17
15
|
create(context) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const angularHandler = (node, parserServices) => {
|
|
17
|
+
let parent = node.parent;
|
|
18
|
+
while (parent) {
|
|
19
|
+
if (parent.type === 'Element' && isDBComponent(parent, COMPONENTS.DBAccordion)) {
|
|
20
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
21
|
+
context.report({
|
|
22
|
+
loc,
|
|
23
|
+
messageId: MESSAGE_IDS.ACCORDION_NO_NESTED
|
|
24
|
+
});
|
|
21
25
|
return;
|
|
22
|
-
let parent = node.parent;
|
|
23
|
-
while (parent) {
|
|
24
|
-
if (parent.type === 'JSXElement' &&
|
|
25
|
-
isDBComponent(parent.openingElement, 'DBAccordion')) {
|
|
26
|
-
context.report({
|
|
27
|
-
node: node.openingElement,
|
|
28
|
-
messageId: 'noNested'
|
|
29
|
-
});
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
parent = parent.parent;
|
|
33
26
|
}
|
|
27
|
+
parent = parent.parent;
|
|
34
28
|
}
|
|
35
29
|
};
|
|
30
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBAccordion, angularHandler);
|
|
31
|
+
if (angularVisitors)
|
|
32
|
+
return angularVisitors;
|
|
33
|
+
const checkAccordion = (node) => {
|
|
34
|
+
const openingElement = node.openingElement || node;
|
|
35
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBAccordion))
|
|
36
|
+
return;
|
|
37
|
+
let parent = node.parent;
|
|
38
|
+
while (parent) {
|
|
39
|
+
const parentOpening = parent.openingElement || parent;
|
|
40
|
+
if ((parent.type === 'JSXElement' ||
|
|
41
|
+
parent.type === 'VElement') &&
|
|
42
|
+
isDBComponent(parentOpening, COMPONENTS.DBAccordion)) {
|
|
43
|
+
context.report({
|
|
44
|
+
node: openingElement,
|
|
45
|
+
messageId: MESSAGE_IDS.ACCORDION_NO_NESTED
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
parent = parent.parent;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return defineTemplateBodyVisitor(context, { VElement: checkAccordion, Element: checkAccordion }, { JSXElement: checkAccordion });
|
|
36
53
|
}
|
|
37
|
-
}
|
|
54
|
+
};
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
declare const _default:
|
|
3
|
-
|
|
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_CORNER_TEXT_TOO_LONG]: string;
|
|
12
|
+
[MESSAGE_IDS.BADGE_CORNER_MISSING_LABEL]: string;
|
|
13
|
+
};
|
|
14
|
+
schema: never[];
|
|
15
|
+
};
|
|
16
|
+
create(context: any): any;
|
|
4
17
|
};
|
|
5
18
|
export default _default;
|
|
@@ -1,76 +1,114 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
-
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#${name}`);
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularFix, createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
4
3
|
function getTextContent(node) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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;
|
|
9
18
|
}
|
|
10
|
-
export default
|
|
11
|
-
name: 'badge-corner-placement-rules',
|
|
19
|
+
export default {
|
|
12
20
|
meta: {
|
|
13
21
|
type: 'problem',
|
|
14
22
|
docs: {
|
|
15
|
-
description: 'Ensure DBBadge with corner placement has max 3 characters and label'
|
|
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'
|
|
16
25
|
},
|
|
17
26
|
fixable: 'code',
|
|
18
27
|
messages: {
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
21
30
|
},
|
|
22
31
|
schema: []
|
|
23
32
|
},
|
|
24
|
-
defaultOptions: [],
|
|
25
33
|
create(context) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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}"`));
|
|
50
88
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
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}"`));
|
|
62
99
|
}
|
|
63
|
-
return fixes;
|
|
64
100
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
101
|
+
return fixes;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (!label) {
|
|
106
|
+
context.report({
|
|
107
|
+
node: openingElement,
|
|
108
|
+
messageId: MESSAGE_IDS.BADGE_CORNER_MISSING_LABEL
|
|
109
|
+
});
|
|
73
110
|
}
|
|
74
111
|
};
|
|
112
|
+
return defineTemplateBodyVisitor(context, { VElement: checkBadge, Element: checkBadge }, { JSXElement: checkBadge });
|
|
75
113
|
}
|
|
76
|
-
}
|
|
114
|
+
};
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
declare const _default:
|
|
3
|
-
|
|
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;
|
|
4
16
|
};
|
|
5
17
|
export default _default;
|
|
@@ -1,67 +1,129 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
-
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#${name}`);
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularFix, createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
4
3
|
const INTERACTIVE_PARENTS = ['DBButton', 'DBLink', 'button', 'a'];
|
|
5
|
-
export default
|
|
6
|
-
name: 'badge-no-inline-in-interactive',
|
|
4
|
+
export default {
|
|
7
5
|
meta: {
|
|
8
6
|
type: 'problem',
|
|
9
7
|
docs: {
|
|
10
|
-
description: 'Prevent inline placement for DBBadge inside interactive elements'
|
|
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'
|
|
11
10
|
},
|
|
12
11
|
fixable: 'code',
|
|
13
12
|
messages: {
|
|
14
|
-
|
|
13
|
+
[MESSAGE_IDS.BADGE_NO_INLINE_IN_INTERACTIVE]: MESSAGES.BADGE_NO_INLINE_IN_INTERACTIVE
|
|
15
14
|
},
|
|
16
15
|
schema: []
|
|
17
16
|
},
|
|
18
|
-
defaultOptions: [],
|
|
19
17
|
create(context) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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' &&
|
|
43
79
|
a.name.name === 'placement');
|
|
44
80
|
if (placementAttr) {
|
|
45
81
|
return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
|
|
46
82
|
}
|
|
47
83
|
else {
|
|
48
|
-
const lastAttr =
|
|
49
|
-
.
|
|
84
|
+
const lastAttr = openingElement.attributes[openingElement.attributes
|
|
85
|
+
.length - 1];
|
|
50
86
|
const insertPos = lastAttr
|
|
51
87
|
? lastAttr.range[1]
|
|
52
|
-
:
|
|
53
|
-
.range[1];
|
|
88
|
+
: openingElement.name.range[1];
|
|
54
89
|
return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
|
|
55
90
|
}
|
|
56
91
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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;
|
|
60
121
|
}
|
|
61
122
|
}
|
|
62
|
-
parent = parent.parent;
|
|
63
123
|
}
|
|
124
|
+
parent = parent.parent;
|
|
64
125
|
}
|
|
65
126
|
};
|
|
127
|
+
return defineTemplateBodyVisitor(context, { VElement: checkBadge, Element: checkBadge }, { JSXElement: checkBadge });
|
|
66
128
|
}
|
|
67
|
-
}
|
|
129
|
+
};
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
declare const _default:
|
|
3
|
-
|
|
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;
|
|
4
17
|
};
|
|
5
18
|
export default _default;
|
|
@@ -1,45 +1,81 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getAttributeValue, hasChildOfType, isDBComponent } from '../../shared/utils.js';
|
|
3
|
-
|
|
4
|
-
export default createRule({
|
|
5
|
-
name: 'button-no-text-requires-tooltip',
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, hasChildOfType, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
export default {
|
|
6
4
|
meta: {
|
|
7
5
|
type: 'problem',
|
|
8
6
|
docs: {
|
|
9
|
-
description: 'Ensure DBButton with noText has icon and DBTooltip child'
|
|
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'
|
|
10
9
|
},
|
|
11
10
|
fixable: 'code',
|
|
12
11
|
messages: {
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
15
14
|
},
|
|
16
15
|
schema: []
|
|
17
16
|
},
|
|
18
|
-
defaultOptions: [],
|
|
19
17
|
create(context) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
43
79
|
const closingTag = node.closingElement;
|
|
44
80
|
if (!closingTag)
|
|
45
81
|
return null;
|
|
@@ -51,9 +87,23 @@ export default createRule({
|
|
|
51
87
|
: 'DBTooltip';
|
|
52
88
|
return fixer.insertTextBefore(closingTag, `\n <${tooltipName}>Describe action</${tooltipName}>`);
|
|
53
89
|
}
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
});
|
|
56
105
|
}
|
|
57
106
|
};
|
|
107
|
+
return defineTemplateBodyVisitor(context, { VElement: checkButton, Element: checkButton }, { JSXElement: checkButton });
|
|
58
108
|
}
|
|
59
|
-
}
|
|
109
|
+
};
|