styled-components-to-stylex-codemod 0.0.39 → 0.0.41

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.
@@ -1,4 +1,4 @@
1
- import { i as resolveBarrelReExport, r as findImportSource } from "./extract-external-interface-CdHbvfxu.mjs";
1
+ import { o as resolveBarrelReExportBinding, r as findImportSource } from "./extract-external-interface-B61N1a6q.mjs";
2
2
  //#region src/internal/prepass/compute-leaf-set.ts
3
3
  /**
4
4
  * Computes which styled-component bindings are "leaves" for leaves-only mode:
@@ -171,9 +171,12 @@ function computeGlobalLeafKeys(args) {
171
171
  if (!importInfo) return false;
172
172
  const initialDefFile = resolve(importInfo.source, file);
173
173
  if (!initialDefFile) return false;
174
- const defReal = toRealPath(resolveBarrelReExport(initialDefFile, importInfo.isDefault ? "default" : importInfo.exportedName, resolve, cachedRead) ?? initialDefFile);
174
+ const reExport = resolveBarrelReExportBinding(initialDefFile, importInfo.isDefault ? "default" : importInfo.exportedName, resolve, cachedRead);
175
+ const defFile = reExport?.filePath ?? initialDefFile;
176
+ const exportedName = reExport?.exportedName ?? importInfo.exportedName;
177
+ const defReal = toRealPath(defFile);
175
178
  if (!transformSet.has(defReal)) return false;
176
- return leafKeyExists(defReal, importInfo.exportedName, importInfo.isDefault, cachedRead, globalLeaves);
179
+ return leafKeyExists(defReal, exportedName, exportedName === "default" || importInfo.isDefault, cachedRead, globalLeaves);
177
180
  };
178
181
  const tryResolveAdapterIntrinsic = (file, ident) => {
179
182
  if (!resolveBaseComponent) return false;
@@ -292,6 +292,9 @@ function getImportSourceRes(localName) {
292
292
  return cached;
293
293
  }
294
294
  function resolveBarrelReExport(filePath, name, resolve, read) {
295
+ return resolveBarrelReExportBinding(filePath, name, resolve, read)?.filePath ?? null;
296
+ }
297
+ function resolveBarrelReExportBinding(filePath, name, resolve, read) {
295
298
  const basename = path.basename(filePath);
296
299
  if (basename !== "index.ts" && basename !== "index.tsx") return null;
297
300
  let src;
@@ -300,14 +303,25 @@ function resolveBarrelReExport(filePath, name, resolve, read) {
300
303
  } catch {
301
304
  return null;
302
305
  }
303
- const namedMatch = src.match(getBarrelExportRe(name));
304
- if (namedMatch?.[1]) return resolve(namedMatch[1], filePath);
306
+ for (const match of src.matchAll(/export\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g)) {
307
+ const sourceName = getReExportedSourceName(match[1] ?? "", name);
308
+ const specifier = match[2];
309
+ if (!sourceName || !specifier) continue;
310
+ const resolved = resolve(specifier, filePath);
311
+ if (resolved) return {
312
+ filePath: resolved,
313
+ exportedName: sourceName
314
+ };
315
+ }
305
316
  for (const match of src.matchAll(/export\s*\*\s*from\s*["']([^"']+)["']/g)) {
306
317
  const specifier = match[1];
307
318
  if (!specifier) continue;
308
319
  const resolved = resolve(specifier, filePath);
309
320
  if (resolved) try {
310
- if (fileExports(read(resolved), name)) return resolved;
321
+ if (fileExports(read(resolved), name)) return {
322
+ filePath: resolved,
323
+ exportedName: name
324
+ };
311
325
  } catch {}
312
326
  }
313
327
  return null;
@@ -315,6 +329,16 @@ function resolveBarrelReExport(filePath, name, resolve, read) {
315
329
  function fileExports(src, name) {
316
330
  return getFileExportsRe(name).test(src);
317
331
  }
332
+ function getReExportedSourceName(specifiers, exportedName) {
333
+ for (const raw of specifiers.split(",")) {
334
+ const parts = raw.trim().split(/\s+as\s+/);
335
+ const local = parts[0]?.trim();
336
+ const exported = (parts[1] ?? parts[0])?.trim();
337
+ if (!local || !exported) continue;
338
+ if (exported === exportedName) return local;
339
+ }
340
+ return null;
341
+ }
318
342
  const fileExportsReCache = /* @__PURE__ */ new Map();
