@orgajs/orgx 1.0.7 → 2.0.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.
Files changed (113) hide show
  1. package/LICENSE.org +22 -0
  2. package/dist/index.d.ts +11 -5
  3. package/dist/lib/compile.d.ts +45 -0
  4. package/dist/lib/condition.browser.d.ts +1 -0
  5. package/dist/lib/condition.d.ts +1 -0
  6. package/dist/lib/core.d.ts +57 -0
  7. package/dist/lib/evaluate.d.ts +27 -0
  8. package/dist/lib/plugin/recma-document.d.ts +63 -0
  9. package/dist/lib/plugin/recma-jsx-build.d.ts +14 -0
  10. package/dist/lib/plugin/recma-jsx-rewrite.d.ts +50 -0
  11. package/dist/lib/plugin/recma-stringify.d.ts +13 -0
  12. package/dist/lib/plugin/rehype-recma.d.ts +49 -0
  13. package/dist/lib/run.d.ts +26 -0
  14. package/dist/lib/util/estree-util-create.d.ts +13 -0
  15. package/dist/lib/util/estree-util-declaration-to-expression.d.ts +19 -0
  16. package/dist/lib/util/estree-util-is-declaration.d.ts +15 -0
  17. package/dist/lib/util/estree-util-specifiers-to-declarations.d.ts +14 -0
  18. package/dist/lib/util/estree-util-to-binary-addition.d.ts +8 -0
  19. package/dist/lib/util/estree-util-to-id-or-member-expression.d.ts +11 -0
  20. package/dist/lib/util/render-error.d.ts +2 -0
  21. package/dist/lib/util/resolve-evaluate-options.d.ts +66 -0
  22. package/dist/lib/util/resolve-file-and-options.d.ts +16 -0
  23. package/index.js +14 -0
  24. package/lib/compile.js +54 -0
  25. package/lib/condition.browser.js +1 -0
  26. package/lib/condition.js +3 -0
  27. package/lib/core.js +100 -0
  28. package/lib/evaluate.js +41 -0
  29. package/lib/plugin/recma-document.js +607 -0
  30. package/lib/plugin/recma-jsx-build.js +53 -0
  31. package/lib/plugin/recma-jsx-rewrite.js +616 -0
  32. package/lib/plugin/recma-stringify.js +42 -0
  33. package/lib/plugin/rehype-recma.js +218 -0
  34. package/lib/run.js +31 -0
  35. package/lib/types.d.ts +46 -0
  36. package/lib/util/estree-util-create.js +27 -0
  37. package/lib/util/estree-util-declaration-to-expression.js +32 -0
  38. package/lib/util/estree-util-is-declaration.js +20 -0
  39. package/lib/util/estree-util-specifiers-to-declarations.js +90 -0
  40. package/lib/util/estree-util-to-binary-addition.js +23 -0
  41. package/lib/util/estree-util-to-id-or-member-expression.js +108 -0
  42. package/lib/util/render-error.js +91 -0
  43. package/lib/util/resolve-evaluate-options.js +64 -0
  44. package/lib/util/resolve-file-and-options.js +40 -0
  45. package/package.json +36 -29
  46. package/CHANGELOG.md +0 -71
  47. package/dist/compile.d.ts +0 -6
  48. package/dist/compile.d.ts.map +0 -1
  49. package/dist/compile.js +0 -12
  50. package/dist/compile.js.map +0 -1
  51. package/dist/estree/create.d.ts +0 -2
  52. package/dist/estree/create.d.ts.map +0 -1
  53. package/dist/estree/create.js +0 -14
  54. package/dist/estree/create.js.map +0 -1
  55. package/dist/estree/declaration-to-expression.d.ts +0 -3
  56. package/dist/estree/declaration-to-expression.d.ts.map +0 -1
  57. package/dist/estree/declaration-to-expression.js +0 -12
  58. package/dist/estree/declaration-to-expression.js.map +0 -1
  59. package/dist/estree/error.d.ts +0 -61
  60. package/dist/estree/error.d.ts.map +0 -1
  61. package/dist/estree/error.js +0 -87
  62. package/dist/estree/error.js.map +0 -1
  63. package/dist/estree/is-declaration.d.ts +0 -3
  64. package/dist/estree/is-declaration.d.ts.map +0 -1
  65. package/dist/estree/is-declaration.js +0 -9
  66. package/dist/estree/is-declaration.js.map +0 -1
  67. package/dist/estree/position-from-estree.d.ts +0 -13
  68. package/dist/estree/position-from-estree.d.ts.map +0 -1
  69. package/dist/estree/position-from-estree.js +0 -34
  70. package/dist/estree/position-from-estree.js.map +0 -1
  71. package/dist/estree/specifiers-to-object-pattern.d.ts +0 -5
  72. package/dist/estree/specifiers-to-object-pattern.d.ts.map +0 -1
  73. package/dist/estree/specifiers-to-object-pattern.js +0 -33
  74. package/dist/estree/specifiers-to-object-pattern.js.map +0 -1
  75. package/dist/evaluate.d.ts +0 -22
  76. package/dist/evaluate.d.ts.map +0 -1
  77. package/dist/evaluate.js +0 -27
  78. package/dist/evaluate.js.map +0 -1
  79. package/dist/index.d.ts.map +0 -1
  80. package/dist/index.js +0 -11
  81. package/dist/index.js.map +0 -1
  82. package/dist/plugin/estree-jsx-build.d.ts +0 -6
  83. package/dist/plugin/estree-jsx-build.d.ts.map +0 -1
  84. package/dist/plugin/estree-jsx-build.js +0 -38
  85. package/dist/plugin/estree-jsx-build.js.map +0 -1
  86. package/dist/plugin/estree-jsx-rewrite.d.ts +0 -6
  87. package/dist/plugin/estree-jsx-rewrite.d.ts.map +0 -1
  88. package/dist/plugin/estree-jsx-rewrite.js +0 -214
  89. package/dist/plugin/estree-jsx-rewrite.js.map +0 -1
  90. package/dist/plugin/estree-stringify.d.ts +0 -2
  91. package/dist/plugin/estree-stringify.d.ts.map +0 -1
  92. package/dist/plugin/estree-stringify.js +0 -127
  93. package/dist/plugin/estree-stringify.js.map +0 -1
  94. package/dist/plugin/estree-wrap-in-content.d.ts +0 -18
  95. package/dist/plugin/estree-wrap-in-content.d.ts.map +0 -1
  96. package/dist/plugin/estree-wrap-in-content.js +0 -375
  97. package/dist/plugin/estree-wrap-in-content.js.map +0 -1
  98. package/dist/plugin/rehype-estree.d.ts +0 -12
  99. package/dist/plugin/rehype-estree.d.ts.map +0 -1
  100. package/dist/plugin/rehype-estree.js +0 -122
  101. package/dist/plugin/rehype-estree.js.map +0 -1
  102. package/dist/plugin/rehype-set-layout.d.ts +0 -6
  103. package/dist/plugin/rehype-set-layout.d.ts.map +0 -1
  104. package/dist/plugin/rehype-set-layout.js +0 -30
  105. package/dist/plugin/rehype-set-layout.js.map +0 -1
  106. package/dist/processor.d.ts +0 -14
  107. package/dist/processor.d.ts.map +0 -1
  108. package/dist/processor.js +0 -52
  109. package/dist/processor.js.map +0 -1
  110. package/dist/utils/remove-quotes.d.ts +0 -3
  111. package/dist/utils/remove-quotes.d.ts.map +0 -1
  112. package/dist/utils/remove-quotes.js +0 -6
  113. package/dist/utils/remove-quotes.js.map +0 -1
