styled-components-to-stylex-codemod 0.0.48 → 0.0.50

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.
@@ -5,6 +5,7 @@ import { readFileSync } from "node:fs";
5
5
  * Logger and warning types for transform diagnostics.
6
6
  * Core concepts: severity classification and source context reporting.
7
7
  */
8
+ const CASCADE_CONFLICT_WARNING = "styled(ImportedComponent) wraps a component whose file uses styled-components — convert the base component's file first to avoid CSS cascade conflicts";
8
9
  const UNSUPPORTED_SHOULD_FORWARD_PROP_WARNING = "Unsupported shouldForwardProp pattern (only !prop.startsWith(), ![].includes(prop), and prop !== are supported)";
9
10
  /**
10
11
  * When fileCount <= this threshold, warnings are printed per-file inline and
@@ -134,6 +135,7 @@ function createContextReplacer() {
134
135
  return value;
135
136
  };
136
137
  }
138
+ const MAX_DEPENDED_FILE_GROUPS = 15;
137
139
  var LoggerReport = class {
138
140
  warnings;
139
141
  fileCount;
@@ -163,6 +165,20 @@ var LoggerReport = class {
163
165
  lines.push("");
164
166
  lines.push(`▸ ${group.message} (${group.warnings.length})`);
165
167
  lines.push("");
168
+ const dependedFileGroups = this.groupDependedFiles(group);
169
+ if (dependedFileGroups.length > 0) {
170
+ lines.push(" Top depended files:");
171
+ lines.push("");
172
+ for (const [index, dependedFileGroup] of dependedFileGroups.entries()) {
173
+ const usageCount = dependedFileGroup.usageFiles.length;
174
+ const usageLabel = usageCount === 1 ? "usage file" : "usage files";
175
+ lines.push(` ${index + 1}. ${dependedFileGroup.dependedFilePath} (${usageCount} ${usageLabel})`);
176
+ for (const usageFile of dependedFileGroup.usageFiles.slice(0, MAX_EXAMPLES)) lines.push(` ${usageFile}`);
177
+ const remainingUsageFiles = usageCount - MAX_EXAMPLES;
178
+ if (remainingUsageFiles > 0) lines.push(` ... and ${remainingUsageFiles} more usage file(s)`);
179
+ lines.push("");
180
+ }
181
+ }
166
182
  const seenFiles = /* @__PURE__ */ new Set();
167
183
  const uniqueLocations = [];
168
184
  for (const loc of group.warnings) if (!seenFiles.has(loc.filePath)) {
@@ -212,6 +228,21 @@ var LoggerReport = class {
212
228
  }
213
229
  return Array.from(groupMap.values()).sort((a, b) => b.warnings.length - a.warnings.length);
214
230
  }
231
+ groupDependedFiles(group) {
232
+ if (group.message !== "styled(ImportedComponent) wraps a component whose file uses styled-components — convert the base component's file first to avoid CSS cascade conflicts") return [];
233
+ const groupMap = /* @__PURE__ */ new Map();
234
+ for (const warning of group.warnings) {
235
+ const dependedFilePath = getCascadeDependedFilePath(warning);
236
+ if (!dependedFilePath) continue;
237
+ const dependedFileWarnings = groupMap.get(dependedFilePath) ?? [];
238
+ dependedFileWarnings.push(warning);
239
+ groupMap.set(dependedFilePath, dependedFileWarnings);
240
+ }
241
+ return Array.from(groupMap.entries()).map(([dependedFilePath, warnings]) => ({
242
+ dependedFilePath,
243
+ usageFiles: uniqueSorted(warnings.map((warning) => warning.filePath))
244
+ })).sort((a, b) => b.usageFiles.length - a.usageFiles.length).slice(0, MAX_DEPENDED_FILE_GROUPS);
245
+ }
215
246
  getSnippet(filePath, loc) {
216
247
  if (!loc) return;
217
248
  const lines = this.getFileLines(filePath);
@@ -240,6 +271,18 @@ var LoggerReport = class {
240
271
  }
241
272
  }
