@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,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.FORM_LABEL_REQUIRED]: string;
|
|
11
|
+
};
|
|
12
|
+
schema: never[];
|
|
13
|
+
};
|
|
14
|
+
create(context: any): any;
|
|
15
|
+
};
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
2
|
+
import { MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
3
|
+
const FORM_COMPONENTS = [
|
|
4
|
+
'DBInput',
|
|
5
|
+
'DBTextarea',
|
|
6
|
+
'DBSelect',
|
|
7
|
+
'DBCustomSelect',
|
|
8
|
+
'DBCheckbox',
|
|
9
|
+
'DBRadio',
|
|
10
|
+
'DBSwitch'
|
|
11
|
+
];
|
|
12
|
+
const COMPONENTS_WITH_CHILDREN_LABEL = ['DBCheckbox', 'DBRadio', 'DBSwitch'];
|
|
13
|
+
export default {
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: {
|
|
17
|
+
description: 'Ensure form components have a label attribute for accessibility',
|
|
18
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#form-label-required'
|
|
19
|
+
},
|
|
20
|
+
messages: {
|
|
21
|
+
[MESSAGE_IDS.FORM_LABEL_REQUIRED]: MESSAGES.FORM_LABEL_REQUIRED
|
|
22
|
+
},
|
|
23
|
+
schema: []
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
const checkFormComponent = (node) => {
|
|
27
|
+
const openingElement = node.openingElement || node;
|
|
28
|
+
const component = FORM_COMPONENTS.find((comp) => isDBComponent(openingElement, comp));
|
|
29
|
+
if (!component)
|
|
30
|
+
return;
|
|
31
|
+
const label = getAttributeValue(openingElement, 'label');
|
|
32
|
+
const hasChildren = node.children?.some((child) => (child.type === 'JSXText' && child.value.trim() !== '') ||
|
|
33
|
+
(child.type === 'VText' && child.value.trim() !== ''));
|
|
34
|
+
const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.includes(component);
|
|
35
|
+
if (!label && !(canUseChildren && hasChildren)) {
|
|
36
|
+
context.report({
|
|
37
|
+
node: openingElement,
|
|
38
|
+
messageId: MESSAGE_IDS.FORM_LABEL_REQUIRED,
|
|
39
|
+
data: { component }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return defineTemplateBodyVisitor(context, { VElement: checkFormComponent, Element: checkFormComponent }, { JSXElement: checkFormComponent });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -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.FORM_VALIDATION_MESSAGE_REQUIRED]: string;
|
|
11
|
+
};
|
|
12
|
+
schema: never[];
|
|
13
|
+
};
|
|
14
|
+
create(context: any): any;
|
|
15
|
+
};
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
2
|
+
import { MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
3
|
+
const FORM_COMPONENTS = [
|
|
4
|
+
'DBInput',
|
|
5
|
+
'DBTextarea',
|
|
6
|
+
'DBSelect',
|
|
7
|
+
'DBCustomSelect',
|
|
8
|
+
'DBCheckbox'
|
|
9
|
+
];
|
|
10
|
+
export default {
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Ensure form components with validation have invalidMessage',
|
|
15
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#form-validation-message-required'
|
|
16
|
+
},
|
|
17
|
+
messages: {
|
|
18
|
+
[MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED]: MESSAGES.FORM_VALIDATION_MESSAGE_REQUIRED
|
|
19
|
+
},
|
|
20
|
+
schema: []
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const checkFormComponent = (node) => {
|
|
24
|
+
const openingElement = node.openingElement || node;
|
|
25
|
+
const component = FORM_COMPONENTS.find((comp) => isDBComponent(openingElement, comp));
|
|
26
|
+
if (!component)
|
|
27
|
+
return;
|
|
28
|
+
const invalidMessage = getAttributeValue(openingElement, 'invalidMessage');
|
|
29
|
+
if (invalidMessage)
|
|
30
|
+
return;
|
|
31
|
+
const required = getAttributeValue(openingElement, 'required');
|
|
32
|
+
if (required) {
|
|
33
|
+
context.report({
|
|
34
|
+
node: openingElement,
|
|
35
|
+
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
36
|
+
data: { component, attribute: 'required' }
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (component === 'DBInput' || component === 'DBTextarea') {
|
|
41
|
+
const maxLength = getAttributeValue(openingElement, 'maxLength');
|
|
42
|
+
const minLength = getAttributeValue(openingElement, 'minLength');
|
|
43
|
+
if (maxLength) {
|
|
44
|
+
context.report({
|
|
45
|
+
node: openingElement,
|
|
46
|
+
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
47
|
+
data: { component, attribute: 'maxLength' }
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (minLength) {
|
|
52
|
+
context.report({
|
|
53
|
+
node: openingElement,
|
|
54
|
+
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
55
|
+
data: { component, attribute: 'minLength' }
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (component === 'DBInput') {
|
|
61
|
+
const min = getAttributeValue(openingElement, 'min');
|
|
62
|
+
const max = getAttributeValue(openingElement, 'max');
|
|
63
|
+
const pattern = getAttributeValue(openingElement, 'pattern');
|
|
64
|
+
if (min) {
|
|
65
|
+
context.report({
|
|
66
|
+
node: openingElement,
|
|
67
|
+
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
68
|
+
data: { component, attribute: 'min' }
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (max) {
|
|
73
|
+
context.report({
|
|
74
|
+
node: openingElement,
|
|
75
|
+
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
76
|
+
data: { component, attribute: 'max' }
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (pattern) {
|
|
81
|
+
context.report({
|
|
82
|
+
node: openingElement,
|
|
83
|
+
messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
|
|
84
|
+
data: { component, attribute: 'pattern' }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return defineTemplateBodyVisitor(context, { VElement: checkFormComponent, Element: checkFormComponent }, { JSXElement: checkFormComponent });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -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.HEADER_MISSING_BURGER_MENU_LABEL]: string;
|
|
11
|
+
};
|
|
12
|
+
schema: never[];
|
|
13
|
+
};
|
|
14
|
+
create(context: any): any;
|
|
15
|
+
};
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,43 @@
|
|
|
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 DBHeader has burgerMenuLabel for accessibility',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#header-burger-menu-label-required'
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
[MESSAGE_IDS.HEADER_MISSING_BURGER_MENU_LABEL]: MESSAGES.HEADER_MISSING_BURGER_MENU_LABEL
|
|
12
|
+
},
|
|
13
|
+
schema: []
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
const angularHandler = (node, parserServices) => {
|
|
17
|
+
const burgerMenuLabel = getAttributeValue(node, 'burgerMenuLabel');
|
|
18
|
+
if (!burgerMenuLabel) {
|
|
19
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
20
|
+
context.report({
|
|
21
|
+
loc,
|
|
22
|
+
messageId: MESSAGE_IDS.HEADER_MISSING_BURGER_MENU_LABEL
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBHeader, angularHandler);
|
|
27
|
+
if (angularVisitors)
|
|
28
|
+
return angularVisitors;
|
|
29
|
+
const checkHeader = (node) => {
|
|
30
|
+
const openingElement = node.openingElement || node;
|
|
31
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBHeader))
|
|
32
|
+
return;
|
|
33
|
+
const burgerMenuLabel = getAttributeValue(openingElement, 'burgerMenuLabel');
|
|
34
|
+
if (!burgerMenuLabel) {
|
|
35
|
+
context.report({
|
|
36
|
+
node: openingElement,
|
|
37
|
+
messageId: MESSAGE_IDS.HEADER_MISSING_BURGER_MENU_LABEL
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return defineTemplateBodyVisitor(context, { VElement: checkHeader, Element: checkHeader }, { JSXElement: checkHeader });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -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.ICON_PREFER_ATTRIBUTE]: string;
|
|
12
|
+
};
|
|
13
|
+
schema: never[];
|
|
14
|
+
};
|
|
15
|
+
create(context: any): any;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
const COMPONENTS_WITH_ICON_ATTR = [
|
|
4
|
+
'DBInput',
|
|
5
|
+
'DBBrand',
|
|
6
|
+
'DBButton',
|
|
7
|
+
'DBCustomSelectListItem',
|
|
8
|
+
'DBCustomSelect',
|
|
9
|
+
'DBInfotext',
|
|
10
|
+
'DBLink',
|
|
11
|
+
'DBNavigationItem',
|
|
12
|
+
'DBNotification',
|
|
13
|
+
'DBSelect',
|
|
14
|
+
'DBSwitch',
|
|
15
|
+
'DBTabItem',
|
|
16
|
+
'DBTag'
|
|
17
|
+
];
|
|
18
|
+
export default {
|
|
19
|
+
meta: {
|
|
20
|
+
type: 'suggestion',
|
|
21
|
+
docs: {
|
|
22
|
+
description: 'Prefer icon attribute over DBIcon child component',
|
|
23
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#prefer-icon-attribute'
|
|
24
|
+
},
|
|
25
|
+
fixable: 'code',
|
|
26
|
+
messages: {
|
|
27
|
+
[MESSAGE_IDS.ICON_PREFER_ATTRIBUTE]: MESSAGES.ICON_PREFER_ATTRIBUTE
|
|
28
|
+
},
|
|
29
|
+
schema: []
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
const checkComponent = (node) => {
|
|
33
|
+
const openingElement = node.openingElement || node;
|
|
34
|
+
const component = COMPONENTS_WITH_ICON_ATTR.find((comp) => isDBComponent(openingElement, comp));
|
|
35
|
+
if (!component)
|
|
36
|
+
return;
|
|
37
|
+
const iconChild = node.children?.find((child) => (child.type === 'JSXElement' ||
|
|
38
|
+
child.type === 'VElement') &&
|
|
39
|
+
isDBComponent(child.openingElement || child, 'DBIcon'));
|
|
40
|
+
if (iconChild) {
|
|
41
|
+
const iconChildOpening = iconChild.openingElement || iconChild;
|
|
42
|
+
const iconValue = getAttributeValue(iconChildOpening, 'icon');
|
|
43
|
+
context.report({
|
|
44
|
+
node: iconChild,
|
|
45
|
+
messageId: MESSAGE_IDS.ICON_PREFER_ATTRIBUTE,
|
|
46
|
+
data: { component },
|
|
47
|
+
fix(fixer) {
|
|
48
|
+
if (!iconValue || typeof iconValue !== 'string')
|
|
49
|
+
return null;
|
|
50
|
+
const fixes = [];
|
|
51
|
+
fixes.push(fixer.remove(iconChild));
|
|
52
|
+
if (node.openingElement) {
|
|
53
|
+
// JSX
|
|
54
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
55
|
+
const insertPos = lastAttr
|
|
56
|
+
? lastAttr.range[1]
|
|
57
|
+
: openingElement.name.range[1];
|
|
58
|
+
fixes.push(fixer.insertTextAfterRange([insertPos, insertPos], ` icon="${iconValue}"`));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Vue
|
|
62
|
+
const attrs = openingElement.startTag.attributes;
|
|
63
|
+
if (attrs.length > 0) {
|
|
64
|
+
const lastAttr = attrs[attrs.length - 1];
|
|
65
|
+
const insertPos = lastAttr.range[1];
|
|
66
|
+
fixes.push(fixer.insertTextAfterRange([insertPos, insertPos], ` icon="${iconValue}"`));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const insertPos = openingElement.startTag.range[0] +
|
|
70
|
+
1 +
|
|
71
|
+
openingElement.rawName.length;
|
|
72
|
+
fixes.push(fixer.insertTextAfterRange([insertPos, insertPos], ` icon="${iconValue}"`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return fixes;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return defineTemplateBodyVisitor(context, { VElement: checkComponent, Element: checkComponent }, { JSXElement: checkComponent });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -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
|
+
messages: {
|
|
10
|
+
[MESSAGE_IDS.INPUT_FILE_MISSING_ACCEPT]: string;
|
|
11
|
+
[MESSAGE_IDS.INPUT_INVALID_MULTIPLE]: string;
|
|
12
|
+
[MESSAGE_IDS.INPUT_INVALID_ACCEPT]: string;
|
|
13
|
+
};
|
|
14
|
+
schema: never[];
|
|
15
|
+
};
|
|
16
|
+
create(context: any): any;
|
|
17
|
+
};
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { 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 DBInput file type has accept and validate file-only attributes',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#input-file-type-validation'
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
[MESSAGE_IDS.INPUT_FILE_MISSING_ACCEPT]: MESSAGES.INPUT_FILE_MISSING_ACCEPT,
|
|
12
|
+
[MESSAGE_IDS.INPUT_INVALID_MULTIPLE]: MESSAGES.INPUT_INVALID_MULTIPLE,
|
|
13
|
+
[MESSAGE_IDS.INPUT_INVALID_ACCEPT]: MESSAGES.INPUT_INVALID_ACCEPT
|
|
14
|
+
},
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const checkInput = (node) => {
|
|
19
|
+
const openingElement = node.openingElement || node;
|
|
20
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBInput))
|
|
21
|
+
return;
|
|
22
|
+
const type = getAttributeValue(openingElement, 'type');
|
|
23
|
+
const accept = getAttributeValue(openingElement, 'accept');
|
|
24
|
+
const multiple = getAttributeValue(openingElement, 'multiple');
|
|
25
|
+
if (type === 'file') {
|
|
26
|
+
if (!accept) {
|
|
27
|
+
context.report({
|
|
28
|
+
node: openingElement,
|
|
29
|
+
messageId: MESSAGE_IDS.INPUT_FILE_MISSING_ACCEPT
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (multiple) {
|
|
35
|
+
context.report({
|
|
36
|
+
node: openingElement,
|
|
37
|
+
messageId: MESSAGE_IDS.INPUT_INVALID_MULTIPLE
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (accept) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: openingElement,
|
|
43
|
+
messageId: MESSAGE_IDS.INPUT_INVALID_ACCEPT
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return defineTemplateBodyVisitor(context, { VElement: checkInput, Element: checkInput }, { JSXElement: checkInput });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -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.INPUT_TYPE_REQUIRED]: string;
|
|
12
|
+
};
|
|
13
|
+
schema: never[];
|
|
14
|
+
};
|
|
15
|
+
create(context: any): any;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,54 @@
|
|
|
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: 'suggestion',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Ensure DBInput has type attribute for better developer experience',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#input-type-required'
|
|
9
|
+
},
|
|
10
|
+
fixable: 'code',
|
|
11
|
+
messages: {
|
|
12
|
+
[MESSAGE_IDS.INPUT_TYPE_REQUIRED]: MESSAGES.INPUT_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.INPUT_TYPE_REQUIRED,
|
|
24
|
+
fix(fixer) {
|
|
25
|
+
const fixData = createAngularFix(context, node, ' type="text"');
|
|
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.DBInput, angularHandler);
|
|
34
|
+
if (angularVisitors)
|
|
35
|
+
return angularVisitors;
|
|
36
|
+
const checkInput = (node) => {
|
|
37
|
+
const openingElement = node.openingElement || node;
|
|
38
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBInput))
|
|
39
|
+
return;
|
|
40
|
+
const type = getAttributeValue(openingElement, 'type');
|
|
41
|
+
if (!type) {
|
|
42
|
+
context.report({
|
|
43
|
+
node: openingElement,
|
|
44
|
+
messageId: MESSAGE_IDS.INPUT_TYPE_REQUIRED,
|
|
45
|
+
fix(fixer) {
|
|
46
|
+
const fixData = createJsxVueFix(node, openingElement, ' type="text"');
|
|
47
|
+
return fixer.insertTextAfterRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return defineTemplateBodyVisitor(context, { VElement: checkInput, Element: checkInput }, { JSXElement: checkInput });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
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.LINK_MISSING_TARGET_BLANK]: string;
|
|
12
|
+
[MESSAGE_IDS.LINK_MISSING_REFERRER_POLICY]: string;
|
|
13
|
+
[MESSAGE_IDS.LINK_MISSING_CONTENT_EXTERNAL]: string;
|
|
14
|
+
};
|
|
15
|
+
schema: never[];
|
|
16
|
+
};
|
|
17
|
+
create(context: any): any;
|
|
18
|
+
};
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { COMPONENTS, MESSAGES, MESSAGE_IDS } from '../../shared/constants.js';
|
|
2
|
+
import { createAngularFix, createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, isDBComponent } from '../../shared/utils.js';
|
|
3
|
+
export default {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Ensure external links have proper security attributes',
|
|
8
|
+
url: 'https://github.com/db-ux-design-system/core-web/blob/main/packages/eslint-plugin/README.md#link-external-security'
|
|
9
|
+
},
|
|
10
|
+
fixable: 'code',
|
|
11
|
+
messages: {
|
|
12
|
+
[MESSAGE_IDS.LINK_MISSING_TARGET_BLANK]: MESSAGES.LINK_MISSING_TARGET_BLANK,
|
|
13
|
+
[MESSAGE_IDS.LINK_MISSING_REFERRER_POLICY]: MESSAGES.LINK_MISSING_REFERRER_POLICY,
|
|
14
|
+
[MESSAGE_IDS.LINK_MISSING_CONTENT_EXTERNAL]: MESSAGES.LINK_MISSING_CONTENT_EXTERNAL
|
|
15
|
+
},
|
|
16
|
+
schema: []
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const angularHandler = (node, parserServices) => {
|
|
20
|
+
const content = getAttributeValue(node, 'content');
|
|
21
|
+
const target = getAttributeValue(node, 'target');
|
|
22
|
+
const referrerPolicy = getAttributeValue(node, 'referrerPolicy');
|
|
23
|
+
const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
|
|
24
|
+
if (content === 'external' && target !== '_blank') {
|
|
25
|
+
context.report({
|
|
26
|
+
loc,
|
|
27
|
+
messageId: MESSAGE_IDS.LINK_MISSING_TARGET_BLANK,
|
|
28
|
+
fix(fixer) {
|
|
29
|
+
const fixData = createAngularFix(context, node, ' target="_blank"');
|
|
30
|
+
if (!fixData)
|
|
31
|
+
return null;
|
|
32
|
+
return fixer.insertTextBeforeRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (content === 'external' && !referrerPolicy) {
|
|
37
|
+
context.report({
|
|
38
|
+
loc,
|
|
39
|
+
messageId: MESSAGE_IDS.LINK_MISSING_REFERRER_POLICY,
|
|
40
|
+
fix(fixer) {
|
|
41
|
+
const fixData = createAngularFix(context, node, ' referrerPolicy="no-referrer"');
|
|
42
|
+
if (!fixData)
|
|
43
|
+
return null;
|
|
44
|
+
return fixer.insertTextBeforeRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (target === '_blank' && content !== 'external') {
|
|
49
|
+
context.report({
|
|
50
|
+
loc,
|
|
51
|
+
messageId: MESSAGE_IDS.LINK_MISSING_CONTENT_EXTERNAL,
|
|
52
|
+
fix(fixer) {
|
|
53
|
+
const fixData = createAngularFix(context, node, ' content="external"');
|
|
54
|
+
if (!fixData)
|
|
55
|
+
return null;
|
|
56
|
+
return fixer.insertTextBeforeRange([fixData.insertPos, fixData.insertPos], fixData.attributeText);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const angularVisitors = createAngularVisitors(context, COMPONENTS.DBLink, angularHandler);
|
|
62
|
+
if (angularVisitors)
|
|
63
|
+
return angularVisitors;
|
|
64
|
+
const checkLink = (node) => {
|
|
65
|
+
const openingElement = node.openingElement || node;
|
|
66
|
+
if (!isDBComponent(openingElement, COMPONENTS.DBLink))
|
|
67
|
+
return;
|
|
68
|
+
const content = getAttributeValue(openingElement, 'content');
|
|
69
|
+
const target = getAttributeValue(openingElement, 'target');
|
|
70
|
+
const referrerPolicy = getAttributeValue(openingElement, 'referrerPolicy');
|
|
71
|
+
if (content === 'external') {
|
|
72
|
+
if (target !== '_blank') {
|
|
73
|
+
context.report({
|
|
74
|
+
node: openingElement,
|
|
75
|
+
messageId: MESSAGE_IDS.LINK_MISSING_TARGET_BLANK,
|
|
76
|
+
fix(fixer) {
|
|
77
|
+
if (node.openingElement) {
|
|
78
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
79
|
+
const insertPos = lastAttr
|
|
80
|
+
? lastAttr.range[1]
|
|
81
|
+
: openingElement.name.range[1];
|
|
82
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' target="_blank"');
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const attrs = openingElement.startTag.attributes;
|
|
86
|
+
if (attrs.length > 0) {
|
|
87
|
+
return fixer.insertTextAfterRange([
|
|
88
|
+
attrs[attrs.length - 1].range[1],
|
|
89
|
+
attrs[attrs.length - 1].range[1]
|
|
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
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (!referrerPolicy) {
|
|
103
|
+
context.report({
|
|
104
|
+
node: openingElement,
|
|
105
|
+
messageId: MESSAGE_IDS.LINK_MISSING_REFERRER_POLICY,
|
|
106
|
+
fix(fixer) {
|
|
107
|
+
if (node.openingElement) {
|
|
108
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
109
|
+
const insertPos = lastAttr
|
|
110
|
+
? lastAttr.range[1]
|
|
111
|
+
: openingElement.name.range[1];
|
|
112
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' referrerPolicy="no-referrer"');
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const attrs = openingElement.startTag.attributes;
|
|
116
|
+
if (attrs.length > 0) {
|
|
117
|
+
return fixer.insertTextAfterRange([
|
|
118
|
+
attrs[attrs.length - 1].range[1],
|
|
119
|
+
attrs[attrs.length - 1].range[1]
|
|
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
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (target === '_blank' && content !== 'external') {
|
|
134
|
+
context.report({
|
|
135
|
+
node: openingElement,
|
|
136
|
+
messageId: MESSAGE_IDS.LINK_MISSING_CONTENT_EXTERNAL,
|
|
137
|
+
fix(fixer) {
|
|
138
|
+
if (node.openingElement) {
|
|
139
|
+
const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
|
|
140
|
+
const insertPos = lastAttr
|
|
141
|
+
? lastAttr.range[1]
|
|
142
|
+
: openingElement.name.range[1];
|
|
143
|
+
return fixer.insertTextAfterRange([insertPos, insertPos], ' content="external"');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const attrs = openingElement.startTag.attributes;
|
|
147
|
+
if (attrs.length > 0) {
|
|
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
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
return defineTemplateBodyVisitor(context, { VElement: checkLink, Element: checkLink }, { JSXElement: checkLink });
|
|
165
|
+
}
|
|
166
|
+
};
|