@orgajs/orgx 2.5.2 → 2.6.1

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.
@@ -40,17 +40,17 @@
40
40
  * @property {EstreeFunction} node
41
41
  */
42
42
 
43
- import { stringifyPosition } from 'unist-util-stringify-position'
44
- import { positionFromEstree } from 'unist-util-position-from-estree'
45
43
  import { name as isIdentifierName } from 'estree-util-is-identifier-name'
46
44
  import { walk } from 'estree-walker'
47
45
  import { analyze } from 'periscopic'
46
+ import { positionFromEstree } from 'unist-util-position-from-estree'
47
+ import { stringifyPosition } from 'unist-util-stringify-position'
48
48
  import { specifiersToDeclarations } from '../util/estree-util-specifiers-to-declarations.js'
49
+ import { toBinaryAddition } from '../util/estree-util-to-binary-addition.js'
49
50
  import {
50
- toIdOrMemberExpression,
51
- toJsxIdOrMemberExpression,
51
+ toIdOrMemberExpression,
52
+ toJsxIdOrMemberExpression
52
53
  } from '../util/estree-util-to-id-or-member-expression.js'
53
- import { toBinaryAddition } from '../util/estree-util-to-binary-addition.js'
54
54
 
55
55
  const own = {}.hasOwnProperty
56
56
 
@@ -64,494 +64,494 @@ const own = {}.hasOwnProperty
64
64
  * @type {import('unified').Plugin<[RecmaJsxRewriteOptions | null | undefined] | [], Program>}
65
65
  */
66
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
- }
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?._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
555
  }
556
556
 
557
557
  /**
@@ -560,29 +560,29 @@ export function recmaJsxRewrite(options) {
560
560
  * @returns {Statement | ModuleDeclaration}
561
561
  */
562
562
  function createImportProvider(providerImportSource, outputFormat) {
563
- /** @type {Array<ImportSpecifier>} */
564
- const specifiers = [
565
- {
566
- type: 'ImportSpecifier',
567
- imported: { type: 'Identifier', name: 'useOrgComponents' },
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
- }
563
+ /** @type {Array<ImportSpecifier>} */
564
+ const specifiers = [
565
+ {
566
+ type: 'ImportSpecifier',
567
+ imported: { type: 'Identifier', name: 'useOrgComponents' },
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
586
  }
587
587
 
588
588
  /**
@@ -591,7 +591,7 @@ function createImportProvider(providerImportSource, outputFormat) {
591
591
  * @returns {boolean}
592
592
  */
593
593
  function isNamedFunction(node, name) {
594
- return Boolean(node && 'id' in node && node.id && node.id.name === name)
594
+ return Boolean(node && 'id' in node && node.id && node.id.name === name)
595
595
  }
596
596
 
597
597
  /**
@@ -600,17 +600,17 @@ function isNamedFunction(node, name) {
600
600
  * @returns {boolean}
601
601
  */
602
602
  function inScope(scope, id) {
603
- /** @type {Scope | undefined} */
604
- let currentScope = scope
603
+ /** @type {Scope | undefined} */
604
+ let currentScope = scope
605
605
 
606
- while (currentScope) {
607
- if (currentScope.declarations.has(id)) {
608
- return true
609
- }
606
+ while (currentScope) {
607
+ if (currentScope.declarations.has(id)) {
608
+ return true
609
+ }
610
610
 
611
- // @ts-expect-error: `node`s have been added when entering.
612
- currentScope = currentScope.parent
613
- }
611
+ // @ts-expect-error: `node`s have been added when entering.
612
+ currentScope = currentScope.parent
613
+ }
614
614
 
615
- return false
615
+ return false
616
616
  }