242
273
  };
274
+ function getCascadeDependedFilePath(warning) {
275
+ const context = warning.context;
276
+ if (!context || typeof context !== "object") return;
277
+ const record = context;
278
+ const definitionPath = record.definitionPath;
279
+ if (typeof definitionPath === "string") return definitionPath;
280
+ const importedPath = record.importedPath;
281
+ return typeof importedPath === "string" ? importedPath : void 0;
282
+ }
283
+ function uniqueSorted(values) {
284
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
285
+ }
243
286
  const WARN_BG_COLOR = "\x1B[43m";
244
287
  const WARN_TEXT_COLOR = "\x1B[30m";
245
288
  const ERROR_BG_COLOR = "\x1B[41m";
@@ -380,4 +423,29 @@ function getFileImportsFromRes(name) {
380
423
  return cached;
381
424
  }
382
425
  //#endregion
383
- export { resolveBarrelReExport as a, UNSUPPORTED_SHOULD_FORWARD_PROP_WARNING as c, getReExportedSourceName as i, fileImportsFrom as n, resolveBarrelReExportBinding as o, findImportSource as r, Logger as s, fileExports as t };
426
+ //#region src/internal/utilities/ast-walk.ts
427
+ const SKIPPED_KEYS = new Set([
428
+ "loc",
429
+ "comments",
430
+ "leadingComments",
431
+ "trailingComments"
432
+ ]);
433
+ function walkAst(root, visitor) {
434
+ const visit = (node) => {
435
+ if (!node || typeof node !== "object") return;
436
+ if (Array.isArray(node)) {
437
+ for (const child of node) visit(child);
438
+ return;
439
+ }
440
+ const n = node;
441
+ visitor(n);
442
+ for (const key of Object.keys(n)) {
443
+ if (SKIPPED_KEYS.has(key)) continue;
444
+ const child = n[key];
445
+ if (child && typeof child === "object") visit(child);
446
+ }
447
+ };
448
+ visit(root);
449
+ }
450
+ //#endregion
451
+ export { getReExportedSourceName as a, CASCADE_CONFLICT_WARNING as c, findImportSource as i, Logger as l, fileExports as n, resolveBarrelReExport as o, fileImportsFrom as r, resolveBarrelReExportBinding as s, walkAst as t, UNSUPPORTED_SHOULD_FORWARD_PROP_WARNING as u };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as CollectedWarning, c as MarkerFileContext, l as defineAdapter, n as TransformMode, o as AdapterInput, s as ImportSource } from "./transform-types-Bshr_dBf.mjs";
1
+ import { a as CollectedWarning, c as MarkerFileContext, l as defineAdapter, n as TransformMode, o as AdapterInput, s as ImportSource } from "./transform-types-CHRHLCj_.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { c as describeValue, i as defineAdapter, n as mergeMarkerDeclarations, s as assertValidAdapterInput, t as transformedComponentAcceptsSx } from "./sx-surface-BzqO3hcC.mjs";
2
- import { a as resolveBarrelReExport, s as Logger } from "./extract-external-interface-CvkkJZb1.mjs";
3
- import { r as extractStyledDefBasesFromSource } from "./compute-leaf-set-90UrZ9WP.mjs";
2
+ import { l as Logger, o as resolveBarrelReExport, t as walkAst } from "./ast-walk-DgShpexa.mjs";
3
+ import { i as extractStyledDefBasesFromSource, t as createPrepassParser } from "./prepass-parser-BRDlW3DI.mjs";
4
4
  import { r as toRealPath } from "./path-utils-BC4U8X_q.mjs";
5
5
  import jscodeshift from "jscodeshift";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -8,6 +8,162 @@ import { dirname, join, resolve } from "node:path";
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { glob, readFile, writeFile } from "node:fs/promises";
10
10
  import { spawn } from "node:child_process";