@@ -0,0 +1,616 @@
1
+ /**
2
+ * @typedef {import('estree-jsx').Expression} Expression
3
+ * @typedef {import('estree-jsx').Function} EstreeFunction
4
+ * @typedef {import('estree-jsx').Identifier} Identifier
5
+ * @typedef {import('estree-jsx').ImportSpecifier} ImportSpecifier
6
+ * @typedef {import('estree-jsx').JSXElement} JSXElement
7
+ * @typedef {import('estree-jsx').ModuleDeclaration} ModuleDeclaration
8
+ * @typedef {import('estree-jsx').Node} Node
9
+ * @typedef {import('estree-jsx').ObjectPattern} ObjectPattern
10
+ * @typedef {import('estree-jsx').Program} Program
11
+ * @typedef {import('estree-jsx').Property} Property
12
+ * @typedef {import('estree-jsx').Statement} Statement
13
+ * @typedef {import('estree-jsx').VariableDeclarator} VariableDeclarator
14
+ *
15
+ * @typedef {import('periscopic').Scope & {node: Node}} Scope
16
+ */
17
+
18
+ /**
19
+ * @typedef RecmaJsxRewriteOptions
20
+ * Configuration for internal plugin `recma-jsx-rewrite`.
21
+ * @property {'function-body' | 'program' | null | undefined} [outputFormat='program']
22
+ * Whether to use an import statement or `arguments[0]` to get the provider.
23
+ * @property {string | null | undefined} [providerImportSource]
24
+ * Place to import a provider from.
25
+ * @property {boolean | null | undefined} [development=false]
26
+ * Whether to add extra info to error messages in generated code.
27
+ *
28
+ * This also results in the development automatic JSX runtime
29
+ * (`/jsx-dev-runtime`, `jsxDEV`) being used, which passes positional info to
30
+ * nodes.
31
+ * The default can be set to `true` in Node.js through environment variables:
32
+ * set `NODE_ENV=development`.
33
+ *
34
+ * @typedef StackEntry
35
+ * @property {Array<string>} objects
36
+ * @property {Array<string>} components
37
+ * @property {Array<string>} tags
38
+ * @property {Record<string, {node: JSXElement, component: boolean}>} references
39
+ * @property {Map<string|number, string>} idToInvalidComponentName
40
+ * @property {EstreeFunction} node
41
+ */
42
+
43
+ import { stringifyPosition } from 'unist-util-stringify-position'
44
+ import { positionFromEstree } from 'unist-util-position-from-estree'
45
+ import { name as isIdentifierName } from 'estree-util-is-identifier-name'
46
+ import { walk } from 'estree-walker'
47
+ import { analyze } from 'periscopic'
48
+ import { specifiersToDeclarations } from '../util/estree-util-specifiers-to-declarations.js'
49
+ import {
50
+ toIdOrMemberExpression,
51
+ toJsxIdOrMemberExpression,
52
+ } from '../util/estree-util-to-id-or-member-expression.js'
53
+ import { toBinaryAddition } from '../util/estree-util-to-binary-addition.js'
54
+
55
+ const own = {}.hasOwnProperty
56
+
57
+ /**
58
+ * A plugin that rewrites JSX in functions to accept components as
59
+ * `props.components` (when the function is called `_createOrgContent`), or from
60
+ * a provider (if there is one).
61
+ * It also makes sure that any undefined components are defined: either from
62
+ * received components or as a function that throws an error.
63
+ *
64
+ * @type {import('unified').Plugin<[RecmaJsxRewriteOptions | null | undefined] | [], Program>}
65
+ */
66
+ export function recmaJsxRewrite(options) {
67
+ // Always given inside `@mdx-js/mdx`
68
+ /* c8 ignore next */
69
+ const { development, providerImportSource, outputFormat } = options || {}
70
+
71
+ return (tree, file) => {
72
+ // Find everything that’s defined in the top-level scope.
73
+ const scopeInfo = analyze(tree)
74
+ /** @type {Array<StackEntry>} */
75
+ const fnStack = []
76
+ let importProvider = false
77
+ let createErrorHelper = false
78
+ /** @type {Scope | undefined} */
79
+ let currentScope
80
+
81
+ walk(tree, {
82
+ enter(node) {
83
+ const newScope = /** @type {Scope | undefined} */ (
84
+ scopeInfo.map.get(node)
85
+ )
86
+
87
+ if (
88
+ node.type === 'FunctionDeclaration' ||
89
+ node.type === 'FunctionExpression' ||
90
+ node.type === 'ArrowFunctionExpression'
91
+ ) {
92
+ fnStack.push({
93
+ objects: [],
94
+ components: [],
95
+ tags: [],
96
+ references: {},
97
+ idToInvalidComponentName: new Map(),
98
+ node,
99
+ })
100
+
101
+ // OrgContent only ever contains OrgLayout
102
+ if (
103
+ isNamedFunction(node, 'OrgContent') &&
104
+ newScope &&
105
+ !inScope(newScope, 'OrgLayout')
106
+ ) {
107
+ fnStack[0].components.push('OrgLayout')
108
+ }
109
+ }
110
+
111
+ const fnScope = fnStack[0]
112
+ if (
113
+ !fnScope ||
114
+ (!isNamedFunction(fnScope.node, '_createOrgContent') &&
115
+ !providerImportSource)
116
+ ) {
117
+ return
118
+ }
119
+
120
+ if (newScope) {
121
+ newScope.node = node
122
+ currentScope = newScope
123
+ }
124
+
125
+ if (currentScope && node.type === 'JSXElement') {
126
+ let name = node.openingElement.name
127
+
128
+ // `<x.y>`, `<Foo.Bar>`, `<x.y.z>`.
129
+ if (name.type === 'JSXMemberExpression') {
130
+ /** @type {Array<string>} */
131
+ const ids = []
132
+
133
+ // Find the left-most identifier.
134
+ while (name.type === 'JSXMemberExpression') {
135
+ ids.unshift(name.property.name)
136
+ name = name.object
137
+ }
138
+
139
+ ids.unshift(name.name)
140
+ const fullId = ids.join('.')
141
+ const id = name.name
142
+
143
+ const isInScope = inScope(currentScope, id)
144
+
145
+ if (!own.call(fnScope.references, fullId)) {
146
+ const parentScope = /** @type {Scope | undefined} */ (
147
+ currentScope.parent
148
+ )
149
+ if (
150
+ !isInScope ||
151
+ // If the parent scope is `_createOrgContent`, then this
152
+ // references a component we can add a check statement for.
153
+ (parentScope &&
154
+ parentScope.node.type === 'FunctionDeclaration' &&
155
+ isNamedFunction(parentScope.node, '_createOrgContent'))
156
+ ) {
157
+ fnScope.references[fullId] = { node, component: true }
158
+ }
159
+ }
160
+
161
+ if (!fnScope.objects.includes(id) && !isInScope) {
162
+ fnScope.objects.push(id)
163
+ }
164
+ }
165
+ // `<xml:thing>`.
166
+ else if (name.type === 'JSXNamespacedName') {
167
+ // Ignore namespaces.
168
+ }
169
+ // If the name is a valid ES identifier, and it doesn’t start with a
170
+ // lowercase letter, it’s a component.
171
+ // For example, `$foo`, `_bar`, `Baz` are all component names.
172
+ // But `foo` and `b-ar` are tag names.
173
+ else if (isIdentifierName(name.name) && !/^[a-z]/.test(name.name)) {
174
+ const id = name.name
175
+
176
+ if (!inScope(currentScope, id)) {
177
+ // No need to add an error for an undefined layout — we use an
178
+ // `if` later.
179
+ if (id !== 'OrgLayout' && !own.call(fnScope.references, id)) {
180
+ fnScope.references[id] = { node, component: true }
181
+ }
182
+
183
+ if (!fnScope.components.includes(id)) {
184
+ fnScope.components.push(id)
185
+ }
186
+ }
187
+ }
188
+ // @ts-expect-error Allow fields passed through from mdast through hast to
189
+ // esast.
190
+ else if (node.data && node.data._mdxExplicitJsx) {
191
+ // Do not turn explicit JSX into components from `_components`.
192
+ // As in, a given `h1` component is used for `# heading` (next case),
193
+ // but not for `<h1>heading</h1>`.
194
+ } else {
195
+ const id = name.name
196
+
197
+ if (!fnScope.tags.includes(id)) {
198
+ fnScope.tags.push(id)
199
+ }
200
+
201
+ /** @type {Array<string | number>} */
202
+ let jsxIdExpression = ['_components', id]
203
+ if (isIdentifierName(id) === false) {
204
+ let invalidComponentName =
205
+ fnScope.idToInvalidComponentName.get(id)
206
+ if (invalidComponentName === undefined) {
207
+ invalidComponentName = `_component${fnScope.idToInvalidComponentName.size}`
208
+ fnScope.idToInvalidComponentName.set(id, invalidComponentName)
209
+ }
210
+
211
+ jsxIdExpression = [invalidComponentName]
212
+ }
213
+
214
+ node.openingElement.name =
215
+ toJsxIdOrMemberExpression(jsxIdExpression)
216
+
217
+ if (node.closingElement) {
218
+ node.closingElement.name =
219
+ toJsxIdOrMemberExpression(jsxIdExpression)
220
+ }
221
+ }
222
+ }
223
+ },
224
+ leave(node) {
225
+ /** @type {Array<Property>} */
226
+ const defaults = []
227
+ /** @type {Array<string>} */
228
+ const actual = []
229
+ /** @type {Array<Expression>} */
230
+ const parameters = []
231
+ /** @type {Array<VariableDeclarator>} */
232
+ const declarations = []
233
+
234
+ if (currentScope && currentScope.node === node) {
235
+ // @ts-expect-error: `node`s were patched when entering.
236
+ currentScope = currentScope.parent
237
+ }
238
+
239
+ if (
240
+ node.type === 'FunctionDeclaration' ||
241
+ node.type === 'FunctionExpression' ||
242
+ node.type === 'ArrowFunctionExpression'
243
+ ) {
244
+ const fn = node
245
+ const scope = fnStack[fnStack.length - 1]
246
+ /** @type {string} */
247
+ let name
248
+
249
+ for (name of scope.tags) {
250
+ defaults.push({
251
+ type: 'Property',
252
+ kind: 'init',
253
+ key: isIdentifierName(name)
254
+ ? { type: 'Identifier', name }
255
+ : { type: 'Literal', value: name },
256
+ value: { type: 'Literal', value: name },
257
+ method: false,
258
+ shorthand: false,
259
+ computed: false,
260
+ })
261
+ }
262
+
263
+ actual.push(...scope.components)
264
+
265
+ for (name of scope.objects) {
266
+ // In some cases, a component is used directly (`<X>`) but it’s also
267
+ // used as an object (`<X.Y>`).
268
+ if (!actual.includes(name)) {
269
+ actual.push(name)
270
+ }
271
+ }
272
+
273
+ /** @type {Array<Statement>} */
274
+ const statements = []
275
+
276
+ if (
277
+ defaults.length > 0 ||
278
+ actual.length > 0 ||
279
+ scope.idToInvalidComponentName.size > 0
280
+ ) {
281
+ if (providerImportSource) {
282
+ importProvider = true
283
+ parameters.push({
284
+ type: 'CallExpression',
285
+ callee: { type: 'Identifier', name: '_provideComponents' },
286
+ arguments: [],
287
+ optional: false,
288
+ })
289
+ }
290
+
291
+ // Accept `components` as a prop if this is the `OrgContent` or
292
+ // `_createOrgContent` function.
293
+ if (
294
+ isNamedFunction(scope.node, 'OrgContent') ||
295
+ isNamedFunction(scope.node, '_createOrgContent')
296
+ ) {
297
+ parameters.push(toIdOrMemberExpression(['props', 'components']))
298
+ }
299
+
300
+ if (defaults.length > 0 || parameters.length > 1) {
301
+ parameters.unshift({
302
+ type: 'ObjectExpression',
303
+ properties: defaults,
304
+ })
305
+ }
306
+
307
+ // If we’re getting components from several sources, merge them.
308
+ /** @type {Expression} */
309
+ let componentsInit =
310
+ parameters.length > 1
311
+ ? {
312
+ type: 'CallExpression',
313
+ callee: toIdOrMemberExpression(['Object', 'assign']),
314
+ arguments: parameters,
315
+ optional: false,
316
+ }
317
+ : parameters[0].type === 'MemberExpression'
318
+ ? // If we’re only getting components from `props.components`,
319
+ // make sure it’s defined.
320
+ {
321
+ type: 'LogicalExpression',
322
+ operator: '||',
323
+ left: parameters[0],
324
+ right: { type: 'ObjectExpression', properties: [] },
325
+ }
326
+ : parameters[0]
327
+
328
+ /** @type {ObjectPattern | undefined} */
329
+ let componentsPattern
330
+
331
+ // Add components to scope.
332
+ // For `['MyComponent', 'OrgLayout']` this generates:
333
+ // ```js
334
+ // const {MyComponent, wrapper: OrgLayout} = _components
335
+ // ```
336
+ // Note that OrgLayout is special as it’s taken from
337
+ // `_components.wrapper`.
338
+ if (actual.length > 0) {
339
+ componentsPattern = {
340
+ type: 'ObjectPattern',
341
+ properties: actual.map((name) => ({
342
+ type: 'Property',
343
+ kind: 'init',
344
+ key: {
345
+ type: 'Identifier',
346
+ name: name === 'OrgLayout' ? 'wrapper' : name,
347
+ },
348
+ value: { type: 'Identifier', name },
349
+ method: false,
350
+ shorthand: name !== 'OrgLayout',
351
+ computed: false,
352
+ })),
353
+ }
354
+ }
355
+
356
+ if (scope.tags.length > 0) {
357
+ declarations.push({
358
+ type: 'VariableDeclarator',
359
+ id: { type: 'Identifier', name: '_components' },
360
+ init: componentsInit,
361
+ })
362
+ componentsInit = { type: 'Identifier', name: '_components' }
363
+ }
364
+
365
+ if (isNamedFunction(scope.node, '_createOrgContent')) {
366
+ for (const [
367
+ id,
368
+ componentName,
369
+ ] of scope.idToInvalidComponentName) {
370
+ // For JSX IDs that can’t be represented as JavaScript IDs (as in,
371
+ // those with dashes, such as `custom-element`), generate a
372
+ // separate variable that is a valid JS ID (such as `_component0`),
373
+ // and takes it from components:
374
+ // `const _component0 = _components['custom-element']`
375
+ declarations.push({
376
+ type: 'VariableDeclarator',
377
+ id: { type: 'Identifier', name: componentName },
378
+ init: {
379
+ type: 'MemberExpression',
380
+ object: { type: 'Identifier', name: '_components' },
381
+ property: { type: 'Literal', value: id },
382
+ computed: true,
383
+ optional: false,
384
+ },
385
+ })
386
+ }
387
+ }
388
+
389
+ if (componentsPattern) {
390
+ declarations.push({
391
+ type: 'VariableDeclarator',
392
+ id: componentsPattern,
393
+ init: componentsInit,
394
+ })
395
+ }
396
+
397
+ if (declarations.length > 0) {
398
+ statements.push({
399
+ type: 'VariableDeclaration',
400
+ kind: 'const',
401
+ declarations,
402
+ })
403
+ }
404
+ }
405
+
406
+ /** @type {string} */
407
+ let key
408
+
409
+ // Add partials (so for `x.y.z` it’d generate `x` and `x.y` too).
410
+ for (key in scope.references) {
411
+ if (own.call(scope.references, key)) {
412
+ const parts = key.split('.')
413
+ let index = 0
414
+ while (++index < parts.length) {
415
+ const partial = parts.slice(0, index).join('.')
416
+ if (!own.call(scope.references, partial)) {
417
+ scope.references[partial] = {
418
+ node: scope.references[key].node,
419
+ component: false,
420
+ }
421
+ }
422
+ }
423
+ }
424
+ }
425
+
426
+ const references = Object.keys(scope.references).sort()
427
+ let index = -1
428
+ while (++index < references.length) {
429
+ const id = references[index]
430
+ const info = scope.references[id]
431
+ const place = stringifyPosition(positionFromEstree(info.node))
432
+ /** @type {Array<Expression>} */
433
+ const parameters = [
434
+ { type: 'Literal', value: id },
435
+ { type: 'Literal', value: info.component },
436
+ ]
437
+
438
+ createErrorHelper = true
439
+
440
+ if (development && place !== '1:1-1:1') {
441
+ parameters.push({ type: 'Literal', value: place })
442
+ }
443
+
444
+ statements.push({
445
+ type: 'IfStatement',
446
+ test: {
447
+ type: 'UnaryExpression',
448
+ operator: '!',
449
+ prefix: true,
450
+ argument: toIdOrMemberExpression(id.split('.')),
451
+ },
452
+ consequent: {
453
+ type: 'ExpressionStatement',
454
+ expression: {
455
+ type: 'CallExpression',
456
+ callee: { type: 'Identifier', name: '_missingMdxReference' },
457
+ arguments: parameters,
458
+ optional: false,
459
+ },
460
+ },
461
+ alternate: null,
462
+ })
463
+ }
464
+
465
+ if (statements.length > 0) {
466
+ // Arrow functions with an implied return:
467
+ if (fn.body.type !== 'BlockStatement') {
468
+ fn.body = {
469
+ type: 'BlockStatement',
470
+ body: [{ type: 'ReturnStatement', argument: fn.body }],
471
+ }
472
+ }
473
+
474
+ fn.body.body.unshift(...statements)
475
+ }
476
+
477
+ fnStack.pop()
478
+ }
479
+ },
480
+ })
481
+
482
+ // If a provider is used (and can be used), import it.
483
+ if (importProvider && providerImportSource) {
484
+ tree.body.unshift(
485
+ createImportProvider(providerImportSource, outputFormat)
486
+ )
487
+ }
488
+
489
+ // If potentially missing components are used.
490
+ if (createErrorHelper) {
491
+ /** @type {Array<Expression>} */
492
+ const message = [
493
+ { type: 'Literal', value: 'Expected ' },
494
+ {
495
+ type: 'ConditionalExpression',
496
+ test: { type: 'Identifier', name: 'component' },
497
+ consequent: { type: 'Literal', value: 'component' },
498
+ alternate: { type: 'Literal', value: 'object' },
499
+ },
500
+ { type: 'Literal', value: ' `' },
501
+ { type: 'Identifier', name: 'id' },
502
+ {
503
+ type: 'Literal',
504
+ value:
505
+ '` to be defined: you likely forgot to import, pass, or provide it.',
506
+ },
507
+ ]
508
+
509
+ /** @type {Array<Identifier>} */
510
+ const parameters = [
511
+ { type: 'Identifier', name: 'id' },
512
+ { type: 'Identifier', name: 'component' },
513
+ ]
514
+
515
+ if (development) {
516
+ message.push({
517
+ type: 'ConditionalExpression',
518
+ test: { type: 'Identifier', name: 'place' },
519
+ consequent: toBinaryAddition([
520
+ { type: 'Literal', value: '\nIt’s referenced in your code at `' },
521
+ { type: 'Identifier', name: 'place' },
522
+ {
523
+ type: 'Literal',
524
+ value: (file.path ? '` in `' + file.path : '') + '`',
525
+ },
526
+ ]),
527
+ alternate: { type: 'Literal', value: '' },
528
+ })
529
+
530
+ parameters.push({ type: 'Identifier', name: 'place' })
531
+ }
532
+
533
+ tree.body.push({
534
+ type: 'FunctionDeclaration',
535
+ id: { type: 'Identifier', name: '_missingMdxReference' },
536
+ generator: false,
537
+ async: false,
538
+ params: parameters,
539
+ body: {
540
+ type: 'BlockStatement',
541
+ body: [
542
+ {
543
+ type: 'ThrowStatement',
544
+ argument: {
545
+ type: 'NewExpression',
546
+ callee: { type: 'Identifier', name: 'Error' },
547
+ arguments: [toBinaryAddition(message)],
548
+ },
549
+ },
550
+ ],
551
+ },
552
+ })
553
+ }
554
+ }
555
+ }
556
+
557
+ /**
558
+ * @param {string} providerImportSource
559
+ * @param {RecmaJsxRewriteOptions['outputFormat']} outputFormat
560
+ * @returns {Statement | ModuleDeclaration}
561
+ */
562
+ function createImportProvider(providerImportSource, outputFormat) {
563
+ /** @type {Array<ImportSpecifier>} */
564
+ const specifiers = [
565
+ {
566
+ type: 'ImportSpecifier',
567
+ imported: { type: 'Identifier', name: 'useMDXComponents' },
568
+ local: { type: 'Identifier', name: '_provideComponents' },
569
+ },
570
+ ]
571
+
572
+ return outputFormat === 'function-body'
573
+ ? {
574
+ type: 'VariableDeclaration',
575
+ kind: 'const',
576
+ declarations: specifiersToDeclarations(
577
+ specifiers,
578
+ toIdOrMemberExpression(['arguments', 0])
579
+ ),
580
+ }
581
+ : {
582
+ type: 'ImportDeclaration',
583
+ specifiers,
584
+ source: { type: 'Literal', value: providerImportSource },
585
+ }
586
+ }
587
+
588
+ /**
589
+ * @param {EstreeFunction} node
590
+ * @param {string} name
591
+ * @returns {boolean}
592
+ */
593
+ function isNamedFunction(node, name) {
594
+ return Boolean(node && 'id' in node && node.id && node.id.name === name)
595
+ }
596
+
597
+ /**
598
+ * @param {Scope} scope
599
+ * @param {string} id
600
+ * @returns {boolean}
601
+ */
602
+ function inScope(scope, id) {
603
+ /** @type {Scope | undefined} */
604
+ let currentScope = scope
605
+
606
+ while (currentScope) {
607
+ if (currentScope.declarations.has(id)) {
608
+ return true
609
+ }
610
+
611
+ // @ts-expect-error: `node`s have been added when entering.
612
+ currentScope = currentScope.parent
613
+ }
614
+
615
+ return false
616
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @typedef {import('estree-jsx').Program} Program
3
+ * @typedef {typeof import('source-map').SourceMapGenerator} SourceMapGenerator
4
+ *
5
+ * @typedef RecmaStringifyOptions
6
+ * Configuration for internal plugin `recma-stringify`.
7
+ * @property {SourceMapGenerator | null | undefined} [SourceMapGenerator]
8
+ * Generate a source map by passing a `SourceMapGenerator` from `source-map`
9
+ * in.
10
+ */
11
+
12
+ import { toJs, jsx } from 'estree-util-to-js'
13
+
14
+ /**
15
+ * A plugin that adds an esast compiler: a small wrapper around `astring` to add
16
+ * support for serializing JSX.
17
+ *
18
+ * @this {import('unified').Processor}
19
+ * @type {import('unified').Plugin<[RecmaStringifyOptions | null | undefined] | [], Program, string>}
20
+ */
21
+ export function recmaStringify(options) {
22
+ // Always given inside `@mdx-js/mdx`
23
+ /* c8 ignore next */
24
+ const { SourceMapGenerator } = options || {}
25
+
26
+ Object.assign(this, { Compiler: compiler })
27
+
28
+ /** @type {import('unified').CompilerFunction<Program, string>} */
29
+ function compiler(tree, file) {
30
+ const result = SourceMapGenerator
31
+ ? toJs(tree, {
32
+ filePath: file.path || 'unknown.mdx',
33
+ SourceMapGenerator,
34
+ handlers: jsx,
35
+ })
36
+ : toJs(tree, { handlers: jsx })
37
+
38
+ file.map = result.map
39
+
40
+ return result.value
41
+ }
42
+ }