safe-mdx 1.0.4 → 1.2.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.
package/dist/safe-mdx.js CHANGED
@@ -1,15 +1,20 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { cloneElement, Suspense } from 'react';
1
+ import React, { Suspense, cloneElement } from 'react';
2
+ import Evaluate from 'eval-estree-expression';
3
3
  import { Fragment } from 'react';
4
+ import { DynamicEsmComponent } from './dynamic-esm-component.js';
5
+ import { parseEsmImports, extractComponentInfo } from './esm-parser.js';
4
6
  const HtmlToJsxConverter = React.lazy(() => import('./HtmlToJsxConverter.js').then((module) => ({
5
7
  default: module.HtmlToJsxConverter,
6
8
  })));
7
- export const SafeMdxRenderer = React.memo(function SafeMdxRenderer({ components, markdown = '', mdast = null, renderNode, }) {
9
+ export const SafeMdxRenderer = React.memo(function SafeMdxRenderer({ components, markdown = '', mdast = null, renderNode, componentPropsSchema, createElement, allowClientEsmImports = false, }) {
8
10
  const visitor = new MdastToJsx({
9
11
  markdown,
10
12
  mdast,
11
13
  components,
12
14
  renderNode,
15
+ componentPropsSchema,
16
+ createElement,
17
+ allowClientEsmImports,
13
18
  });
14
19
  const result = visitor.run();
15
20
  return result;
@@ -21,10 +26,17 @@ export class MdastToJsx {
21
26
  c;
22
27
  errors = [];
23
28
  renderNode;
24
- constructor({ markdown: code = '', mdast, components = {}, renderNode, }) {
29
+ componentPropsSchema;
30
+ createElement;
31
+ esmImports = new Map();
32
+ allowClientEsmImports;
33
+ constructor({ markdown: code = '', mdast, components = {}, renderNode, componentPropsSchema, createElement = React.createElement, allowClientEsmImports = false, }) {
25
34
  this.str = code;
26
35
  this.mdast = mdast;
27
36
  this.renderNode = renderNode;
37
+ this.componentPropsSchema = componentPropsSchema;
38
+ this.createElement = createElement;
39
+ this.allowClientEsmImports = allowClientEsmImports;
28
40
  this.c = {
29
41
  ...Object.fromEntries(nativeTags.map((tag) => {
30
42
  return [tag, tag];
@@ -32,6 +44,30 @@ export class MdastToJsx {
32
44
  ...components,
33
45
  };
34
46
  }
47
+ validateComponentProps(componentName, props, line) {
48
+ if (!this.componentPropsSchema ||
49
+ !this.componentPropsSchema[componentName]) {
50
+ return;
51
+ }
52
+ const schema = this.componentPropsSchema[componentName];
53
+ let result = schema['~standard'].validate(props);
54
+ if (result instanceof Promise) {
55
+ // Ignore async validation errors as requested
56
+ return;
57
+ }
58
+ else {
59
+ if (result.issues) {
60
+ result.issues.forEach((issue) => {
61
+ const propPath = issue.path?.join('.') || 'unknown';
62
+ this.errors.push({
63
+ message: `Invalid props for component "${componentName}" at "${propPath}": ${issue.message}`,
64
+ line,
65
+ schemaPath: issue.path?.join('.'),
66
+ });
67
+ });
68
+ }
69
+ }
70
+ }
35
71
  mapMdastChildren(node) {
36
72
  const res = node.children
37
73
  ?.flatMap((child) => this.mdastTransformer(child))
@@ -76,24 +112,253 @@ export class MdastToJsx {
76
112
  if (!node.name) {
77
113
  return [];
78
114
  }
79
- const Component = accessWithDot(this.c, node.name);
80
- if (!Component) {
81
- this.errors.push({
82
- message: `Unsupported jsx component ${node.name}`,
115
+ // Check if this is an ESM imported component (only if allowed)
116
+ const esmImportInfo = this.allowClientEsmImports ? this.esmImports.get(node.name) : null;
117
+ let Component;
118
+ if (esmImportInfo) {
119
+ // Handle ESM imported component
120
+ const { importUrl, componentName } = extractComponentInfo(esmImportInfo);
121
+ Component = DynamicEsmComponent;
122
+ let attrsList = this.getJsxAttrs(node, (err) => {
123
+ this.errors.push(err);
83
124
  });
84
- return null;
125
+ let attrs = Object.fromEntries(attrsList);
126
+ return this.createElement(Component, { ...attrs, importUrl, componentName }, this.mapJsxChildren(node));
127
+ }
128
+ else {
129
+ Component = accessWithDot(this.c, node.name);
130
+ if (!Component) {
131
+ this.errors.push({
132
+ message: `Unsupported jsx component ${node.name}`,
133
+ line: node.position?.start?.line,
134
+ });
135
+ return null;
136
+ }
85
137
  }
86
- let attrsList = getJsxAttrs(node, (err) => {
138
+ let attrsList = this.getJsxAttrs(node, (err) => {
87
139
  this.errors.push(err);
88
140
  });
89
141
  let attrs = Object.fromEntries(attrsList);
90
- return (_jsx(Component, { ...attrs, children: this.mapJsxChildren(node) }));
142
+ // Validate component props with schema if available
143
+ this.validateComponentProps(node.name, attrs, node.position?.start?.line);
144
+ return this.createElement(Component, attrs, this.mapJsxChildren(node));
91
145
  }
92
146
  default: {
93
147
  return this.mdastTransformer(node);
94
148
  }
95
149
  }
96
150
  }
151
+ transformJsxElement(jsxElement, onError, line) {
152
+ try {
153
+ // Handle JSX opening element
154
+ if (jsxElement.openingElement) {
155
+ const tagName = jsxElement.openingElement.name?.type === 'JSXIdentifier' ? jsxElement.openingElement.name.name : null;
156
+ if (!tagName) {
157
+ onError?.({
158
+ message: 'JSX element missing component name',
159
+ line: line,
160
+ });
161
+ return null;
162
+ }
163
+ // Check if this is an ESM imported component (only if allowed)
164
+ const esmImportInfo = this.allowClientEsmImports ? this.esmImports.get(tagName) : null;
165
+ let Component;
166
+ if (esmImportInfo) {
167
+ // Handle ESM imported component
168
+ const { importUrl, componentName } = extractComponentInfo(esmImportInfo);
169
+ Component = DynamicEsmComponent;
170
+ }
171
+ else {
172
+ // Get the component from the regular component map
173
+ Component = accessWithDot(this.c, tagName);
174
+ if (!Component) {
175
+ onError?.({
176
+ message: `Unsupported jsx component ${tagName} in attribute`,
177
+ line: line,
178
+ });
179
+ return null;
180
+ }
181
+ }
182
+ // Extract attributes
183
+ const props = {};
184
+ if (jsxElement.openingElement.attributes) {
185
+ for (const attr of jsxElement.openingElement.attributes) {
186
+ if (attr.type === 'JSXAttribute' && attr.name?.type === 'JSXIdentifier' && attr.name.name) {
187
+ if (attr.value) {
188
+ if (attr.value.type === 'Literal') {
189
+ props[attr.name.name] = attr.value.value;
190
+ }
191
+ else if (attr.value.type === 'JSXExpressionContainer') {
192
+ if (attr.value.expression.type === 'Literal') {
193
+ props[attr.name.name] = attr.value.expression.value;
194
+ }
195
+ }
196
+ }
197
+ else {
198
+ props[attr.name.name] = true;
199
+ }
200
+ }
201
+ }
202
+ }
203
+ // Extract children
204
+ const children = [];
205
+ if (jsxElement.children) {
206
+ for (const child of jsxElement.children) {
207
+ if (child.type === 'JSXText') {
208
+ children.push(child.value);
209
+ }
210
+ else if (child.type === 'JSXElement') {
211
+ const childElement = this.transformJsxElement(child);
212
+ if (childElement) {
213
+ children.push(childElement);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ // Handle ESM imported components by adding required props
219
+ if (esmImportInfo) {
220
+ const { importUrl, componentName } = extractComponentInfo(esmImportInfo);
221
+ return this.createElement(Component, { ...props, importUrl, componentName }, ...children);
222
+ }
223
+ else {
224
+ return this.createElement(Component, props, ...children);
225
+ }
226
+ }
227
+ }
228
+ catch (error) {
229
+ // Return null if transformation fails
230
+ onError?.({
231
+ message: `Failed to transform JSX element: ${error instanceof Error ? error.message : 'Unknown error'}`,
232
+ line: line,
233
+ });
234
+ return null;
235
+ }
236
+ return null;
237
+ }
238
+ getJsxAttrs(node, onError = console.error) {
239
+ let attrsList = [];
240
+ for (const attr of node.attributes) {
241
+ if (attr.type === 'mdxJsxExpressionAttribute') {
242
+ // Handle spread expressions like {...{key: '1'}}
243
+ if (attr.data?.estree) {
244
+ try {
245
+ const program = attr.data.estree;
246
+ if (program.body?.length > 0 &&
247
+ program.body[0].type === 'ExpressionStatement') {
248
+ const expression = program.body[0].expression;
249
+ try {
250
+ const result = Evaluate.evaluate.sync(expression);
251
+ // Handle spread syntax - merge the evaluated object
252
+ if (typeof result === 'object' && result != null) {
253
+ const entries = Object.entries(result);
254
+ attrsList.push(...entries);
255
+ }
256
+ }
257
+ catch (error) {
258
+ onError({
259
+ message: `Failed to evaluate expression attribute: ${attr.value
260
+ .replace(/\n+/g, ' ')
261
+ .replace(/ +/g, ' ')}`,
262
+ line: attr.position?.start?.line,
263
+ });
264
+ }
265
+ }
266
+ }
267
+ catch (error) {
268
+ onError({
269
+ message: `Failed to evaluate expression attribute: ${attr.value
270
+ .replace(/\n+/g, ' ')
271
+ .replace(/ +/g, ' ')}`,
272
+ line: attr.position?.start?.line,
273
+ });
274
+ }
275
+ }
276
+ else {
277
+ onError({
278
+ message: `Expressions in jsx props are not supported (${attr.value
279
+ .replace(/\n+/g, ' ')
280
+ .replace(/ +/g, ' ')})`,
281
+ line: attr.position?.start?.line,
282
+ });
283
+ }
284
+ continue;
285
+ }
286
+ if (attr.type !== 'mdxJsxAttribute') {
287
+ onError({
288
+ message: `non mdxJsxAttribute attribute is not supported: ${attr}`,
289
+ line: node.position?.start?.line,
290
+ });
291
+ continue;
292
+ }
293
+ const v = attr.value;
294
+ if (typeof v === 'string' || typeof v === 'number') {
295
+ attrsList.push([attr.name, v]);
296
+ continue;
297
+ }
298
+ if (v === null) {
299
+ attrsList.push([attr.name, true]);
300
+ continue;
301
+ }
302
+ if (v?.type === 'mdxJsxAttributeValueExpression') {
303
+ // Manual parsing fallback for simple values
304
+ if (v.value === 'true') {
305
+ attrsList.push([attr.name, true]);
306
+ continue;
307
+ }
308
+ if (v.value === 'false') {
309
+ attrsList.push([attr.name, false]);
310
+ continue;
311
+ }
312
+ if (v.value === 'null') {
313
+ attrsList.push([attr.name, null]);
314
+ continue;
315
+ }
316
+ if (v.value === 'undefined') {
317
+ attrsList.push([attr.name, undefined]);
318
+ continue;
319
+ }
320
+ if (v.data?.estree) {
321
+ try {
322
+ // Extract the expression from the Program body
323
+ const program = v.data.estree;
324
+ if (program.body?.length > 0 &&
325
+ program.body[0].type === 'ExpressionStatement') {
326
+ const expression = program.body[0].expression;
327
+ // Check if this is a JSX element
328
+ if (expression.type === 'JSXElement') {
329
+ // Transform JSX element to React element
330
+ const jsxElement = this.transformJsxElement(expression, onError, attr.position?.start?.line);
331
+ if (jsxElement) {
332
+ attrsList.push([attr.name, jsxElement]);
333
+ continue;
334
+ }
335
+ }
336
+ try {
337
+ // Evaluate the expression synchronously
338
+ const result = Evaluate.evaluate.sync(expression);
339
+ attrsList.push([attr.name, result]);
340
+ continue;
341
+ }
342
+ catch (error) {
343
+ onError({
344
+ message: `Failed to evaluate expression attribute: ${attr.name}={${v.value}}`,
345
+ line: attr.position?.start?.line,
346
+ });
347
+ }
348
+ }
349
+ }
350
+ catch (error) {
351
+ // Fall back to the original manual parsing for backwards compatibility
352
+ }
353
+ }
354
+ onError({
355
+ message: `Expressions in jsx prop not evaluated: (${attr.name}={${v.value}})`,
356
+ line: attr.position?.start?.line,
357
+ });
358
+ }
359
+ }
360
+ return attrsList;
361
+ }
97
362
  run() {
98
363
  const res = this.mdastTransformer(this.mdast);
99
364
  if (Array.isArray(res) && res.length === 1) {
@@ -114,9 +379,13 @@ export class MdastToJsx {
114
379
  }
115
380
  switch (node.type) {
116
381
  case 'mdxjsEsm': {
117
- const start = node.position?.start?.offset;
118
- const end = node.position?.end?.offset;
119
- let text = this.str.slice(start, end);
382
+ // Parse ESM imports and merge into our imports map (only if allowed)
383
+ if (this.allowClientEsmImports) {
384
+ const parsedImports = parseEsmImports(node, (err) => this.errors.push(err));
385
+ parsedImports.forEach((value, key) => {
386
+ this.esmImports.set(key, value);
387
+ });
388
+ }
120
389
  return [];
121
390
  }
122
391
  case 'mdxJsxTextElement':
@@ -144,6 +413,34 @@ export class MdastToJsx {
144
413
  if (!node.value) {
145
414
  return [];
146
415
  }
416
+ // Check if we have an estree AST
417
+ if (node.data?.estree) {
418
+ try {
419
+ // Extract the expression from the Program body
420
+ const program = node.data.estree;
421
+ if (program.body?.length > 0 &&
422
+ program.body[0].type === 'ExpressionStatement') {
423
+ const expression = program.body[0].expression;
424
+ try {
425
+ // Evaluate the expression synchronously
426
+ const result = Evaluate.evaluate.sync(expression);
427
+ return result;
428
+ }
429
+ catch (error) {
430
+ this.errors.push({
431
+ message: `Failed to evaluate expression: ${node.value}`,
432
+ line: node.position?.start?.line,
433
+ });
434
+ }
435
+ }
436
+ }
437
+ catch (error) {
438
+ this.errors.push({
439
+ message: `Failed to evaluate expression: ${node.value}`,
440
+ line: node.position?.start?.line,
441
+ });
442
+ }
443
+ }
147
444
  return [];
148
445
  }
149
446
  case 'yaml': {
@@ -155,16 +452,16 @@ export class MdastToJsx {
155
452
  case 'heading': {
156
453
  const level = node.depth;
157
454
  const Tag = this.c[`h${level}`] ?? `h${level}`;
158
- return (_jsx(Tag, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
455
+ return this.createElement(Tag, node.data?.hProperties, this.mapMdastChildren(node));
159
456
  }
160
457
  case 'paragraph': {
161
- return (_jsx(this.c.p, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
458
+ return this.createElement(this.c.p, node.data?.hProperties, this.mapMdastChildren(node));
162
459
  }
163
460
  case 'blockquote': {
164
- return (_jsx(this.c.blockquote, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
461
+ return this.createElement(this.c.blockquote, node.data?.hProperties, this.mapMdastChildren(node));
165
462
  }
166
463
  case 'thematicBreak': {
167
- return _jsx(this.c.hr, { ...node.data?.hProperties });
464
+ return this.createElement(this.c.hr, node.data?.hProperties);
168
465
  }
169
466
  case 'code': {
170
467
  if (!node.value) {
@@ -172,7 +469,7 @@ export class MdastToJsx {
172
469
  }
173
470
  const language = node.lang || '';
174
471
  const code = node.value;
175
- const codeBlock = (className) => (_jsx(this.c.pre, { ...node.data?.hProperties, children: _jsx(this.c.code, { className: className, children: code }) }));
472
+ const codeBlock = (className) => this.createElement(this.c.pre, node.data?.hProperties, this.createElement(this.c.code, { className }, code));
176
473
  if (language) {
177
474
  return codeBlock(`language-${language}`);
178
475
  }
@@ -180,23 +477,26 @@ export class MdastToJsx {
180
477
  }
181
478
  case 'list': {
182
479
  if (node.ordered) {
183
- return (_jsx(this.c.ol, { start: node.start, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
480
+ return this.createElement(this.c.ol, { start: node.start, ...node.data?.hProperties }, this.mapMdastChildren(node));
184
481
  }
185
- return (_jsx(this.c.ul, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
482
+ return this.createElement(this.c.ul, node.data?.hProperties, this.mapMdastChildren(node));
186
483
  }
187
484
  case 'listItem': {
188
485
  // https://github.com/syntax-tree/mdast-util-gfm-task-list-item#syntax-tree
189
486
  if (node?.checked != null) {
190
- return (_jsx(this.c.li, { "data-checked": node.checked, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
487
+ return this.createElement(this.c.li, {
488
+ 'data-checked': node.checked,
489
+ ...node.data?.hProperties,
490
+ }, this.mapMdastChildren(node));
191
491
  }
192
- return (_jsx(this.c.li, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
492
+ return this.createElement(this.c.li, node.data?.hProperties, this.mapMdastChildren(node));
193
493
  }
194
494
  case 'text': {
195
495
  if (!node.value) {
196
496
  return [];
197
497
  }
198
498
  if (node.data?.hProperties) {
199
- return (_jsx(this.c.span, { ...node.data.hProperties, children: node.value }));
499
+ return this.createElement(this.c.span, node.data.hProperties, node.value);
200
500
  }
201
501
  return node.value;
202
502
  }
@@ -204,60 +504,68 @@ export class MdastToJsx {
204
504
  const src = node.url || '';
205
505
  const alt = node.alt || '';
206
506
  const title = node.title || '';
207
- return (_jsx(this.c.img, { src: src, alt: alt, title: title, ...node.data?.hProperties }));
507
+ return this.createElement(this.c.img, {
508
+ src,
509
+ alt,
510
+ title,
511
+ ...node.data?.hProperties,
512
+ });
208
513
  }
209
514
  case 'link': {
210
515
  const href = node.url || '';
211
516
  const title = node.title || '';
212
- return (_jsx(this.c.a, { href, title, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
517
+ return this.createElement(this.c.a, { href, title, ...node.data?.hProperties }, this.mapMdastChildren(node));
213
518
  }
214
519
  case 'strong': {
215
- return (_jsx(this.c.strong, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
520
+ return this.createElement(this.c.strong, node.data?.hProperties, this.mapMdastChildren(node));
216
521
  }
217
522
  case 'emphasis': {
218
- return (_jsx(this.c.em, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
523
+ return this.createElement(this.c.em, node.data?.hProperties, this.mapMdastChildren(node));
219
524
  }
220
525
  case 'delete': {
221
- return (_jsx(this.c.del, { ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
526
+ return this.createElement(this.c.del, node.data?.hProperties, this.mapMdastChildren(node));
222
527
  }
223
528
  case 'inlineCode': {
224
529
  if (!node.value) {
225
530
  return [];
226
531
  }
227
- return (_jsx(this.c.code, { ...node.data?.hProperties, children: node.value }));
532
+ return this.createElement(this.c.code, node.data?.hProperties, node.value);
228
533
  }
229
534
  case 'break': {
230
- return _jsx(this.c.br, { ...node.data?.hProperties });
535
+ return this.createElement(this.c.br, node.data?.hProperties);
231
536
  }
232
537
  case 'root': {
233
538
  if (node.data?.hProperties) {
234
- return (_jsx(this.c.div, { ...node.data.hProperties, children: this.mapMdastChildren(node) }));
539
+ return this.createElement(this.c.div, node.data.hProperties, this.mapMdastChildren(node));
235
540
  }
236
- return _jsx(Fragment, { children: this.mapMdastChildren(node) });
541
+ return this.createElement(Fragment, null, this.mapMdastChildren(node));
237
542
  }
238
543
  case 'table': {
239
544
  const [head, ...body] = React.Children.toArray(this.mapMdastChildren(node));
240
- return (_jsxs(this.c.table, { ...node.data?.hProperties, children: [head && _jsx(this.c.thead, { children: head }), !!body?.length && _jsx(this.c.tbody, { children: body })] }));
545
+ return this.createElement(this.c.table, node.data?.hProperties, head && this.createElement(this.c.thead, null, head), !!body?.length &&
546
+ this.createElement(this.c.tbody, null, body));
241
547
  }
242
548
  case 'tableRow': {
243
- return (_jsx(this.c.tr, { className: '', ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
549
+ return this.createElement(this.c.tr, { className: '', ...node.data?.hProperties }, this.mapMdastChildren(node));
244
550
  }
245
551
  case 'tableCell': {
246
552
  let content = this.mapMdastChildren(node);
247
- return (_jsx(this.c.td, { className: '', ...node.data?.hProperties, children: content }));
553
+ return this.createElement(this.c.td, { className: '', ...node.data?.hProperties }, content);
248
554
  }
249
555
  case 'definition': {
250
556
  return [];
251
557
  }
252
558
  case 'linkReference': {
253
559
  let href = '';
560
+ let title = '';
254
561
  mdastBfs(this.mdast, (child) => {
255
562
  if (child.type === 'definition' &&
256
563
  child.identifier === node.identifier) {
257
- href = child.url;
564
+ href = child.url || '';
565
+ title = child.title || '';
258
566
  }
259
567
  });
260
- return (_jsx(this.c.a, { href: href, ...node.data?.hProperties, children: this.mapMdastChildren(node) }));
568
+ return this.createElement(this.c.a, { href, title, ...node.data?.hProperties }, this.mapMdastChildren(node));
261
569
  }
262
570
  case 'footnoteReference': {
263
571
  return [];
@@ -272,7 +580,11 @@ export class MdastToJsx {
272
580
  if (!text) {
273
581
  return [];
274
582
  }
275
- return (_jsx(Suspense, { fallback: null, children: _jsx(HtmlToJsxConverter, { htmlText: text, instance: this, node: node }) }));
583
+ return this.createElement(Suspense, { fallback: null }, this.createElement(HtmlToJsxConverter, {
584
+ htmlText: text,
585
+ instance: this,
586
+ node,
587
+ }));
276
588
  }
277
589
  case 'imageReference': {
278
590
  return [];
@@ -287,68 +599,6 @@ export class MdastToJsx {
287
599
  }
288
600
  }
289
601
  }
290
- export function getJsxAttrs(node, onError = console.error) {
291
- let attrsList = node.attributes
292
- .map((attr) => {
293
- if (attr.type === 'mdxJsxExpressionAttribute') {
294
- onError({
295
- message: `Expressions in jsx props are not supported (${attr.value
296
- .replace(/\n+/g, ' ')
297
- .replace(/ +/g, ' ')})`,
298
- });
299
- return;
300
- }
301
- if (attr.type !== 'mdxJsxAttribute') {
302
- throw new Error(`non mdxJsxAttribute is not supported: ${attr}`);
303
- }
304
- const v = attr.value;
305
- if (typeof v === 'string' || typeof v === 'number') {
306
- return [attr.name, v];
307
- }
308
- if (v === null) {
309
- return [attr.name, true];
310
- }
311
- if (v?.type === 'mdxJsxAttributeValueExpression') {
312
- if (v.value === 'true') {
313
- return [attr.name, true];
314
- }
315
- if (v.value === 'false') {
316
- return [attr.name, false];
317
- }
318
- if (v.value === 'null') {
319
- return [attr.name, null];
320
- }
321
- if (v.value === 'undefined') {
322
- return [attr.name, undefined];
323
- }
324
- let quote = ['"', "'", '`'].find((q) => v.value.startsWith(q) && v.value.endsWith(q));
325
- if (quote) {
326
- let value = v.value;
327
- if (quote !== '"') {
328
- value = v.value.replace(new RegExp(quote, 'g'), '"');
329
- }
330
- return [attr.name, JSON.parse(value)];
331
- }
332
- const number = Number(v.value);
333
- if (!isNaN(number)) {
334
- return [attr.name, number];
335
- }
336
- const parsedJson = safeJsonParse(v.value);
337
- if (parsedJson) {
338
- return [attr.name, parsedJson];
339
- }
340
- onError({
341
- message: `Expressions in jsx props are not supported (${attr.name}={${v.value}})`,
342
- });
343
- }
344
- else {
345
- console.log('unhandled attr', { attr }, attr.type);
346
- }
347
- return;
348
- })
349
- .filter(isTruthy);
350
- return attrsList;
351
- }
352
602
  function isTruthy(val) {
353
603
  return Boolean(val);
354
604
  }