11
+ //#region src/internal/prepass/resolve-static-members.ts
12
+ /**
13
+ * Resolves the underlying component name(s) a static member access like `Select.Option` refers to,
14
+ * by structurally walking the defining module's AST.
15
+ *
16
+ * This replaces an earlier regex-over-source approach: regexes could not reliably skip type
17
+ * annotations, stop at the right `;`, or distinguish identifiers from type names. We parse once
18
+ * (cached per source) with the shared prepass parser and answer member lookups against real nodes.
19
+ */
20
+ /**
21
+ * Given the module `source`, the candidate root component names a local binding resolves to, and a
22
+ * static member path (`["Option"]` for `Select.Option`), returns every component name the member
23
+ * path can statically resolve to, plus the final member name as a fallback (matching the prior
24
+ * behavior so downstream metadata lookups still have something to try).
25
+ */
26
+ function resolveStaticMemberComponentNames(source, rootNames, memberPath, parserName = "tsx") {
27
+ const program = parseProgram(source, parserName);
28
+ const fallbackMember = memberPath[memberPath.length - 1];
29
+ const fallback = fallbackMember ? [fallbackMember] : [];
30
+ if (!program) return [...new Set([...rootNames, ...fallback])];
31
+ const index = buildModuleIndex(program);
32
+ let owners = expandStaticComponentOwners(index, rootNames);
33
+ for (const memberName of memberPath) {
34
+ const nextOwners = /* @__PURE__ */ new Set();
35
+ for (const ownerName of owners) for (const target of findStaticMemberTargets(index, ownerName, memberName)) nextOwners.add(target);
36
+ owners = nextOwners;
37
+ if (owners.size === 0) break;
38
+ }
39
+ return [...new Set([...owners, ...fallback])];
40
+ }
41
+ const programCache = /* @__PURE__ */ new Map();
42
+ function parseProgram(source, parserName) {
43
+ const cached = programCache.get(source);
44
+ if (cached !== void 0) return cached;
45
+ const parsed = tryParse(source, parserName);
46
+ programCache.set(source, parsed);
47
+ return parsed;
48
+ }
49
+ function tryParse(source, parserName) {
50
+ for (const name of parserName === "tsx" ? ["tsx", "ts"] : [parserName]) try {
51
+ const ast = createPrepassParser(name).parse(source);
52
+ return ast.program ?? ast;
53
+ } catch {}
54
+ return null;
55
+ }
56
+ function buildModuleIndex(program) {
57
+ const initializers = /* @__PURE__ */ new Map();
58
+ const memberAssignments = /* @__PURE__ */ new Map();
59
+ walkAst(program, (node) => {
60
+ if (node.type === "VariableDeclarator") {
61
+ const name = identifierName(node.id);
62
+ const init = node.init;
63
+ if (name && init) initializers.set(name, init);
64
+ return;
65
+ }
66
+ if (node.type === "AssignmentExpression" && node.operator === "=") {
67
+ const target = capitalizedIdentifierName(node.right);
68
+ const member = staticMemberAccess(node.left);
69
+ if (target && member) addMemberTarget(memberAssignments, member.object, member.property, target);
70
+ }
71
+ });
72
+ return {
73
+ initializers,
74
+ memberAssignments
75
+ };
76
+ }
77
+ /**
78
+ * Expands the root binding names to every capitalized identifier transitively referenced from their
79
+ * initializers (e.g. `const X = cond ? A : B` pulls in `A` and `B`). Mirrors the breadth the member
80
+ * lookup needs without assuming a particular declaration shape.
81
+ */
82
+ function expandStaticComponentOwners(index, rootNames) {
83
+ const owners = new Set(rootNames);
84
+ const visit = (name) => {
85
+ const init = index.initializers.get(name);
86
+ if (!init) return;
87
+ for (const referenced of collectCapitalizedIdentifiers(init)) if (!owners.has(referenced)) {
88
+ owners.add(referenced);
89
+ visit(referenced);
90
+ }
91
+ };
92
+ for (const name of rootNames) visit(name);
93
+ return owners;
94
+ }
95
+ function findStaticMemberTargets(index, ownerName, memberName) {
96
+ const targets = new Set(index.memberAssignments.get(ownerName)?.get(memberName) ?? []);
97
+ const init = index.initializers.get(ownerName);
98
+ for (const objectLiteral of objectLiteralsFromInitializer(index, init)) collectMemberTargetsFromObjectLiteral(objectLiteral, memberName, targets);
99
+ return targets;
100
+ }
101
+ /** Object expressions an owner's value is built from: a direct literal or `Object.assign(...)` args. */
102
+ function objectLiteralsFromInitializer(index, init) {
103
+ if (!init) return [];
104
+ if (init.type === "ObjectExpression") return [init];
105
+ if (isObjectAssignCall(init)) {
106
+ const literals = [];
107
+ for (const arg of init.arguments ?? []) if (arg.type === "ObjectExpression") literals.push(arg);
108
+ else {
109
+ const referenced = identifierName(arg);
110
+ const referencedInit = referenced ? index.initializers.get(referenced) : void 0;
111
+ if (referencedInit?.type === "ObjectExpression") literals.push(referencedInit);
112
+ }
113
+ return literals;
114
+ }
115
+ return [];
116
+ }
117
+ function collectMemberTargetsFromObjectLiteral(objectLiteral, memberName, targets) {
118
+ for (const property of objectLiteral.properties ?? []) {
119
+ if (property.type !== "ObjectProperty" && property.type !== "Property") continue;
120
+ if (identifierName(property.key) !== memberName) continue;
121
+ const target = capitalizedIdentifierName(property.value);
122
+ if (target) targets.add(target);
123
+ }
124
+ }
125
+ function isObjectAssignCall(node) {
126
+ if (node.type !== "CallExpression") return false;
127
+ const member = staticMemberAccess(node.callee);
128
+ return member?.object === "Object" && member.property === "assign";
129
+ }
130
+ function collectCapitalizedIdentifiers(node) {
131
+ const names = /* @__PURE__ */ new Set();
132
+ walkAst(node, (child) => {
133
+ if (child.type === "Identifier" && /^[A-Z]/.test(String(child.name))) names.add(String(child.name));
134
+ });
135
+ return names;
136
+ }
137
+ function staticMemberAccess(node) {
138
+ if (!node || node.type !== "MemberExpression" && node.type !== "OptionalMemberExpression" || node.computed === true) return null;
139
+ const object = identifierName(node.object);
140
+ const property = identifierName(node.property);
141
+ return object && property ? {
142
+ object,
143
+ property
144
+ } : null;
145
+ }
146
+ function addMemberTarget(memberAssignments, object, property, target) {
147
+ let byMember = memberAssignments.get(object);
148
+ if (!byMember) {
149
+ byMember = /* @__PURE__ */ new Map();
150
+ memberAssignments.set(object, byMember);
151
+ }
152
+ let targets = byMember.get(property);
153
+ if (!targets) {
154
+ targets = /* @__PURE__ */ new Set();
155
+ byMember.set(property, targets);
156
+ }
157
+ targets.add(target);
158
+ }
159
+ function identifierName(node) {
160
+ return node?.type === "Identifier" && typeof node.name === "string" ? node.name : null;
161
+ }
162
+ function capitalizedIdentifierName(node) {
163
+ const name = identifierName(node);
164
+ return name && /^[A-Z]/.test(name) ? name : null;
165
+ }
166
+ //#endregion
11
167
  //#region src/run.ts
