Skip to content

Commit b6071e4

Browse files
authored
chore(compiler): prevent duplicate props (#1121)
When adding props to LingoComponent and LingoAttributeComponent prevent duplicate props. Update prop value if it exists.
1 parent cd67722 commit b6071e4

3 files changed

Lines changed: 72 additions & 108 deletions

File tree

.changeset/happy-lies-hug.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@lingo.dev/_compiler": patch
3+
"lingo.dev": patch
4+
---
5+
6+
compiler: prevent duplicate props

packages/compiler/src/jsx-attribute-scope-inject.ts

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import _ from "lodash";
55
import { ModuleId } from "./_const";
66
import { getJsxElementName, getNestedJsxElements } from "./utils/jsx-element";
77
import { collectJsxAttributeScopes } from "./utils/jsx-attribute-scope";
8+
import { setJsxAttributeValue } from "./utils/jsx-attribute";
89

910
export const lingoJsxAttributeScopeInjectMutation = createCodeMutation(
1011
(payload) => {
@@ -46,51 +47,33 @@ export const lingoJsxAttributeScopeInjectMutation = createCodeMutation(
4647
);
4748

4849
// Add $fileKey prop
49-
jsxScope.node.openingElement.attributes.push(
50-
t.jsxAttribute(
51-
t.jsxIdentifier("$fileKey"),
52-
t.stringLiteral(payload.relativeFilePath),
53-
),
54-
);
50+
setJsxAttributeValue(jsxScope, "$fileKey", payload.relativeFilePath);
5551

5652
// Add $attributes prop
57-
jsxScope.node.openingElement.attributes.push(
58-
t.jsxAttribute(
59-
t.jsxIdentifier("$attributes"),
60-
t.jsxExpressionContainer(
61-
t.objectExpression(
62-
attributes.map((attributeDefinition) => {
63-
const [attribute, key = ""] = attributeDefinition.split(":");
64-
return t.objectProperty(
65-
t.stringLiteral(attribute),
66-
t.stringLiteral(key),
67-
);
68-
}),
69-
),
70-
),
53+
setJsxAttributeValue(
54+
jsxScope,
55+
"$attributes",
56+
t.objectExpression(
57+
attributes.map((attributeDefinition) => {
58+
const [attribute, key = ""] = attributeDefinition.split(":");
59+
return t.objectProperty(
60+
t.stringLiteral(attribute),
61+
t.stringLiteral(key),
62+
);
63+
}),
7164
),
7265
);
7366

7467
// // Extract $variables from original JSX scope
7568
// const $variables = getJsxVariables(originalJsxScope);
7669
// if ($variables.properties.length > 0) {
77-
// jsxScope.node.openingElement.attributes.push(
78-
// t.jsxAttribute(
79-
// t.jsxIdentifier("$variables"),
80-
// t.jsxExpressionContainer($variables),
81-
// ),
82-
// );
70+
// setJsxAttributeValue(jsxScope, "$variables", $variables);
8371
// }
8472

8573
// // Extract nested JSX elements
8674
// const $elements = getNestedJsxElements(originalJsxScope);
8775
// if ($elements.elements.length > 0) {
88-
// jsxScope.node.openingElement.attributes.push(
89-
// t.jsxAttribute(
90-
// t.jsxIdentifier("$elements"),
91-
// t.jsxExpressionContainer($elements),
92-
// ),
93-
// );
76+
// setJsxAttributeValue(jsxScope, "$elements", $elements);
9477
// }
9578

9679
if (mode === "server") {
@@ -99,18 +82,14 @@ export const lingoJsxAttributeScopeInjectMutation = createCodeMutation(
9982
exportedName: "loadDictionary",
10083
moduleName: ModuleId.ReactRSC,
10184
});
102-
jsxScope.node.openingElement.attributes.push(
103-
t.jsxAttribute(
104-
t.jsxIdentifier("$loadDictionary"),
105-
t.jsxExpressionContainer(
106-
t.arrowFunctionExpression(
107-
[t.identifier("locale")],
108-
t.callExpression(
109-
t.identifier(loadDictionaryImport.importedName),
110-
[t.identifier("locale")],
111-
),
112-
),
113-
),
85+
setJsxAttributeValue(
86+
jsxScope,
87+
"$loadDictionary",
88+
t.arrowFunctionExpression(
89+
[t.identifier("locale")],
90+
t.callExpression(t.identifier(loadDictionaryImport.importedName), [
91+
t.identifier("locale"),
92+
]),
11493
),
11594
);
11695
}

packages/compiler/src/jsx-scope-inject.ts

Lines changed: 43 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getJsxVariables } from "./utils/jsx-variables";
1212
import { getJsxFunctions } from "./utils/jsx-functions";
1313
import { getJsxExpressions } from "./utils/jsx-expressions";
1414
import { collectJsxScopes, getJsxScopeAttribute } from "./utils/jsx-scope";
15+
import { setJsxAttributeValue } from "./utils/jsx-attribute";
1516

1617
export const lingoJsxScopeInjectMutation = createCodeMutation((payload) => {
1718
const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
@@ -36,103 +37,81 @@ export const lingoJsxScopeInjectMutation = createCodeMutation((payload) => {
3637
continue;
3738
}
3839

39-
// Build new attributes array, preserving all original attributes
40-
const originalAttributes = jsxScope.node.openingElement.attributes.slice();
40+
// Create new JSXElement with original attributes
41+
const newNode = t.jsxElement(
42+
t.jsxOpeningElement(
43+
t.jsxIdentifier(lingoComponentImport.importedName),
44+
jsxScope.node.openingElement.attributes.slice(), // original attributes
45+
true, // selfClosing
46+
),
47+
null, // no closing element
48+
[], // no children
49+
true, // selfClosing
50+
);
51+
52+
// Create a NodePath wrapper for the new node to use setJsxAttributeValue
53+
const newNodePath = {
54+
node: newNode,
55+
} as any;
4156

4257
// Add $as prop
4358
const as = /^[A-Z]/.test(originalJsxElementName)
44-
? t.jsxExpressionContainer(t.identifier(originalJsxElementName))
45-
: t.stringLiteral(originalJsxElementName);
46-
originalAttributes.push(t.jsxAttribute(t.jsxIdentifier("$as"), as));
59+
? t.identifier(originalJsxElementName)
60+
: originalJsxElementName;
61+
setJsxAttributeValue(newNodePath, "$as", as);
62+
4763
// Add $fileKey prop
48-
originalAttributes.push(
49-
t.jsxAttribute(
50-
t.jsxIdentifier("$fileKey"),
51-
t.stringLiteral(payload.relativeFilePath),
52-
),
53-
);
64+
setJsxAttributeValue(newNodePath, "$fileKey", payload.relativeFilePath);
65+
5466
// Add $entryKey prop
55-
originalAttributes.push(
56-
t.jsxAttribute(
57-
t.jsxIdentifier("$entryKey"),
58-
t.stringLiteral(getJsxScopeAttribute(jsxScope)!),
59-
),
67+
setJsxAttributeValue(
68+
newNodePath,
69+
"$entryKey",
70+
getJsxScopeAttribute(jsxScope)!,
6071
);
6172

6273
// Extract $variables from original JSX scope before lingo component was inserted
6374
const $variables = getJsxVariables(jsxScope);
6475
if ($variables.properties.length > 0) {
65-
originalAttributes.push(
66-
t.jsxAttribute(
67-
t.jsxIdentifier("$variables"),
68-
t.jsxExpressionContainer($variables),
69-
),
70-
);
76+
setJsxAttributeValue(newNodePath, "$variables", $variables);
7177
}
78+
7279
// Extract nested JSX elements
7380
const $elements = getNestedJsxElements(jsxScope);
7481
if ($elements.elements.length > 0) {
75-
originalAttributes.push(
76-
t.jsxAttribute(
77-
t.jsxIdentifier("$elements"),
78-
t.jsxExpressionContainer($elements),
79-
),
80-
);
82+
setJsxAttributeValue(newNodePath, "$elements", $elements);
8183
}
84+
8285
// Extract nested functions
8386
const $functions = getJsxFunctions(jsxScope);
8487
if ($functions.properties.length > 0) {
85-
originalAttributes.push(
86-
t.jsxAttribute(
87-
t.jsxIdentifier("$functions"),
88-
t.jsxExpressionContainer($functions),
89-
),
90-
);
88+
setJsxAttributeValue(newNodePath, "$functions", $functions);
9189
}
90+
9291
// Extract expressions
9392
const $expressions = getJsxExpressions(jsxScope);
9493
if ($expressions.elements.length > 0) {
95-
originalAttributes.push(
96-
t.jsxAttribute(
97-
t.jsxIdentifier("$expressions"),
98-
t.jsxExpressionContainer($expressions),
99-
),
100-
);
94+
setJsxAttributeValue(newNodePath, "$expressions", $expressions);
10195
}
96+
10297
if (mode === "server") {
10398
// Add $loadDictionary prop
10499
const loadDictionaryImport = getOrCreateImport(payload.ast, {
105100
exportedName: "loadDictionary",
106101
moduleName: ModuleId.ReactRSC,
107102
});
108-
originalAttributes.push(
109-
t.jsxAttribute(
110-
t.jsxIdentifier("$loadDictionary"),
111-
t.jsxExpressionContainer(
112-
t.arrowFunctionExpression(
113-
[t.identifier("locale")],
114-
t.callExpression(
115-
t.identifier(loadDictionaryImport.importedName),
116-
[t.identifier("locale")],
117-
),
118-
),
119-
),
103+
setJsxAttributeValue(
104+
newNodePath,
105+
"$loadDictionary",
106+
t.arrowFunctionExpression(
107+
[t.identifier("locale")],
108+
t.callExpression(t.identifier(loadDictionaryImport.importedName), [
109+
t.identifier("locale"),
110+
]),
120111
),
121112
);
122113
}
123114

124-
// Create new JSXElement (self-closing)
125-
const newNode = t.jsxElement(
126-
t.jsxOpeningElement(
127-
t.jsxIdentifier(lingoComponentImport.importedName),
128-
originalAttributes,
129-
true, // selfClosing
130-
),
131-
null, // no closing element
132-
[], // no children
133-
true, // selfClosing
134-
);
135-
136115
jsxScope.replaceWith(newNode);
137116
}
138117

0 commit comments

Comments
 (0)