Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions __tests__/transformers/preprocess-jsx-expressions.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import { mix } from '../../lib';
import { preprocessJSXExpressions } from '../../processor/transform/mdxish/preprocess-jsx-expressions';

describe('preprocessJSXExpressions', () => {
describe('using the default jsx context', () => {
it('should evaluate the string operations', () => {
const content = 'Hello {uppercase("world")} {lowercase("WORLD")}';
const result = mix(content);
expect(result).toContain('Hello WORLD world');
expect(result).not.toContain('{uppercase("world")}');
expect(result).not.toContain('{lowercase("WORLD")}');
});

it('should evaluate the number operations when the operations are mentioned', () => {
const content = 'Hello {add(1, 2)} {subtract(3, 4)} {multiply(5, 6)} {divide(4, 2)}';
const result = mix(content);
expect(result).toContain('Hello 3 -1 30 2');
expect(result).not.toContain('{add(1, 2)}');
expect(result).not.toContain('{subtract(3, 4)}');
expect(result).not.toContain('{multiply(5, 6)}');
expect(result).not.toContain('{divide(4, 2)}');
});

it('should evaluate number operations when math symbols are used', () => {
const content = '{1 + 2 - 1} {4 * 2 / 2}';
const result = mix(content);
expect(result).toContain('2 4');
expect(result).not.toContain('{1 + 2 - 1}');
expect(result).not.toContain('{4 * 2 / 2}');
});

it('should not evaluate operations when not in braces', () => {
const content = '1 + 2 uppercase("world")';
const result = mix(content);
expect(result).toContain(content);
expect(result).not.toContain('WORLD');
expect(result).not.toContain('3');
});
});

describe('Step 3: Evaluate attribute expressions', () => {
it('should evaluate JSX attribute expressions and convert them to string attributes', () => {
const context = {
Expand Down
4 changes: 2 additions & 2 deletions lib/mdxish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import mdxishComponentBlocks from '../processor/transform/mdxish/mdxish-componen
import mdxishHtmlBlocks from '../processor/transform/mdxish/mdxish-html-blocks';
import magicBlockRestorer from '../processor/transform/mdxish/mdxish-magic-blocks';
import mdxishTables from '../processor/transform/mdxish/mdxish-tables';
import { preprocessJSXExpressions, type JSXContext } from '../processor/transform/mdxish/preprocess-jsx-expressions';
import { preprocessJSXExpressions, type JSXContext, DEFAULT_JSX_CONTEXT } from '../processor/transform/mdxish/preprocess-jsx-expressions';
import variablesTextTransformer from '../processor/transform/mdxish/variables-text';
import tailwindTransformer from '../processor/transform/tailwind';

Expand All @@ -46,7 +46,7 @@ const defaultTransformers = [calloutTransformer, codeTabsTransformer, gemojiTran
* @see {@link https://github.com/readmeio/rmdx/blob/main/docs/mdxish-flow.md}
*/
export function mdxish(mdContent: string, opts: MdxishOpts = {}): Root {
const { components: userComponents = {}, jsxContext = {}, useTailwind } = opts;
const { components: userComponents = {}, jsxContext = DEFAULT_JSX_CONTEXT, useTailwind } = opts;

const components: CustomComponents = {
...loadComponents(),
Expand Down
14 changes: 13 additions & 1 deletion processor/transform/mdxish/preprocess-jsx-expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
* Pre-processes JSX-like expressions before markdown parsing.
* Converts href={'value'} to href="value", evaluates {expressions}, etc.
*/

export type JSXContext = Record<string, unknown>;

// Common operations that might be used in JSX expressions
// These are not exhaustive, but are a good starting point
// We probably want to just use a library that'll load most of the common operations for us
export const DEFAULT_JSX_CONTEXT: JSXContext = {
uppercase: (value: string) => value.toUpperCase(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Struggling to understand why we're constructing a special context with these operations? If I understand correctly ES language features are already available when we eval the expressions. And I don't believe these functions are available in strict MDX so why/how would a user even know they exist?

Instead of Hello {uppercase("world")} I would instead write Hello {"world".toUpperCase()}
and instead of {add(1, 2)} I would write {1 + 2}
and so on...

Is there a requirement I'm missing here?

lowercase: (value: string) => value.toLowerCase(),

add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b,
multiply: (a: number, b: number) => a * b,
divide: (a: number, b: number) => a / b,
};

// Base64 encode (Node.js + browser compatible)
function base64Encode(str: string): string {
if (typeof Buffer !== 'undefined') {
Expand Down