319
343
  function getFileExportsRe(name) {
320
344
  let re = fileExportsReCache.get(name);
@@ -324,16 +348,7 @@ function getFileExportsRe(name) {
324
348
  }
325
349
  return re;
326
350
  }
327
- const barrelExportReCache = /* @__PURE__ */ new Map();
328
- function getBarrelExportRe(name) {
329
- let re = barrelExportReCache.get(name);
330
- if (!re) {
331
- re = new RegExp(String.raw`export\s*\{[^}]*\b${name}\b[^}]*\}\s*from\s*["']([^"']+)["']`);
332
- barrelExportReCache.set(name, re);
333
- }
334
- return re;
335
- }
336
- function fileImportsFrom(usageSrc, usageFile, name, defFile, resolve) {
351
+ function fileImportsFrom(usageSrc, usageFile, name, defFile, resolve, read) {
337
352
  const [namedRe, defaultRe] = getFileImportsFromRes(name);
338
353
  namedRe.lastIndex = 0;
339
354
  defaultRe.lastIndex = 0;
@@ -343,11 +358,17 @@ function fileImportsFrom(usageSrc, usageFile, name, defFile, resolve) {
343
358
  const specifier = match[1];
344
359
  if (!specifier) continue;
345
360
  const resolved = resolve(specifier, usageFile);
346
- if (resolved && path.resolve(resolved) === path.resolve(defFile)) return true;
361
+ if (resolved && importCanReferenceDefinition(resolved, name, defFile, resolve, read)) return true;
347
362
  if (specifier.endsWith(stem) || specifier.endsWith(`${parent}/${stem}`) || specifier.endsWith(parent)) return true;
348
363
  }
349
364
  return false;
350
365
  }
366
+ function importCanReferenceDefinition(resolvedImport, name, defFile, resolve, read) {
367
+ if (path.resolve(resolvedImport) === path.resolve(defFile)) return true;
368
+ if (!read) return false;
369
+ const reExportedFile = resolveBarrelReExport(resolvedImport, name, resolve, read);
370
+ return reExportedFile !== null && path.resolve(reExportedFile) === path.resolve(defFile);
371
+ }
351
372
  const fileImportsFromReCache = /* @__PURE__ */ new Map();
352
373
  function getFileImportsFromRes(name) {
353
374
  let cached = fileImportsFromReCache.get(name);
@@ -358,4 +379,4 @@ function getFileImportsFromRes(name) {
358
379
  return cached;
359
380
  }
360
381
  //#endregion
361
- export { Logger as a, resolveBarrelReExport as i, fileImportsFrom as n, findImportSource as r, fileExports as t };
382
+ export { resolveBarrelReExport as a, getReExportedSourceName as i, fileImportsFrom as n, resolveBarrelReExportBinding as o, findImportSource as r, Logger as s, fileExports as t };
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-DJpFQ5xm.mjs";
1
+ import { a as CollectedWarning, c as MarkerFileContext, l as defineAdapter, n as TransformMode, o as AdapterInput, s as ImportSource } from "./transform-types-Bl-oPkjl.mjs";
2
2
 
3
3
  //#region src/run.d.ts
4
4
  interface RunTransformOptions {
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { o as assertValidAdapterInput, r as defineAdapter, s as describeValue, t as mergeMarkerDeclarations } from "./merge-markers-BC5YNB7D.mjs";
2
- import { a as Logger, i as resolveBarrelReExport } from "./extract-external-interface-CdHbvfxu.mjs";
3
- import { r as extractStyledDefBasesFromSource } from "./compute-leaf-set-Drcu2eju.mjs";
1
+ import { a as defineAdapter, c as assertValidAdapterInput, l as describeValue, r as mergeMarkerDeclarations, t as detectExportedComponentSxProp } from "./wrapped-component-interface-yvZ-_HMO.mjs";
2
+ import { a as resolveBarrelReExport, s as Logger } from "./extract-external-interface-B61N1a6q.mjs";
3
+ import { r as extractStyledDefBasesFromSource } from "./compute-leaf-set-Be5Cgxnr.mjs";
4
4
  import { n as toRealPath } from "./path-utils-BIpoL4Ue.mjs";
5
5
  import jscodeshift from "jscodeshift";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -146,10 +146,10 @@ async function runTransform(options) {
146
146
  `Pattern(s): ${consumerPatterns.join(", ")}`,
147
147
  "Check that the glob pattern is correct and files exist."
148
148
  ].join("\n"));
149
- const { createModuleResolver } = await import("./resolve-imports-BlxKezSJ.mjs").then((n) => n.n);
149
+ const { createModuleResolver } = await import("./resolve-imports-DgSAddIF.mjs").then((n) => n.n);
150
150
  const sharedResolver = createModuleResolver();
151
151
  filePaths = orderFilesByLocalImportDependencies(filePaths, sharedResolver, toRealPath);
152
- const { runPrepass } = await import("./run-prepass-qEr_Mc3y.mjs");
152
+ const { runPrepass } = await import("./run-prepass-J4t6jkCI.mjs");
153
153
  const absoluteFiles = filePaths.map((f) => resolve(f));
154
154
  const absoluteConsumers = consumerFilePaths.map((f) => resolve(f));
155
155
  let prepassResult;
@@ -180,6 +180,7 @@ async function runTransform(options) {
180
180
  };
181
181
  }
182
182
  const transformedFiles = /* @__PURE__ */ new Set();
183
+ const transformedFileSources = /* @__PURE__ */ new Map();
183
184
  const crossFilePrepassResult = {
184
185
  ...prepassResult.crossFileInfo,
185
186
  transformedFiles
@@ -243,12 +244,16 @@ async function runTransform(options) {
243
244
  if (!resolvedImport) return;
244
245
  const resolvedPath = toRealPath(resolvedImport);
245
246
  const definitionSourcePath = resolveExistingSourcePath(resolveBarrelReExport(resolvedPath, ctx.importedName, prepassResolve, cachedRead) ?? resolvedPath);
246
- if (!transformedFiles.has(toRealPath(definitionSourcePath))) return;
247
247
  const autoInterfaceNames = ctx.importedName === "default" ? [ctx.localName, ctx.importedName] : [ctx.importedName];
248
248
  const styledDefinitionNames = getStyledDefinitionNames(definitionSourcePath);
249
249
  const sourceComponentNames = ctx.importedName === "default" ? [ctx.localName, getDefaultExportedName(definitionSourcePath)].filter((name) => typeof name === "string") : [ctx.importedName];
250
- const definitionSource = cachedRead(definitionSourcePath);
251
- if (!(definitionSource.includes("sx?: stylex.StyleXStyles") && sourceComponentNames.some((name) => definitionSource.includes(name))) && !sourceComponentNames.some((name) => styledDefinitionNames.has(name))) return;
250
+ if (sourceComponentNames.some((name) => detectExportedComponentSxProp({
251
+ absolutePath: definitionSourcePath,
252
+ componentName: name,
253
+ sourceOverrides: transformedFileSources
254
+ }))) return { acceptsSx: true };
255
+ if (!transformedFiles.has(toRealPath(definitionSourcePath))) return;
256
+ if (!sourceComponentNames.some((name) => styledDefinitionNames.has(name))) return;
252
257
  return autoInterfaceNames.map((name) => lookupAutoExternalInterface(definitionSourcePath, name)).find((result) => result !== void 0)?.styles ? { acceptsSx: true } : void 0;
253
258
  }
254
259
  };
@@ -297,7 +302,7 @@ async function runTransform(options) {
297
302
  sidecarFiles,
298
303
  bridgeResults,
299
304
  transformedFiles,
300
- transformedFileSources: /* @__PURE__ */ new Map(),
305
+ transformedFileSources,
301
306
  transientPropRenames,
302
307
  allowPartialMigration: options.allowPartialMigration ?? (leavesOnly ? true : false),
303
308
  transformMode: leavesOnly ? "leavesOnly" : options.transformMode ?? "all",
@@ -1,3 +1,4 @@
1
+ import "node:module";
1
2
  import { ResolverFactory } from "oxc-resolver";
2
3
  //#region \0rolldown/runtime.js
3
4
  var __defProp = Object.defineProperty;
@@ -1,5 +1,5 @@
1
- import { a as Logger, i as resolveBarrelReExport, n as fileImportsFrom, r as findImportSource, t as fileExports } from "./extract-external-interface-CdHbvfxu.mjs";
2
- import { n as extractStyledDefBasesFromAstProgram, r as extractStyledDefBasesFromSource, t as computeGlobalLeafKeys } from "./compute-leaf-set-Drcu2eju.mjs";
1
+ import { a as resolveBarrelReExport, n as fileImportsFrom, o as resolveBarrelReExportBinding, r as findImportSource, s as Logger, t as fileExports } from "./extract-external-interface-B61N1a6q.mjs";
2
+ import { n as extractStyledDefBasesFromAstProgram, r as extractStyledDefBasesFromSource, t as computeGlobalLeafKeys } from "./compute-leaf-set-Be5Cgxnr.mjs";
3
3
  import { r as escapeRegex } from "./string-utils-DD9wdRHW.mjs";
4
4
  import { a as PLACEHOLDER_RE, i as readStaticJsxLiteral, n as createComponentPropUsageInfo, r as mergeComponentPropUsage, t as KNOWN_NON_ELEMENT_PROPS } from "./prop-usage-D6ZiDfzz.mjs";
5
5
  import { t as isSelectorContext } from "./selector-context-heuristic-6_jSRGkZ.mjs";
@@ -229,6 +229,10 @@ function buildImportMapFromNodes(importNodes) {
229
229
  source: sourceValue,
230
230
  importedName: "default"
231
231
  });
232
+ else if (spec.type === "ImportNamespaceSpecifier") map.set(localName, {
233
+ source: sourceValue,
234
+ importedName: "*"
235
+ });
232
236
  else if (spec.type === "ImportSpecifier") {
233
237
  const importedName = getNodeName(spec.imported) ?? localName;
234
238
  map.set(localName, {
@@ -533,14 +537,19 @@ async function runPrepass(options) {
533
537
  const rgHits = createExternalInterface ? rgClassNameStyleFilter(uniqueAllFiles) : void 0;
534
538
  const jsxHits = rgJsxComponentFilter(uniqueAllFiles);
535
539
  const scanAndRecord = (filePath, source) => {
536
- if (createExternalInterface) for (const result of scanConsumerProps(source, allStyledNames)) {
537
- addToSetMap(classNameStyleUsages, result.name, filePath);
538
- if (result.className) addToSetMap(classNameUsages, result.name, filePath);
539
- if (result.style) addToSetMap(styleUsages, result.name, filePath);
540
- if (result.elementProps) addToSetMap(elementPropUsages, result.name, filePath);
541
- if (result.spreadProps) addToSetMap(spreadPropUsages, result.name, filePath);
540
+ const jsxScan = scanConsumerJsxUsages(filePath, source, allStyledNames, parser);
541
+ if (createExternalInterface) for (const result of jsxScan.propResults) {
542
+ const usage = {
543
+ filePath,
544
+ importSource: result.importSource
545
+ };
546
+ if (result.className || result.style || result.spreadProps) addConsumerUsage(classNameStyleUsages, result.name, usage);
547
+ if (result.className) addConsumerUsage(classNameUsages, result.name, usage);
548
+ if (result.style) addConsumerUsage(styleUsages, result.name, usage);
549
+ if (result.elementProps) addConsumerUsage(elementPropUsages, result.name, usage);
550
+ if (result.spreadProps) addConsumerUsage(spreadPropUsages, result.name, usage);
542
551
  }
543
- for (const usage of scanConsumerStaticPropUsages(filePath, source, allStyledNames, parser)) {
552
+ for (const usage of jsxScan.staticPropUsages) {
544
553
  const entries = propUsageCandidates.get(usage.name) ?? [];
545
554
  entries.push(usage);
546
555
  propUsageCandidates.set(usage.name, entries);
@@ -584,7 +593,7 @@ async function runPrepass(options) {
584
593
  }
585
594
  return entry;
586
595
  };
587
- const matchUsagesToDefinitions = (usages, field) => {
596
+ const matchUsageFilesToDefinitions = (usages, field) => {
588
597
  if (usages.size === 0) return;
589
598
  for (const [defFile, names] of styledDefFiles) {
590
599
  const defSrc = cachedRead(defFile);
@@ -596,20 +605,32 @@ async function runPrepass(options) {
596
605
  ensure(defFile, name)[field] = true;
597
606
  continue;
598
607
  }
599
- for (const usageFile of usageFiles) if (fileImportsFrom(cachedRead(usageFile), usageFile, name, defFile, resolve$1)) {
608
+ for (const usageFile of usageFiles) if (fileImportsFrom(cachedRead(usageFile), usageFile, name, defFile, resolve$1, cachedRead)) {
600
609
  ensure(defFile, name)[field] = true;
601
610
  break;
602
611
  }
603
612
  }
604
613
  }
605
614
  };
606
- matchUsagesToDefinitions(asUsages, "as");
607
- matchUsagesToDefinitions(refUsages, "ref");
608
- matchUsagesToDefinitions(classNameStyleUsages, "styles");
609
- matchUsagesToDefinitions(classNameUsages, "className");
610
- matchUsagesToDefinitions(styleUsages, "style");
611
- matchUsagesToDefinitions(elementPropUsages, "elementProps");
612
- matchUsagesToDefinitions(spreadPropUsages, "spreadProps");
615
+ const matchConsumerUsagesToDefinitions = (usages, field) => {
616
+ if (usages.size === 0) return;
617
+ for (const [defFile, names] of styledDefFiles) {
618
+ const defSrc = cachedRead(defFile);
619
+ for (const name of names) {
620
+ const usageRefs = usages.get(name);
621
+ if (!usageRefs) continue;
622
+ if (!fileExports(defSrc, name)) continue;
623
+ if (usageRefs.some((usageRef) => consumerUsageReferencesDefinition(usageRef, name, defFile, cachedRead, resolve$1))) ensure(defFile, name)[field] = true;
624
+ }
625
+ }
626
+ };
627
+ matchUsageFilesToDefinitions(asUsages, "as");
628
+ matchUsageFilesToDefinitions(refUsages, "ref");
629
+ matchConsumerUsagesToDefinitions(classNameStyleUsages, "styles");
630
+ matchConsumerUsagesToDefinitions(classNameUsages, "className");
631
+ matchConsumerUsagesToDefinitions(styleUsages, "style");
632
+ matchConsumerUsagesToDefinitions(elementPropUsages, "elementProps");
633
+ matchConsumerUsagesToDefinitions(spreadPropUsages, "spreadProps");
613
634
  {
614
635
  const seen = /* @__PURE__ */ new Set();
615
636
  for (const { file, name } of styledCallUsages) {
@@ -759,8 +780,27 @@ function importTextMentionsIdentifier(importText, identifier) {
759
780
  }
760
781
  return re.test(importText);
761
782
  }
762
- /** Quick pre-check: does this source mention className or style in a JSX prop context? */
763
- const CLASSNAME_STYLE_QUICK_RE = /\b(className|style)\s*[={]/;
783
+ function addConsumerUsage(map, name, usage) {
784
+ const entries = map.get(name) ?? [];
785
+ if (!entries.some((entry) => entry.filePath === usage.filePath && entry.importSource === usage.importSource)) {
786
+ entries.push(usage);
787
+ map.set(name, entries);
788
+ }
789
+ }
790
+ function consumerUsageReferencesDefinition(usage, name, defFile, cachedRead, resolve) {
791
+ if (usage.importSource) return importSourceReferencesDefinition(usage.importSource, usage.filePath, name, defFile, resolve, cachedRead);
792
+ if (usage.filePath === defFile) return true;
793
+ return fileImportsFrom(cachedRead(usage.filePath), usage.filePath, name, defFile, resolve, cachedRead);
794
+ }
795
+ function importSourceReferencesDefinition(importSource, usageFile, name, defFile, resolve$2, cachedRead) {
796
+ const resolved = resolve$2(importSource, usageFile);
797
+ if (!resolved) return false;
798
+ if (resolve(resolved) === resolve(defFile)) return true;
799
+ const binding = resolveBarrelReExportBinding(resolved, name, resolve$2, cachedRead);
800
+ return binding !== null && resolve(binding.filePath) === resolve(defFile);
801
+ }
802
+ /** Quick pre-check: does this source mention JSX that might use external consumer props? */
803
+ const CONSUMER_PROPS_QUICK_RE = /<(?:[A-Z]|[A-Za-z_$][A-Za-z0-9_$]*\.)|\b(className|style)\s*[={]|\{\.\.\./;
764
804
  /** Matches `import { Original as Local, ... }` — captures original and local names. */
765
805
  const IMPORT_ALIAS_ENTRY_RE = /\b(\w+)\s+as\s+(\w+)/g;
766
806
  /**
@@ -783,99 +823,166 @@ function buildLocalToImportedMap(source) {
783
823
  /**
784
824
  * Scan source for JSX usage of specific components with className, style,
785
825
  * element-specific props, or JSX spread.
786
- * Uses a two-step approach: first quick-checks for className/style keywords,
787
- * then scans JSX open tags to match component names — avoids building a huge
788
- * alternation regex when there are hundreds of component names.
789
- * Handles aliased imports (e.g., `import { Alert as MyAlert }`) by resolving
790
- * local tag names back to their original exported names.
826
+ * Uses the prepass parser so comments and string literals cannot be mistaken
827
+ * for real JSX consumers.
791
828
  */
792
- function scanConsumerProps(source, componentNames) {
793
- if (!CLASSNAME_STYLE_QUICK_RE.test(source)) return [];
794
- let aliasMap;
795
- const resultMap = /* @__PURE__ */ new Map();
796
- for (const m of source.matchAll(/<([A-Z][A-Za-z0-9]*)\b([^<>]*?)(?:\/>|>)/gs)) {
797
- const tagName = m[1];
798
- const attrText = m[2] ?? "";
799
- if (!tagName) continue;
800
- let resolvedName;
801
- if (componentNames.has(tagName)) resolvedName = tagName;
802
- else {
803
- aliasMap ??= buildLocalToImportedMap(source);
804
- const originalName = aliasMap.get(tagName);
805
- if (originalName && componentNames.has(originalName)) resolvedName = originalName;
806
- }
807
- if (!resolvedName) continue;
808
- if (!/\b(?:className|style)\s*[={]/.test(attrText) && !/\{\.\.\./.test(attrText)) continue;
809
- let entry = resultMap.get(resolvedName);
810
- if (!entry) {
811
- entry = {
812
- name: resolvedName,
813
- className: false,
814
- style: false,
815
- elementProps: false,
816
- spreadProps: false
817
- };
818
- resultMap.set(resolvedName, entry);
819
- }
820
- if (/\bclassName\s*[={]/.test(attrText)) entry.className = true;
821
- if (/\bstyle\s*[={]/.test(attrText)) entry.style = true;
822
- if (/\{\.\.\./.test(attrText)) entry.spreadProps = true;
823
- for (const pm of attrText.matchAll(/\b([a-z][a-zA-Z-]*)(?=\s*[={]|\s+[a-z]|\s*$)/gi)) {
824
- const propName = pm[1];
825
- if (!KNOWN_NON_ELEMENT_PROPS.has(propName) && !propName.startsWith("$")) {
826
- entry.elementProps = true;
827
- break;
828
- }
829
- }
830
- }
831
- return [...resultMap.values()];
832
- }
833
- function scanConsumerStaticPropUsages(filePath, source, componentNames, parser) {
834
- if (!/<[A-Z]/.test(source)) return [];
829
+ function scanConsumerJsxUsages(filePath, source, componentNames, parser) {
830
+ if (!CONSUMER_PROPS_QUICK_RE.test(source)) return {
831
+ propResults: [],
832
+ staticPropUsages: []
833
+ };
835
834
  let ast;
836
835
  try {
837
836
  ast = parser.parse(source);
838
837
  } catch {
839
- return [];
838
+ return {
839
+ propResults: scanConsumerPropsRegexFallback(source, componentNames),
840
+ staticPropUsages: []
841
+ };
840
842
  }
841
843
  const importNodes = [];
842
844
  const jsxOpenings = [];
843
845
  walkForImportsAndJsxOpenings(ast.program ?? ast, importNodes, jsxOpenings);
844
846
  const importMap = buildImportMapFromNodes(importNodes);
845
- const usages = [];
847
+ const resultMap = /* @__PURE__ */ new Map();
848
+ const staticPropUsages = [];
846
849
  for (const opening of jsxOpenings) {
847
- const tagName = getJsxOpeningIdentifierName(opening.name);
848
- if (!tagName) continue;
849
- const importEntry = importMap.get(tagName);
850
- const resolvedName = componentNames.has(tagName) ? tagName : importEntry && componentNames.has(importEntry.importedName) ? importEntry.importedName : void 0;
851
- if (!resolvedName) continue;
852
- const props = {};
853
- let hasSpread = false;
854
- for (const attr of opening.attributes ?? []) {
855
- if (!attr) continue;
856
- if (attr.type === "JSXSpreadAttribute") {
857
- hasSpread = true;
858
- continue;
850
+ const resolvedComponent = resolveJsxOpeningComponent(opening.name, importMap, componentNames);
851
+ if (!resolvedComponent) continue;
852
+ const openingUsage = readConsumerOpeningUsage(opening);
853
+ const { externalProps } = openingUsage;
854
+ if (externalProps.className || externalProps.style || externalProps.spreadProps || externalProps.elementProps) {
855
+ const key = `${resolvedComponent.name}\0${resolvedComponent.importSource ?? ""}`;
856
+ let entry = resultMap.get(key);
857
+ if (!entry) {
858
+ entry = {
859
+ name: resolvedComponent.name,
860
+ importSource: resolvedComponent.importSource,
861
+ className: false,
862
+ style: false,
863
+ elementProps: false,
864
+ spreadProps: false
865
+ };
866
+ resultMap.set(key, entry);
859
867
  }
860
- if (attr.type !== "JSXAttribute") continue;
861
- const propName = getJsxAttributeName(attr.name);
862
- if (!propName || KNOWN_NON_ELEMENT_PROPS.has(propName)) continue;
868
+ entry.className ||= externalProps.className;
869
+ entry.style ||= externalProps.style;
870
+ entry.spreadProps ||= externalProps.spreadProps;
871
+ entry.elementProps ||= externalProps.elementProps;
872
+ }
873
+ staticPropUsages.push({
874
+ name: resolvedComponent.name,
875
+ filePath,
876
+ importSource: resolvedComponent.importSource,
877
+ usage: openingUsage.staticUsage
878
+ });
879
+ }
880
+ return {
881
+ propResults: [...resultMap.values()],
882
+ staticPropUsages
883
+ };
884
+ }
885
+ function resolveJsxOpeningComponent(name, importMap, componentNames) {
886
+ const localName = getJsxOpeningIdentifierName(name);
887
+ if (localName) {
888
+ if (componentNames.has(localName)) return { name: localName };
889
+ const importEntry = importMap.get(localName);
890
+ return importEntry && componentNames.has(importEntry.importedName) ? {
891
+ name: importEntry.importedName,
892
+ importSource: importEntry.source
893
+ } : void 0;
894
+ }
895
+ const member = getJsxMemberNameParts(name);
896
+ if (!member || !componentNames.has(member.propertyName)) return;
897
+ const importEntry = importMap.get(member.objectName);
898
+ return importEntry?.importedName === "*" ? {
899
+ name: member.propertyName,
900
+ importSource: importEntry.source
901
+ } : void 0;
902
+ }
903
+ function readConsumerOpeningUsage(opening) {
904
+ const externalProps = {
905
+ className: false,
906
+ style: false,
907
+ elementProps: false,
908
+ spreadProps: false
909
+ };
910
+ const props = {};
911
+ let hasSpread = false;
912
+ for (const attr of opening.attributes ?? []) {
913
+ if (!attr) continue;
914
+ if (attr.type === "JSXSpreadAttribute") {
915
+ externalProps.spreadProps = true;
916
+ hasSpread = true;
917
+ continue;
918
+ }
919
+ if (attr.type !== "JSXAttribute") continue;
920
+ const propName = getJsxAttributeName(attr.name);
921
+ if (!propName) continue;
922
+ if (propName === "className") externalProps.className = true;
923
+ else if (propName === "style") externalProps.style = true;
924
+ else if (isElementConsumerProp(propName)) externalProps.elementProps = true;
925
+ if (!KNOWN_NON_ELEMENT_PROPS.has(propName)) {
863
926
  const value = readStaticJsxLiteral(attr);
864
927
  props[propName] = value === void 0 ? { kind: "unknown" } : {
865
928
  kind: "static",
866
929
  value
867
930
  };
868
931
  }
869
- usages.push({
870
- name: resolvedName,
871
- filePath,
872
- usage: {
873
- props,
874
- hasSpread
932
+ }
933
+ return {
934
+ externalProps,
935
+ staticUsage: {
936
+ props,
937
+ hasSpread
938
+ }
939
+ };
940
+ }
941
+ function isElementConsumerProp(propName) {
942
+ return !KNOWN_NON_ELEMENT_PROPS.has(propName) && !propName.startsWith("$");
943
+ }
944
+ function scanConsumerPropsRegexFallback(source, componentNames) {
945
+ const resultMap = /* @__PURE__ */ new Map();
946
+ let aliasMap;
947
+ for (const match of source.matchAll(/<([A-Z][A-Za-z0-9]*)\b([^<>]*?)(?:\/>|>)/gs)) {
948
+ const tagName = match[1];
949
+ const attrText = match[2] ?? "";
950
+ if (!tagName) continue;
951
+ const resolvedName = componentNames.has(tagName) ? tagName : (() => {
952
+ aliasMap ??= buildLocalToImportedMap(source);
953
+ const originalName = aliasMap.get(tagName);
954
+ return originalName && componentNames.has(originalName) ? originalName : void 0;
955
+ })();
956
+ if (!resolvedName) continue;
957
+ const className = /\bclassName\s*[={]/.test(attrText);
958
+ const style = /\bstyle\s*[={]/.test(attrText);
959
+ const spreadProps = /\{\.\.\./.test(attrText);
960
+ let elementProps = false;
961
+ for (const propMatch of attrText.matchAll(/\b([a-z][a-zA-Z-]*)(?=\s*[={]|\s+[a-z]|\s*$)/gi)) {
962
+ const propName = propMatch[1];
963
+ if (isElementConsumerProp(propName)) {
964
+ elementProps = true;
965
+ break;
875
966
  }
876
- });
967
+ }
968
+ if (!className && !style && !spreadProps && !elementProps) continue;
969
+ let entry = resultMap.get(resolvedName);
970
+ if (!entry) {
971
+ entry = {
972
+ name: resolvedName,
973
+ className: false,
974
+ style: false,
975
+ elementProps: false,
976
+ spreadProps: false
977
+ };
978
+ resultMap.set(resolvedName, entry);
979
+ }
980
+ entry.className ||= className;
981
+ entry.style ||= style;
982
+ entry.spreadProps ||= spreadProps;
983
+ entry.elementProps ||= elementProps;
877
984
  }
878
- return usages;
985
+ return [...resultMap.values()];
879
986
  }
880
987
  function buildPropUsageByFile(args) {
881
988
  const { styledDefFiles, propUsageCandidates, cachedRead, resolve, toRealPath } = args;
@@ -886,8 +993,7 @@ function buildPropUsageByFile(args) {
886
993
  const candidates = propUsageCandidates.get(name);
887
994
  if (!candidates || !fileExports(defSrc, name)) continue;
888
995
  for (const candidate of candidates) {
889
- const usageFile = candidate.filePath;
890
- if (usageFile !== defFile && !fileImportsFrom(cachedRead(usageFile), usageFile, name, defFile, resolve)) continue;
996
+ if (!consumerUsageReferencesDefinition(candidate, name, defFile, cachedRead, resolve)) continue;
891
997
  mergeComponentPropUsage(getOrCreateComponentPropUsage(getOrCreatePropUsageFileMap(propUsageByFile, toRealPath(defFile)), name), candidate.usage);
892
998
  }
893
999
  }
@@ -917,10 +1023,7 @@ function walkForImportsAndJsxOpenings(node, imports, jsxOpenings) {
917
1023
  imports.push(n);
918
1024
  return;
919
1025
  }
920
- if (n.type === "JSXOpeningElement") {
921
- jsxOpenings.push(n);
922
- return;
923
- }
1026
+ if (n.type === "JSXOpeningElement") jsxOpenings.push(n);
924
1027
  for (const key of Object.keys(n)) {
925
1028
  if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
926
1029
  const val = n[key];
@@ -933,6 +1036,16 @@ function getJsxOpeningIdentifierName(name) {
933
1036
  if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
934
1037
  return null;
935
1038
  }
1039
+ function getJsxMemberNameParts(name) {
1040
+ if (!name || name.type !== "JSXMemberExpression") return null;
1041
+ const object = name.object;
1042
+ const property = name.property;
1043
+ if (object?.type !== "JSXIdentifier" || typeof object.name !== "string" || property?.type !== "JSXIdentifier" || typeof property.name !== "string") return null;
1044
+ return {
1045
+ objectName: object.name,
1046
+ propertyName: property.name
1047
+ };
1048
+ }
936
1049
  function getJsxAttributeName(name) {
937
1050
  if (!name) return null;
938
1051
  if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
@@ -967,7 +1080,7 @@ function rgClassNameStyleFilter(files) {
967
1080
  return;
968
1081
  }
969
1082
  }
970
- /** Use ripgrep to find files with PascalCase JSX tags. */
1083
+ /** Use ripgrep to find files with PascalCase or namespace JSX tags. */
971
1084
  function rgJsxComponentFilter(files) {
972
1085
  const dirs = deduplicateParentDirs(files);
973
1086
  if (dirs.length === 0) return;
@@ -982,7 +1095,7 @@ function rgJsxComponentFilter(files) {
982
1095
  "*.mjs",
983
1096
  "*.cjs"
984
1097
  ].map((glob) => `--glob ${shellQuote(glob)}`).join(" ");
985
- const output = execSync(`rg -l ${shellQuote(String.raw`<[A-Z]`)} ${globArgs} ${dirs.map(shellQuote).join(" ")}`, {
1098
+ const output = execSync(`rg -l ${shellQuote(String.raw`<([A-Z]|[A-Za-z_$][A-Za-z0-9_$]*\.)`)} ${globArgs} ${dirs.map(shellQuote).join(" ")}`, {
986
1099
  encoding: "utf-8",
987
1100
  maxBuffer: 10 * 1024 * 1024
988
1101
  });