safe-mdx 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import dedent from 'dedent';
3
3
  import { htmlToJsx } from 'html-to-jsx-transform';
4
4
  import { renderToStaticMarkup } from 'react-dom/server';
5
5
  import { expect, test } from 'vitest';
6
+ import { z } from 'zod';
6
7
  import { mdxParse } from './parse.js';
7
8
  import { MdastToJsx, mdastBfs } from './safe-mdx.js';
8
9
  import { completeJsxTags } from './streaming.js';
@@ -14,9 +15,9 @@ const components = {
14
15
  return _jsx("div", { children: children });
15
16
  },
16
17
  };
17
- function render(code) {
18
+ function render(code, componentPropsSchema) {
18
19
  const mdast = mdxParse(code);
19
- const visitor = new MdastToJsx({ markdown: code, mdast, components });
20
+ const visitor = new MdastToJsx({ markdown: code, mdast, components, componentPropsSchema });
20
21
  const result = visitor.run();
21
22
  const html = renderToStaticMarkup(result);
22
23
  // console.log(JSON.stringify(result, null, 2))
@@ -441,6 +442,7 @@ test('missing components are ignored', () => {
441
442
  {
442
443
  "errors": [
443
444
  {
445
+ "line": 1,
444
446
  "message": "Unsupported jsx component MissingComponent",
445
447
  },
446
448
  ],
@@ -474,15 +476,19 @@ test('props parsing', () => {
474
476
  {
475
477
  "errors": [
476
478
  {
479
+ "line": 7,
477
480
  "message": "Expressions in jsx props are not supported (expression1={1 + 3})",
478
481
  },
479
482
  {
483
+ "line": 8,
480
484
  "message": "Expressions in jsx props are not supported (expression2={Boolean(1)})",
481
485
  },
482
486
  {
487
+ "line": 9,
483
488
  "message": "Expressions in jsx props are not supported (jsx={<SomeComponent />})",
484
489
  },
485
490
  {
491
+ "line": 13,
486
492
  "message": "Expressions in jsx props are not supported (...{ spread: true })",
487
493
  },
488
494
  ],
@@ -2243,4 +2249,190 @@ _No documentation_
2243
2249
  </React.Fragment>
2244
2250
  `);
2245
2251
  });
2252
+ test('component props schema validation with zod', () => {
2253
+ const HeadingSchema = z.object({
2254
+ level: z.number().min(1).max(6),
2255
+ title: z.string().optional(),
2256
+ });
2257
+ const CardsSchema = z.object({
2258
+ count: z.number().positive(),
2259
+ variant: z.enum(['default', 'outline']).optional(),
2260
+ });
2261
+ const componentPropsSchema = {
2262
+ Heading: HeadingSchema,
2263
+ Cards: CardsSchema,
2264
+ };
2265
+ const code = dedent `
2266
+ <Heading level={2} title="test">Valid heading</Heading>
2267
+
2268
+ <Cards count={3} variant="outline">Valid cards</Cards>
2269
+
2270
+ <Heading level={10} title="test">Invalid heading - level too high</Heading>
2271
+
2272
+ <Cards count={-1}>Invalid cards - negative count</Cards>
2273
+
2274
+ <Cards count="not a number">Invalid cards - wrong type</Cards>
2275
+ `;
2276
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2277
+ {
2278
+ "errors": [
2279
+ {
2280
+ "line": 5,
2281
+ "message": "Invalid props for component "Heading" at "level": Number must be less than or equal to 6",
2282
+ "schemaPath": "level",
2283
+ },
2284
+ {
2285
+ "line": 7,
2286
+ "message": "Invalid props for component "Cards" at "count": Number must be greater than 0",
2287
+ "schemaPath": "count",
2288
+ },
2289
+ {
2290
+ "line": 9,
2291
+ "message": "Invalid props for component "Cards" at "count": Expected number, received string",
2292
+ "schemaPath": "count",
2293
+ },
2294
+ ],
2295
+ "html": "<h1>Valid heading</h1><div>Valid cards</div><h1>Invalid heading - level too high</h1><div>Invalid cards - negative count</div><div>Invalid cards - wrong type</div>",
2296
+ "result": <React.Fragment>
2297
+ <Heading
2298
+ level={2}
2299
+ title="test"
2300
+ >
2301
+ Valid heading
2302
+ </Heading>
2303
+ <Cards
2304
+ count={3}
2305
+ variant="outline"
2306
+ >
2307
+ Valid cards
2308
+ </Cards>
2309
+ <Heading
2310
+ level={10}
2311
+ title="test"
2312
+ >
2313
+ Invalid heading - level too high
2314
+ </Heading>
2315
+ <Cards
2316
+ count={-1}
2317
+ >
2318
+ Invalid cards - negative count
2319
+ </Cards>
2320
+ <Cards
2321
+ count="not a number"
2322
+ >
2323
+ Invalid cards - wrong type
2324
+ </Cards>
2325
+ </React.Fragment>,
2326
+ }
2327
+ `);
2328
+ });
2329
+ test('schema validation without errors', () => {
2330
+ const HeadingSchema = z.object({
2331
+ level: z.number().min(1).max(6),
2332
+ title: z.string().optional(),
2333
+ });
2334
+ const componentPropsSchema = {
2335
+ Heading: HeadingSchema,
2336
+ };
2337
+ const code = dedent `
2338
+ <Heading level={2} title="test">Valid heading</Heading>
2339
+ <Heading level={1}>Another valid heading</Heading>
2340
+ `;
2341
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2342
+ {
2343
+ "errors": [],
2344
+ "html": "<h1>Valid heading</h1><h1>Another valid heading</h1>",
2345
+ "result": <React.Fragment>
2346
+ <Heading
2347
+ level={2}
2348
+ title="test"
2349
+ >
2350
+ Valid heading
2351
+ </Heading>
2352
+ <Heading
2353
+ level={1}
2354
+ >
2355
+ Another valid heading
2356
+ </Heading>
2357
+ </React.Fragment>,
2358
+ }
2359
+ `);
2360
+ });
2361
+ test('component without schema should not be validated', () => {
2362
+ const HeadingSchema = z.object({
2363
+ level: z.number().min(1).max(6),
2364
+ });
2365
+ const componentPropsSchema = {
2366
+ Heading: HeadingSchema,
2367
+ };
2368
+ const code = dedent `
2369
+ <Heading level={2}>Valid heading with schema</Heading>
2370
+ <Cards invalidProp="anything">Cards without schema - should not be validated</Cards>
2371
+ `;
2372
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2373
+ {
2374
+ "errors": [],
2375
+ "html": "<h1>Valid heading with schema</h1><div>Cards without schema - should not be validated</div>",
2376
+ "result": <React.Fragment>
2377
+ <Heading
2378
+ level={2}
2379
+ >
2380
+ Valid heading with schema
2381
+ </Heading>
2382
+ <Cards
2383
+ invalidProp="anything"
2384
+ >
2385
+ Cards without schema - should not be validated
2386
+ </Cards>
2387
+ </React.Fragment>,
2388
+ }
2389
+ `);
2390
+ });
2391
+ test('validation error includes schema path', () => {
2392
+ const ComplexSchema = z.object({
2393
+ user: z.object({
2394
+ name: z.string(),
2395
+ age: z.number().min(0),
2396
+ }),
2397
+ settings: z.object({
2398
+ theme: z.enum(['light', 'dark']),
2399
+ }),
2400
+ });
2401
+ const componentPropsSchema = {
2402
+ Heading: ComplexSchema,
2403
+ };
2404
+ const code = dedent `
2405
+ <Heading user={{ name: "test", age: -1 }} settings={{ theme: "invalid" }}>Complex validation</Heading>
2406
+ `;
2407
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2408
+ {
2409
+ "errors": [
2410
+ {
2411
+ "line": 1,
2412
+ "message": "Expressions in jsx props are not supported (user={{ name: "test", age: -1 }})",
2413
+ },
2414
+ {
2415
+ "line": 1,
2416
+ "message": "Expressions in jsx props are not supported (settings={{ theme: "invalid" }})",
2417
+ },
2418
+ {
2419
+ "line": 1,
2420
+ "message": "Invalid props for component "Heading" at "user": Required",
2421
+ "schemaPath": "user",
2422
+ },
2423
+ {
2424
+ "line": 1,
2425
+ "message": "Invalid props for component "Heading" at "settings": Required",
2426
+ "schemaPath": "settings",
2427
+ },
2428
+ ],
2429
+ "html": "<h1>Complex validation</h1>",
2430
+ "result": <React.Fragment>
2431
+ <Heading>
2432
+ Complex validation
2433
+ </Heading>
2434
+ </React.Fragment>,
2435
+ }
2436
+ `);
2437
+ });
2246
2438
  //# sourceMappingURL=safe-mdx.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"safe-mdx.test.js","sourceRoot":"","sources":["../src/safe-mdx.test.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"safe-mdx.test.js","sourceRoot":"","sources":["../src/safe-mdx.test.tsx"],"names":[],"mappings":";AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAA6B,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAEhD,MAAM,UAAU,GAAG;IACf,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;QACvB,OAAO,uBAAK,QAAQ,GAAM,CAAA;IAC9B,CAAC;IACD,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;QACrB,OAAO,wBAAM,QAAQ,GAAO,CAAA;IAChC,CAAC;CACJ,CAAA;AAED,SAAS,MAAM,CAAC,IAAI,EAAE,oBAA2C;IAC7D,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC,CAAA;IAC3F,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAC5B,MAAM,IAAI,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAA;IACzC,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,CAAA;AACzD,CAAC;AAED,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;IACnB,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,qBAAqB,CAAC,eAAe,CAAC,CAAA;IACrE,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,qBAAqB,CAAC,eAAe,CAAC,CAAA;IACvE,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CACzD,2BAA2B,CAC9B,CAAA;IACD,MAAM,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC,CAAC,qBAAqB,CACtE,mCAAmC,CACtC,CAAA;AACL,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE;IAC7B,MAAM,IAAI,GAAG,MAAM,CAAA;;;;;;;;;;;KAWlB,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6B1C,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACpD,MAAM,IAAI,GAAG,MAAM,CAAA;;;;;;;;;;;KAWlB,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;KAe3D,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAA;;;;;;;;;;;;;;;;;;KAkBlB,CAAA;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAE5B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;QAClB,OAAO,CAAC,CAAC,QAAQ,CAAA;IACrB,CAAC,CAAC,CAAA;IACF,MAAM,CAAC,KAAK,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoEnC,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;IACf,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;SAMZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;KAgBvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AACF,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE;IACrB,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;;;SAQZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;KAavB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AACF,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;IACf,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;;SAOZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyEvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AACF,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAC1B,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;SAMZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiCvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE;IACpB,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;SAEZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;KAYvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;IACnB,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;SAIZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;KAgBvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AACF,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IACxC,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;SAMZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;KAevB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IACxC,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;SAEZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;KAWvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE;IACvB,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;+BAKU,SAAS;;;;;;;;;;;;;;;SAe/B,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyCvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AACF,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;IAChB,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;SAIZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;KAcvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,kFAAkF;AAClF,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE;IACtB,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2ZZ,CAAC,CACL,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAomCvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAC9B,MAAM,IAAI,GAAG,MAAM,CAAA;CACtB,CAAA;IACG,MAAM,CACF,MAAM,CAAC,MAAM,CAAA;;;;;;;;KAQhB,CAAC,CACD,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;KAqBvB,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,0BAA0B,EAAE,GAAG,EAAE;IAClC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDd,CAAA;IACC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;IACxB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoFxC,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACpD,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;QAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC/B,CAAC,CAAA;IAEF,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;QACzB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC5B,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;KACrD,CAAC,CAAA;IAEF,MAAM,oBAAoB,GAAyB;QAC/C,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,WAAW;KACrB,CAAA;IAED,MAAM,IAAI,GAAG,MAAM,CAAA;;;;;;;;;;KAUlB,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmDhE,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAC1C,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;QAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC/B,CAAC,CAAA;IAEF,MAAM,oBAAoB,GAAyB;QAC/C,OAAO,EAAE,aAAa;KACzB,CAAA;IAED,MAAM,IAAI,GAAG,MAAM,CAAA;;;KAGlB,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;KAkBhE,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAC1D,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;QAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;KAClC,CAAC,CAAA;IAEF,MAAM,oBAAoB,GAAyB;QAC/C,OAAO,EAAE,aAAa;KACzB,CAAA;IAED,MAAM,IAAI,GAAG,MAAM,CAAA;;;KAGlB,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;KAiBhE,CAAC,CAAA;AACN,CAAC,CAAC,CAAA;AAEF,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IAC/C,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;QAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;SACzB,CAAC;QACF,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACnC,CAAC;KACL,CAAC,CAAA;IAEF,MAAM,oBAAoB,GAAyB;QAC/C,OAAO,EAAE,aAAa;KACzB,CAAA;IAED,MAAM,IAAI,GAAG,MAAM,CAAA;;KAElB,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6BhE,CAAC,CAAA;AACN,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safe-mdx",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Render MDX in React without eval",
6
6
  "repository": "https://github.com/holocron-hq/safe-mdx",
@@ -31,6 +31,7 @@
31
31
  "react": "*"
32
32
  },
33
33
  "dependencies": {
34
+ "@standard-schema/spec": "^1.0.0",
34
35
  "collapse-white-space": "^2.1.0",
35
36
  "html-to-jsx-transform": "^1.0.0",
36
37
  "remark": "^15.0.1",
@@ -51,7 +52,8 @@
51
52
  "react": "^19.1.0",
52
53
  "react-dom": "^19.1.0",
53
54
  "typescript": "5.8.3",
54
- "vitest": "^3.1.3"
55
+ "vitest": "^3.1.3",
56
+ "zod": "^3.25.67"
55
57
  },
56
58
  "scripts": {
57
59
  "build": "tsc",
@@ -0,0 +1,46 @@
1
+ import { bench, describe } from 'vitest'
2
+ import { mdxParse } from './parse.js'
3
+ import { MdastToJsx } from './safe-mdx.js'
4
+
5
+ let longMdxContent = await fetch(
6
+ 'https://raw.githubusercontent.com/colinhacks/zod/0a49fa39348b7c72b19ddedc3b0f879bd395304b/packages/docs/content/packages/v3.mdx',
7
+ ).then((x) => x.text())
8
+
9
+ function Callout({ children }: { children: any }) {
10
+ return (
11
+ <div
12
+ style={{
13
+ borderLeft: '4px solid #0070f3',
14
+ background: '#f0f8ff',
15
+ padding: '8px 16px',
16
+ margin: '16px 0',
17
+ }}
18
+ >
19
+ {children}
20
+ </div>
21
+ )
22
+ }
23
+
24
+ const mdast = mdxParse(longMdxContent)
25
+
26
+ describe('safe-mdx performance benchmarks', () => {
27
+ bench('MdastToJsx class processing (long MDX)', () => {
28
+ const visitor = new MdastToJsx({
29
+ markdown: longMdxContent,
30
+ mdast,
31
+ components: { Callout },
32
+ })
33
+ visitor.run()
34
+ })
35
+
36
+ bench('MdastToJsx with noop createElement (long MDX)', () => {
37
+ const noopCreateElement = () => null
38
+ const visitor = new MdastToJsx({
39
+ markdown: longMdxContent,
40
+ mdast,
41
+ components: { Callout },
42
+ createElement: noopCreateElement,
43
+ })
44
+ visitor.run()
45
+ })
46
+ })
@@ -3,8 +3,9 @@ import { htmlToJsx } from 'html-to-jsx-transform'
3
3
  import React from 'react'
4
4
  import { renderToStaticMarkup } from 'react-dom/server'
5
5
  import { expect, test } from 'vitest'
6
+ import { z } from 'zod'
6
7
  import { mdxParse } from './parse.js'
7
- import { MdastToJsx, mdastBfs } from './safe-mdx.js'
8
+ import { MdastToJsx, mdastBfs, type ComponentPropsSchema } from './safe-mdx.js'
8
9
  import { completeJsxTags } from './streaming.js'
9
10
 
10
11
  const components = {
@@ -16,9 +17,9 @@ const components = {
16
17
  },
17
18
  }
18
19
 
19
- function render(code) {
20
+ function render(code, componentPropsSchema?: ComponentPropsSchema) {
20
21
  const mdast = mdxParse(code)
21
- const visitor = new MdastToJsx({ markdown: code, mdast, components })
22
+ const visitor = new MdastToJsx({ markdown: code, mdast, components, componentPropsSchema })
22
23
  const result = visitor.run()
23
24
  const html = renderToStaticMarkup(result)
24
25
  // console.log(JSON.stringify(result, null, 2))
@@ -474,6 +475,7 @@ test('missing components are ignored', () => {
474
475
  {
475
476
  "errors": [
476
477
  {
478
+ "line": 1,
477
479
  "message": "Unsupported jsx component MissingComponent",
478
480
  },
479
481
  ],
@@ -510,15 +512,19 @@ test('props parsing', () => {
510
512
  {
511
513
  "errors": [
512
514
  {
515
+ "line": 7,
513
516
  "message": "Expressions in jsx props are not supported (expression1={1 + 3})",
514
517
  },
515
518
  {
519
+ "line": 8,
516
520
  "message": "Expressions in jsx props are not supported (expression2={Boolean(1)})",
517
521
  },
518
522
  {
523
+ "line": 9,
519
524
  "message": "Expressions in jsx props are not supported (jsx={<SomeComponent />})",
520
525
  },
521
526
  {
527
+ "line": 13,
522
528
  "message": "Expressions in jsx props are not supported (...{ spread: true })",
523
529
  },
524
530
  ],
@@ -2288,3 +2294,206 @@ _No documentation_
2288
2294
  </React.Fragment>
2289
2295
  `)
2290
2296
  })
2297
+
2298
+ test('component props schema validation with zod', () => {
2299
+ const HeadingSchema = z.object({
2300
+ level: z.number().min(1).max(6),
2301
+ title: z.string().optional(),
2302
+ })
2303
+
2304
+ const CardsSchema = z.object({
2305
+ count: z.number().positive(),
2306
+ variant: z.enum(['default', 'outline']).optional(),
2307
+ })
2308
+
2309
+ const componentPropsSchema: ComponentPropsSchema = {
2310
+ Heading: HeadingSchema,
2311
+ Cards: CardsSchema,
2312
+ }
2313
+
2314
+ const code = dedent`
2315
+ <Heading level={2} title="test">Valid heading</Heading>
2316
+
2317
+ <Cards count={3} variant="outline">Valid cards</Cards>
2318
+
2319
+ <Heading level={10} title="test">Invalid heading - level too high</Heading>
2320
+
2321
+ <Cards count={-1}>Invalid cards - negative count</Cards>
2322
+
2323
+ <Cards count="not a number">Invalid cards - wrong type</Cards>
2324
+ `
2325
+
2326
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2327
+ {
2328
+ "errors": [
2329
+ {
2330
+ "line": 5,
2331
+ "message": "Invalid props for component "Heading" at "level": Number must be less than or equal to 6",
2332
+ "schemaPath": "level",
2333
+ },
2334
+ {
2335
+ "line": 7,
2336
+ "message": "Invalid props for component "Cards" at "count": Number must be greater than 0",
2337
+ "schemaPath": "count",
2338
+ },
2339
+ {
2340
+ "line": 9,
2341
+ "message": "Invalid props for component "Cards" at "count": Expected number, received string",
2342
+ "schemaPath": "count",
2343
+ },
2344
+ ],
2345
+ "html": "<h1>Valid heading</h1><div>Valid cards</div><h1>Invalid heading - level too high</h1><div>Invalid cards - negative count</div><div>Invalid cards - wrong type</div>",
2346
+ "result": <React.Fragment>
2347
+ <Heading
2348
+ level={2}
2349
+ title="test"
2350
+ >
2351
+ Valid heading
2352
+ </Heading>
2353
+ <Cards
2354
+ count={3}
2355
+ variant="outline"
2356
+ >
2357
+ Valid cards
2358
+ </Cards>
2359
+ <Heading
2360
+ level={10}
2361
+ title="test"
2362
+ >
2363
+ Invalid heading - level too high
2364
+ </Heading>
2365
+ <Cards
2366
+ count={-1}
2367
+ >
2368
+ Invalid cards - negative count
2369
+ </Cards>
2370
+ <Cards
2371
+ count="not a number"
2372
+ >
2373
+ Invalid cards - wrong type
2374
+ </Cards>
2375
+ </React.Fragment>,
2376
+ }
2377
+ `)
2378
+ })
2379
+
2380
+ test('schema validation without errors', () => {
2381
+ const HeadingSchema = z.object({
2382
+ level: z.number().min(1).max(6),
2383
+ title: z.string().optional(),
2384
+ })
2385
+
2386
+ const componentPropsSchema: ComponentPropsSchema = {
2387
+ Heading: HeadingSchema,
2388
+ }
2389
+
2390
+ const code = dedent`
2391
+ <Heading level={2} title="test">Valid heading</Heading>
2392
+ <Heading level={1}>Another valid heading</Heading>
2393
+ `
2394
+
2395
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2396
+ {
2397
+ "errors": [],
2398
+ "html": "<h1>Valid heading</h1><h1>Another valid heading</h1>",
2399
+ "result": <React.Fragment>
2400
+ <Heading
2401
+ level={2}
2402
+ title="test"
2403
+ >
2404
+ Valid heading
2405
+ </Heading>
2406
+ <Heading
2407
+ level={1}
2408
+ >
2409
+ Another valid heading
2410
+ </Heading>
2411
+ </React.Fragment>,
2412
+ }
2413
+ `)
2414
+ })
2415
+
2416
+ test('component without schema should not be validated', () => {
2417
+ const HeadingSchema = z.object({
2418
+ level: z.number().min(1).max(6),
2419
+ })
2420
+
2421
+ const componentPropsSchema: ComponentPropsSchema = {
2422
+ Heading: HeadingSchema,
2423
+ }
2424
+
2425
+ const code = dedent`
2426
+ <Heading level={2}>Valid heading with schema</Heading>
2427
+ <Cards invalidProp="anything">Cards without schema - should not be validated</Cards>
2428
+ `
2429
+
2430
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2431
+ {
2432
+ "errors": [],
2433
+ "html": "<h1>Valid heading with schema</h1><div>Cards without schema - should not be validated</div>",
2434
+ "result": <React.Fragment>
2435
+ <Heading
2436
+ level={2}
2437
+ >
2438
+ Valid heading with schema
2439
+ </Heading>
2440
+ <Cards
2441
+ invalidProp="anything"
2442
+ >
2443
+ Cards without schema - should not be validated
2444
+ </Cards>
2445
+ </React.Fragment>,
2446
+ }
2447
+ `)
2448
+ })
2449
+
2450
+ test('validation error includes schema path', () => {
2451
+ const ComplexSchema = z.object({
2452
+ user: z.object({
2453
+ name: z.string(),
2454
+ age: z.number().min(0),
2455
+ }),
2456
+ settings: z.object({
2457
+ theme: z.enum(['light', 'dark']),
2458
+ }),
2459
+ })
2460
+
2461
+ const componentPropsSchema: ComponentPropsSchema = {
2462
+ Heading: ComplexSchema,
2463
+ }
2464
+
2465
+ const code = dedent`
2466
+ <Heading user={{ name: "test", age: -1 }} settings={{ theme: "invalid" }}>Complex validation</Heading>
2467
+ `
2468
+
2469
+ expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
2470
+ {
2471
+ "errors": [
2472
+ {
2473
+ "line": 1,
2474
+ "message": "Expressions in jsx props are not supported (user={{ name: "test", age: -1 }})",
2475
+ },
2476
+ {
2477
+ "line": 1,
2478
+ "message": "Expressions in jsx props are not supported (settings={{ theme: "invalid" }})",
2479
+ },
2480
+ {
2481
+ "line": 1,
2482
+ "message": "Invalid props for component "Heading" at "user": Required",
2483
+ "schemaPath": "user",
2484
+ },
2485
+ {
2486
+ "line": 1,
2487
+ "message": "Invalid props for component "Heading" at "settings": Required",
2488
+ "schemaPath": "settings",
2489
+ },
2490
+ ],
2491
+ "html": "<h1>Complex validation</h1>",
2492
+ "result": <React.Fragment>
2493
+ <Heading>
2494
+ Complex validation
2495
+ </Heading>
2496
+ </React.Fragment>,
2497
+ }
2498
+ `)
2499
+ })