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