Skip to content

Commit 6592520

Browse files
committed
Fix Babel plugin crash when handling unresolved component names
- Add support for JSXMemberExpression in getNodeName function
1 parent 17949da commit 6592520

4 files changed

Lines changed: 109 additions & 1 deletion

File tree

packages/react-native-babel-plugin/src/actions/rum/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,12 @@ function jsxChildToRuntimeCall(
237237
: t.memberExpression(obj, t.identifier(prop)),
238238
(null as unknown) as Babel.types.Expression
239239
);
240+
} else if (elementName && t.isValidIdentifier(elementName)) {
241+
nameNode = t.identifier(elementName);
242+
} else if (elementName) {
243+
nameNode = t.stringLiteral(elementName);
240244
} else {
241-
nameNode = t.identifier(elementName || 'unknown');
245+
nameNode = addNamed(programPath, 'Fragment', 'react/jsx-runtime');
242246
}
243247

244248
// Convert props to an object

packages/react-native-babel-plugin/src/utils/nodeProcessing.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,28 @@ export function getNodeName(
8484
return node;
8585
}
8686

87+
if (t.isJSXMemberExpression(node)) {
88+
if (t.isJSXIdentifier(node.object)) {
89+
return `${node.object.name}.${node.property.name}`;
90+
}
91+
92+
let nodeName = node.property.name;
93+
let nodeTracker:
94+
| Babel.types.JSXIdentifier
95+
| Babel.types.JSXMemberExpression = node.object;
96+
97+
while (t.isJSXMemberExpression(nodeTracker)) {
98+
nodeName = `${nodeTracker.property.name}.${nodeName}`;
99+
nodeTracker = nodeTracker.object;
100+
}
101+
102+
if (t.isJSXIdentifier(nodeTracker)) {
103+
nodeName = `${nodeTracker.name}.${nodeName}`;
104+
}
105+
106+
return nodeName;
107+
}
108+
87109
if (!('name' in node)) {
88110
return null;
89111
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
* Copyright 2016-Present Datadog, Inc.
4+
*/
5+
6+
import * as Babel from '@babel/core';
7+
8+
import { getNodeName } from '../src/utils/nodeProcessing';
9+
10+
const t = Babel.types;
11+
12+
describe('getNodeName', () => {
13+
it('should return the string as-is when given a string', () => {
14+
expect(getNodeName(t, 'View')).toBe('View');
15+
});
16+
17+
it('should return the name of a JSXIdentifier', () => {
18+
const node = t.jsxIdentifier('Text');
19+
expect(getNodeName(t, node)).toBe('Text');
20+
});
21+
22+
it('should return dotted name for a JSXMemberExpression', () => {
23+
const node = t.jsxMemberExpression(
24+
t.jsxIdentifier('Card'),
25+
t.jsxIdentifier('Title')
26+
);
27+
expect(getNodeName(t, node)).toBe('Card.Title');
28+
});
29+
30+
it('should return dotted name for a deeply nested JSXMemberExpression', () => {
31+
const node = t.jsxMemberExpression(
32+
t.jsxMemberExpression(t.jsxIdentifier('A'), t.jsxIdentifier('B')),
33+
t.jsxIdentifier('C')
34+
);
35+
expect(getNodeName(t, node)).toBe('A.B.C');
36+
});
37+
38+
it('should return null for a node without a name property', () => {
39+
const node = t.numericLiteral(42);
40+
expect(getNodeName(t, node)).toBeNull();
41+
});
42+
43+
it('should return null for an empty statement node', () => {
44+
const node = t.emptyStatement();
45+
expect(getNodeName(t, node)).toBeNull();
46+
});
47+
});

packages/react-native-babel-plugin/test/plugin.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,4 +1643,39 @@ describe('Babel plugin: wrap interaction handlers for RUM ( with memoization )',
16431643
}"
16441644
`);
16451645
});
1646+
1647+
it('should resolve member expression component names in getContent', () => {
1648+
const options: Partial<PluginOptions> = {
1649+
components: {
1650+
useContent: true,
1651+
useNamePrefix: true,
1652+
tracked: [
1653+
{
1654+
name: 'Card',
1655+
handlers: [{ event: 'onPress', action: 'TAP' }]
1656+
}
1657+
]
1658+
}
1659+
};
1660+
1661+
const input = `
1662+
import { View, Text } from 'react-native';
1663+
function Card({ children, onPress }: any) {
1664+
return <View onPress={onPress}>{children}</View>;
1665+
}
1666+
Card.Title = ({ children }: any) => <Text>{children}</Text>;
1667+
1668+
function Screen() {
1669+
return (
1670+
<Card onPress={() => {}}>
1671+
<Card.Title>Welcome</Card.Title>
1672+
</Card>
1673+
);
1674+
}
1675+
`;
1676+
1677+
const output = transformCode(input, options);
1678+
expect(output).not.toContain('(unknown,');
1679+
expect(output).toContain('Card.Title');
1680+
});
16461681
});

0 commit comments

Comments
 (0)