Skip to content

Commit 55a925b

Browse files
authored
dropdown-list-item-interactive codemod improvements (#3303)
1 parent df99f7f commit 55a925b

File tree

6 files changed

+122
-106
lines changed

6 files changed

+122
-106
lines changed

.changeset/kind-rooms-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashicorp/design-system-codemods": patch
3+
---
4+
5+
`dropdown-list-item-interactive` - Reworked codemod to support usage in complex conditionals, and values with concatenated handlebars values.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
<Hds::Dropdown as |dd|>
6+
<dd.Interactive @href="#" @text="Edit {{this.name}}" />
7+
</Hds::Dropdown>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
<Hds::Dropdown as |dd|>
6+
<dd.Interactive @href="#">Edit {{this.name}}</dd.Interactive>
7+
</Hds::Dropdown>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
<Hds::Dropdown as |dd|>
6+
{{#if this.showError}}
7+
<dd.Interactive
8+
@href="#"
9+
@text="Error State"
10+
@icon="alert-triangle-fill"
11+
data-test-button="error-state"
12+
/>
13+
{{else if this.showWarning}}
14+
<dd.Interactive
15+
@href="#"
16+
@text="Warning State"
17+
@icon="info-fill"
18+
data-test-button="warning-state"
19+
/>
20+
{{else}}
21+
<dd.Interactive
22+
@href="#"
23+
@text="Default State"
24+
data-test-button="default-state"
25+
/>
26+
{{/if}}
27+
</Hds::Dropdown>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: MPL-2.0
4+
}}
5+
<Hds::Dropdown as |dd|>
6+
{{#if this.showError}}
7+
<dd.Interactive @href="#" @icon="alert-triangle-fill" data-test-button="error-state">Error State</dd.Interactive>
8+
{{else if this.showWarning}}
9+
<dd.Interactive @href="#" @icon="info-fill" data-test-button="warning-state">Warning State</dd.Interactive>
10+
{{else}}
11+
<dd.Interactive @href="#" data-test-button="default-state">Default State</dd.Interactive>
12+
{{/if}}
13+
</Hds::Dropdown>

packages/codemods/transforms/v4/dropdown-list-item-interactive/index.js

Lines changed: 63 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -5,121 +5,78 @@
55

66
const CODEMOD_ANALYSIS = process.env.CODEMOD_ANALYSIS;
77

8-
function processChildren(children, asPrefix, b) {
9-
let hasUpdatedChildren = false;
10-
let processedChildren = [];
11-
12-
children.forEach((child) => {
13-
let updatedChild;
14-
let isProcessed = false;
15-
16-
// Check if the child is an ElementNode with the specified tag
17-
if (child.type === 'ElementNode' && child.tag === `${asPrefix}.Interactive`) {
18-
const textAttr = child.attributes.find((a) => a.name === '@text');
19-
20-
if (textAttr) {
21-
const childOutputAttributes = child.attributes.filter((a) => a.name !== '@text');
22-
23-
const isHandlebarsAttr = textAttr.value.type === 'MustacheStatement';
24-
25-
let children = [];
26-
27-
// Handle different types of MustacheStatement values
28-
if (isHandlebarsAttr) {
29-
if (textAttr.value.path.type === 'NumberLiteral') {
30-
children = [b.mustache(b.number(textAttr.value.path.value))];
31-
} else if (textAttr.value.path.type === 'StringLiteral') {
32-
children = [b.mustache(b.string(textAttr.value.path.value))];
33-
} else {
34-
children = [
35-
b.mustache(
36-
textAttr.value.path.original,
37-
[...textAttr.value.params],
38-
textAttr.value.hash
39-
),
40-
];
41-
}
42-
} else {
43-
children = [b.text(textAttr.value.chars)];
44-
}
45-
46-
// Create a new element with the updated children and attributes
47-
updatedChild = b.element(
48-
{ name: child.tag, selfClosing: false },
49-
{
50-
children,
51-
attrs: childOutputAttributes,
52-
modifiers: child.modifiers,
53-
blockParams: child.blockParams,
54-
}
55-
);
56-
57-
isProcessed = true;
58-
} else {
59-
updatedChild = child;
60-
}
61-
} else if (child.type === 'BlockStatement') {
62-
// Recursively process children of BlockStatement nodes
63-
const { hasUpdatedChildren: nestedHasUpdated, processedChildren: nestedProcessed } =
64-
processChildren(child.program.body, asPrefix, b);
65-
66-
if (nestedHasUpdated) {
67-
updatedChild = b.block(
68-
child.path,
69-
child.params,
70-
child.hash,
71-
b.program(nestedProcessed, child.program.blockParams),
72-
child.inverse
73-
);
74-
isProcessed = true;
75-
} else {
76-
updatedChild = child;
77-
}
78-
}
79-
80-
processedChildren.push(updatedChild || child);
81-
hasUpdatedChildren = hasUpdatedChildren || isProcessed;
82-
});
83-
84-
return { hasUpdatedChildren, processedChildren };
85-
}
86-
878
module.exports = function ({ source /*, path*/ }, { parse, visit }) {
889
const ast = parse(source);
8910

11+
// A stack is used to correctly handle nested <Hds::Dropdown> components
12+
const asPrefixStack = [];
13+
9014
return visit(ast, (env) => {
91-
let { builders: b } = env.syntax;
15+
const { builders: b } = env.syntax;
9216

9317
return {
94-
ElementNode(node) {
95-
// Check if the node is an Hds::Dropdown element
96-
if (node.type === 'ElementNode' && node.tag === 'Hds::Dropdown') {
97-
if (node.blockParams && node.blockParams.length > 0) {
98-
const asPrefix = node.blockParams[0];
99-
100-
// Process the children of the Hds::Dropdown element
101-
const { hasUpdatedChildren, processedChildren } = processChildren(
102-
node.children,
103-
asPrefix,
104-
b
105-
);
18+
ElementNode: {
19+
// "enter" is called before visiting the node's children
20+
enter(node) {
21+
// If we encounter a Dropdown, push its `as` parameter onto the stack
22+
if (node.tag === 'Hds::Dropdown') {
23+
if (node.blockParams && node.blockParams.length > 0) {
24+
asPrefixStack.push(node.blockParams[0]);
25+
} else {
26+
// Push a falsy value to keep the stack balanced if there's no block param
27+
asPrefixStack.push(null);
28+
}
29+
}
10630

107-
// Return the updated element if any children were updated
108-
if (hasUpdatedChildren && !CODEMOD_ANALYSIS) {
109-
return [
110-
b.element(
111-
{ name: node.tag, selfClosing: false },
112-
{
113-
attrs: node.attributes,
114-
children: processedChildren,
115-
modifiers: node.modifiers,
116-
blockParams: node.blockParams,
117-
}
118-
),
119-
];
31+
// Get the current prefix from the top of the stack
32+
const asPrefix = asPrefixStack[asPrefixStack.length - 1];
33+
34+
// If there's a prefix and this node is the one we want to transform...
35+
if (!CODEMOD_ANALYSIS && asPrefix && node.tag === `${asPrefix}.Interactive`) {
36+
const textAttr = node.attributes.find((a) => a.name === '@text');
37+
38+
if (textAttr) {
39+
const childOutputAttributes = node.attributes.filter((a) => a.name !== '@text');
40+
const attrValue = textAttr.value;
41+
let newChildren = [];
42+
43+
if (attrValue.type === 'ConcatStatement') {
44+
newChildren = attrValue.parts;
45+
} else if (attrValue.type === 'MustacheStatement') {
46+
if (attrValue.path.type === 'NumberLiteral') {
47+
newChildren = [b.mustache(b.number(attrValue.path.value))];
48+
} else if (attrValue.path.type === 'StringLiteral') {
49+
newChildren = [b.mustache(b.string(attrValue.path.value))];
50+
} else {
51+
newChildren = [
52+
b.mustache(attrValue.path.original, [...attrValue.params], attrValue.hash),
53+
];
54+
}
55+
} else {
56+
newChildren = [b.text(attrValue.chars)];
57+
}
58+
59+
// Return a new element to replace the current one
60+
// The visitor will automatically use this returned value
61+
return b.element(
62+
{ name: node.tag, selfClosing: false },
63+
{
64+
children: newChildren,
65+
attrs: childOutputAttributes,
66+
modifiers: node.modifiers,
67+
blockParams: node.blockParams,
68+
}
69+
);
12070
}
12171
}
122-
}
72+
},
73+
// "exit" is called after visiting the node's children
74+
exit(node) {
75+
// As we leave a Dropdown, pop its prefix off the stack
76+
if (node.tag === 'Hds::Dropdown') {
77+
asPrefixStack.pop();
78+
}
79+
},
12380
},
12481
};
12582
});

0 commit comments

Comments
 (0)