12
168
  /**
13
169
  * Runs the codemod over input files with an adapter.
@@ -149,7 +305,7 @@ async function runTransform(options) {
149
305
  const { createModuleResolver } = await import("./resolve-imports-DgSAddIF.mjs").then((n) => n.n);
150
306
  const sharedResolver = createModuleResolver();
151
307
  filePaths = orderFilesByLocalImportDependencies(filePaths, sharedResolver, toRealPath);
152
- const { runPrepass } = await import("./run-prepass-1yZOVT3P.mjs");
308
+ const { runPrepass } = await import("./run-prepass-Da3N174M.mjs");
153
309
  const absoluteFiles = filePaths.map((f) => resolve(f));
154
310
  const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
155
311
  let prepassResult;
@@ -249,12 +405,15 @@ async function runTransform(options) {
249
405
  if (!resolvedImport) return;
250
406
  const resolvedPath = toRealPath(resolvedImport);
251
407
  const definitionSourcePath = resolveExistingSourcePath(resolveBarrelReExport(resolvedPath, ctx.importedName, prepassResolve, cachedRead) ?? resolvedPath);
252
- const autoInterfaceNames = ctx.importedName === "default" ? [ctx.localName, ctx.importedName] : [ctx.importedName];
408
+ const memberPath = ctx.memberPath ?? [];
409
+ const autoInterfaceNames = memberPath.length > 0 ? [ctx.localName, memberPath[memberPath.length - 1]] : ctx.importedName === "default" ? [ctx.localName, ctx.importedName] : [ctx.importedName];
253
410
  const styledDefinitionNames = getStyledDefinitionNames(definitionSourcePath);
254
- const sourceComponentNames = ctx.importedName === "default" ? [ctx.localName, getDefaultExportedName(definitionSourcePath)].filter((name) => typeof name === "string") : [ctx.importedName];
411
+ const rootSourceComponentNames = ctx.importedName === "default" ? [ctx.localName, getDefaultExportedName(definitionSourcePath)].filter((name) => typeof name === "string") : [ctx.importedName];
412
+ const sourceComponentNames = memberPath.length > 0 ? resolveStaticMemberComponentNames(cachedRead(definitionSourcePath), rootSourceComponentNames, memberPath) : rootSourceComponentNames;
255
413
  const typedComponent = findTypedComponentMetadata(prepassResult.typeScriptMetadata, definitionSourcePath, sourceComponentNames);
256
414
  if (typedComponent?.supportsSxProp === true) return {
257
415
  acceptsSx: true,
416
+ ...typedComponent.sxTarget ? { sxTarget: typedComponent.sxTarget } : {},
258
417
  sxExcludedProperties: typedComponent.sxExcludedProperties,
259
418
  sxAllowedProperties: typedComponent.sxAllowedProperties
260
419
  };
@@ -339,7 +498,23 @@ async function runTransform(options) {
339
498
  transformedFiles: /* @__PURE__ */ new Set()
