Skip to content

Commit 017d4eb

Browse files
committed
fix dynamic element and components any letter case usage
1 parent 8f17b21 commit 017d4eb

File tree

3 files changed

+142
-13
lines changed

3 files changed

+142
-13
lines changed

packages/ripple/src/compiler/phases/2-analyze/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,23 @@ const visitors = {
567567

568568
mark_control_flow_has_template(path);
569569

570+
// Store capitalized name for dynamic components/elements
571+
if (node.id.tracked) {
572+
const original_name = node.id.name;
573+
const capitalized_name = original_name.charAt(0).toUpperCase() + original_name.slice(1);
574+
node.metadata.ts_name = capitalized_name;
575+
node.metadata.original_name = original_name;
576+
577+
// Mark the binding as a dynamic component so we can capitalize it everywhere
578+
const binding = context.state.scope.get(original_name);
579+
if (binding) {
580+
if (!binding.metadata) {
581+
binding.metadata = {};
582+
}
583+
binding.metadata.is_dynamic_component = true;
584+
}
585+
}
586+
570587
if (is_dom_element) {
571588
if (node.id.name === 'head') {
572589
// head validation

packages/ripple/src/compiler/phases/3-transform/client/index.js

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,15 @@ const visitors = {
161161
if (is_reference(node, parent)) {
162162
if (context.state.to_ts) {
163163
if (node.tracked) {
164+
// Check if this identifier is used as a dynamic component/element
165+
// by checking if it has a capitalized name in metadata
166+
const binding = context.state.scope.get(node.name);
167+
if (binding?.metadata?.is_dynamic_component) {
168+
// Capitalize the identifier for TypeScript
169+
const capitalizedName = node.name.charAt(0).toUpperCase() + node.name.slice(1);
170+
const capitalizedNode = { ...node, name: capitalizedName };
171+
return b.member(capitalizedNode, b.literal('#v'), true);
172+
}
164173
return b.member(node, b.literal('#v'), true);
165174
}
166175
} else {
@@ -441,6 +450,22 @@ const visitors = {
441450
return context.next();
442451
},
443452

453+
VariableDeclarator(node, context) {
454+
// In TypeScript mode, capitalize identifiers that are used as dynamic components
455+
if (context.state.to_ts && node.id.type === 'Identifier') {
456+
const binding = context.state.scope.get(node.id.name);
457+
if (binding?.metadata?.is_dynamic_component) {
458+
const capitalizedName = node.id.name.charAt(0).toUpperCase() + node.id.name.slice(1);
459+
return {
460+
...node,
461+
id: { ...node.id, name: capitalizedName },
462+
init: node.init ? context.visit(node.init) : null
463+
};
464+
}
465+
}
466+
return context.next();
467+
},
468+
444469
FunctionDeclaration(node, context) {
445470
return visit_function(node, context);
446471
},
@@ -1397,7 +1422,8 @@ function transform_ts_child(node, context) {
13971422
// Do we need to do something special here?
13981423
state.init.push(b.stmt(visit(node.expression, { ...state })));
13991424
} else if (node.type === 'Element') {
1400-
const type = node.id.name;
1425+
// Use capitalized name for dynamic components/elements in TypeScript output
1426+
const type = node.metadata?.ts_name || node.id.name;
14011427
const children = [];
14021428
let has_children_props = false;
14031429

@@ -1463,7 +1489,17 @@ function transform_ts_child(node, context) {
14631489
}
14641490

14651491
const opening_type = b.jsx_id(type);
1466-
opening_type.loc = node.id.loc;
1492+
// Use node.id.loc if available, otherwise create a loc based on the element's position
1493+
opening_type.loc = node.id.loc || {
1494+
start: {
1495+
line: node.loc.start.line,
1496+
column: node.loc.start.column + 2, // After "<@"
1497+
},
1498+
end: {
1499+
line: node.loc.start.line,
1500+
column: node.loc.start.column + 2 + type.length,
1501+
},
1502+
};
14671503

14681504
let closing_type = undefined;
14691505

@@ -1481,9 +1517,15 @@ function transform_ts_child(node, context) {
14811517
};
14821518
}
14831519

1484-
state.init.push(
1485-
b.stmt(b.jsx_element(opening_type, attributes, children, node.selfClosing, closing_type)),
1486-
);
1520+
const jsxElement = b.jsx_element(opening_type, attributes, children, node.selfClosing, closing_type);
1521+
// Preserve metadata from Element node for mapping purposes
1522+
if (node.metadata && (node.metadata.ts_name || node.metadata.original_name)) {
1523+
jsxElement.metadata = {
1524+
ts_name: node.metadata.ts_name,
1525+
original_name: node.metadata.original_name
1526+
};
1527+
}
1528+
state.init.push(b.stmt(jsxElement));
14871529
} else if (node.type === 'IfStatement') {
14881530
const consequent_scope = context.state.scopes.get(node.consequent);
14891531
const consequent = b.block(

packages/ripple/src/compiler/phases/3-transform/segments.js

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
3030
let sourceIndex = 0;
3131
let generatedIndex = 0;
3232

33+
// Map to track capitalized names: original name -> capitalized name
34+
/** @type {Map<string, string>} */
35+
const capitalizedNames = new Map();
36+
// Reverse map: capitalized name -> original name
37+
/** @type {Map<string, string>} */
38+
const reverseCapitalizedNames = new Map();
39+
40+
// Pre-walk to collect capitalized names from JSXElement nodes (transformed AST)
41+
// These are identifiers that are used as dynamic components/elements
42+
walk(ast, null, {
43+
_(node, { next }) {
44+
// Check JSXElement nodes with metadata (preserved from Element nodes)
45+
if (node.type === 'JSXElement' && node.metadata?.ts_name && node.metadata?.original_name) {
46+
capitalizedNames.set(node.metadata.original_name, node.metadata.ts_name);
47+
reverseCapitalizedNames.set(node.metadata.ts_name, node.metadata.original_name);
48+
}
49+
next();
50+
}
51+
});
52+
3353
/**
3454
* Check if character is a word boundary (not alphanumeric or underscore)
3555
* @param {string} char
@@ -132,7 +152,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
132152
};
133153

134154
// Collect text tokens from AST nodes
135-
/** @type {string[]} */
155+
// Tokens can be either strings or objects with source/generated properties
156+
/** @type {Array<string | {source: string, generated: string}>} */
136157
const tokens = [];
137158

138159
// We have to visit everything in generated order to maintain correct indices
@@ -142,12 +163,37 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
142163
// Only collect tokens from nodes with .loc (skip synthesized nodes like children attribute)
143164
if (node.type === 'Identifier' && node.name) {
144165
if (node.loc) {
145-
tokens.push(node.name);
166+
// Check if this identifier was capitalized (reverse lookup)
167+
const originalName = reverseCapitalizedNames.get(node.name);
168+
if (originalName) {
169+
// This is a capitalized name in generated code, map to lowercase in source
170+
tokens.push({ source: originalName, generated: node.name });
171+
} else {
172+
// Check if this identifier should be capitalized (forward lookup)
173+
const capitalizedName = capitalizedNames.get(node.name);
174+
if (capitalizedName) {
175+
tokens.push({ source: node.name, generated: capitalizedName });
176+
} else {
177+
tokens.push(node.name);
178+
}
179+
}
146180
}
147181
return; // Leaf node, don't traverse further
148182
} else if (node.type === 'JSXIdentifier' && node.name) {
149183
if (node.loc) {
150-
tokens.push(node.name);
184+
// Check if this was capitalized (reverse lookup)
185+
const originalName = reverseCapitalizedNames.get(node.name);
186+
if (originalName) {
187+
tokens.push({ source: originalName, generated: node.name });
188+
} else {
189+
// Check if this should be capitalized (forward lookup)
190+
const capitalizedName = capitalizedNames.get(node.name);
191+
if (capitalizedName) {
192+
tokens.push({ source: node.name, generated: capitalizedName });
193+
} else {
194+
tokens.push(node.name);
195+
}
196+
}
151197
}
152198
return; // Leaf node, don't traverse further
153199
} else if (node.type === 'Literal' && node.raw) {
@@ -252,7 +298,20 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
252298

253299
// 3. Push closing tag name (not visited by AST walker)
254300
if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
255-
tokens.push(node.closingElement.name.name);
301+
const closingName = node.closingElement.name.name;
302+
// Check if this was capitalized (reverse lookup)
303+
const originalName = reverseCapitalizedNames.get(closingName);
304+
if (originalName) {
305+
tokens.push({ source: originalName, generated: closingName });
306+
} else {
307+
// Check if this should be capitalized (forward lookup)
308+
const capitalizedName = capitalizedNames.get(closingName);
309+
if (capitalizedName) {
310+
tokens.push({ source: closingName, generated: capitalizedName });
311+
} else {
312+
tokens.push(closingName);
313+
}
314+
}
256315
}
257316

258317
return;
@@ -1031,15 +1090,26 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
10311090
});
10321091

10331092
// Process each token in order
1034-
for (const text of tokens) {
1035-
const sourcePos = findInSource(text);
1036-
const genPos = findInGenerated(text);
1093+
for (const token of tokens) {
1094+
let sourceText, generatedText;
1095+
1096+
if (typeof token === 'string') {
1097+
sourceText = token;
1098+
generatedText = token;
1099+
} else {
1100+
// Token with different source and generated names
1101+
sourceText = token.source;
1102+
generatedText = token.generated;
1103+
}
1104+
1105+
const sourcePos = findInSource(sourceText);
1106+
const genPos = findInGenerated(generatedText);
10371107

10381108
if (sourcePos !== null && genPos !== null) {
10391109
mappings.push({
10401110
sourceOffsets: [sourcePos],
10411111
generatedOffsets: [genPos],
1042-
lengths: [text.length],
1112+
lengths: [sourceText.length],
10431113
data: mapping_data,
10441114
});
10451115
}

0 commit comments

Comments
 (0)