@lexical/compiler 0.0.0-bootstrap.0 → 0.49.1-nightly.20260902.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.
@@ -0,0 +1,1725 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ var parser = require('@babel/parser');
12
+ var MagicString = require('magic-string');
13
+ var fs = require('node:fs');
14
+ var path = require('node:path');
15
+
16
+ function _interopNamespaceDefault(e) {
17
+ var n = Object.create(null);
18
+ if (e) {
19
+ for (var k in e) {
20
+ n[k] = e[k];
21
+ }
22
+ }
23
+ n.default = e;
24
+ return n;
25
+ }
26
+
27
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
28
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
29
+
30
+ /**
31
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
32
+ *
33
+ * This source code is licensed under the MIT license found in the
34
+ * LICENSE file in the root directory of this source tree.
35
+ *
36
+ */
37
+
38
+
39
+ /**
40
+ * The Lexical factories that are annotated with `__NO_SIDE_EFFECTS__` at
41
+ * their definition. That annotation is only honored by esbuild for calls in
42
+ * the same file as the definition (and not at all by webpack/terser), so
43
+ * every module-scope call site needs its own `__PURE__` annotation for
44
+ * bundlers to tree-shake unused extension/command/rule definitions out of
45
+ * application bundles.
46
+ *
47
+ * Argument-position calls (e.g. `configExtension` / `safeCast` inside a
48
+ * `defineExtension` config) matter too: a pure call is only removable when
49
+ * its arguments are also side-effect-free, so one unannotated nested call
50
+ * pins the entire enclosing definition.
51
+ *
52
+ * @type {ReadonlyArray<string>}
53
+ */
54
+ const PURE_FACTORY_FUNCTIONS$1 = [
55
+ 'configExtension',
56
+ 'createCommand',
57
+ 'createContextState',
58
+ 'createImportState',
59
+ 'createRenderState',
60
+ 'createState',
61
+ 'declarePeerDependency',
62
+ 'defineExtension',
63
+ 'defineImportRule',
64
+ 'defineOverlayRules',
65
+ 'domOverride',
66
+ 'safeCast',
67
+ ];
68
+
69
+ /**
70
+ * The factories whose implementation is a trivial expression over their own
71
+ * arguments, and the shape a call to them can be replaced with. Eliding the
72
+ * call is better than annotating it: a literal is inert for every bundler,
73
+ * with no annotation to preserve and nothing left to pin the definition it
74
+ * appears in.
75
+ *
76
+ * Each of these is marked `@lexical-inline <form>` where it is defined, and
77
+ * the unit tests check both that the marker and this table agree and that
78
+ * the real function still returns what the form claims. `createCommand` is
79
+ * deliberately not here: `{type: x}` is more bytes than a call to a minified
80
+ * one-character name.
81
+ *
82
+ * - `identity` returns its single argument
83
+ * - `args` returns all of its arguments as an array
84
+ *
85
+ * @type {ReadonlyMap<string, {form: 'args' | 'identity'}>}
86
+ */
87
+ const INLINE_FACTORIES = new Map([
88
+ ['configExtension', {form: 'args'}],
89
+ ['declarePeerDependency', {form: 'args'}],
90
+ ['defineExtension', {form: 'identity'}],
91
+ ['defineImportRule', {form: 'identity'}],
92
+ ['safeCast', {form: 'identity'}],
93
+ ]);
94
+
95
+ /** The marker a definition uses to declare itself inlinable. */
96
+ // A JSDoc tag, so it has to start its line (after the comment's own `*`).
97
+ // Prose that merely mentions the tag — including this file's own
98
+ // documentation of it — is not a marker.
99
+ const INLINE_MARKER_RE =
100
+ /^[^\S\n]*\*?[^\S\n]*@lexical-inline(?:[^\S\n]+(\S+))?[^\S\n]*$/m;
101
+
102
+ /**
103
+ * An error the author has to act on — a malformed marker, or a call this
104
+ * transform cannot prove is safe — as opposed to source it cannot parse.
105
+ * The plugin passes an unparseable module through with a warning; these it
106
+ * rethrows, because ignoring them would silently produce the bundle the
107
+ * author was trying to avoid.
108
+ */
109
+ class PureAnnotationsError extends Error {
110
+ /** @param {string} message */
111
+ constructor(message) {
112
+ super(message);
113
+ this.name = 'PureAnnotationsError';
114
+ }
115
+ }
116
+
117
+ // The tag that declares an object's methods side-effect free, so that a
118
+ // namespace reached through a relative import or a local alias — as
119
+ // @lexical/html's own modules reach `selBase` — is recognized without
120
+ // having to be listed in the table above.
121
+ const NAMESPACE_MARKER_RE =
122
+ /^[^\S\n]*\*?[^\S\n]*@lexical-pure-namespace[^\S\n]*$/m;
123
+
124
+ /** The forms a `@lexical-inline` marker may name. */
125
+ const INLINE_FORMS = new Set(['args', 'identity']);
126
+
127
+ /**
128
+ * Imported objects whose method calls are side-effect free, so that a
129
+ * module-scope call like `sel.tag('p').attr('data-x', true)` is annotated
130
+ * the same way a call to a factory function is. Only the outermost call of
131
+ * a chain is annotated: rollup, terser and esbuild all drop the whole chain
132
+ * from that one annotation.
133
+ *
134
+ * As with the factories, the name is only trusted when it is imported from
135
+ * a Lexical package.
136
+ */
137
+ const PURE_NAMESPACES$1 = ['sel'];
138
+
139
+ /**
140
+ * The form each inlinable factory is replaced with, for the tests that keep
141
+ * this table honest and for anyone who wants to know what `inline` does.
142
+ *
143
+ * @type {ReadonlyMap<string, string>}
144
+ */
145
+ const INLINE_FACTORY_FORMS$1 = new Map(
146
+ Array.from(INLINE_FACTORIES, ([name, spec]) => [name, spec.form]),
147
+ );
148
+
149
+ /**
150
+ * The annotation inserted before a module-scope factory call. No trailing
151
+ * space: a block comment is a token separator on its own, so the space would
152
+ * only be there to be stripped again by the minifier.
153
+ */
154
+ const ANNOTATION = '/* @__PURE__ */';
155
+
156
+ /** Matches the contents of a comment that is already a pure annotation. */
157
+ /**
158
+ * `overwrite` clears the intro and outro of every chunk it spans unless it is
159
+ * told to touch the content only — and an annotation this transform appended
160
+ * to a nested call lives in the outro of the chunk that ends where that call
161
+ * begins. Replacing a call's `factory(` with `([` would take the annotation
162
+ * on its first argument with it.
163
+ *
164
+ * @type {{contentOnly: true}}
165
+ */
166
+ const CONTENT_ONLY = {contentOnly: true};
167
+
168
+ const PURE_ANNOTATION_RE = /[#@]__PURE__/;
169
+
170
+ /** The file extensions the bundler plugin transforms by default. */
171
+ const DEFAULT_INCLUDE = /\.(?:[cm]?[jt]sx?)$/;
172
+
173
+ /**
174
+ * Module specifiers whose exports are trusted to be the Lexical factories.
175
+ * Every factory in PURE_FACTORY_FUNCTIONS is declared side-effect free in
176
+ * these packages, so an import from one of them is evidence enough. A call
177
+ * to a same-named function from anywhere else is only annotated when the
178
+ * declaration it resolves to is itself marked side-effect free.
179
+ */
180
+ const DEFAULT_SOURCES = [/^lexical$/, /^@lexical\//];
181
+
182
+ /** Matches the annotation a definition uses to declare itself pure. */
183
+ const NO_SIDE_EFFECTS_RE = /[#@]__NO_SIDE_EFFECTS__/;
184
+
185
+ /** An import of a module next to this one, which may declare a namespace. */
186
+ const RELATIVE_IMPORT_RE = /\bfrom\s*['"]\./;
187
+
188
+ /**
189
+ * Expression types that read the same with or without the parentheses the
190
+ * call they replace was written with: they are a single delimited token, so
191
+ * no surrounding operator can bind into them. Anything else keeps the
192
+ * parentheses, which costs two characters a minifier removes anyway.
193
+ */
194
+ const SELF_DELIMITING_TYPES = new Set([
195
+ 'ArrayExpression',
196
+ 'BigIntLiteral',
197
+ 'BooleanLiteral',
198
+ 'DecimalLiteral',
199
+ 'Identifier',
200
+ 'JSXElement',
201
+ 'JSXFragment',
202
+ 'NullLiteral',
203
+ 'NumericLiteral',
204
+ 'ObjectExpression',
205
+ 'RegExpLiteral',
206
+ 'StringLiteral',
207
+ 'TemplateLiteral',
208
+ 'ThisExpression',
209
+ ]);
210
+
211
+ /** Extensions tried when resolving an extensionless relative import. */
212
+ const RESOLVE_EXTENSIONS = [
213
+ '.ts',
214
+ '.tsx',
215
+ '.mts',
216
+ '.cts',
217
+ '.js',
218
+ '.jsx',
219
+ '.mjs',
220
+ '.cjs',
221
+ ];
222
+
223
+ /**
224
+ * Node types whose contents are not evaluated when the module is
225
+ * initialized. A `__PURE__` annotation only affects tree-shaking of
226
+ * module-scope evaluation, so the walk never descends into these.
227
+ */
228
+ /**
229
+ * Built-ins whose construction or call has no side effects. Bundlers know
230
+ * this for themselves (rollup's known globals, esbuild's primitive
231
+ * handling), so requiring an annotation on `new Map([...])` inside a
232
+ * definition would be noise.
233
+ */
234
+ const PURE_GLOBALS = new Set([
235
+ 'Array',
236
+ 'BigInt',
237
+ 'Boolean',
238
+ 'Map',
239
+ 'Number',
240
+ 'Object',
241
+ 'RegExp',
242
+ 'Set',
243
+ 'String',
244
+ 'Symbol',
245
+ 'WeakMap',
246
+ 'WeakSet',
247
+ ]);
248
+
249
+ /**
250
+ * A dependency's own code. It is still annotated — a Lexical consumed
251
+ * through its `source` export condition is resolved from here — but `strict`
252
+ * does not apply to it: a definition somebody else shipped is not the
253
+ * building project's to fix, and neither is a marker they got wrong.
254
+ */
255
+ const NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
256
+
257
+ const DEFERRED_TYPES = new Set([
258
+ 'ArrowFunctionExpression',
259
+ 'ClassAccessorProperty',
260
+ 'ClassMethod',
261
+ 'ClassPrivateMethod',
262
+ 'ClassPrivateProperty',
263
+ 'ClassProperty',
264
+ 'FunctionDeclaration',
265
+ 'FunctionExpression',
266
+ 'ObjectMethod',
267
+ 'PropertyDefinition',
268
+ 'StaticBlock',
269
+ 'TSDeclareMethod',
270
+ ]);
271
+
272
+ /** AST properties that never contain child nodes worth visiting. */
273
+ /** @type {ReadonlySet<string>} */
274
+ const EMPTY_NAMES = new Set();
275
+
276
+ const SKIPPED_KEYS = new Set([
277
+ 'comments',
278
+ 'extra',
279
+ 'innerComments',
280
+ 'leadingComments',
281
+ 'loc',
282
+ 'trailingComments',
283
+ ]);
284
+
285
+ /**
286
+ * @param {undefined | string} filename
287
+ * @param {undefined | ReadonlyArray<any>} extraPlugins
288
+ * @returns {Array<any>} the Babel parser plugins to parse this file with
289
+ */
290
+ function parserPluginsFor(filename, extraPlugins) {
291
+ const name = typeof filename === 'string' ? filename : '';
292
+ const isTypeScript = /\.[cm]?tsx?$/i.test(name);
293
+ /** @type {Array<any>} */
294
+ const plugins = ['explicitResourceManagement'];
295
+ if (isTypeScript) {
296
+ plugins.push('typescript');
297
+ }
298
+ // `<T>(value: T) => value` in a .ts file is a generic arrow function, not
299
+ // an opening JSX element, so the jsx plugin must stay off there. Any other
300
+ // extension (including an unknown one) is parsed with jsx enabled.
301
+ if (!isTypeScript || /\.[cm]?tsx$/i.test(name)) {
302
+ plugins.push('jsx');
303
+ }
304
+ return extraPlugins ? plugins.concat(extraPlugins) : plugins;
305
+ }
306
+
307
+ /**
308
+ * Find the block comment that ends immediately before `offset` (ignoring
309
+ * whitespace), if any.
310
+ *
311
+ * This reads the source text rather than the parsed `leadingComments` so
312
+ * that a comment attached to an enclosing statement (one that precedes
313
+ * `export const x = f()` rather than the call itself, where it does nothing)
314
+ * is not mistaken for an annotation on the call.
315
+ *
316
+ * @param {string} code
317
+ * @param {number} offset
318
+ * @returns {null | {end: number, start: number, value: string}}
319
+ */
320
+ function blockCommentBefore(code, offset) {
321
+ let end = offset;
322
+ while (end > 0 && /\s/.test(code[end - 1])) {
323
+ end--;
324
+ }
325
+ if (end < 4 || code[end - 1] !== '/' || code[end - 2] !== '*') {
326
+ return null;
327
+ }
328
+ const start = code.lastIndexOf('/*', end - 2);
329
+ if (start === -1) {
330
+ return null;
331
+ }
332
+ return {end, start, value: code.slice(start + 2, end - 2)};
333
+ }
334
+
335
+ /**
336
+ * @param {string} code
337
+ * @param {number} offset the start of a call expression
338
+ * @returns {boolean} true when the call already carries a pure annotation
339
+ */
340
+ function hasPureAnnotation(code, offset) {
341
+ const comment = blockCommentBefore(code, offset);
342
+ return comment !== null && PURE_ANNOTATION_RE.test(comment.value);
343
+ }
344
+
345
+ /**
346
+ * The identifier a member-expression callee is rooted at, so that every call
347
+ * in `sel.tag('p').attr('x')` resolves to `sel`.
348
+ *
349
+ * @param {any} callee
350
+ * @returns {null | string} the name, or null when the callee is not rooted
351
+ * at a plain identifier
352
+ */
353
+ function rootObjectName(callee) {
354
+ for (let node = callee; node; ) {
355
+ if (node.type === 'Identifier') {
356
+ return node.name;
357
+ } else if (node.type === 'MemberExpression') {
358
+ node = node.object;
359
+ } else if (node.type === 'CallExpression') {
360
+ node = node.callee;
361
+ } else {
362
+ return null;
363
+ }
364
+ }
365
+ return null;
366
+ }
367
+
368
+ /**
369
+ * The names a nested block binds for itself with `let`, `const`, `class` or
370
+ * a function declaration. Only these shadow an outer binding: `var` is
371
+ * scoped to the module, so a `var` of an imported name is a redeclaration
372
+ * error rather than a shadow.
373
+ *
374
+ * @param {any} node
375
+ * @returns {null | Set<string>} the names, or null when this node binds none
376
+ */
377
+ function blockScopedNames(node) {
378
+ // Only a nested block: the module's own declarations are the bindings the
379
+ // transform resolved in the first place, not shadows of them.
380
+ const body =
381
+ node.type === 'BlockStatement'
382
+ ? node.body
383
+ : node.type === 'SwitchCase'
384
+ ? node.consequent
385
+ : null;
386
+ if (body === null) {
387
+ return null;
388
+ }
389
+ /** @type {null | Set<string>} */
390
+ let names = null;
391
+ /** @param {string} name */
392
+ const add = name => {
393
+ names = names || new Set();
394
+ names.add(name);
395
+ };
396
+ for (const statement of body) {
397
+ if (statement.type === 'VariableDeclaration' && statement.kind !== 'var') {
398
+ for (const declarator of statement.declarations) {
399
+ if (declarator.id.type === 'Identifier') {
400
+ add(declarator.id.name);
401
+ }
402
+ }
403
+ } else if (
404
+ (statement.type === 'FunctionDeclaration' ||
405
+ statement.type === 'ClassDeclaration') &&
406
+ statement.id
407
+ ) {
408
+ add(statement.id.name);
409
+ }
410
+ }
411
+ return names;
412
+ }
413
+
414
+ /**
415
+ * Walk every node that is evaluated when the module is initialized, calling
416
+ * `visitor` with each module-scope call to one of the factories, or to a
417
+ * method of one of the namespaces, and where it sits: `discarded` when the
418
+ * call's value is thrown away (it is the whole of an expression statement),
419
+ * and `statementStart` when it begins one, both of which constrain what it
420
+ * can be replaced with. `namespace` marks a method call, which is annotated
421
+ * but never replaced.
422
+ *
423
+ * @param {any} node
424
+ * @param {{functions: ReadonlyMap<string, any> | ReadonlySet<string>, namespaces?: ReadonlySet<string>}} names
425
+ * @param {(node: any, position: {discarded: boolean, namespace?: boolean, statementStart: boolean}) => void} visitor
426
+ * @param {{discarded: boolean, shadowed: ReadonlySet<string>, statementStarts: Set<number>}} [state]
427
+ */
428
+ function visitModuleScopeCalls(node, names, visitor, state) {
429
+ const {functions, namespaces} = names;
430
+ const walkState = state || {
431
+ discarded: false,
432
+ shadowed: EMPTY_NAMES,
433
+ statementStarts: new Set(),
434
+ };
435
+ if (Array.isArray(node)) {
436
+ for (const child of node) {
437
+ visitModuleScopeCalls(child, names, visitor, {
438
+ discarded: false,
439
+ shadowed: walkState.shadowed,
440
+ statementStarts: walkState.statementStarts,
441
+ });
442
+ }
443
+ return;
444
+ }
445
+ if (!node || typeof node !== 'object' || typeof node.type !== 'string') {
446
+ return;
447
+ }
448
+ if (DEFERRED_TYPES.has(node.type)) {
449
+ return;
450
+ }
451
+ if (node.type === 'ExpressionStatement') {
452
+ // Every node that begins an expression statement starts at the same
453
+ // offset, so recording that one offset identifies the whole leftmost
454
+ // chain — the positions where an object literal would be read as a
455
+ // block, whatever it is nested in.
456
+ walkState.statementStarts.add(node.expression.start);
457
+ }
458
+ // Only a plain CallExpression: `factory?.(x)` parses as an
459
+ // OptionalCallExpression, and it evaluates to undefined when the factory
460
+ // is missing rather than to what the factory returns.
461
+ if (node.type === 'CallExpression' && node.callee) {
462
+ const position = {
463
+ discarded: walkState.discarded,
464
+ statementStart: walkState.statementStarts.has(node.start),
465
+ };
466
+ if (
467
+ node.callee.type === 'Identifier' &&
468
+ functions.has(node.callee.name) &&
469
+ !walkState.shadowed.has(node.callee.name)
470
+ ) {
471
+ visitor(node, position);
472
+ } else if (namespaces !== undefined && namespaces.size > 0) {
473
+ const root = rootObjectName(node.callee);
474
+ if (
475
+ root !== null &&
476
+ namespaces.has(root) &&
477
+ !walkState.shadowed.has(root)
478
+ ) {
479
+ visitor(node, {...position, namespace: true});
480
+ }
481
+ }
482
+ }
483
+ // A block that rebinds one of the names is talking about something else
484
+ // inside itself, and that something else may well have side effects.
485
+ const bound = blockScopedNames(node);
486
+ const shadowed =
487
+ bound === null
488
+ ? walkState.shadowed
489
+ : new Set([...walkState.shadowed, ...bound]);
490
+ for (const key of Object.keys(node)) {
491
+ if (!SKIPPED_KEYS.has(key)) {
492
+ const value = node[key];
493
+ if (value && typeof value === 'object') {
494
+ visitModuleScopeCalls(value, names, visitor, {
495
+ discarded:
496
+ node.type === 'ExpressionStatement' && key === 'expression',
497
+ shadowed,
498
+ statementStarts: walkState.statementStarts,
499
+ });
500
+ }
501
+ }
502
+ }
503
+ }
504
+
505
+ /**
506
+ * True when a call to a factory of this form can be replaced by the literal
507
+ * it would have returned. Anything the form cannot account for — a spread
508
+ * argument, the wrong number of arguments, a comma expression that would
509
+ * lose its parentheses — is annotated instead.
510
+ *
511
+ * @param {any} node the call expression
512
+ * @param {{form: string}} spec
513
+ * @returns {boolean}
514
+ */
515
+ function canInlineCall(node, spec) {
516
+ const args = node.arguments;
517
+ if (spec.form === 'identity') {
518
+ return (
519
+ args.length === 1 &&
520
+ args[0].type !== 'SpreadElement' &&
521
+ args[0].type !== 'SequenceExpression'
522
+ );
523
+ }
524
+ return true;
525
+ }
526
+
527
+ /**
528
+ * Walk every node in the tree, calling `visit` with each node and the node
529
+ * and key that hold it. Unlike visitModuleScopeCalls this does not stop at
530
+ * function boundaries: it is used to prove that a name is *not* referenced
531
+ * anywhere else in the module.
532
+ *
533
+ * @param {any} node
534
+ * @param {(node: any, parent: any, key: string) => void} visit
535
+ * @param {any} [parent]
536
+ * @param {string} [key]
537
+ */
538
+ function walkAllNodes(node, visit, parent, key) {
539
+ if (Array.isArray(node)) {
540
+ for (const child of node) {
541
+ walkAllNodes(child, visit, parent, key);
542
+ }
543
+ return;
544
+ }
545
+ if (!node || typeof node !== 'object' || typeof node.type !== 'string') {
546
+ return;
547
+ }
548
+ visit(node, parent, /** @type {string} */ (key));
549
+ for (const childKey of Object.keys(node)) {
550
+ if (!SKIPPED_KEYS.has(childKey)) {
551
+ const value = node[childKey];
552
+ if (value && typeof value === 'object') {
553
+ walkAllNodes(value, visit, node, childKey);
554
+ }
555
+ }
556
+ }
557
+ }
558
+
559
+ /**
560
+ * True when an identifier node is a reference to a binding rather than a
561
+ * name in some other position (a property name, an import specifier, ...).
562
+ * Anything unrecognized counts as a reference, so an unexpected position
563
+ * keeps the import rather than removing one that is still needed.
564
+ *
565
+ * @param {any} node
566
+ * @param {any} parent
567
+ * @param {string} key
568
+ * @returns {boolean}
569
+ */
570
+ function isValueReference(node, parent, key) {
571
+ if (node.type !== 'Identifier' && node.type !== 'JSXIdentifier') {
572
+ return false;
573
+ }
574
+ if (!parent) {
575
+ return true;
576
+ }
577
+ switch (parent.type) {
578
+ case 'ImportDefaultSpecifier':
579
+ case 'ImportNamespaceSpecifier':
580
+ case 'ImportSpecifier':
581
+ return false;
582
+ case 'ExportSpecifier':
583
+ // `export {imported as somethingElse}` still refers to the binding.
584
+ return key === 'local';
585
+ case 'OptionalMemberExpression':
586
+ case 'MemberExpression':
587
+ return key !== 'property' || parent.computed === true;
588
+ case 'ClassMethod':
589
+ case 'ClassProperty':
590
+ case 'ObjectMethod':
591
+ case 'ObjectProperty':
592
+ return key !== 'key' || parent.computed === true;
593
+ default:
594
+ return true;
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Remove the import specifiers whose only uses were calls that have just
600
+ * been replaced by a literal, so the transform does not leave behind an
601
+ * import that nothing references (which bundlers then warn about).
602
+ *
603
+ * @param {MagicString} magicString
604
+ * @param {string} code
605
+ * @param {any} program
606
+ * @param {ReadonlyArray<any>} inlinedCalls the call nodes that were replaced
607
+ */
608
+ function removeUnusedImports(magicString, code, program, inlinedCalls) {
609
+ const callees = new Set(inlinedCalls.map(node => node.callee));
610
+ const names = new Set(inlinedCalls.map(node => node.callee.name));
611
+ if (names.size === 0) {
612
+ return;
613
+ }
614
+ // A name is still needed if anything other than an inlined callee refers
615
+ // to it, anywhere in the module.
616
+ walkAllNodes(program, (node, parent, key) => {
617
+ if (
618
+ names.has(node.name) &&
619
+ !callees.has(node) &&
620
+ isValueReference(node, parent, key)
621
+ ) {
622
+ names.delete(node.name);
623
+ }
624
+ });
625
+ for (const statement of program.body) {
626
+ if (
627
+ statement.type !== 'ImportDeclaration' ||
628
+ statement.importKind === 'type'
629
+ ) {
630
+ continue;
631
+ }
632
+ const {specifiers} = statement;
633
+ const named = specifiers.filter(
634
+ /** @param {any} specifier */ specifier =>
635
+ specifier.type === 'ImportSpecifier',
636
+ );
637
+ const removable = named.filter(
638
+ /** @param {any} specifier */ specifier =>
639
+ specifier.importKind !== 'type' && names.has(specifier.local.name),
640
+ );
641
+ if (removable.length === 0) {
642
+ continue;
643
+ }
644
+ if (removable.length < named.length) {
645
+ // Some names are still used: take out each unused one along with the
646
+ // comma that separates it from the named import next to it. Adjacent
647
+ // unused ones go as a single range — two overlapping removals would
648
+ // leave the comma of the first behind (`import {a, } from 'x'`).
649
+ const removableSet = new Set(removable);
650
+ for (let start = 0; start < named.length; start++) {
651
+ if (!removableSet.has(named[start])) {
652
+ continue;
653
+ }
654
+ let end = start;
655
+ while (end + 1 < named.length && removableSet.has(named[end + 1])) {
656
+ end++;
657
+ }
658
+ if (end + 1 < named.length) {
659
+ magicString.remove(named[start].start, named[end + 1].start);
660
+ } else {
661
+ magicString.remove(named[start - 1].end, named[end].end);
662
+ }
663
+ start = end;
664
+ }
665
+ continue;
666
+ }
667
+ const close = code.indexOf('}', named[named.length - 1].end);
668
+ if (named.length < specifiers.length) {
669
+ // A default or namespace import is still there, so only the braced
670
+ // clause and the comma before it go.
671
+ const previous = specifiers[specifiers.indexOf(named[0]) - 1];
672
+ magicString.remove(code.indexOf(',', previous.end), close + 1);
673
+ continue;
674
+ }
675
+ // Nothing is imported from the module any more. Dropping the import
676
+ // drops the module, which is only safe because it has no side effects to
677
+ // run: the factories come either from a Lexical package (all of which
678
+ // are `sideEffects: false`) or from a module that declares functions
679
+ // side-effect free and inlinable. A module that exports such a factory
680
+ // and also does something at import time is out of scope by
681
+ // construction — Lexical has no modules with top-level side effects, and
682
+ // neither should a module that a build is told to inline from.
683
+ magicString.remove(statement.start, statement.end);
684
+ }
685
+ }
686
+
687
+ /**
688
+ * The offset just past a call's opening parenthesis, which is where its
689
+ * argument list begins — including any parentheses the first argument was
690
+ * written with, and any type arguments before it (`safeCast<T>(x)`).
691
+ *
692
+ * @param {string} code
693
+ * @param {any} node a call expression
694
+ * @returns {number}
695
+ */
696
+ function argumentsStart(code, node) {
697
+ const typeArguments = node.typeArguments || node.typeParameters;
698
+ const from = Math.max(
699
+ node.callee.end,
700
+ typeArguments ? typeArguments.end : node.callee.end,
701
+ );
702
+ return code.indexOf('(', from) + 1;
703
+ }
704
+
705
+ /**
706
+ * Replace a call with the literal the factory would have returned, keeping
707
+ * the argument source text (and any nested edits within it) intact.
708
+ *
709
+ * @param {MagicString} magicString
710
+ * @param {string} code
711
+ * @param {any} node the call expression
712
+ * @param {{form: string}} spec
713
+ * @param {boolean} [statementStart] whether the call begins an expression
714
+ * statement, where an object literal would be read as a block
715
+ */
716
+ function inlineCall(magicString, code, node, spec, statementStart) {
717
+ const comment = blockCommentBefore(code, node.start);
718
+ if (comment !== null && PURE_ANNOTATION_RE.test(comment.value)) {
719
+ // Left in front of a literal the annotation does nothing, and bundlers
720
+ // report it as misplaced.
721
+ magicString.remove(comment.start, node.start);
722
+ }
723
+ const args = node.arguments;
724
+ if (spec.form !== 'identity') {
725
+ // The argument list becomes the array literal, so the edit is on the
726
+ // call's own parentheses — `factory(` and its `)` — rather than on where
727
+ // the first and last arguments start and end. Those exclude parentheses
728
+ // an argument was written with (`configExtension(Ext, (cfg))`), which
729
+ // would be left behind unbalanced.
730
+ //
731
+ // Parenthesized so that the replacement binds exactly like the call it
732
+ // replaces, wherever that call was: a bare `[` at the start of an
733
+ // expression statement is a member access on whatever the previous line
734
+ // ended with, which is how ASI reads `f()\n[a, b].length`. Minifiers
735
+ // drop the parentheses again.
736
+ magicString.overwrite(
737
+ node.start,
738
+ argumentsStart(code, node),
739
+ '([',
740
+ CONTENT_ONLY,
741
+ );
742
+ magicString.overwrite(node.end - 1, node.end, '])', CONTENT_ONLY);
743
+ return;
744
+ }
745
+ // The call's own parentheses were doing two jobs: grouping the argument
746
+ // (`safeCast(a || b).c` must not become `a || b.c`) and, at the start of an
747
+ // expression statement, keeping an object literal from being read as a
748
+ // block. Keep them unless the argument reads the same without them.
749
+ const keepParens =
750
+ statementStart === true || !SELF_DELIMITING_TYPES.has(args[0].type);
751
+ if (keepParens) {
752
+ magicString.overwrite(node.start, args[0].start, '(', CONTENT_ONLY);
753
+ magicString.overwrite(args[0].end, node.end, ')', CONTENT_ONLY);
754
+ } else {
755
+ magicString.remove(node.start, args[0].start);
756
+ magicString.remove(args[0].end, node.end);
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Caches for the files read while resolving relative imports. They live for
762
+ * the lifetime of the process, which is what a build wants; a watch-mode
763
+ * process that changes whether a declaration is marked side-effect free has
764
+ * to be restarted for that to take effect (the annotations do not change
765
+ * behavior, only what a bundler is allowed to drop).
766
+ */
767
+ const resolvedFileCache = new Map();
768
+ const declarationCache = new Map();
769
+
770
+ /**
771
+ * The form named by a declaration's `@lexical-inline` marker, if it has one.
772
+ *
773
+ * A marker naming a form this transform does not implement is an error
774
+ * rather than a marker that silently does nothing: the whole point of the
775
+ * marker is to say what a call may be replaced with, and a typo would leave
776
+ * the factory quietly un-inlined.
777
+ *
778
+ * @param {undefined | ReadonlyArray<any>} comments
779
+ * @param {undefined | string} filename the module the comments came from
780
+ * @returns {null | string}
781
+ */
782
+ function inlineMarkerForm(comments, filename) {
783
+ if (comments) {
784
+ for (const comment of comments) {
785
+ const match = INLINE_MARKER_RE.exec(comment.value);
786
+ if (match) {
787
+ const form = match[1] || '';
788
+ if (!INLINE_FORMS.has(form)) {
789
+ const where = filename ? ` in ${filename}` : '';
790
+ throw new PureAnnotationsError(
791
+ `@lexical/compiler: unknown @lexical-inline form ` +
792
+ `${form ? `"${form}"` : '(none given)'}${where}. Write ` +
793
+ `\`@lexical-inline <form>\` with one of: ` +
794
+ `${Array.from(INLINE_FORMS).sort().join(', ')}.`,
795
+ );
796
+ }
797
+ return form;
798
+ }
799
+ }
800
+ }
801
+ return null;
802
+ }
803
+
804
+ /**
805
+ * @param {undefined | ReadonlyArray<any>} comments
806
+ * @returns {boolean} true when one of the comments is a NO_SIDE_EFFECTS annotation
807
+ */
808
+ function hasNoSideEffectsComment(comments) {
809
+ return (
810
+ comments !== undefined &&
811
+ comments !== null &&
812
+ comments.some(comment => NO_SIDE_EFFECTS_RE.test(comment.value))
813
+ );
814
+ }
815
+
816
+ /**
817
+ * The names this module declares (at module scope) with a NO_SIDE_EFFECTS
818
+ * annotation. Calls to these are safe to annotate wherever they appear,
819
+ * because the definition itself says so. The value is the form from the
820
+ * declaration's `@lexical-inline` marker, when it has one: purity alone is
821
+ * evidence enough to annotate a call, but replacing one requires knowing the
822
+ * shape of what the function returns.
823
+ *
824
+ * @param {any} program
825
+ * @param {undefined | string} filename the module being read, for errors
826
+ * @returns {Map<string, null | string>} declared name to its inline form
827
+ */
828
+ function pureDeclaredNames(program, filename) {
829
+ /** @type {Map<string, null | string>} */
830
+ const names = new Map();
831
+ for (const statement of program.body) {
832
+ const declaration =
833
+ statement.type === 'ExportNamedDeclaration' && statement.declaration
834
+ ? statement.declaration
835
+ : statement;
836
+ const comments = (statement.leadingComments || []).concat(
837
+ declaration === statement ? [] : declaration.leadingComments || [],
838
+ );
839
+ const declared = hasNoSideEffectsComment(comments);
840
+ const inlineForm = inlineMarkerForm(comments, filename);
841
+ if (
842
+ // An overloaded function carries its documentation — and so its
843
+ // annotation — on the first signature, which is a TSDeclareFunction;
844
+ // the implementation that follows is a bare FunctionDeclaration.
845
+ (declaration.type === 'FunctionDeclaration' ||
846
+ declaration.type === 'TSDeclareFunction') &&
847
+ declaration.id
848
+ ) {
849
+ if (declared) {
850
+ names.set(declaration.id.name, inlineForm);
851
+ }
852
+ } else if (declaration.type === 'VariableDeclaration') {
853
+ for (const declarator of declaration.declarations) {
854
+ const declaratorComments = (declarator.leadingComments || []).concat(
855
+ (declarator.init && declarator.init.leadingComments) || [],
856
+ );
857
+ if (
858
+ declarator.id.type === 'Identifier' &&
859
+ (declared || hasNoSideEffectsComment(declaratorComments))
860
+ ) {
861
+ names.set(
862
+ declarator.id.name,
863
+ inlineForm || inlineMarkerForm(declaratorComments, filename),
864
+ );
865
+ }
866
+ }
867
+ }
868
+ }
869
+ return names;
870
+ }
871
+
872
+ /**
873
+ * Resolve a relative import specifier to a file on disk, trying the
874
+ * extensions a bundler would (including the `.js` specifier TypeScript uses
875
+ * for a `.ts` file).
876
+ *
877
+ * @param {string} filename the importing module
878
+ * @param {string} specifier a relative specifier
879
+ * @returns {null | string} the resolved path, or null when there is no such file
880
+ */
881
+ function resolveRelativeSpecifier(filename, specifier) {
882
+ const base = path__namespace.resolve(path__namespace.dirname(filename), specifier);
883
+ const cached = resolvedFileCache.get(base);
884
+ if (cached !== undefined) {
885
+ return cached;
886
+ }
887
+ /** @type {Array<string>} */
888
+ const candidates = [];
889
+ const extension = path__namespace.extname(base);
890
+ if (extension !== '') {
891
+ candidates.push(base);
892
+ if (/^\.[cm]?js$/.test(extension)) {
893
+ const withoutExtension = base.slice(0, -extension.length);
894
+ candidates.push(
895
+ withoutExtension + extension.replace('js', 'ts'),
896
+ withoutExtension + '.tsx',
897
+ );
898
+ }
899
+ }
900
+ for (const candidate of RESOLVE_EXTENSIONS) {
901
+ candidates.push(base + candidate);
902
+ }
903
+ for (const candidate of RESOLVE_EXTENSIONS) {
904
+ candidates.push(path__namespace.join(base, `index${candidate}`));
905
+ }
906
+ let resolved = null;
907
+ for (const candidate of candidates) {
908
+ try {
909
+ if (fs__namespace.statSync(candidate).isFile()) {
910
+ resolved = candidate;
911
+ break;
912
+ }
913
+ } catch (_err) {
914
+ // Not this candidate; try the next one.
915
+ }
916
+ }
917
+ resolvedFileCache.set(base, resolved);
918
+ return resolved;
919
+ }
920
+
921
+ /**
922
+ * The side-effect-free declarations of another file, parsed and cached.
923
+ *
924
+ * @param {string} filename
925
+ * @param {undefined | ReadonlyArray<any>} parserPlugins
926
+ * @returns {ReadonlyMap<string, null | string>}
927
+ */
928
+ function pureDeclaredNamesOfFile(filename, parserPlugins) {
929
+ return declarationsOfFile(filename, parserPlugins).functions;
930
+ }
931
+
932
+ /**
933
+ * What a module next to this one declares: the functions it says are
934
+ * side-effect free, and the objects it marks as pure namespaces. Parsed
935
+ * once per file for the life of the process — see the cache note above.
936
+ *
937
+ * @param {string} filename
938
+ * @param {undefined | ReadonlyArray<any>} parserPlugins
939
+ * @returns {{functions: ReadonlyMap<string, null | string>, namespaces: ReadonlySet<string>}}
940
+ */
941
+ function declarationsOfFile(filename, parserPlugins) {
942
+ const cached = declarationCache.get(filename);
943
+ if (cached !== undefined) {
944
+ return cached;
945
+ }
946
+ /** @type {null | any} */
947
+ let program = null;
948
+ try {
949
+ program = parser.parse(fs__namespace.readFileSync(filename, 'utf8'), {
950
+ plugins: parserPluginsFor(filename, parserPlugins),
951
+ sourceType: 'module',
952
+ }).program;
953
+ } catch (_err) {
954
+ // A file this cannot read or parse simply provides no evidence. A
955
+ // malformed marker inside a file it *can* read is a different matter:
956
+ // reading the declarations happens outside this catch so that its error
957
+ // reaches the build.
958
+ }
959
+ const declarations =
960
+ program === null
961
+ ? {functions: new Map(), namespaces: new Set()}
962
+ : {
963
+ functions: pureDeclaredNames(program, filename),
964
+ namespaces: namespaceDeclaredNames(program),
965
+ };
966
+ declarationCache.set(filename, declarations);
967
+ return declarations;
968
+ }
969
+
970
+ /**
971
+ * The local names in this module that refer to a side-effect-free factory,
972
+ * and may therefore have their module-scope calls annotated.
973
+ *
974
+ * A name qualifies when it is imported from a trusted Lexical package, when
975
+ * it is declared in this module with a NO_SIDE_EFFECTS annotation, or when
976
+ * it is imported from a relative module that declares it that way. A local
977
+ * helper that merely shares a name with one of the factories does not
978
+ * qualify, which is what keeps the transform from claiming that somebody
979
+ * else's `safeCast` or `createState` has no side effects.
980
+ *
981
+ * @param {any} program
982
+ * @param {ReadonlySet<string>} functions
983
+ * @param {TransformPureAnnotationsOptions} opts
984
+ * @returns {Map<string, {inline: undefined | any, name: string}>} local name
985
+ * to the factory it refers to, and the form it may be inlined with
986
+ */
987
+ function collectFactoryNames(program, functions, opts) {
988
+ const sources = toRegExpArray(opts.sources, DEFAULT_SOURCES);
989
+ /** @type {Map<string, {inline: undefined | any, name: string}>} */
990
+ const names = new Map();
991
+ /**
992
+ * A call is only replaced by a literal when the shape of what the factory
993
+ * returns is known. An import from a Lexical package is taken on trust
994
+ * (this package ships with that Lexical); anything else has to say so
995
+ * itself with a `@lexical-inline` marker whose form matches the table.
996
+ *
997
+ * @param {string} local
998
+ * @param {string} name
999
+ * @param {null | string | undefined} markerForm
1000
+ * @param {boolean} trusted
1001
+ */
1002
+ function add(local, name, markerForm, trusted) {
1003
+ const spec = INLINE_FACTORIES.get(name);
1004
+ const inline =
1005
+ spec !== undefined
1006
+ ? // One of Lexical's own: an import from a Lexical package is taken
1007
+ // on trust, anything else has to agree with the table.
1008
+ trusted || markerForm === spec.form
1009
+ ? spec
1010
+ : undefined
1011
+ : // A factory of your own: its marker is the only thing that says
1012
+ // what its calls may be replaced with, so it is the authority.
1013
+ markerForm
1014
+ ? {form: markerForm}
1015
+ : undefined;
1016
+ names.set(local, {inline, name});
1017
+ }
1018
+ for (const [name, markerForm] of pureDeclaredNames(program, opts.filename)) {
1019
+ // A declaration in this module carries its own evidence, so it does not
1020
+ // have to be one of the known factory names: marking your own factory
1021
+ // `__NO_SIDE_EFFECTS__` is enough for the build to annotate its calls.
1022
+ // (A relatively imported one still has to be listed in `functions`, so
1023
+ // that a build does not parse every module it imports looking for one.)
1024
+ add(name, name, markerForm, false);
1025
+ }
1026
+ for (const statement of program.body) {
1027
+ if (statement.type !== 'ImportDeclaration') {
1028
+ continue;
1029
+ }
1030
+ const source = statement.source.value;
1031
+ const trusted = matchesAny(sources, source);
1032
+ const relative =
1033
+ !trusted &&
1034
+ opts.relativeImports !== false &&
1035
+ typeof opts.filename === 'string' &&
1036
+ source.startsWith('.');
1037
+ if (!trusted && !relative) {
1038
+ continue;
1039
+ }
1040
+ for (const specifier of statement.specifiers) {
1041
+ if (
1042
+ specifier.type !== 'ImportSpecifier' ||
1043
+ specifier.imported.type !== 'Identifier'
1044
+ ) {
1045
+ continue;
1046
+ }
1047
+ if (trusted) {
1048
+ if (functions.has(specifier.imported.name)) {
1049
+ add(specifier.local.name, specifier.imported.name, null, true);
1050
+ }
1051
+ continue;
1052
+ }
1053
+ const resolved = resolveRelativeSpecifier(
1054
+ /** @type {string} */ (opts.filename),
1055
+ source,
1056
+ );
1057
+ const declared =
1058
+ resolved === null
1059
+ ? undefined
1060
+ : pureDeclaredNamesOfFile(resolved, opts.parserPlugins).get(
1061
+ specifier.imported.name,
1062
+ );
1063
+ if (declared !== undefined) {
1064
+ add(specifier.local.name, specifier.imported.name, declared, false);
1065
+ }
1066
+ }
1067
+ }
1068
+ return names;
1069
+ }
1070
+
1071
+ /**
1072
+ * @param {string} code
1073
+ * @param {ReadonlySet<string>} functions
1074
+ * @param {ReadonlySet<string>} namespaces
1075
+ * @returns {boolean} true when the source mentions any of the names at all
1076
+ */
1077
+ function mayNeedAnnotations(code, functions, namespaces) {
1078
+ if (
1079
+ code.includes('__NO_SIDE_EFFECTS__') ||
1080
+ code.includes('@lexical-pure-namespace') ||
1081
+ RELATIVE_IMPORT_RE.test(code)
1082
+ ) {
1083
+ // Modules that may annotate calls for reasons other than the names in
1084
+ // the list: a declaration of their own, or a relatively imported
1085
+ // namespace, which is only known by reading that module.
1086
+ return true;
1087
+ }
1088
+ for (const name of functions) {
1089
+ if (code.includes(name)) {
1090
+ return true;
1091
+ }
1092
+ }
1093
+ for (const name of namespaces) {
1094
+ if (code.includes(name)) {
1095
+ return true;
1096
+ }
1097
+ }
1098
+ return false;
1099
+ }
1100
+
1101
+ /**
1102
+ * @typedef {Object} TransformPureAnnotationsOptions
1103
+ * @property {string} [filename] Path of the module being transformed, used
1104
+ * to choose the TypeScript and JSX parser plugins.
1105
+ * @property {Iterable<string>} [functions] Names of the factories whose
1106
+ * module-scope calls are annotated. Defaults to PURE_FACTORY_FUNCTIONS.
1107
+ * @property {boolean} [inline] Replace calls to the factories whose result
1108
+ * is a trivial expression over their arguments with that expression,
1109
+ * rather than annotating them. Off by default: it assumes the Lexical
1110
+ * being built matches this package's version.
1111
+ * @property {ReadonlyArray<any>} [parserPlugins] Extra Babel parser
1112
+ * plugins, for syntax the defaults do not cover.
1113
+ * @property {boolean} [relativeImports] Set to false to skip reading
1114
+ * relatively imported modules to look for a NO_SIDE_EFFECTS declaration.
1115
+ * @property {Iterable<string>} [namespaces] Imported objects whose
1116
+ * method calls are side-effect free, defaults to PURE_NAMESPACES.
1117
+ * @property {boolean} [sourceMap] Set to false to skip source map generation.
1118
+ * @property {RegExp | ReadonlyArray<RegExp>} [sources] Module specifiers
1119
+ * whose exports are trusted to be the factories, defaults to `lexical` and
1120
+ * `@lexical/*`.
1121
+ * @property {boolean} [strict] Throw when a call evaluated inside one of the
1122
+ * definitions is not known to be side-effect free, since it pins the
1123
+ * definition into every bundle that imports the module.
1124
+ */
1125
+
1126
+ /**
1127
+ * @typedef {Object} PureAnnotationsResult
1128
+ * @property {number} count Number of annotations that were inserted.
1129
+ * @property {string} code
1130
+ * @property {number} inlined Number of calls that were replaced by the
1131
+ * literal they would have returned.
1132
+ * @property {any} [map]
1133
+ */
1134
+
1135
+ /**
1136
+ * The names this module declares with a `@lexical-pure-namespace` marker:
1137
+ * objects whose methods are side-effect free.
1138
+ *
1139
+ * @param {any} program
1140
+ * @returns {Set<string>}
1141
+ */
1142
+ function namespaceDeclaredNames(program) {
1143
+ /** @type {Set<string>} */
1144
+ const names = new Set();
1145
+ /** @param {undefined | ReadonlyArray<any>} comments */
1146
+ const marked = comments =>
1147
+ (comments || []).some(comment => NAMESPACE_MARKER_RE.test(comment.value));
1148
+ for (const statement of program.body) {
1149
+ const declaration =
1150
+ statement.type === 'ExportNamedDeclaration' && statement.declaration
1151
+ ? statement.declaration
1152
+ : statement;
1153
+ const outer = marked(statement.leadingComments);
1154
+ if (declaration.type !== 'VariableDeclaration') {
1155
+ continue;
1156
+ }
1157
+ for (const declarator of declaration.declarations) {
1158
+ if (
1159
+ declarator.id.type === 'Identifier' &&
1160
+ (outer ||
1161
+ marked(declaration.leadingComments) ||
1162
+ marked(declarator.leadingComments) ||
1163
+ (declarator.init && marked(declarator.init.leadingComments)))
1164
+ ) {
1165
+ names.add(declarator.id.name);
1166
+ }
1167
+ }
1168
+ }
1169
+ return names;
1170
+ }
1171
+
1172
+ /**
1173
+ * The `@lexical-pure-namespace` names a relatively imported module declares.
1174
+ *
1175
+ * @param {string} filename
1176
+ * @param {undefined | ReadonlyArray<any>} parserPlugins
1177
+ * @returns {ReadonlySet<string>}
1178
+ */
1179
+ function namespaceNamesOfFile(filename, parserPlugins) {
1180
+ return declarationsOfFile(filename, parserPlugins).namespaces;
1181
+ }
1182
+
1183
+ /**
1184
+ * The local names in this module bound to one of the side-effect-free
1185
+ * namespaces, imported from a trusted Lexical package. A relative import is
1186
+ * not enough here: unlike a factory function, whose declaration can say
1187
+ * `__NO_SIDE_EFFECTS__` for itself, an object's methods are only known to
1188
+ * be pure by the table above.
1189
+ *
1190
+ * @param {any} program
1191
+ * @param {ReadonlySet<string>} namespaces
1192
+ * @param {TransformPureAnnotationsOptions} opts
1193
+ * @returns {Set<string>} local names
1194
+ */
1195
+ function collectNamespaceNames(program, namespaces, opts) {
1196
+ /** @type {Set<string>} */
1197
+ const names = new Set();
1198
+ const sources = toRegExpArray(opts.sources, DEFAULT_SOURCES);
1199
+ for (const name of namespaceDeclaredNames(program)) {
1200
+ names.add(name);
1201
+ }
1202
+ for (const statement of program.body) {
1203
+ if (statement.type !== 'ImportDeclaration') {
1204
+ continue;
1205
+ }
1206
+ const source = statement.source.value;
1207
+ const trusted = matchesAny(sources, source);
1208
+ const relative =
1209
+ !trusted &&
1210
+ opts.relativeImports !== false &&
1211
+ typeof opts.filename === 'string' &&
1212
+ source.startsWith('.');
1213
+ if (!trusted && !relative) {
1214
+ continue;
1215
+ }
1216
+ /** @type {undefined | ReadonlySet<string>} */
1217
+ let declared;
1218
+ for (const specifier of statement.specifiers) {
1219
+ if (
1220
+ specifier.type !== 'ImportSpecifier' ||
1221
+ specifier.imported.type !== 'Identifier'
1222
+ ) {
1223
+ continue;
1224
+ }
1225
+ if (trusted) {
1226
+ if (namespaces.has(specifier.imported.name)) {
1227
+ names.add(specifier.local.name);
1228
+ }
1229
+ continue;
1230
+ }
1231
+ if (declared === undefined) {
1232
+ const resolved = resolveRelativeSpecifier(
1233
+ /** @type {string} */ (opts.filename),
1234
+ source,
1235
+ );
1236
+ declared =
1237
+ resolved === null
1238
+ ? new Set()
1239
+ : namespaceNamesOfFile(resolved, opts.parserPlugins);
1240
+ }
1241
+ if (declared.has(specifier.imported.name)) {
1242
+ names.add(specifier.local.name);
1243
+ }
1244
+ }
1245
+ }
1246
+ // `const sel = selBase;` — an alias is the same object. One pass is
1247
+ // enough for the aliases these modules actually write.
1248
+ for (const statement of program.body) {
1249
+ if (statement.type !== 'VariableDeclaration') {
1250
+ continue;
1251
+ }
1252
+ for (const declarator of statement.declarations) {
1253
+ if (
1254
+ declarator.id.type === 'Identifier' &&
1255
+ declarator.init &&
1256
+ declarator.init.type === 'Identifier' &&
1257
+ names.has(declarator.init.name)
1258
+ ) {
1259
+ names.add(declarator.id.name);
1260
+ }
1261
+ }
1262
+ }
1263
+ return names;
1264
+ }
1265
+
1266
+ /**
1267
+ * Every call evaluated inside `node`'s arguments, in the order they appear.
1268
+ * Calls in a deferred position (a callback, a method body) are not: they do
1269
+ * not run when the module is initialized, so they pin nothing.
1270
+ *
1271
+ * @param {any} node a call expression
1272
+ * @param {(call: any) => void} visit
1273
+ */
1274
+ function visitEvaluatedCalls(node, visit) {
1275
+ /** @param {any} value */
1276
+ const walk = value => {
1277
+ if (Array.isArray(value)) {
1278
+ value.forEach(walk);
1279
+ return;
1280
+ }
1281
+ if (
1282
+ !value ||
1283
+ typeof value !== 'object' ||
1284
+ typeof value.type !== 'string' ||
1285
+ DEFERRED_TYPES.has(value.type)
1286
+ ) {
1287
+ return;
1288
+ }
1289
+ if (
1290
+ value.type === 'CallExpression' ||
1291
+ value.type === 'NewExpression' ||
1292
+ value.type === 'TaggedTemplateExpression'
1293
+ ) {
1294
+ visit(value);
1295
+ }
1296
+ for (const key of Object.keys(value)) {
1297
+ if (!SKIPPED_KEYS.has(key)) {
1298
+ walk(value[key]);
1299
+ }
1300
+ }
1301
+ };
1302
+ node.arguments.forEach(walk);
1303
+ }
1304
+
1305
+ /**
1306
+ * A call in the arguments of an extension, command, or rule definition is
1307
+ * evaluated when the module is initialized, and an unannotated one pins the
1308
+ * whole definition into every bundle that imports the module — the
1309
+ * definition is only droppable when everything it is built from is too.
1310
+ *
1311
+ * With `strict`, that is an error naming the call rather than a definition
1312
+ * that quietly stops tree-shaking.
1313
+ *
1314
+ * @param {string} code
1315
+ * @param {undefined | string} filename
1316
+ * @param {ReadonlyArray<any>} factoryCalls
1317
+ * @param {ReadonlySet<number>} covered offsets this transform annotates or
1318
+ * replaces
1319
+ */
1320
+ function assertArgumentsArePure(code, filename, factoryCalls, covered) {
1321
+ /** @type {Array<string>} */
1322
+ const violations = [];
1323
+ /** @type {Set<number>} */
1324
+ const reported = new Set();
1325
+ for (const call of factoryCalls) {
1326
+ visitEvaluatedCalls(call, inner => {
1327
+ const callee = inner.callee || inner.tag;
1328
+ if (
1329
+ covered.has(inner.start) ||
1330
+ hasPureAnnotation(code, inner.start) ||
1331
+ reported.has(inner.start) ||
1332
+ (callee.type === 'Identifier' && PURE_GLOBALS.has(callee.name))
1333
+ ) {
1334
+ return;
1335
+ }
1336
+ reported.add(inner.start);
1337
+ const text = code.slice(callee.start, callee.end).replace(/\s+/g, ' ');
1338
+ const where = inner.loc
1339
+ ? `${inner.loc.start.line}:${inner.loc.start.column + 1}`
1340
+ : '?';
1341
+ violations.push(
1342
+ ` ${where} ${text}(...) inside ${code.slice(
1343
+ call.callee.start,
1344
+ call.callee.end,
1345
+ )}(...)`,
1346
+ );
1347
+ });
1348
+ }
1349
+ if (violations.length > 0) {
1350
+ throw new PureAnnotationsError(
1351
+ `@lexical/compiler: ${violations.length} call(s) evaluated ` +
1352
+ `inside a definition in ${filename || '<unknown>'} are not known to ` +
1353
+ `be side-effect free, so the definition cannot be tree-shaken:\n` +
1354
+ `${violations.join('\n')}\n` +
1355
+ `Make the call lazy, or declare the function side-effect free ` +
1356
+ `(@__NO_SIDE_EFFECTS__ plus the transform's \`functions\`/` +
1357
+ `\`namespaces\` options) so that its calls are annotated too.`,
1358
+ );
1359
+ }
1360
+ }
1361
+
1362
+ /**
1363
+ * The local names in `code` whose module-scope calls this transform would
1364
+ * annotate — the same decision {@link transformPureAnnotations} makes, for
1365
+ * tooling that needs to ask about a module without rewriting it (Lexical's
1366
+ * `no-pure-annotation` ESLint rule removes a hand-written annotation only
1367
+ * for a name this returns, so that the build is guaranteed to put it back).
1368
+ *
1369
+ * `functions` are called directly, `namespaces` are the objects whose method
1370
+ * calls (`sel.tag('p').attr('x')`) are annotated.
1371
+ *
1372
+ * @param {string} code
1373
+ * @param {TransformPureAnnotationsOptions} [options]
1374
+ * @returns {{functions: Set<string>, namespaces: Set<string>}}
1375
+ */
1376
+ function pureCallNames$1(code, options) {
1377
+ const opts = options || {};
1378
+ const functions = new Set(opts.functions || PURE_FACTORY_FUNCTIONS$1);
1379
+ const ast = parser.parse(code, {
1380
+ errorRecovery: false,
1381
+ plugins: parserPluginsFor(opts.filename, opts.parserPlugins),
1382
+ sourceFilename: opts.filename,
1383
+ sourceType: 'module',
1384
+ });
1385
+ return {
1386
+ functions: new Set(
1387
+ collectFactoryNames(ast.program, functions, opts).keys(),
1388
+ ),
1389
+ namespaces: collectNamespaceNames(
1390
+ ast.program,
1391
+ new Set(opts.namespaces || PURE_NAMESPACES$1),
1392
+ opts,
1393
+ ),
1394
+ };
1395
+ }
1396
+
1397
+ /**
1398
+ * Insert a `__PURE__` annotation before every module-scope call to one of the
1399
+ * side-effect-free Lexical factories that does not already have one.
1400
+ *
1401
+ * Returns `null` when the module needs no annotations, so a caller can pass
1402
+ * the original source through untouched.
1403
+ *
1404
+ * @param {string} code
1405
+ * @param {TransformPureAnnotationsOptions} [options]
1406
+ * @returns {null | PureAnnotationsResult}
1407
+ */
1408
+ function transformPureAnnotations$1(code, options) {
1409
+ const opts = options || {};
1410
+ const functions = new Set(opts.functions || PURE_FACTORY_FUNCTIONS$1);
1411
+ const namespaceSet = new Set(opts.namespaces || PURE_NAMESPACES$1);
1412
+ if (!mayNeedAnnotations(code, functions, namespaceSet)) {
1413
+ return null;
1414
+ }
1415
+ const ast = parser.parse(code, {
1416
+ errorRecovery: false,
1417
+ plugins: parserPluginsFor(opts.filename, opts.parserPlugins),
1418
+ sourceFilename: opts.filename,
1419
+ sourceType: 'module',
1420
+ });
1421
+ // Only the calls whose callee resolves to a factory this module imported
1422
+ // from Lexical (or to a declaration that says it is side-effect free) are
1423
+ // annotated; a same-named local helper is left alone.
1424
+ const factoryNames = collectFactoryNames(ast.program, functions, opts);
1425
+ const namespaceNames = collectNamespaceNames(ast.program, namespaceSet, opts);
1426
+ if (factoryNames.size === 0 && namespaceNames.size === 0) {
1427
+ return null;
1428
+ }
1429
+ /** @type {Set<number>} */
1430
+ const annotated = new Set();
1431
+ /** @type {Array<[any, {form: string}, boolean]>} */
1432
+ const inlined = [];
1433
+ /** @type {Array<any>} */
1434
+ const factoryCalls = [];
1435
+ visitModuleScopeCalls(
1436
+ ast.program,
1437
+ {functions: factoryNames, namespaces: namespaceNames},
1438
+ (node, position) => {
1439
+ if (position.namespace === true) {
1440
+ // Every call in a chain starts at the same offset, and one
1441
+ // annotation there covers the whole chain.
1442
+ if (!hasPureAnnotation(code, node.start)) {
1443
+ annotated.add(node.start);
1444
+ }
1445
+ return;
1446
+ }
1447
+ factoryCalls.push(node);
1448
+ const factory = /** @type {{inline: undefined | any, name: string}} */ (
1449
+ factoryNames.get(node.callee.name)
1450
+ );
1451
+ const spec = opts.inline === true ? factory.inline : undefined;
1452
+ if (
1453
+ spec !== undefined &&
1454
+ !position.discarded &&
1455
+ canInlineCall(node, spec)
1456
+ ) {
1457
+ inlined.push([node, spec, position.statementStart]);
1458
+ } else if (!hasPureAnnotation(code, node.start)) {
1459
+ annotated.add(node.start);
1460
+ }
1461
+ },
1462
+ );
1463
+ const offsets = Array.from(annotated);
1464
+ if (opts.strict === true) {
1465
+ const covered = new Set(annotated);
1466
+ for (const [node] of inlined) {
1467
+ covered.add(node.start);
1468
+ }
1469
+ assertArgumentsArePure(code, opts.filename, factoryCalls, covered);
1470
+ }
1471
+ if (offsets.length === 0 && inlined.length === 0) {
1472
+ return null;
1473
+ }
1474
+ const magicString = new MagicString(code);
1475
+ for (const offset of offsets) {
1476
+ magicString.appendLeft(offset, ANNOTATION);
1477
+ }
1478
+ for (const [node, spec, statementStart] of inlined) {
1479
+ inlineCall(magicString, code, node, spec, statementStart);
1480
+ }
1481
+ removeUnusedImports(
1482
+ magicString,
1483
+ code,
1484
+ ast.program,
1485
+ inlined.map(([node]) => node),
1486
+ );
1487
+ return {
1488
+ code: magicString.toString(),
1489
+ count: offsets.length,
1490
+ inlined: inlined.length,
1491
+ map:
1492
+ opts.sourceMap === false
1493
+ ? undefined
1494
+ : magicString.generateMap({hires: 'boundary', source: opts.filename}),
1495
+ };
1496
+ }
1497
+
1498
+ /**
1499
+ * @param {undefined | RegExp | ReadonlyArray<RegExp>} value
1500
+ * @param {ReadonlyArray<RegExp>} defaultValue
1501
+ * @returns {ReadonlyArray<RegExp>}
1502
+ */
1503
+ function toRegExpArray(value, defaultValue) {
1504
+ if (value === undefined) {
1505
+ return defaultValue;
1506
+ }
1507
+ return Array.isArray(value) ? value : [/** @type {RegExp} */ (value)];
1508
+ }
1509
+
1510
+ /**
1511
+ * @param {ReadonlyArray<RegExp>} patterns
1512
+ * @param {string} id
1513
+ * @returns {boolean}
1514
+ */
1515
+ function matchesAny(patterns, id) {
1516
+ return patterns.some(pattern => pattern.test(id));
1517
+ }
1518
+
1519
+ /**
1520
+ * Strip the query string and hash a bundler may append to a module id
1521
+ * (e.g. `Foo.tsx?vue&type=script`).
1522
+ *
1523
+ * @param {string} id
1524
+ * @returns {string}
1525
+ */
1526
+ function cleanModuleId(id) {
1527
+ return id.replace(/[?#][\s\S]*$/, '');
1528
+ }
1529
+
1530
+ /**
1531
+ * @typedef {Object} PureAnnotationsOptions
1532
+ * @property {'post' | 'pre'} [enforce] Vite plugin ordering, `'post'` by
1533
+ * default so the transform runs after TypeScript/JSX has been compiled away.
1534
+ * @property {RegExp | ReadonlyArray<RegExp>} [exclude] Module ids to skip.
1535
+ * @property {Iterable<string>} [functions] Names of the factories whose
1536
+ * module-scope calls are annotated. Defaults to PURE_FACTORY_FUNCTIONS.
1537
+ * @property {RegExp | ReadonlyArray<RegExp>} [include] Module ids to
1538
+ * transform, defaults to every .js/.jsx/.ts/.tsx (and .mjs/.cjs/.mts/.cts)
1539
+ * module.
1540
+ * @property {boolean} [inline] Replace calls to the factories whose result
1541
+ * is a trivial expression over their arguments with that expression,
1542
+ * rather than annotating them. Off by default: it assumes the Lexical
1543
+ * being built matches this package's version.
1544
+ * @property {ReadonlyArray<any>} [parserPlugins] Extra Babel parser
1545
+ * plugins, for syntax the defaults do not cover.
1546
+ * @property {boolean} [relativeImports] Set to false to skip reading
1547
+ * relatively imported modules to look for a NO_SIDE_EFFECTS declaration.
1548
+ * @property {Iterable<string>} [namespaces] Imported objects whose
1549
+ * method calls are side-effect free, defaults to PURE_NAMESPACES.
1550
+ * @property {boolean} [sourceMap] Set to false to skip source map generation.
1551
+ * @property {RegExp | ReadonlyArray<RegExp>} [sources] Module specifiers
1552
+ * whose exports are trusted to be the factories, defaults to `lexical` and
1553
+ * `@lexical/*`.
1554
+ * @property {boolean} [strict] Throw when a call evaluated inside one of the
1555
+ * definitions is not known to be side-effect free, since it pins the
1556
+ * definition into every bundle that imports the module.
1557
+ */
1558
+
1559
+ /**
1560
+ * A Vite/Rollup plugin that inserts the `__PURE__` annotations that let
1561
+ * bundlers tree-shake unused Lexical extension, command, and rule
1562
+ * definitions. Use it when building Lexical (or your own extensions) from
1563
+ * TypeScript source rather than from the published `dist` bundles, which
1564
+ * already carry the annotations.
1565
+ *
1566
+ * @param {PureAnnotationsOptions} [options]
1567
+ * @returns {any} the plugin object
1568
+ */
1569
+ function pureAnnotations$1(options) {
1570
+ const opts = options || {};
1571
+ const functions = new Set(opts.functions || PURE_FACTORY_FUNCTIONS$1);
1572
+ const include = toRegExpArray(opts.include, [DEFAULT_INCLUDE]);
1573
+ const exclude = toRegExpArray(opts.exclude, []);
1574
+ return {
1575
+ enforce: opts.enforce === undefined ? 'post' : opts.enforce,
1576
+ name: '@lexical/compiler',
1577
+ /**
1578
+ * @param {string} code
1579
+ * @param {string} id
1580
+ * @returns {null | {code: string, map: any}}
1581
+ */
1582
+ transform(code, id) {
1583
+ const filename = cleanModuleId(id);
1584
+ if (!matchesAny(include, filename) || matchesAny(exclude, filename)) {
1585
+ return null;
1586
+ }
1587
+ const dependency = NODE_MODULES_RE.test(filename);
1588
+ let result = null;
1589
+ try {
1590
+ result = transformPureAnnotations$1(code, {
1591
+ filename,
1592
+ functions,
1593
+ inline: opts.inline,
1594
+ namespaces: opts.namespaces,
1595
+ parserPlugins: opts.parserPlugins,
1596
+ relativeImports: opts.relativeImports,
1597
+ sourceMap: opts.sourceMap,
1598
+ sources: opts.sources,
1599
+ strict: opts.strict === true && !dependency,
1600
+ });
1601
+ } catch (err) {
1602
+ if (err instanceof PureAnnotationsError && !dependency) {
1603
+ // Not something to shrug off: the module says one thing and means
1604
+ // another, or a definition cannot be tree-shaken.
1605
+ throw err;
1606
+ }
1607
+ // A module this plugin cannot parse is a module it has nothing to
1608
+ // say about; warn rather than failing the consumer's build.
1609
+ if (this && typeof this.warn === 'function') {
1610
+ this.warn(
1611
+ `@lexical/compiler could not parse ${id}, no annotations were added: ${
1612
+ err instanceof Error ? err.message : String(err)
1613
+ }`,
1614
+ );
1615
+ }
1616
+ return null;
1617
+ }
1618
+ return result === null
1619
+ ? null
1620
+ : {
1621
+ code: result.code,
1622
+ map: result.map === undefined ? null : result.map,
1623
+ };
1624
+ },
1625
+ };
1626
+ }
1627
+
1628
+ /**
1629
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
1630
+ *
1631
+ * This source code is licensed under the MIT license found in the
1632
+ * LICENSE file in the root directory of this source tree.
1633
+ *
1634
+ */
1635
+
1636
+
1637
+ // The comments here spell the annotations without their leading sigil
1638
+ // (`__PURE__`) because bundlers scan comments for annotations, and one that
1639
+ // does not precede a call expression is reported as misplaced (rollup's
1640
+ // INVALID_ANNOTATION).
1641
+
1642
+ /**
1643
+ * The subset of a source map that bundlers consume, matching what
1644
+ * `magic-string` produces. Declared structurally so this package needs no
1645
+ * dependency on any particular bundler's types.
1646
+ */
1647
+
1648
+ /**
1649
+ * The structural shape of the returned plugin. It is assignable to Vite's
1650
+ * `Plugin` and to Rollup's `Plugin`, so it can be dropped into the `plugins`
1651
+ * array of either.
1652
+ */
1653
+
1654
+ /**
1655
+ * The Lexical factories that are annotated with `__NO_SIDE_EFFECTS__` at
1656
+ * their definition and whose module-scope call sites therefore need a
1657
+ * `__PURE__` annotation for bundlers to tree-shake unused definitions.
1658
+ */
1659
+ const PURE_FACTORY_FUNCTIONS = PURE_FACTORY_FUNCTIONS$1;
1660
+
1661
+ /**
1662
+ * Objects imported from a Lexical package whose method calls are
1663
+ * side-effect free, so that a module-scope `sel.tag('p')` is annotated the
1664
+ * way a factory call is. Only the outermost call of a chain is annotated:
1665
+ * rollup, terser and esbuild all drop the whole chain from that one.
1666
+ */
1667
+ const PURE_NAMESPACES = PURE_NAMESPACES$1;
1668
+
1669
+ /**
1670
+ * The factories that `inline` replaces with a literal, and the form each one
1671
+ * takes: `identity` returns its single argument and `args` returns all of its
1672
+ * arguments as an array. Each is marked `@lexical-inline <form>` where it is
1673
+ * defined.
1674
+ */
1675
+ const INLINE_FACTORY_FORMS = INLINE_FACTORY_FORMS$1;
1676
+
1677
+ /**
1678
+ * Insert a `__PURE__` annotation before every module-scope call to one of the
1679
+ * side-effect-free Lexical factories that does not already have one. Returns
1680
+ * `null` when the module needs no annotations.
1681
+ */
1682
+ function transformPureAnnotations(code, options) {
1683
+ return transformPureAnnotations$1(code, options);
1684
+ }
1685
+
1686
+ /**
1687
+ * The local names in `code` whose module-scope calls the transform would
1688
+ * annotate: `functions` are called directly, `namespaces` are objects whose
1689
+ * method calls are annotated. A name qualifies by being imported from a
1690
+ * Lexical package, declared in the module as side-effect free, or imported
1691
+ * from a relative module that declares it that way. For tooling that needs
1692
+ * the same decision without rewriting the module — a lint rule that removes
1693
+ * a hand-written annotation only where the build puts one back. Throws on
1694
+ * source it cannot parse.
1695
+ */
1696
+ function pureCallNames(code, options) {
1697
+ return pureCallNames$1(code, options);
1698
+ }
1699
+
1700
+ /**
1701
+ * A Vite/Rollup plugin that inserts the `__PURE__` annotations that let
1702
+ * bundlers tree-shake unused Lexical extension, command, and rule
1703
+ * definitions.
1704
+ *
1705
+ * The published `dist` bundles already carry the annotations, so this is for
1706
+ * builds that compile Lexical from its TypeScript source (the `source`
1707
+ * export condition, a git checkout, or a vendored copy) — and for annotating
1708
+ * the extensions and commands your own application defines.
1709
+ *
1710
+ * ```js
1711
+ * import {pureAnnotations} from '@lexical/compiler';
1712
+ *
1713
+ * export default defineConfig({plugins: [pureAnnotations()]});
1714
+ * ```
1715
+ */
1716
+ function pureAnnotations(options) {
1717
+ return pureAnnotations$1(options);
1718
+ }
1719
+
1720
+ exports.INLINE_FACTORY_FORMS = INLINE_FACTORY_FORMS;
1721
+ exports.PURE_FACTORY_FUNCTIONS = PURE_FACTORY_FUNCTIONS;
1722
+ exports.PURE_NAMESPACES = PURE_NAMESPACES;
1723
+ exports.pureAnnotations = pureAnnotations;
1724
+ exports.pureCallNames = pureCallNames;
1725
+ exports.transformPureAnnotations = transformPureAnnotations;