Skip to content

Commit c2dbefe

Browse files
authored
to_ts/TSX: emit nested component declarations as component props instead of children thunk content (#774)
1 parent 21dd402 commit c2dbefe

File tree

5 files changed

+78
-2
lines changed

5 files changed

+78
-2
lines changed

.changeset/silent-lamps-raise.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'ripple': patch
3+
---
4+
5+
Fixes language server type support for nested component call inside a parent
6+
components that become props and should not be marked as unused by typescript

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,12 +2711,42 @@ function transform_ts_child(node, context) {
27112711
if (!node.selfClosing && !node.unclosed && !has_children_props && node.children.length > 0) {
27122712
const is_dom_element = is_element_dom_element(node);
27132713
const component_scope = /** @type {ScopeInterface} */ (context.state.scopes.get(node));
2714+
/** @type {AST.Node[]} */
2715+
const non_component_children = [];
2716+
2717+
for (let i = 0; i < node.children.length; i++) {
2718+
const child = node.children[i];
2719+
if (!is_dom_element && child.type === 'Component' && child.id) {
2720+
const transformed_component = /** @type {AST.FunctionDeclaration} */ (
2721+
visit(child, {
2722+
...state,
2723+
scope: component_scope,
2724+
metadata: { await: false },
2725+
})
2726+
);
2727+
const func = b.arrow(
2728+
transformed_component.params,
2729+
transformed_component.body,
2730+
transformed_component.async,
2731+
);
2732+
func.metadata = { ...func.metadata, is_component: true };
2733+
const id = b.jsx_id(
2734+
/** @type {AST.Identifier} */ (child.id).name,
2735+
/** @type {AST.NodeWithLocation} */ (child.id),
2736+
);
2737+
id.metadata = { ...id.metadata, is_component: true };
2738+
attributes.push(b.jsx_attribute(id, b.jsx_expression_container(func)));
2739+
} else {
2740+
non_component_children.push(child);
2741+
}
2742+
}
27142743
const thunk =
2715-
/** @type {AST.Identifier} */ (node.id).name === 'style'
2744+
/** @type {AST.Identifier} */ (node.id).name === 'style' ||
2745+
non_component_children.length === 0
27162746
? null
27172747
: b.thunk(
27182748
b.block(
2719-
transform_body(node.children, {
2749+
transform_body(non_component_children, {
27202750
...context,
27212751
state: {
27222752
...state,

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,26 @@ export function convert_source_map_to_mappings(
639639
if (node.name) {
640640
visit(node.name);
641641
}
642+
643+
if (
644+
node.name.type === 'JSXIdentifier' &&
645+
node.name.metadata?.is_component &&
646+
node.name.loc
647+
) {
648+
const mapping = get_mapping_from_node(
649+
node.name,
650+
src_to_gen_map,
651+
gen_line_offsets,
652+
mapping_data,
653+
);
654+
mapping.sourceOffsets = [
655+
/** @type {AST.NodeWithLocation} */ (node.name).start - 'component '.length,
656+
];
657+
mapping.lengths = ['component'.length];
658+
659+
mapping.data.customData.hover = replace_label_to_component;
660+
mappings.push(mapping);
661+
}
642662
if (node.value) {
643663
visit(node.value);
644664
}

packages/ripple/src/compiler/types/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@ declare module 'estree-jsx' {
575575

576576
interface JSXIdentifier {
577577
tracked?: boolean;
578+
metadata: BaseNodeMetaData & {
579+
is_component?: boolean;
580+
};
578581
}
579582

580583
interface JSXEmptyExpression {

packages/ripple/tests/client/compiler/compiler.basic.test.ripple

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,23 @@ export component App() {
357357
expect(() => compile(code, 'test.ripple')).not.toThrow();
358358
});
359359

360+
it('converts nested component children into props in Volar mappings', () => {
361+
const source = `
362+
export component App() {
363+
<ark.div class="host-class" data-value="42">
364+
component asChild({ children, href, ...rest }: { href: string; [key: string]: any }) {
365+
<a id="aschild-anchor" {href} {...rest} data-extra="yes">{'Link'}</a>
366+
}
367+
</ark.div>
368+
}
369+
`;
370+
const result = compile_to_volar_mappings(source, 'test.ripple').code;
371+
372+
expect(result).toContain('<ark.div class="host-class" data-value="42" asChild={');
373+
expect(result).not.toContain('children={() =>');
374+
expect(result).not.toContain('function asChild');
375+
});
376+
360377
it('should not error on `this` MemberExpression with a UpdateExpression', () => {
361378
const code = `
362379
class Test {

0 commit comments

Comments
 (0)