-
Notifications
You must be signed in to change notification settings - Fork 663
fix: support lowercase member expressions in JSX elements #1474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import React from 'react'; | ||
|
|
||
| // Simulating Tanstack Forms pattern | ||
| function useForm() { | ||
| return { | ||
| AppForm: ({ children }: { children: React.ReactNode }) => ( | ||
| <form style={{ padding: '20px', border: '2px solid blue' }}>{children}</form> | ||
| ), | ||
| CancelButton: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( | ||
| <button | ||
| type="button" | ||
| onClick={onClick} | ||
| style={{ | ||
| padding: '10px 20px', | ||
| backgroundColor: 'red', | ||
| color: 'white', | ||
| marginRight: '10px', | ||
| border: 'none', | ||
| borderRadius: '4px', | ||
| cursor: 'pointer' | ||
| }} | ||
| > | ||
| {children} | ||
| </button> | ||
| ), | ||
| SubmitButton: ({ children }: { children: React.ReactNode }) => ( | ||
| <button | ||
| type="submit" | ||
| style={{ | ||
| padding: '10px 20px', | ||
| backgroundColor: 'green', | ||
| color: 'white', | ||
| border: 'none', | ||
| borderRadius: '4px', | ||
| cursor: 'pointer' | ||
| }} | ||
| > | ||
| {children} | ||
| </button> | ||
| ), | ||
| }; | ||
| } | ||
|
|
||
| // Simulating uppercase workaround | ||
| function useFormUppercase() { | ||
| return { | ||
| AppForm: ({ children }: { children: React.ReactNode }) => ( | ||
| <form style={{ padding: '20px', border: '2px solid green' }}>{children}</form> | ||
| ), | ||
| CancelButton: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( | ||
| <button | ||
| type="button" | ||
| onClick={onClick} | ||
| style={{ | ||
| padding: '10px 20px', | ||
| backgroundColor: 'red', | ||
| color: 'white', | ||
| marginRight: '10px', | ||
| border: 'none', | ||
| borderRadius: '4px', | ||
| cursor: 'pointer' | ||
| }} | ||
| > | ||
| {children} | ||
| </button> | ||
| ), | ||
| SubmitButton: ({ children }: { children: React.ReactNode }) => ( | ||
| <button | ||
| type="submit" | ||
| style={{ | ||
| padding: '10px 20px', | ||
| backgroundColor: 'green', | ||
| color: 'white', | ||
| border: 'none', | ||
| borderRadius: '4px', | ||
| cursor: 'pointer' | ||
| }} | ||
| > | ||
| {children} | ||
| </button> | ||
| ), | ||
| }; | ||
| } | ||
|
|
||
| export default function TanstackFormTest() { | ||
| const form = useForm(); | ||
| const FormContent = useFormUppercase(); | ||
|
|
||
| const handleCancel = () => { | ||
| alert('Cancel clicked!'); | ||
| }; | ||
|
|
||
| return ( | ||
| <div style={{ padding: '20px' }}> | ||
| <h2>Tanstack Forms + Lingo.dev Compiler Issue #1165</h2> | ||
|
|
||
| <div style={{ marginBottom: '40px' }}> | ||
| <h3>Broken: Lowercase variable name (form)</h3> | ||
| <p>Using: <code>const form = useForm()</code></p> | ||
| <p>Expected: Blue border, styled buttons with click functionality</p> | ||
| <form.AppForm> | ||
| <div> | ||
| <form.CancelButton onClick={handleCancel}>Cancel</form.CancelButton> | ||
| <form.SubmitButton>Submit</form.SubmitButton> | ||
| </div> | ||
| </form.AppForm> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h3>Working: Uppercase variable name (FormContent)</h3> | ||
| <p>Using: <code>const FormContent = useFormUppercase()</code></p> | ||
| <p>Expected: Green border, styled buttons with click functionality</p> | ||
| <FormContent.AppForm> | ||
| <div> | ||
| <FormContent.CancelButton onClick={handleCancel}>Cancel</FormContent.CancelButton> | ||
| <FormContent.SubmitButton>Submit</FormContent.SubmitButton> | ||
| </div> | ||
| </FormContent.AppForm> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -55,7 +55,10 @@ export const lingoJsxScopeInjectMutation = createCodeMutation((payload) => { | |||||||||
| } as any; | ||||||||||
|
|
||||||||||
| // Add $as prop | ||||||||||
| const as = /^[A-Z]/.test(originalJsxElementName) | ||||||||||
| // Check if it's a member expression (contains dot) or starts with uppercase | ||||||||||
|
||||||||||
| // Check if it's a member expression (contains dot) or starts with uppercase | |
| // Check if it's a member expression (contains dot) or starts with uppercase. | |
| // Member expressions (e.g., form.Button) and uppercase names (e.g., Button) | |
| // should be treated as component references, not HTML element strings. |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using t.identifier() with a dotted string like "form.Button" creates an invalid AST node because JavaScript identifiers cannot contain dots. For member expressions, you need to build a proper member expression AST structure.
Consider creating a helper function that parses the dotted string and builds the correct AST:
function createMemberExpressionFromString(str: string): t.Expression {
const parts = str.split('.');
if (parts.length === 1) {
return t.identifier(parts[0]);
}
let expr: t.Expression = t.identifier(parts[0]);
for (let i = 1; i < parts.length; i++) {
expr = t.memberExpression(expr, t.identifier(parts[i]));
}
return expr;
}Then use it like:
const as = isMemberExpression || isComponent
? createMemberExpressionFromString(originalJsxElementName)
: originalJsxElementName;This will correctly generate form.Button as a member expression AST node instead of an invalid identifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The heading says "Broken" but this test file is included in the PR that fixes the issue. After the fix, this case should work correctly. Consider updating the heading to something like "Previously broken: Lowercase variable name (form) - now fixed" to accurately reflect the current state.