Skip to content

feat: add option to perserve variable syntax in plain text compiler#1345

Merged
HugoHSun merged 2 commits intonextfrom
hugo/cx-98-search-variables
Feb 19, 2026
Merged

feat: add option to perserve variable syntax in plain text compiler#1345
HugoHSun merged 2 commits intonextfrom
hugo/cx-98-search-variables

Conversation

@HugoHSun
Copy link
Contributor

@HugoHSun HugoHSun commented Feb 17, 2026

PR App Fix CX-98

🧰 Changes

Adds a preserveVariableSyntax option to the plain() text compiler so it can output variables in {user.key} format instead of resolving them to bare key names.

When we index docs for search, we want to keep the variable syntax intact so the frontend can interpolate them with actual user values at display time (e.g. showing "Welcome Owlbert" instead of "Welcome name" in search results).

Both MDX expressions ({user.name}) and legacy <Variable> tags get unified into the same {user.key} output format. Also added a bunch of edge case tests.

Related:

  • ReadMe: readmeio/readme/pull/17331
  • Gitto: readmeio/gitto/pull/1833

🧬 QA & Testing

npm link @readme/markdown
npx tsx -e "
    const RDMD = require('@readme/markdown');
    const hast = RDMD.hast('{user.name}');
    console.log('default:', RDMD.plain(hast));
    console.log('preserved:', RDMD.plain(hast, { preserveVariableSyntax: true }));
"

Expected output:

default: name
preserved: {user.name}

@HugoHSun HugoHSun marked this pull request as ready for review February 17, 2026 08:28
@HugoHSun HugoHSun requested a review from dannobytes February 17, 2026 08:28
@kevinports kevinports self-requested a review February 17, 2026 15:01
it('falls back to key name for unresolved variables when others are provided', () => {
const md = 'Hello {user.name} from {user.company}';

expect(plain(hast(md), { variables: { name: 'Owlbert' } })).toBe('Hello Owlbert from company');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this fallback technically new or existing behavior in our system?

i'm a lil cautious and worried about having this be our default behavior. from a user and privacy perspective, i would expect user variables to never become publicized, since user var names are technically "private" or internal information. this currently reveals unresolved user var names and lets them show up in search results.

so if a user var is undefined, i'd expect the plain text compiler to simply omit it

    const md = 'Hello {user.name} from {user.company} and good bye';

    expect(plain(hast(md), { variables: { name: 'Owlbert' } })).toBe('Hello Owlbert from and good bye');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! This is actually existing behaviour that predates this PR. The view mode already falls back to the capitalised variable name when rendering docs, and search indexing / table of contents do the same with the key name. This PR doesn't change any of that, it just adds the preserveVariableSyntax option on top for the search indexing path.

image

I think it's less of a privacy concern since the markdown source is always visible in the frontend anyway, and variable names tend to be pretty generic stuff. Also we can update the behaviour when interpolating in the frontend (readmeio/readme/pull/17331), like omitting it if unresolved at rendering time

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for adding tests

Comment on lines 120 to 122
const txt = '{user.name}';

expect(plain(hast(txt), { preserveVariableSyntax: true })).toBe('{user.name}');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we give txt some before and after text to check that it correctly interpolates?

Suggested change
const txt = '{user.name}';
expect(plain(hast(txt), { preserveVariableSyntax: true })).toBe('{user.name}');
const txt = 'Hello {user.name} and good bye';
expect(plain(hast(txt), { preserveVariableSyntax: true })).toBe('Hello {user.name} and good bye');

Comment on lines 126 to 129
const txt = '<Variable name="company">company</Variable>';
const tree = hast(txt);

expect(plain(tree, { preserveVariableSyntax: true })).toBe('{user.company}');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, to add before + after text

Comment on lines 132 to 136
it('preserves multiple variables with preserveVariableSyntax option', () => {
const md = 'Hello {user.name}, welcome to {user.company}!';

expect(plain(hast(md), { preserveVariableSyntax: true })).toBe('Hello {user.name} , welcome to {user.company} !');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a duplicate test of L119, is probably unnecessary and can be removed

lib/plain.ts Outdated
Comment on lines 14 to 16
/** When true, outputs variables using `{user.key}` syntax instead of resolving
* to values or bare key names. Used by search indexing so the frontend can
* interpolate variables at display time. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** When true, outputs variables using `{user.key}` syntax instead of resolving
* to values or bare key names. Used by search indexing so the frontend can
* interpolate variables at display time. */
/**
* When true, outputs variables using `{user.key}` syntax instead of resolving
* to values or bare key names. Used by search indexing so the frontend can
* interpolate variables at display time.
*/

@HugoHSun HugoHSun requested a review from mjcuva February 18, 2026 04:28
@HugoHSun HugoHSun merged commit 5ab350e into next Feb 19, 2026
11 checks passed
@HugoHSun HugoHSun deleted the hugo/cx-98-search-variables branch February 19, 2026 02:07
rafegoldberg pushed a commit that referenced this pull request Feb 20, 2026
## Version 13.3.0
### ✨ New & Improved

* **mdxish:** add legacy variable tokenizer ([#1339](#1339)) ([8e8b11b](8e8b11b))
* add option to perserve variable syntax in plain text compiler ([#1345](#1345)) ([5ab350e](5ab350e))
* **mdxish:** resolve variables in code blocks ([#1350](#1350)) ([a6460f8](a6460f8))
* **mdxish:** use variable name for heading slug generation ([#1340](#1340)) ([61a97d3](61a97d3))

<!--SKIP CI-->
@rafegoldberg
Copy link
Contributor

This PR was released!

🚀 Changes included in v13.3.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants