Conversation
| if (expr.type !== 'BinaryExpression' || expr.operator !== '-') return false; | ||
|
|
||
| // Check right side is an Identifier | ||
| if (expr.right.type !== 'Identifier') return false; |
There was a problem hiding this comment.
To prevent edge cases like {user.number_variable - 1} or {user.num-1} being parsed as hyphenated variable.
There was a problem hiding this comment.
can you add a test for this and/or other edge cases? i can imagine there are a few others, and if we're only verifying the "right" side to not be an identifier, is this enough?
thinking of some others:
- if we have something like
{1 - user.some-variable}or{1 - user.variable}, what's the correct behavior that should happen here? - what about `{user.number_var - 1 - user.another_var}? this check would fail and you may proceed to think this is a hyphenated user var right?
There was a problem hiding this comment.
this raises some good points
- for
{1 - user.some-variable}or{1 - user.variable}, it's will pass here but not transformed to as it fails the regex check later requiring to start with {user. {user.number_var - 1 - user.another_var}won't transform too
BinaryExpression ← expr (outermost)
├── left: BinaryExpression
│ ├── left: MemberExpression(user.number_var)
│ ├── operator: '-'
│ └── right: Literal(1)
├── operator: '-'
└── right: MemberExpression(user.another_var) ← expr.right
rafegoldberg
left a comment
There was a problem hiding this comment.
Logic looks fine, but this code needs clean up.
processor/transform/variables.ts
Outdated
| // Check right side is an Identifier | ||
| if (expr.right.type !== 'Identifier') return false; |
There was a problem hiding this comment.
This comment is redundant (and also says the opposite of what the next line checks for, which is confusing.) Please remove:
| // Check right side is an Identifier | |
| if (expr.right.type !== 'Identifier') return false; | |
| if (expr.right.type !== 'Identifier') return false; |
processor/transform/variables.ts
Outdated
| // Must be a BinaryExpression with subtraction operator | ||
| if (expr.type !== 'BinaryExpression' || expr.operator !== '-') return false; |
There was a problem hiding this comment.
This comment is redundant (and also says the opposite of what the next line checks for, which is confusing.) Please remove:
| // Must be a BinaryExpression with subtraction operator | |
| if (expr.type !== 'BinaryExpression' || expr.operator !== '-') return false; | |
| if (expr.type !== 'BinaryExpression' || expr.operator !== '-') return false; |
processor/transform/variables.ts
Outdated
| // Hyphenated variable: `user.X-API-Key` parsed as `user.X - API - Key` | ||
| // Extract the full variable name from node.value | ||
| const value = node.value; | ||
| const match = value.match(/^user\.([a-zA-Z_][a-zA-Z0-9_-]*)$/); |
There was a problem hiding this comment.
This regex is unnecessarily complicated. Let's use the built in alphanumeric character class to simplify this:
| const match = value.match(/^user\.([a-zA-Z_][a-zA-Z0-9_-]*)$/); | |
| const match = value.match(/^user\.([\w-]*)$/); |
| if (expr.type !== 'BinaryExpression' || expr.operator !== '-') return false; | ||
|
|
||
| // Check right side is an Identifier | ||
| if (expr.right.type !== 'Identifier') return false; |
There was a problem hiding this comment.
can you add a test for this and/or other edge cases? i can imagine there are a few others, and if we're only verifying the "right" side to not be an identifier, is this enough?
thinking of some others:
- if we have something like
{1 - user.some-variable}or{1 - user.variable}, what's the correct behavior that should happen here? - what about `{user.number_var - 1 - user.another_var}? this check would fail and you may proceed to think this is a hyphenated user var right?
| expect(code.type).toBe('code'); | ||
| expect(code.value).toBe('{user.X-API-Key}'); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
can we add a few more tests for other edge cases where the transformer should not parse the raw md as a readme variable?
There was a problem hiding this comment.
hmmm I think this is more tricky than it seems since the current behaviour (without this PR) is technically correct for JS expressions in MDX.
- the PR logic transforms
{user.a-1-b}as variablea-1-b, but{user.a-b-1}is still the expressionuser.a - b - 1 {user.a-false}isuser.a - falsesince false is a literal not identifier- hyphenated variables won't be transformed if it's not at the start of the expression. So
{1 - user.some-variable}is still evaluated as the expression{1 - user.some - variable}
I think it's fine to only allow hyphenated variables at the start, but should we allow non-identifiers (e.g. const, true, false, 1...) at the end of the variable?
rafegoldberg
left a comment
There was a problem hiding this comment.
QAed this and it doesn't work at all. Try it for yourself by running the start script on this branch, adding a hyphenated my-var here, and then hitting this link:
Also, per @kevinports' comment here the current approach is to use proper bracket accessor notation in these instances, so: {user['my-var']}. Not sure we actually want to allow random hyphenated values within this expression syntax in the first place?
| // Hyphenated variable: `user.X-API-Key` parsed as `user.X - API - Key` | ||
| // Extract the full variable name from node.value | ||
| const value = node.value; | ||
| const match = value.match(/^user\.([\w-]*)$/); |
There was a problem hiding this comment.
Per this thread with @maximilianfalco, I was wrong about the \w selector. Let's revert back to your original @HugoHSun.
There was a problem hiding this comment.
Sorry I should have made the context more clear:
This PR is targeted at MDX and variablesTransformer only runs in mdast() (via ast-processor.ts), which is NOT used by compile(). So here it always get compiled as the JS expression.
The only path where this PR's fix actually works is the visual editor in the main app:
-
Slate's
normalizeNodecallsrenderingLibrary.mdast()→variablesTransformerdetects the
hyphenated variable → creates areadme-variablenode -
On save,
readme-to-mdx.tsserializes it as{user["x-api"]}because x-api isn't a valid JS identifier -
The viewer's
compile()→run()handles bracket notation fine
Screen.Recording.2026-02-13.at.5.07.55.pm.mov
There was a problem hiding this comment.
After thinking about it more, I reckon too we shouldn't allow hyphenated variables in dot notation (e.g. {user.test-var}). Especially in this package since it's also used outside of the app. It introduces ambiguity on whether it's an hyphenated variable or minus operation. Bracket notation for weird variable names (e.g. {user["test-var"], {user["test var"]}) is the way to go since they are valid JS expressions which get evaluated with the user proxy.
There was a problem hiding this comment.
whoops, did not mean to approve this as it isn't working as stated rn. see above for detail.
Update: closing this PR since the fix for bracket variables are in https://github.com/readmeio/readme/pull/17315 |



🧰 Changes
Provides a workaround for user variables with hyphen in the MDX Slate editor, originially
-always get parsed to the subtraction operator in JS.export const total = 10{user.variable - total}or else
{user.variable-total}get interpreted as a custom variablevariable-totalEditor
View
🧬 QA & Testing