340
499
  },
341
500
  silent: true,
342
- isolateFiles: true
501
+ isolateFiles: true,
502
+ createIsolatedOptions(filePath) {
503
+ const isolatedTransformedFiles = /* @__PURE__ */ new Set();
504
+ return {
505
+ ...runnerOptions,
506
+ dry: true,
507
+ print: false,
508
+ sidecarFiles: /* @__PURE__ */ new Map(),
509
+ bridgeResults: /* @__PURE__ */ new Map(),
510
+ transformedFiles: isolatedTransformedFiles,
511
+ transformedFileSources: /* @__PURE__ */ new Map(),
512
+ transientPropRenames: /* @__PURE__ */ new Map(),
513
+ crossFilePrepassResult: createStandalonePrepassResult(crossFilePrepassResult, filePath, isolatedTransformedFiles),
514
+ silent: true,
515
+ isolateFiles: true
516
+ };
517
+ }
343
518
  });
344
519
  standaloneWarnings = Logger.createReport().getWarnings();
345
520
  Logger._clearCollected();
@@ -480,6 +655,46 @@ function readFileForOrdering(filePath) {
480
655
  return "";
481
656
  }
482
657
  }
658
+ function createStandalonePrepassResult(prepass, filePath, transformedFiles) {
659
+ const standaloneFile = toRealPath(resolve(filePath));
660
+ const selectorUsages = /* @__PURE__ */ new Map();
661
+ const componentsNeedingMarkerSidecar = /* @__PURE__ */ new Map();
662
+ const componentsNeedingGlobalSelectorBridge = /* @__PURE__ */ new Map();
663
+ for (const [consumerPath, usages] of prepass.selectorUsages) {
664
+ const consumerIsTransformed = toRealPath(consumerPath) === standaloneFile;
665
+ const isolatedUsages = usages.map((usage) => ({
666
+ ...usage,
667
+ consumerIsTransformed
668
+ }));
669
+ selectorUsages.set(consumerPath, isolatedUsages);
670
+ for (const usage of isolatedUsages) {
671
+ if (usage.bridgeComponentName) continue;
672
+ if (consumerIsTransformed) addSetMapEntry(componentsNeedingMarkerSidecar, usage.resolvedPath, usage.importedName);
673
+ addSetMapEntry(componentsNeedingGlobalSelectorBridge, usage.resolvedPath, usage.importedName);
674
+ }
675
+ }
676
+ return {
677
+ ...prepass,
678
+ selectorUsages,
679
+ componentsNeedingMarkerSidecar,
680
+ componentsNeedingGlobalSelectorBridge,
681
+ globalLeafKeys: getStandaloneGlobalLeafKeys(prepass.globalLeafKeys, standaloneFile),
682
+ transformedFiles
683
+ };
684
+ }
685
+ function getStandaloneGlobalLeafKeys(globalLeafKeys, standaloneFile) {
686
+ if (!globalLeafKeys) return;
687
+ const filePrefix = `${standaloneFile}:`;
688
+ return new Set([...globalLeafKeys].filter((key) => key.startsWith(filePrefix)));
689
+ }
690
+ function addSetMapEntry(map, key, value) {
691
+ const values = map.get(key);
692
+ if (values) {
693
+ values.add(value);
694
+ return;
695
+ }
696
+ map.set(key, new Set([value]));
697
+ }
483
698
  async function runTransformSequentially(transformModule, filePaths, options) {
484
699
  const transform = await loadTransformFunction(transformModule);
485
700
  const aggregate = {
@@ -491,20 +706,25 @@ async function runTransformSequentially(transformModule, filePaths, options) {
491
706
  files: []
492
707
  };
493
708
  const startedAt = performance.now();
494
- const j = jscodeshift.withParser(options.parser);
495
- const api = {
496
- j,
497
- jscodeshift: j,
498
- stats: () => {},
499
- report: (msg) => {
500
- if (!options.silent) process.stdout.write(`${msg}\n`);
501
- }
709
+ const createApi = () => {
710
+ const j = jscodeshift.withParser(options.parser);
711
+ return {
712
+ j,
713
+ jscodeshift: j,
714
+ stats: () => {},
715
+ report: (msg) => {
716
+ if (!options.silent) process.stdout.write(`${msg}\n`);
717
+ }
718
+ };
502
719
  };
720
+ const sharedApi = createApi();
503
721
  for (const filePath of filePaths) {
722
+ const fileOptions = options.createIsolatedOptions?.(filePath) ?? options;
723
+ const api = fileOptions.isolateFiles === true ? createApi() : sharedApi;
504
724
  if (options.isolateFiles === true) {
505
- options.transformedFiles.clear();
506
- options.transformedFileSources.clear();
507
- options.crossFilePrepassResult?.transformedFiles?.clear();
725
+ fileOptions.transformedFiles.clear();
726
+ fileOptions.transformedFileSources.clear();
727
+ fileOptions.crossFilePrepassResult?.transformedFiles?.clear();
508
728
  }
509
729
  let source;
510
730
  try {
@@ -522,8 +742,8 @@ async function runTransformSequentially(transformModule, filePaths, options) {
522
742
  const output = await transform({
523
743
  path: filePath,
524
744
  source
525
- }, api, options);
526
- if (output !== null) options.transformedFileSources.set(toRealPath(filePath), output);
745
+ }, api, fileOptions);
746
+ if (output !== null) fileOptions.transformedFileSources.set(toRealPath(filePath), output);
527
747
  if (output === null) {
528
748
  aggregate.skip += 1;
529
749
  aggregate.files.push({
@@ -540,8 +760,8 @@ async function runTransformSequentially(transformModule, filePaths, options) {
540
760
  });
541
761
  continue;
542
762
  }
543
- if (options.print) process.stdout.write(`${output}\n`);
544
- if (!options.dry) await writeFile(filePath, output, "utf-8");
763
+ if (fileOptions.print) process.stdout.write(`${output}\n`);
764
+ if (!fileOptions.dry) await writeFile(filePath, output, "utf-8");
545
765
  aggregate.ok += 1;
546
766
  aggregate.files.push({
547
767
  filePath,
@@ -1,4 +1,5 @@
1
- import { o as resolveBarrelReExportBinding, r as findImportSource } from "./extract-external-interface-CvkkJZb1.mjs";
1
+ import { i as findImportSource, s as resolveBarrelReExportBinding } from "./ast-walk-DgShpexa.mjs";
2
+ import { parse } from "@babel/parser";
2
3
  //#region src/internal/prepass/compute-leaf-set.ts
3
4
  /**
4
5
  * Computes which styled-component bindings are "leaves" for leaves-only mode:
@@ -239,4 +240,101 @@ function findDefaultExportedLocalName(source) {
239
240
  return source.match(/\bexport\s+default\s+([A-Z][A-Za-z0-9]*)\b/)?.[1] ?? source.match(/\bexport\s*\{[^}]*\b([A-Z][A-Za-z0-9]*)\s+as\s+default\b[^}]*\}/)?.[1];
240
241
  }
241
242
  //#endregion
242
- export { extractStyledDefBasesFromAstProgram as n, extractStyledDefBasesFromSource as r, computeGlobalLeafKeys as t };
243
+ //#region src/internal/prepass/prepass-parser.ts
244
+ /**
245
+ * Shared babel parser for prepass modules.
246
+ *
247
+ * Uses @babel/parser directly with `tokens: false` for ~35% faster parsing
248
+ * compared to jscodeshift's getParser (which enables token generation).
249
+ *
250
+ * Both scan-cross-file-selectors and extract-external-interface can share this parser
251
+ * to avoid duplicate parser initialization.
252
+ */
253
+ /**
254
+ * Create a babel parser with tokens disabled, matching jscodeshift's plugin set
255
+ * for the given parser name.
256
+ *
257
+ * - `tsx` / `ts`: TypeScript plugins (tsx also includes JSX)
258
+ * - `babel` / `babylon` / `flow`: Flow plugins + JSX
259
+ *
260
+ * Note: jscodeshift's `flow` parser uses the `flow-parser` package (not babel).
261
+ * We always use `@babel/parser` with the `flow` plugin instead, since it produces
262
+ * the same AST node types (ImportDeclaration, TaggedTemplateExpression) that the
263
+ * prepass walks, and avoids an extra parser dependency.
264
+ */
265
+ function createPrepassParser(parserName = "tsx") {
266
+ const options = parserName === "ts" || parserName === "tsx" ? buildOptions(TS_PLUGINS, parserName === "tsx") : buildOptions(FLOW_PLUGINS, true);
267
+ return { parse(source) {
268
+ return parse(source, options);
269
+ } };
270
+ }
271
+ function buildOptions(plugins, includeJsx) {
272
+ return {
273
+ sourceType: "module",
274
+ allowImportExportEverywhere: true,
275
+ allowReturnOutsideFunction: true,
276
+ startLine: 1,
277
+ tokens: false,
278
+ plugins: includeJsx ? ["jsx", ...plugins] : plugins
279
+ };
280
+ }
281
+ /**
282
+ * Plugins for TypeScript parsers (ts, tsx).
283
+ * Same as jscodeshift's tsOptions.plugins minus "jsx" (added conditionally).
284
+ */
285
+ const TS_PLUGINS = [
286
+ "asyncGenerators",
287
+ "decoratorAutoAccessors",
288
+ "bigInt",
289
+ "classPrivateMethods",
290
+ "classPrivateProperties",
291
+ "classProperties",
292
+ "decorators-legacy",
293
+ "doExpressions",
294
+ "dynamicImport",
295
+ "exportDefaultFrom",
296
+ "exportNamespaceFrom",
297
+ "functionBind",
298
+ "functionSent",
299
+ "importAttributes",
300
+ "importMeta",
301
+ "nullishCoalescingOperator",
302
+ "numericSeparator",
303
+ "objectRestSpread",
304
+ "optionalCatchBinding",
305
+ "optionalChaining",
306
+ ["pipelineOperator", { proposal: "minimal" }],
307
+ "throwExpressions",
308
+ "typescript"
309
+ ];
310
+ /**
311
+ * Plugins for Flow/Babylon parsers (babel, babylon, flow).
312
+ * Same as jscodeshift's babylon parser plugins minus "jsx" (added conditionally).
313
+ */
314
+ const FLOW_PLUGINS = [
315
+ ["flow", { all: true }],
316
+ "flowComments",
317
+ "asyncGenerators",
318
+ "bigInt",
319
+ "classProperties",
320
+ "classPrivateProperties",
321
+ "classPrivateMethods",
322
+ ["decorators", { decoratorsBeforeExport: false }],
323
+ "doExpressions",
324
+ "dynamicImport",
325
+ "exportDefaultFrom",
326
+ "exportNamespaceFrom",
327
+ "functionBind",
328
+ "functionSent",
329
+ "importMeta",
330
+ "logicalAssignment",
331
+ "nullishCoalescingOperator",
332
+ "numericSeparator",
333
+ "objectRestSpread",
334
+ "optionalCatchBinding",
335
+ "optionalChaining",
336
+ ["pipelineOperator", { proposal: "minimal" }],
337
+ "throwExpressions"
338
+ ];
339
+ //#endregion
340
+ export { extractStyledDefBasesFromSource as i, computeGlobalLeafKeys as n, extractStyledDefBasesFromAstProgram as r, createPrepassParser as t };
@@ -61,5 +61,19 @@ function mergeComponentPropUsage(info, usage) {
61
61
  if (!propInfo.values.some((existing) => existing === value.value)) propInfo.values.push(value.value);
62
62
  }
63
63
  }
64
+ /**
65
+ * Formats a `prop === value` JS condition for an observed static variant bucket,
66
+ * quoting strings and emitting numbers bare. Shared by every observed-variant emitter.
67
+ */
68
+ function formatObservedVariantCondition(propName, value) {
69
+ return `${propName} === ${typeof value === "number" ? String(value) : JSON.stringify(value)}`;
70
+ }
71
+ function getExhaustiveObservedStaticValues(info, propName) {
72
+ const propUsage = info?.props[propName];
73
+ if (!info || info.hasUnknownUsage || !propUsage || propUsage.hasUnknown) return null;
74
+ if (propUsage.values.length < 1) return null;
75
+ const values = propUsage.values.filter((value) => typeof value === "string" || typeof value === "number");
76
+ return values.length === propUsage.values.length ? values : null;
77
+ }
64
78
  //#endregion
65
- export { readStaticJsxLiteral as i, createComponentPropUsageInfo as n, mergeComponentPropUsage as r, KNOWN_NON_ELEMENT_PROPS as t };
79
+ export { mergeComponentPropUsage as a, getExhaustiveObservedStaticValues as i, createComponentPropUsageInfo as n, readStaticJsxLiteral as o, formatObservedVariantCondition as r, KNOWN_NON_ELEMENT_PROPS as t };