@db-ux/core-eslint-plugin 4.12.0 → 4.13.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @db-ux/core-eslint-plugin
2
2
 
3
+ ## 4.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat: enable Invoker Commands (`command`- and `commandfor`-HTML-attributes) for DBButton - [see commit 00f50c3](https://github.com/db-ux-design-system/core-web/commit/00f50c3fc4508e62f2e30589c00148c54e2fc852)
8
+
9
+ ### Patch Changes
10
+
11
+ - fix: return `undefined` instead of `null` from `getAttributeValue` when attribute is missing - [see commit 5dca414](https://github.com/db-ux-design-system/core-web/commit/5dca414656a7f1a9f33b0ddd2ce4f82ec93e1028)
12
+
13
+ ## 4.12.1
14
+
15
+ _version bump_
16
+
3
17
  ## 4.12.0
4
18
 
5
19
  _version bump_
@@ -14,7 +14,7 @@ export default {
14
14
  },
15
15
  create(context) {
16
16
  const angularHandler = (node, parserServices) => {
17
- let parent = node.parent;
17
+ let { parent } = node;
18
18
  while (parent) {
19
19
  if ((parent.type === 'Element' ||
20
20
  parent.type === 'Element$1') &&
@@ -36,7 +36,7 @@ export default {
36
36
  const openingElement = node.openingElement || node;
37
37
  if (!isDBComponent(openingElement, COMPONENTS.DBAccordion))
38
38
  return;
39
- let parent = node.parent;
39
+ let { parent } = node;
40
40
  while (parent) {
41
41
  const parentOpening = parent.openingElement || parent;
42
42
  if ((parent.type === 'JSXElement' ||
@@ -7,14 +7,14 @@ function getTextContent(node) {
7
7
  return child.value.trim();
8
8
  }
9
9
  if (child.type === 'Text') {
10
- return child.value?.trim() || null;
10
+ return child.value?.trim() || undefined;
11
11
  }
12
12
  if (child.type === 'VText') {
13
- return child.value?.trim() || null;
13
+ return child.value?.trim() || undefined;
14
14
  }
15
15
  }
16
16
  }
17
- return null;
17
+ return undefined;
18
18
  }
19
19
  export default {
20
20
  meta: {
@@ -95,7 +95,7 @@ export default {
95
95
  if (textChild) {
96
96
  fixes.push(fixer.replaceText(textChild, shortText));
97
97
  if (!label) {
98
- const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
98
+ const lastAttr = openingElement.attributes.at(-1);
99
99
  const insertPos = lastAttr
100
100
  ? lastAttr.range[1]
101
101
  : openingElement.name.range[1];
@@ -19,7 +19,7 @@ export default {
19
19
  const placement = getAttributeValue(node, 'placement');
20
20
  if (placement && placement !== 'inline')
21
21
  return;
22
- let parent = node.parent;
22
+ let { parent } = node;
23
23
  while (parent) {
24
24
  if (parent.type === 'Element' || parent.type === 'Element$1') {
25
25
  const parentName = parent.name;
@@ -54,7 +54,7 @@ export default {
54
54
  const placement = getAttributeValue(openingElement, 'placement');
55
55
  if (placement && placement !== 'inline')
56
56
  return;
57
- let parent = node.parent;
57
+ let { parent } = node;
58
58
  while (parent) {
59
59
  if (parent.type === 'JSXElement' ||
60
60
  parent.type === 'VElement' ||
@@ -83,41 +83,29 @@ export default {
83
83
  if (placementAttr) {
84
84
  return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
85
85
  }
86
- else {
87
- const lastAttr = openingElement.attributes[openingElement.attributes
88
- .length - 1];
89
- const insertPos = lastAttr
90
- ? lastAttr.range[1]
91
- : openingElement.name.range[1];
92
- return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
93
- }
86
+ const lastAttr = openingElement.attributes.at(-1);
87
+ const insertPos = lastAttr
88
+ ? lastAttr.range[1]
89
+ : openingElement.name.range[1];
90
+ return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
94
91
  }
95
- else {
96
- // Vue
97
- const placementAttr = openingElement.startTag.attributes.find((a) => a.key.name === 'placement');
98
- if (placementAttr) {
99
- return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
100
- }
101
- else {
102
- const attrs = openingElement.startTag
103
- .attributes;
104
- if (attrs.length > 0) {
105
- const lastAttr = attrs[attrs.length - 1];
106
- return fixer.insertTextAfterRange([
107
- lastAttr.range[1],
108
- lastAttr.range[1]
109
- ], ' placement="corner-top-right"');
110
- }
111
- else {
112
- const insertPos = openingElement.startTag
113
- .range[0] +
114
- 1 +
115
- openingElement.rawName
116
- .length;
117
- return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
118
- }
119
- }
92
+ // Vue
93
+ const placementAttr = openingElement.startTag.attributes.find((a) => a.key.name === 'placement');
94
+ if (placementAttr) {
95
+ return fixer.replaceText(placementAttr, 'placement="corner-top-right"');
96
+ }
97
+ const attrs = openingElement.startTag.attributes;
98
+ if (attrs.length > 0) {
99
+ const lastAttr = attrs.at(-1);
100
+ return fixer.insertTextAfterRange([
101
+ lastAttr.range[1],
102
+ lastAttr.range[1]
103
+ ], ' placement="corner-top-right"');
120
104
  }
105
+ const insertPos = openingElement.startTag.range[0] +
106
+ 1 +
107
+ openingElement.rawName.length;
108
+ return fixer.insertTextAfterRange([insertPos, insertPos], ' placement="corner-top-right"');
121
109
  }
122
110
  });
123
111
  return;
@@ -17,7 +17,7 @@ export default {
17
17
  create(context) {
18
18
  const angularHandler = (node, parserServices) => {
19
19
  const noText = getAttributeValue(node, 'noText');
20
- if (noText === null)
20
+ if (noText === undefined)
21
21
  return;
22
22
  const icon = getAttributeValue(node, 'icon') ||
23
23
  getAttributeValue(node, 'iconLeading') ||
@@ -60,7 +60,7 @@ export default {
60
60
  if (!isDBComponent(openingElement, COMPONENTS.DBButton))
61
61
  return;
62
62
  const noText = getAttributeValue(openingElement, 'noText');
63
- if (noText === null)
63
+ if (noText === undefined)
64
64
  return;
65
65
  const icon = getAttributeValue(openingElement, 'icon') ||
66
66
  getAttributeValue(openingElement, 'iconLeading') ||
@@ -90,21 +90,16 @@ export default {
90
90
  : 'DBTooltip';
91
91
  return fixer.insertTextBefore(closingTag, `\n <${tooltipName}>Describe action</${tooltipName}>`);
92
92
  }
93
- else {
94
- // Vue
95
- if (!node.endTag)
96
- return null;
97
- if (!node.startTag?.range)
98
- return null;
99
- const componentName = openingElement.rawName;
100
- const tooltipName = componentName.includes('-')
101
- ? 'db-tooltip'
102
- : 'DBTooltip';
103
- return fixer.insertTextAfterRange([
104
- node.startTag.range[1],
105
- node.startTag.range[1]
106
- ], `\n <${tooltipName}>Describe action</${tooltipName}>`);
107
- }
93
+ // Vue
94
+ if (!node.endTag)
95
+ return null;
96
+ if (!node.startTag?.range)
97
+ return null;
98
+ const componentName = openingElement.rawName;
99
+ const tooltipName = componentName.includes('-')
100
+ ? 'db-tooltip'
101
+ : 'DBTooltip';
102
+ return fixer.insertTextAfterRange([node.startTag.range[1], node.startTag.range[1]], `\n <${tooltipName}>Describe action</${tooltipName}>`);
108
103
  }
109
104
  });
110
105
  }
@@ -16,9 +16,10 @@ export default {
16
16
  create(context) {
17
17
  const angularHandler = (node, parserServices) => {
18
18
  const type = getAttributeValue(node, 'type');
19
- if (type === null) {
19
+ if (type === undefined) {
20
20
  const hasClickHandler = getAttributeValue(node, '(click)');
21
- const typeValue = hasClickHandler ? 'button' : 'submit';
21
+ const hasCommandFor = getAttributeValue(node, 'commandfor');
22
+ const typeValue = hasClickHandler || hasCommandFor ? 'button' : 'submit';
22
23
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
23
24
  context.report({
24
25
  loc,
@@ -40,12 +41,15 @@ export default {
40
41
  if (!isDBComponent(openingElement, COMPONENTS.DBButton))
41
42
  return;
42
43
  const type = getAttributeValue(openingElement, 'type');
43
- if (type !== null)
44
+ if (type !== undefined)
44
45
  return;
45
46
  const hasClickHandler = getAttributeValue(openingElement, 'onClick') ||
46
47
  getAttributeValue(openingElement, '(click)') ||
47
48
  getAttributeValue(openingElement, '@click');
48
- const typeValue = hasClickHandler ? 'button' : 'submit';
49
+ const typeValue = hasClickHandler ||
50
+ getAttributeValue(openingElement, 'commandfor')
51
+ ? 'button'
52
+ : 'submit';
49
53
  context.report({
50
54
  node: openingElement,
51
55
  messageId: MESSAGE_IDS.BUTTON_TYPE_REQUIRED,
@@ -26,10 +26,11 @@ export default {
26
26
  const input = node.inputs?.find((i) => i.name === 'closeable');
27
27
  // Check for [closeable]="false" - Angular AST structure
28
28
  if (input) {
29
- const val = input.value;
30
- if (val?.type === 'LiteralPrimitive' && val.value === false)
29
+ const value_ = input.value;
30
+ if (value_?.type === 'LiteralPrimitive' &&
31
+ value_.value === false)
31
32
  return;
32
- if (val?.source === 'false')
33
+ if (value_?.source === 'false')
33
34
  return;
34
35
  }
35
36
  else {
@@ -41,7 +42,7 @@ export default {
41
42
  }
42
43
  const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
43
44
  const value = getAttributeValue(node, attribute);
44
- if (value === null || value === '') {
45
+ if (value === undefined || value === '') {
45
46
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
46
47
  context.report({
47
48
  loc,
@@ -100,7 +101,7 @@ export default {
100
101
  const componentName = openingElement.name?.name || openingElement.rawName;
101
102
  const attribute = COMPONENTS_WITH_CLOSE_BUTTON[component];
102
103
  const value = getAttributeValue(openingElement, attribute);
103
- if (value === null || value === '') {
104
+ if (value === undefined || value === '') {
104
105
  context.report({
105
106
  node: openingElement,
106
107
  messageId: MESSAGE_IDS.CLOSE_BUTTON_TEXT_REQUIRED,
@@ -32,7 +32,7 @@ export default {
32
32
  const hasChildren = node.children?.some((child) => (child.type === 'Text' && child.value.trim() !== '') ||
33
33
  child.type === 'Element' ||
34
34
  child.type === 'Element$1');
35
- if (text === null && !hasChildren) {
35
+ if (text === undefined && !hasChildren) {
36
36
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
37
37
  context.report({
38
38
  loc,
@@ -63,7 +63,7 @@ export default {
63
63
  child.type === 'VElement' ||
64
64
  child.type === 'JSXExpressionContainer' ||
65
65
  child.type === 'VExpressionContainer');
66
- if (text === null && !hasChildren) {
66
+ if (text === undefined && !hasChildren) {
67
67
  context.report({
68
68
  node: openingElement,
69
69
  messageId: MESSAGE_IDS.TEXT_OR_CHILDREN_REQUIRED,
@@ -9,7 +9,11 @@ const FORM_COMPONENTS = [
9
9
  'DBRadio',
10
10
  'DBSwitch'
11
11
  ];
12
- const COMPONENTS_WITH_CHILDREN_LABEL = ['DBCheckbox', 'DBRadio', 'DBSwitch'];
12
+ const COMPONENTS_WITH_CHILDREN_LABEL = new Set([
13
+ 'DBCheckbox',
14
+ 'DBRadio',
15
+ 'DBSwitch'
16
+ ]);
13
17
  export default {
14
18
  meta: {
15
19
  type: 'problem',
@@ -30,8 +34,8 @@ export default {
30
34
  return;
31
35
  const label = getAttributeValue(node, 'label');
32
36
  const hasChildren = node.children?.some((child) => child.type === 'Text' && child.value.trim() !== '');
33
- const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.includes(component);
34
- if ((label === null || label === '') &&
37
+ const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.has(component);
38
+ if ((label === undefined || label === '') &&
35
39
  !(canUseChildren && hasChildren)) {
36
40
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
37
41
  context.report({
@@ -60,8 +64,8 @@ export default {
60
64
  const hasChildren = node.children?.some((child) => (child.type === 'JSXText' && child.value.trim() !== '') ||
61
65
  (child.type === 'VText' && child.value.trim() !== '') ||
62
66
  (child.type === 'Text' && child.value.trim() !== ''));
63
- const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.includes(component);
64
- if ((label === null || label === '') &&
67
+ const canUseChildren = COMPONENTS_WITH_CHILDREN_LABEL.has(component);
68
+ if ((label === undefined || label === '') &&
65
69
  !(canUseChildren && hasChildren)) {
66
70
  context.report({
67
71
  node: openingElement,
@@ -26,10 +26,10 @@ export default {
26
26
  if (!component)
27
27
  return;
28
28
  const invalidMessage = getAttributeValue(node, 'invalidMessage');
29
- if (invalidMessage !== null)
29
+ if (invalidMessage !== undefined)
30
30
  return;
31
31
  const required = getAttributeValue(node, 'required');
32
- if (required !== null) {
32
+ if (required !== undefined) {
33
33
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
34
34
  context.report({
35
35
  loc,
@@ -41,7 +41,7 @@ export default {
41
41
  if (component === 'DBInput' || component === 'DBTextarea') {
42
42
  const maxLength = getAttributeValue(node, 'maxLength');
43
43
  const minLength = getAttributeValue(node, 'minLength');
44
- if (maxLength !== null) {
44
+ if (maxLength !== undefined) {
45
45
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
46
46
  context.report({
47
47
  loc,
@@ -53,7 +53,7 @@ export default {
53
53
  });
54
54
  return;
55
55
  }
56
- if (minLength !== null) {
56
+ if (minLength !== undefined) {
57
57
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
58
58
  context.report({
59
59
  loc,
@@ -70,7 +70,7 @@ export default {
70
70
  const min = getAttributeValue(node, 'min');
71
71
  const max = getAttributeValue(node, 'max');
72
72
  const pattern = getAttributeValue(node, 'pattern');
73
- if (min !== null) {
73
+ if (min !== undefined) {
74
74
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
75
75
  context.report({
76
76
  loc,
@@ -79,7 +79,7 @@ export default {
79
79
  });
80
80
  return;
81
81
  }
82
- if (max !== null) {
82
+ if (max !== undefined) {
83
83
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
84
84
  context.report({
85
85
  loc,
@@ -88,7 +88,7 @@ export default {
88
88
  });
89
89
  return;
90
90
  }
91
- if (pattern !== null) {
91
+ if (pattern !== undefined) {
92
92
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
93
93
  context.report({
94
94
  loc,
@@ -110,10 +110,10 @@ export default {
110
110
  return;
111
111
  const componentName = openingElement.name?.name || openingElement.rawName;
112
112
  const invalidMessage = getAttributeValue(openingElement, 'invalidMessage');
113
- if (invalidMessage !== null)
113
+ if (invalidMessage !== undefined)
114
114
  return;
115
115
  const required = getAttributeValue(openingElement, 'required');
116
- if (required !== null) {
116
+ if (required !== undefined) {
117
117
  context.report({
118
118
  node: openingElement,
119
119
  messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
@@ -124,7 +124,7 @@ export default {
124
124
  if (component === 'DBInput' || component === 'DBTextarea') {
125
125
  const maxLength = getAttributeValue(openingElement, 'maxLength');
126
126
  const minLength = getAttributeValue(openingElement, 'minLength');
127
- if (maxLength !== null) {
127
+ if (maxLength !== undefined) {
128
128
  context.report({
129
129
  node: openingElement,
130
130
  messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
@@ -135,7 +135,7 @@ export default {
135
135
  });
136
136
  return;
137
137
  }
138
- if (minLength !== null) {
138
+ if (minLength !== undefined) {
139
139
  context.report({
140
140
  node: openingElement,
141
141
  messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
@@ -151,7 +151,7 @@ export default {
151
151
  const min = getAttributeValue(openingElement, 'min');
152
152
  const max = getAttributeValue(openingElement, 'max');
153
153
  const pattern = getAttributeValue(openingElement, 'pattern');
154
- if (min !== null) {
154
+ if (min !== undefined) {
155
155
  context.report({
156
156
  node: openingElement,
157
157
  messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
@@ -159,7 +159,7 @@ export default {
159
159
  });
160
160
  return;
161
161
  }
162
- if (max !== null) {
162
+ if (max !== undefined) {
163
163
  context.report({
164
164
  node: openingElement,
165
165
  messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
@@ -167,7 +167,7 @@ export default {
167
167
  });
168
168
  return;
169
169
  }
170
- if (pattern !== null) {
170
+ if (pattern !== undefined) {
171
171
  context.report({
172
172
  node: openingElement,
173
173
  messageId: MESSAGE_IDS.FORM_VALIDATION_MESSAGE_REQUIRED,
@@ -15,7 +15,7 @@ export default {
15
15
  create(context) {
16
16
  const angularHandler = (node, parserServices) => {
17
17
  const burgerMenuLabel = getAttributeValue(node, 'burgerMenuLabel');
18
- if (burgerMenuLabel === null || burgerMenuLabel === '') {
18
+ if (burgerMenuLabel === undefined || burgerMenuLabel === '') {
19
19
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
20
20
  context.report({
21
21
  loc,
@@ -31,7 +31,7 @@ export default {
31
31
  if (!isDBComponent(openingElement, COMPONENTS.DBHeader))
32
32
  return;
33
33
  const burgerMenuLabel = getAttributeValue(openingElement, 'burgerMenuLabel');
34
- if (burgerMenuLabel === null || burgerMenuLabel === '') {
34
+ if (burgerMenuLabel === undefined || burgerMenuLabel === '') {
35
35
  context.report({
36
36
  node: openingElement,
37
37
  messageId: MESSAGE_IDS.HEADER_MISSING_BURGER_MENU_LABEL
@@ -79,7 +79,7 @@ export default {
79
79
  fixes.push(fixer.remove(iconChild));
80
80
  if (node.openingElement) {
81
81
  // JSX
82
- const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
82
+ const lastAttr = openingElement.attributes.at(-1);
83
83
  const insertPos = lastAttr
84
84
  ? lastAttr.range[1]
85
85
  : openingElement.name.range[1];
@@ -89,7 +89,7 @@ export default {
89
89
  // Vue
90
90
  const attrs = openingElement.startTag.attributes;
91
91
  if (attrs.length > 0) {
92
- const lastAttr = attrs[attrs.length - 1];
92
+ const lastAttr = attrs.at(-1);
93
93
  const insertPos = lastAttr.range[1];
94
94
  fixes.push(fixer.insertTextAfterRange([insertPos, insertPos], ` icon="${iconValue}"`));
95
95
  }
@@ -20,7 +20,7 @@ export default {
20
20
  const accept = getAttributeValue(node, 'accept');
21
21
  const multiple = getAttributeValue(node, 'multiple');
22
22
  if (type === 'file') {
23
- if (accept === null) {
23
+ if (accept === undefined) {
24
24
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
25
25
  context.report({
26
26
  loc,
@@ -29,14 +29,14 @@ export default {
29
29
  }
30
30
  }
31
31
  else {
32
- if (multiple !== null) {
32
+ if (multiple !== undefined) {
33
33
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
34
34
  context.report({
35
35
  loc,
36
36
  messageId: MESSAGE_IDS.INPUT_INVALID_MULTIPLE
37
37
  });
38
38
  }
39
- if (accept !== null) {
39
+ if (accept !== undefined) {
40
40
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
41
41
  context.report({
42
42
  loc,
@@ -56,7 +56,7 @@ export default {
56
56
  const accept = getAttributeValue(openingElement, 'accept');
57
57
  const multiple = getAttributeValue(openingElement, 'multiple');
58
58
  if (type === 'file') {
59
- if (accept === null) {
59
+ if (accept === undefined) {
60
60
  context.report({
61
61
  node: openingElement,
62
62
  messageId: MESSAGE_IDS.INPUT_FILE_MISSING_ACCEPT
@@ -64,13 +64,13 @@ export default {
64
64
  }
65
65
  }
66
66
  else {
67
- if (multiple !== null) {
67
+ if (multiple !== undefined) {
68
68
  context.report({
69
69
  node: openingElement,
70
70
  messageId: MESSAGE_IDS.INPUT_INVALID_MULTIPLE
71
71
  });
72
72
  }
73
- if (accept !== null) {
73
+ if (accept !== undefined) {
74
74
  context.report({
75
75
  node: openingElement,
76
76
  messageId: MESSAGE_IDS.INPUT_INVALID_ACCEPT
@@ -16,7 +16,7 @@ export default {
16
16
  create(context) {
17
17
  const angularHandler = (node, parserServices) => {
18
18
  const type = getAttributeValue(node, 'type');
19
- if (type === null) {
19
+ if (type === undefined) {
20
20
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
21
21
  context.report({
22
22
  loc,
@@ -38,7 +38,7 @@ export default {
38
38
  if (!isDBComponent(openingElement, COMPONENTS.DBInput))
39
39
  return;
40
40
  const type = getAttributeValue(openingElement, 'type');
41
- if (type === null) {
41
+ if (type === undefined) {
42
42
  context.report({
43
43
  node: openingElement,
44
44
  messageId: MESSAGE_IDS.INPUT_TYPE_REQUIRED,
@@ -75,27 +75,23 @@ export default {
75
75
  messageId: MESSAGE_IDS.LINK_MISSING_TARGET_BLANK,
76
76
  fix(fixer) {
77
77
  if (node.openingElement) {
78
- const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
78
+ const lastAttr = openingElement.attributes.at(-1);
79
79
  const insertPos = lastAttr
80
80
  ? lastAttr.range[1]
81
81
  : openingElement.name.range[1];
82
82
  return fixer.insertTextAfterRange([insertPos, insertPos], ' target="_blank"');
83
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
- }
84
+ const attrs = openingElement.startTag.attributes;
85
+ if (attrs.length > 0) {
86
+ return fixer.insertTextAfterRange([
87
+ attrs.at(-1).range[1],
88
+ attrs.at(-1).range[1]
89
+ ], ' target="_blank"');
98
90
  }
91
+ const insertPos = openingElement.startTag.range[0] +
92
+ 1 +
93
+ openingElement.rawName.length;
94
+ return fixer.insertTextAfterRange([insertPos, insertPos], ' target="_blank"');
99
95
  }
100
96
  });
101
97
  }
@@ -105,27 +101,23 @@ export default {
105
101
  messageId: MESSAGE_IDS.LINK_MISSING_REFERRER_POLICY,
106
102
  fix(fixer) {
107
103
  if (node.openingElement) {
108
- const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
104
+ const lastAttr = openingElement.attributes.at(-1);
109
105
  const insertPos = lastAttr
110
106
  ? lastAttr.range[1]
111
107
  : openingElement.name.range[1];
112
108
  return fixer.insertTextAfterRange([insertPos, insertPos], ' referrerPolicy="no-referrer"');
113
109
  }
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
- }
110
+ const attrs = openingElement.startTag.attributes;
111
+ if (attrs.length > 0) {
112
+ return fixer.insertTextAfterRange([
113
+ attrs.at(-1).range[1],
114
+ attrs.at(-1).range[1]
115
+ ], ' referrerPolicy="no-referrer"');
128
116
  }
117
+ const insertPos = openingElement.startTag.range[0] +
118
+ 1 +
119
+ openingElement.rawName.length;
120
+ return fixer.insertTextAfterRange([insertPos, insertPos], ' referrerPolicy="no-referrer"');
129
121
  }
130
122
  });
131
123
  }
@@ -136,27 +128,20 @@ export default {
136
128
  messageId: MESSAGE_IDS.LINK_MISSING_CONTENT_EXTERNAL,
137
129
  fix(fixer) {
138
130
  if (node.openingElement) {
139
- const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
131
+ const lastAttr = openingElement.attributes.at(-1);
140
132
  const insertPos = lastAttr
141
133
  ? lastAttr.range[1]
142
134
  : openingElement.name.range[1];
143
135
  return fixer.insertTextAfterRange([insertPos, insertPos], ' content="external"');
144
136
  }
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
- }
137
+ const attrs = openingElement.startTag.attributes;
138
+ if (attrs.length > 0) {
139
+ return fixer.insertTextAfterRange([attrs.at(-1).range[1], attrs.at(-1).range[1]], ' content="external"');
159
140
  }
141
+ const insertPos = openingElement.startTag.range[0] +
142
+ 1 +
143
+ openingElement.rawName.length;
144
+ return fixer.insertTextAfterRange([insertPos, insertPos], ' content="external"');
160
145
  }
161
146
  });
162
147
  }
@@ -15,7 +15,7 @@ export default {
15
15
  create(context) {
16
16
  const angularHandler = (node, parserServices) => {
17
17
  const backButtonText = getAttributeValue(node, 'backButtonText');
18
- if (backButtonText === null || backButtonText === '') {
18
+ if (backButtonText === undefined || backButtonText === '') {
19
19
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
20
20
  context.report({
21
21
  loc,
@@ -31,7 +31,7 @@ export default {
31
31
  if (!isDBComponent(openingElement, COMPONENTS.DBNavigationItem))
32
32
  return;
33
33
  const backButtonText = getAttributeValue(openingElement, 'backButtonText');
34
- if (backButtonText === null || backButtonText === '') {
34
+ if (backButtonText === undefined || backButtonText === '') {
35
35
  context.report({
36
36
  node: openingElement,
37
37
  messageId: MESSAGE_IDS.NAVIGATION_ITEM_MISSING_BACK_BUTTON_TEXT
@@ -18,7 +18,7 @@ export default {
18
18
  if (selectedType !== 'tag')
19
19
  return;
20
20
  const removeTagsTexts = getAttributeValue(node, 'removeTagsTexts');
21
- if (removeTagsTexts === null || removeTagsTexts === '') {
21
+ if (removeTagsTexts === undefined || removeTagsTexts === '') {
22
22
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
23
23
  context.report({
24
24
  loc,
@@ -37,7 +37,7 @@ export default {
37
37
  if (selectedType !== 'tag')
38
38
  return;
39
39
  const removeTagsTexts = getAttributeValue(openingElement, 'removeTagsTexts');
40
- if (removeTagsTexts === null || removeTagsTexts === '') {
40
+ if (removeTagsTexts === undefined || removeTagsTexts === '') {
41
41
  context.report({
42
42
  node: openingElement,
43
43
  messageId: MESSAGE_IDS.CUSTOM_SELECT_MISSING_REMOVE_TAGS_TEXTS
@@ -3,7 +3,7 @@ import { createAngularVisitors, defineTemplateBodyVisitor, getAttributeValue, is
3
3
  function hasOptionChildren(node) {
4
4
  return node.children?.some((child) => {
5
5
  if (child.type === 'JSXElement') {
6
- const name = child.openingElement.name;
6
+ const { name } = child.openingElement;
7
7
  if (name.type === 'JSXIdentifier') {
8
8
  return name.name === 'option';
9
9
  }
@@ -30,7 +30,7 @@ export default {
30
30
  const angularHandler = (node, parserServices) => {
31
31
  const options = getAttributeValue(node, 'options');
32
32
  const hasChildren = hasOptionChildren(node);
33
- if (options === null && !hasChildren) {
33
+ if (options === undefined && !hasChildren) {
34
34
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
35
35
  context.report({
36
36
  loc,
@@ -47,7 +47,7 @@ export default {
47
47
  return;
48
48
  const options = getAttributeValue(openingElement, 'options');
49
49
  const hasChildren = hasOptionChildren(node);
50
- if (options === null && !hasChildren) {
50
+ if (options === undefined && !hasChildren) {
51
51
  context.report({
52
52
  node: openingElement,
53
53
  messageId: MESSAGE_IDS.SELECT_MISSING_OPTIONS
@@ -18,7 +18,7 @@ export default {
18
18
  if (behavior !== 'removable')
19
19
  return;
20
20
  const removeButton = getAttributeValue(node, 'removeButton');
21
- if (removeButton === null || removeButton === '') {
21
+ if (removeButton === undefined || removeButton === '') {
22
22
  const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
23
23
  context.report({
24
24
  loc,
@@ -37,7 +37,7 @@ export default {
37
37
  if (behavior !== 'removable')
38
38
  return;
39
39
  const removeButton = getAttributeValue(openingElement, 'removeButton');
40
- if (removeButton === null || removeButton === '') {
40
+ if (removeButton === undefined || removeButton === '') {
41
41
  context.report({
42
42
  node: openingElement,
43
43
  messageId: MESSAGE_IDS.TAG_REMOVABLE_REMOVE_BUTTON_REQUIRED
@@ -14,8 +14,8 @@ function hasInteractiveChild(node) {
14
14
  ? name.name
15
15
  : null;
16
16
  if (tagName &&
17
- INTERACTIVE_ELEMENTS.some((el) => tagName === el ||
18
- tagName === el.toLowerCase().replace('db', 'db-'))) {
17
+ INTERACTIVE_ELEMENTS.some((element) => tagName === element ||
18
+ tagName === element.toLowerCase().replace('db', 'db-'))) {
19
19
  return true;
20
20
  }
21
21
  return hasInteractiveChild(child);
@@ -9,12 +9,12 @@ function isInteractiveElement(node) {
9
9
  if (!tagName)
10
10
  return false;
11
11
  const normalizedTag = tagName.toLowerCase();
12
- return INTERACTIVE_ELEMENTS.some((el) => {
13
- const elLower = el.toLowerCase();
14
- const kebabCase = elLower.startsWith('db')
15
- ? elLower.replace('db', 'db-')
16
- : elLower;
17
- return normalizedTag === elLower || normalizedTag === kebabCase;
12
+ return INTERACTIVE_ELEMENTS.some((element) => {
13
+ const elementLower = element.toLowerCase();
14
+ const kebabCase = elementLower.startsWith('db')
15
+ ? elementLower.replace('db', 'db-')
16
+ : elementLower;
17
+ return normalizedTag === elementLower || normalizedTag === kebabCase;
18
18
  });
19
19
  }
20
20
  export default {
@@ -31,7 +31,7 @@ export default {
31
31
  },
32
32
  create(context) {
33
33
  const angularHandler = (node, parserServices) => {
34
- let parent = node.parent;
34
+ let { parent } = node;
35
35
  while (parent) {
36
36
  if ((parent.type === 'Element' ||
37
37
  parent.type === 'Element$1') &&
@@ -53,14 +53,13 @@ export default {
53
53
  const openingElement = node.openingElement || node;
54
54
  if (!isDBComponent(openingElement, 'DBTooltip'))
55
55
  return;
56
- let parent = node.parent;
56
+ let { parent } = node;
57
57
  while (parent) {
58
- if (parent.type === 'JSXElement' ||
58
+ if ((parent.type === 'JSXElement' ||
59
59
  parent.type === 'VElement' ||
60
- parent.type === 'Element') {
61
- if (isInteractiveElement(parent)) {
62
- return;
63
- }
60
+ parent.type === 'Element') &&
61
+ isInteractiveElement(parent)) {
62
+ return;
64
63
  }
65
64
  parent = parent.parent;
66
65
  }
@@ -40,14 +40,14 @@ type AngularElement = {
40
40
  children?: AngularElement[];
41
41
  };
42
42
  type ElementNode = TSESTree.JSXOpeningElement | VElement | AngularElement;
43
- export declare function getAttributeValue(node: ElementNode, attrName: string): string | boolean | null;
43
+ export declare function getAttributeValue(node: ElementNode, attrName: string): string | boolean | undefined;
44
44
  export declare function hasChildOfType(node: TSESTree.JSXElement | VElement | AngularElement, componentName: string): boolean;
45
45
  export declare function isDBComponent(node: ElementNode, componentName: string): boolean;
46
46
  export declare function defineTemplateBodyVisitor(context: any, templateVisitor: any, scriptVisitor?: any): any;
47
47
  export declare function createAngularVisitors(context: any, componentName: string, handler: (node: any, parserServices: any) => void): {
48
48
  [x: string]: (node: any) => void;
49
49
  } | null;
50
- export declare function toKebabCase(str: string): string;
50
+ export declare function toKebabCase(string_: string): string;
51
51
  export declare function getAngularComponentName(componentName: string): string;
52
52
  export declare function createAngularFix(context: any, node: any, attributeText: string): {
53
53
  insertPos: any;
@@ -18,7 +18,7 @@ export function getAttributeValue(node, attrName) {
18
18
  const input = node.inputs.find((i) => i.name === attrName || i.name === kebabAttrName);
19
19
  if (input)
20
20
  return true;
21
- return null;
21
+ return undefined;
22
22
  }
23
23
  if (isVElement(node)) {
24
24
  const attr = node.startTag.attributes.find((a) => {
@@ -42,23 +42,22 @@ export function getAttributeValue(node, attrName) {
42
42
  keyName === `:${kebabAttrName}`);
43
43
  });
44
44
  if (!attr)
45
- return null;
45
+ return undefined;
46
46
  if (!attr.value)
47
47
  return true;
48
- return attr.value.value;
48
+ return attr.value.value ?? true;
49
49
  }
50
- const variants = [attrName, `[${attrName}]`, `:${attrName}`];
51
- const attr = node.attributes.find((a) => a.type === 'JSXAttribute' &&
52
- variants.includes(a.name.name));
50
+ const variants = new Set([attrName, `[${attrName}]`, `:${attrName}`]);
51
+ const attr = node.attributes.find((a) => a.type === 'JSXAttribute' && variants.has(a.name.name));
53
52
  if (!attr)
54
- return null;
53
+ return undefined;
55
54
  if (!attr.value)
56
55
  return true;
57
56
  if (attr.value.type === 'Literal')
58
57
  return attr.value.value;
59
58
  if (attr.value.type === 'JSXExpressionContainer')
60
59
  return true;
61
- return null;
60
+ return undefined;
62
61
  }
63
62
  export function hasChildOfType(node, componentName) {
64
63
  const kebabName = getAngularComponentName(componentName);
@@ -81,9 +80,9 @@ export function hasChildOfType(node, componentName) {
81
80
  }
82
81
  return node.children.some((child) => {
83
82
  if (child.type === 'JSXElement') {
84
- const openingElement = child.openingElement;
83
+ const { openingElement } = child;
85
84
  if (openingElement.name.type === 'JSXIdentifier') {
86
- const name = openingElement.name.name;
85
+ const { name } = openingElement.name;
87
86
  return name === componentName || name === kebabName;
88
87
  }
89
88
  }
@@ -100,7 +99,7 @@ export function isDBComponent(node, componentName) {
100
99
  }
101
100
  if (node.name.type !== 'JSXIdentifier')
102
101
  return false;
103
- const name = node.name.name;
102
+ const { name } = node.name;
104
103
  return name === componentName || name === kebabName;
105
104
  }
106
105
  export function defineTemplateBodyVisitor(context, templateVisitor, scriptVisitor) {
@@ -114,7 +113,7 @@ export function defineTemplateBodyVisitor(context, templateVisitor, scriptVisito
114
113
  const angularVisitors = {};
115
114
  for (const [key, handler] of Object.entries(templateVisitor)) {
116
115
  if (key === 'VElement' || key === 'Element') {
117
- angularVisitors['Element'] = handler;
116
+ angularVisitors.Element = handler;
118
117
  }
119
118
  else {
120
119
  angularVisitors[key] = handler;
@@ -133,14 +132,16 @@ export function createAngularVisitors(context, componentName, handler) {
133
132
  return null;
134
133
  }
135
134
  const kebabName = getAngularComponentName(componentName);
136
- const wrappedHandler = (node) => handler(node, parserServices);
135
+ const wrappedHandler = (node) => {
136
+ handler(node, parserServices);
137
+ };
137
138
  return {
138
139
  [`Element[name="${kebabName}"]`]: wrappedHandler,
139
140
  [`Element[name="${componentName}"]`]: wrappedHandler
140
141
  };
141
142
  }
142
- export function toKebabCase(str) {
143
- return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
143
+ export function toKebabCase(string_) {
144
+ return string_.replaceAll(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
144
145
  }
145
146
  export function getAngularComponentName(componentName) {
146
147
  // For DB components, convert DBComponentName -> db-component-name
@@ -163,24 +164,18 @@ export function createAngularFix(context, node, attributeText) {
163
164
  export function createJsxVueFix(node, openingElement, attributeText) {
164
165
  if (node.openingElement) {
165
166
  // JSX
166
- const lastAttr = openingElement.attributes[openingElement.attributes.length - 1];
167
+ const lastAttr = openingElement.attributes.at(-1);
167
168
  const insertPos = lastAttr
168
169
  ? lastAttr.range[1]
169
170
  : openingElement.name.range[1];
170
171
  return { insertPos, attributeText };
171
172
  }
172
- else {
173
- // Vue
174
- const attrs = openingElement.startTag.attributes;
175
- if (attrs.length > 0) {
176
- const lastAttr = attrs[attrs.length - 1];
177
- return { insertPos: lastAttr.range[1], attributeText };
178
- }
179
- else {
180
- const insertPos = openingElement.startTag.range[0] +
181
- 1 +
182
- openingElement.rawName.length;
183
- return { insertPos, attributeText };
184
- }
173
+ // Vue
174
+ const attrs = openingElement.startTag.attributes;
175
+ if (attrs.length > 0) {
176
+ const lastAttr = attrs.at(-1);
177
+ return { insertPos: lastAttr.range[1], attributeText };
185
178
  }
179
+ const insertPos = openingElement.startTag.range[0] + 1 + openingElement.rawName.length;
180
+ return { insertPos, attributeText };
186
181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@db-ux/core-eslint-plugin",
3
- "version": "4.12.0",
3
+ "version": "4.13.0",
4
4
  "type": "module",
5
5
  "description": "ESLint plugin for DB UX Design System components",
6
6
  "repository": {
@@ -28,18 +28,18 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@angular-eslint/utils": "21.4.0",
31
- "@typescript-eslint/utils": "^8.61.0"
31
+ "@typescript-eslint/utils": "^8.61.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@angular-eslint/bundled-angular-compiler": "21.4.0",
35
35
  "@angular-eslint/template-parser": "21.4.0",
36
36
  "@angular-eslint/test-utils": "21.4.0",
37
- "@typescript-eslint/parser": "8.61.0",
38
- "@typescript-eslint/rule-tester": "8.61.0",
37
+ "@typescript-eslint/parser": "8.61.1",
38
+ "@typescript-eslint/rule-tester": "8.61.1",
39
39
  "cpr": "3.0.1",
40
40
  "npm-run-all2": "9.0.2",
41
41
  "typescript": "5.9.3",
42
- "vitest": "4.1.8",
42
+ "vitest": "4.1.9",
43
43
  "vue-eslint-parser": "10.4.1"
44
44
  },
45
45
  "publishConfig": {