@db-ux/core-eslint-plugin 4.6.1 → 4.7.0-tabs-34782eb

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 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: componentName, attribute }
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 angularVisitors = createAngularVisitors(context, comp, angularHandler);
39
- if (angularVisitors)
40
- return angularVisitors;
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 angularVisitors = createAngularVisitors(context, comp, angularHandler);
46
- if (angularVisitors)
47
- return angularVisitors;
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 angularVisitors = createAngularVisitors(context, comp, angularHandler);
46
- if (angularVisitors)
47
- return angularVisitors;
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 angularVisitors = createAngularVisitors(context, comp, angularHandler);
51
- if (angularVisitors)
52
- return angularVisitors;
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));
@@ -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;
@@ -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
- if (a.key.name === 'bind' && a.key.argument?.name === attrName)
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 (a.key.name === attrName ||
30
- a.key.name === kebabAttrName ||
31
- a.key.name === `:${attrName}` ||
32
- a.key.name === `:${kebabAttrName}`);
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
- // For DB components, convert DBComponentName -> db-component-name
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.6.1",
3
+ "version": "4.7.0-tabs-34782eb",
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": "^9.0.0 || ^10.0.0"
37
+ "eslint": ">=9.0.0"
38
38
  },
39
39
  "dependencies": {
40
40
  "@angular-eslint/utils": "21.3.1",
41
- "@typescript-eslint/utils": "^8.58.2"
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.58.2",
48
- "@typescript-eslint/rule-tester": "8.58.2",
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.3",
50
+ "vitest": "4.1.5",
51
51
  "vue-eslint-parser": "10.4.0"
52
52
  },
53
53
  "publishConfig": {