@db-ux/core-eslint-plugin 4.7.0-mcp-server-migrate-tool-test-2-0cf8204 → 4.7.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 +7 -0
- package/build/rules/close-button/close-button-text-required.js +58 -5
- package/build/rules/content/text-or-children-required.js +7 -3
- package/build/rules/form/form-label-required.js +7 -3
- package/build/rules/icon/prefer-icon-attribute.js +7 -4
- package/build/shared/utils.d.ts +8 -2
- package/build/shared/utils.js +30 -22
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @db-ux/core-eslint-plugin
|
|
2
2
|
|
|
3
|
+
## 4.7.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix(`DBNotification`): `close-button-text-required` rule now only requires `closeButtonText` when `closeable` attribute is set. - [see commit 9ae9216](https://github.com/db-ux-design-system/core-web/commit/9ae92161a6d07c7a7ab80a4af31777a4d35d09be):
|
|
8
|
+
- refactor(`ESLint`): optimize parsing of multiple Angular components.
|
|
9
|
+
|
|
3
10
|
## 4.6.1
|
|
4
11
|
|
|
5
12
|
_version bump_
|
|
@@ -19,10 +19,26 @@ export default {
|
|
|
19
19
|
},
|
|
20
20
|
create(context) {
|
|
21
21
|
const angularHandler = (node, parserServices) => {
|
|
22
|
-
const componentName = node.name;
|
|
23
22
|
const component = Object.keys(COMPONENTS_WITH_CLOSE_BUTTON).find((comp) => isDBComponent(node, comp));
|
|
24
23
|
if (!component)
|
|
25
24
|
return;
|
|
25
|
+
if (component === 'DBNotification') {
|
|
26
|
+
const input = node.inputs?.find((i) => i.name === 'closeable');
|
|
27
|
+
// Check for [closeable]="false" - Angular AST structure
|
|
28
|
+
if (input) {
|
|
29
|
+
const val = input.value;
|
|
30
|
+
if (val?.type === 'LiteralPrimitive' && val.value === false)
|
|
31
|
+
return;
|
|
32
|
+
if (val?.source === 'false')
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Check for plain attribute closeable (no binding)
|
|
37
|
+
const attr = node.attributes?.find((a) => a.name === 'closeable');
|
|
38
|
+
if (!attr)
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
26
42
|
const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
|
|
27
43
|
const value = getAttributeValue(node, attribute);
|
|
28
44
|
if (value === null || value === '') {
|
|
@@ -30,20 +46,57 @@ export default {
|
|
|
30
46
|
context.report({
|
|
31
47
|
loc,
|
|
32
48
|
messageId: MESSAGE_IDS.CLOSE_BUTTON_TEXT_REQUIRED,
|
|
33
|
-
data: { component:
|
|
49
|
+
data: { component: node.name, attribute }
|
|
34
50
|
});
|
|
35
51
|
}
|
|
36
52
|
};
|
|
53
|
+
const angularVisitors = {};
|
|
37
54
|
for (const comp of Object.keys(COMPONENTS_WITH_CLOSE_BUTTON)) {
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
55
|
+
const visitors = createAngularVisitors(context, comp, angularHandler);
|
|
56
|
+
if (visitors) {
|
|
57
|
+
Object.assign(angularVisitors, visitors);
|
|
58
|
+
}
|
|
41
59
|
}
|
|
60
|
+
if (Object.keys(angularVisitors).length > 0)
|
|
61
|
+
return angularVisitors;
|
|
42
62
|
const checkComponent = (node) => {
|
|
43
63
|
const openingElement = node.openingElement || node;
|
|
44
64
|
const component = Object.keys(COMPONENTS_WITH_CLOSE_BUTTON).find((comp) => isDBComponent(openingElement, comp));
|
|
45
65
|
if (!component)
|
|
46
66
|
return;
|
|
67
|
+
if (component === 'DBNotification') {
|
|
68
|
+
// React: closeable={false}
|
|
69
|
+
const closeableAttr = openingElement.attributes?.find((a) => a.type === 'JSXAttribute' && a.name.name === 'closeable');
|
|
70
|
+
if (closeableAttr?.value?.type === 'JSXExpressionContainer' &&
|
|
71
|
+
closeableAttr.value.expression?.type === 'Literal' &&
|
|
72
|
+
closeableAttr.value.expression.value === false)
|
|
73
|
+
return;
|
|
74
|
+
// Vue: key.name can be a VIdentifier object (key.name.name === 'bind')
|
|
75
|
+
// or a plain string for non-directive attributes
|
|
76
|
+
const isVueCloseableBind = (a) => {
|
|
77
|
+
const keyName = typeof a.key?.name === 'string'
|
|
78
|
+
? a.key.name
|
|
79
|
+
: a.key?.name?.name;
|
|
80
|
+
return (keyName === 'bind' &&
|
|
81
|
+
a.key?.argument?.name === 'closeable');
|
|
82
|
+
};
|
|
83
|
+
const isVueCloseableStatic = (a) => {
|
|
84
|
+
const keyName = typeof a.key?.name === 'string'
|
|
85
|
+
? a.key.name
|
|
86
|
+
: a.key?.name?.name;
|
|
87
|
+
return keyName === 'closeable';
|
|
88
|
+
};
|
|
89
|
+
const vueBindAttr = openingElement.startTag?.attributes?.find(isVueCloseableBind);
|
|
90
|
+
if (vueBindAttr &&
|
|
91
|
+
(vueBindAttr.value?.value === 'false' ||
|
|
92
|
+
vueBindAttr.value?.expression?.value === false))
|
|
93
|
+
return;
|
|
94
|
+
// Only skip if closeable attribute/binding doesn't exist
|
|
95
|
+
const hasCloseable = closeableAttr ||
|
|
96
|
+
openingElement.startTag?.attributes?.some((a) => isVueCloseableStatic(a) || isVueCloseableBind(a));
|
|
97
|
+
if (!hasCloseable)
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
47
100
|
const componentName = openingElement.name?.name || openingElement.rawName;
|
|
48
101
|
const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
|
|
49
102
|
const value = getAttributeValue(openingElement, attribute);
|
|
@@ -41,11 +41,15 @@ export default {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
const angularVisitors = {};
|
|
44
45
|
for (const comp of COMPONENTS_REQUIRING_CONTENT) {
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
46
|
+
const visitors = createAngularVisitors(context, comp, angularHandler);
|
|
47
|
+
if (visitors) {
|
|
48
|
+
Object.assign(angularVisitors, visitors);
|
|
49
|
+
}
|
|
48
50
|
}
|
|
51
|
+
if (Object.keys(angularVisitors).length > 0)
|
|
52
|
+
return angularVisitors;
|
|
49
53
|
const checkComponent = (node) => {
|
|
50
54
|
const openingElement = node.openingElement || node;
|
|
51
55
|
const component = COMPONENTS_REQUIRING_CONTENT.find((comp) => isDBComponent(openingElement, comp));
|
|
@@ -41,11 +41,15 @@ export default {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
+
const angularVisitors = {};
|
|
44
45
|
for (const comp of FORM_COMPONENTS) {
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
46
|
+
const visitors = createAngularVisitors(context, comp, angularHandler);
|
|
47
|
+
if (visitors) {
|
|
48
|
+
Object.assign(angularVisitors, visitors);
|
|
49
|
+
}
|
|
48
50
|
}
|
|
51
|
+
if (Object.keys(angularVisitors).length > 0)
|
|
52
|
+
return angularVisitors;
|
|
49
53
|
const checkFormComponent = (node) => {
|
|
50
54
|
const openingElement = node.openingElement || node;
|
|
51
55
|
const component = FORM_COMPONENTS.find((comp) => isDBComponent(openingElement, comp));
|
|
@@ -37,7 +37,6 @@ export default {
|
|
|
37
37
|
const iconChild = node.children?.find((child) => (child.type === 'Element' || child.type === 'Element$1') &&
|
|
38
38
|
isDBComponent(child, 'DBIcon'));
|
|
39
39
|
if (iconChild) {
|
|
40
|
-
const iconValue = getAttributeValue(iconChild, 'icon');
|
|
41
40
|
const loc = parserServices.convertNodeSourceSpanToLoc(iconChild.sourceSpan);
|
|
42
41
|
context.report({
|
|
43
42
|
loc,
|
|
@@ -46,11 +45,15 @@ export default {
|
|
|
46
45
|
});
|
|
47
46
|
}
|
|
48
47
|
};
|
|
48
|
+
const angularVisitors = {};
|
|
49
49
|
for (const comp of COMPONENTS_WITH_ICON_ATTR) {
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
|
|
50
|
+
const visitors = createAngularVisitors(context, comp, angularHandler);
|
|
51
|
+
if (visitors) {
|
|
52
|
+
Object.assign(angularVisitors, visitors);
|
|
53
|
+
}
|
|
53
54
|
}
|
|
55
|
+
if (Object.keys(angularVisitors).length > 0)
|
|
56
|
+
return angularVisitors;
|
|
54
57
|
const checkComponent = (node) => {
|
|
55
58
|
const openingElement = node.openingElement || node;
|
|
56
59
|
const component = COMPONENTS_WITH_ICON_ATTR.find((comp) => isDBComponent(openingElement, comp));
|
package/build/shared/utils.d.ts
CHANGED
|
@@ -4,10 +4,14 @@ type VElement = {
|
|
|
4
4
|
startTag: {
|
|
5
5
|
attributes: Array<{
|
|
6
6
|
key: {
|
|
7
|
-
name: string
|
|
8
|
-
argument?: {
|
|
7
|
+
name: string | {
|
|
9
8
|
name: string;
|
|
10
9
|
};
|
|
10
|
+
argument?: string | {
|
|
11
|
+
name: string | {
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
11
15
|
};
|
|
12
16
|
value?: {
|
|
13
17
|
value: string;
|
|
@@ -43,6 +47,8 @@ export declare function defineTemplateBodyVisitor(context: any, templateVisitor:
|
|
|
43
47
|
export declare function createAngularVisitors(context: any, componentName: string, handler: (node: any, parserServices: any) => void): {
|
|
44
48
|
[x: string]: (node: any) => void;
|
|
45
49
|
} | null;
|
|
50
|
+
export declare function toKebabCase(str: string): string;
|
|
51
|
+
export declare function getAngularComponentName(componentName: string): string;
|
|
46
52
|
export declare function createAngularFix(context: any, node: any, attributeText: string): {
|
|
47
53
|
insertPos: any;
|
|
48
54
|
attributeText: string;
|
package/build/shared/utils.js
CHANGED
|
@@ -7,9 +7,7 @@ function isAngularElement(node) {
|
|
|
7
7
|
(node.attributes || node.inputs));
|
|
8
8
|
}
|
|
9
9
|
export function getAttributeValue(node, attrName) {
|
|
10
|
-
const kebabAttrName = attrName
|
|
11
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
12
|
-
.toLowerCase();
|
|
10
|
+
const kebabAttrName = toKebabCase(attrName);
|
|
13
11
|
if (isAngularElement(node)) {
|
|
14
12
|
const attr = node.attributes.find((a) => a.name === attrName || a.name === kebabAttrName);
|
|
15
13
|
if (attr) {
|
|
@@ -24,12 +22,24 @@ export function getAttributeValue(node, attrName) {
|
|
|
24
22
|
}
|
|
25
23
|
if (isVElement(node)) {
|
|
26
24
|
const attr = node.startTag.attributes.find((a) => {
|
|
27
|
-
|
|
25
|
+
const keyName = typeof a.key.name === 'string' ? a.key.name : a.key.name?.name;
|
|
26
|
+
const argName = a.key.argument
|
|
27
|
+
? typeof a.key.argument === 'string'
|
|
28
|
+
? a.key.argument
|
|
29
|
+
: typeof a.key.argument.name === 'string'
|
|
30
|
+
? a.key.argument.name
|
|
31
|
+
: a.key.argument.name?.name
|
|
32
|
+
: undefined;
|
|
33
|
+
if (keyName === 'bind' &&
|
|
34
|
+
(argName === attrName ||
|
|
35
|
+
argName === kebabAttrName ||
|
|
36
|
+
argName === attrName.toLowerCase() ||
|
|
37
|
+
argName === kebabAttrName.toLowerCase()))
|
|
28
38
|
return true;
|
|
29
|
-
return (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
return (keyName === attrName ||
|
|
40
|
+
keyName === kebabAttrName ||
|
|
41
|
+
keyName === `:${attrName}` ||
|
|
42
|
+
keyName === `:${kebabAttrName}`);
|
|
33
43
|
});
|
|
34
44
|
if (!attr)
|
|
35
45
|
return null;
|
|
@@ -51,9 +61,7 @@ export function getAttributeValue(node, attrName) {
|
|
|
51
61
|
return null;
|
|
52
62
|
}
|
|
53
63
|
export function hasChildOfType(node, componentName) {
|
|
54
|
-
const kebabName = componentName
|
|
55
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
56
|
-
.toLowerCase();
|
|
64
|
+
const kebabName = getAngularComponentName(componentName);
|
|
57
65
|
if (isAngularElement(node)) {
|
|
58
66
|
return (node.children || []).some((child) => {
|
|
59
67
|
if (child.type === 'Element' || child.type === 'Element$1') {
|
|
@@ -83,9 +91,7 @@ export function hasChildOfType(node, componentName) {
|
|
|
83
91
|
});
|
|
84
92
|
}
|
|
85
93
|
export function isDBComponent(node, componentName) {
|
|
86
|
-
const kebabName = componentName
|
|
87
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
88
|
-
.toLowerCase();
|
|
94
|
+
const kebabName = getAngularComponentName(componentName);
|
|
89
95
|
if (isAngularElement(node)) {
|
|
90
96
|
return node.name === componentName || node.name === kebabName;
|
|
91
97
|
}
|
|
@@ -126,20 +132,22 @@ export function createAngularVisitors(context, componentName, handler) {
|
|
|
126
132
|
if (!isAngular) {
|
|
127
133
|
return null;
|
|
128
134
|
}
|
|
129
|
-
|
|
130
|
-
const kebabName = componentName.startsWith('DB')
|
|
131
|
-
? 'db-' +
|
|
132
|
-
componentName
|
|
133
|
-
.slice(2)
|
|
134
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
135
|
-
.toLowerCase()
|
|
136
|
-
: componentName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
135
|
+
const kebabName = getAngularComponentName(componentName);
|
|
137
136
|
const wrappedHandler = (node) => handler(node, parserServices);
|
|
138
137
|
return {
|
|
139
138
|
[`Element[name="${kebabName}"]`]: wrappedHandler,
|
|
140
139
|
[`Element[name="${componentName}"]`]: wrappedHandler
|
|
141
140
|
};
|
|
142
141
|
}
|
|
142
|
+
export function toKebabCase(str) {
|
|
143
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
144
|
+
}
|
|
145
|
+
export function getAngularComponentName(componentName) {
|
|
146
|
+
// For DB components, convert DBComponentName -> db-component-name
|
|
147
|
+
return componentName.startsWith('DB')
|
|
148
|
+
? 'db-' + toKebabCase(componentName.slice(2))
|
|
149
|
+
: toKebabCase(componentName);
|
|
150
|
+
}
|
|
143
151
|
export function createAngularFix(context, node, attributeText) {
|
|
144
152
|
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
145
153
|
const text = sourceCode.getText();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@db-ux/core-eslint-plugin",
|
|
3
|
-
"version": "4.7.0
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ESLint plugin for DB UX Design System components",
|
|
6
6
|
"repository": {
|
|
@@ -34,20 +34,20 @@
|
|
|
34
34
|
"test:update": "vitest run --config vitest.config.ts --update"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"eslint": "
|
|
37
|
+
"eslint": ">=9.0.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@angular-eslint/utils": "21.3.1",
|
|
41
|
-
"@typescript-eslint/utils": "^8.
|
|
41
|
+
"@typescript-eslint/utils": "^8.59.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@angular-eslint/bundled-angular-compiler": "21.3.1",
|
|
45
45
|
"@angular-eslint/template-parser": "21.3.1",
|
|
46
46
|
"@angular-eslint/test-utils": "21.3.1",
|
|
47
|
-
"@typescript-eslint/parser": "8.
|
|
48
|
-
"@typescript-eslint/rule-tester": "8.
|
|
47
|
+
"@typescript-eslint/parser": "8.59.0",
|
|
48
|
+
"@typescript-eslint/rule-tester": "8.59.0",
|
|
49
49
|
"typescript": "5.9.3",
|
|
50
|
-
"vitest": "4.1.
|
|
50
|
+
"vitest": "4.1.5",
|
|
51
51
|
"vue-eslint-parser": "10.4.0"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|