styled-components-to-stylex-codemod 0.0.37 → 0.0.39
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.
- package/dist/{bridge-consumer-patcher-CKOMofN8.mjs → bridge-consumer-patcher-31jI1854.mjs} +148 -3
- package/dist/compute-leaf-set-Drcu2eju.mjs +239 -0
- package/dist/{extract-external-interface-BgvS5GC0.mjs → extract-external-interface-CdHbvfxu.mjs} +12 -1
- package/dist/{forwarded-as-consumer-patcher-CqxniQIc.mjs → forwarded-as-consumer-patcher-BYCrqzRm.mjs} +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +44 -10
- package/dist/{merge-markers-CJ02ZuW0.mjs → merge-markers-BC5YNB7D.mjs} +85 -8
- package/dist/prop-usage-D6ZiDfzz.mjs +136 -0
- package/dist/{run-prepass-5LTAQkG0.mjs → run-prepass-qEr_Mc3y.mjs} +177 -260
- package/dist/{string-utils-ChXtospT.mjs → string-utils-DD9wdRHW.mjs} +16 -1
- package/dist/{transform-types-CY57kiqK.d.mts → transform-types-DJpFQ5xm.d.mts} +78 -7
- package/dist/transform.d.mts +1 -1
- package/dist/transform.mjs +4741 -1406
- package/dist/{transient-prop-consumer-patcher-CKAzqPfK.mjs → transient-prop-consumer-patcher-DdIYPSFk.mjs} +26 -7
- package/package.json +4 -5
- package/dist/styled-css-DVtGPEUe.mjs +0 -36
- /package/dist/{path-utils-CMR9NmMm.mjs → path-utils-BIpoL4Ue.mjs} +0 -0
- /package/dist/{selector-context-heuristic-DE3JAmpc.mjs → selector-context-heuristic-6_jSRGkZ.mjs} +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { compile } from "stylis";
|
|
2
|
+
//#region src/internal/styled-css.ts
|
|
3
|
+
/** Matches `__SC_EXPR_N__` and captures the slot index in group 1. */
|
|
4
|
+
const PLACEHOLDER_RE = /__SC_EXPR_(\d+)__/;
|
|
5
|
+
function parseStyledTemplateLiteral(template) {
|
|
6
|
+
const parts = [];
|
|
7
|
+
const slots = [];
|
|
8
|
+
for (let i = 0; i < template.quasis.length; i++) {
|
|
9
|
+
const quasi = template.quasis[i];
|
|
10
|
+
parts.push(quasi.value.raw);
|
|
11
|
+
const expr = template.expressions[i];
|
|
12
|
+
if (!expr) continue;
|
|
13
|
+
const placeholder = makeInterpolationPlaceholder(i);
|
|
14
|
+
const startOffset = parts.join("").length;
|
|
15
|
+
parts.push(placeholder);
|
|
16
|
+
const endOffset = parts.join("").length;
|
|
17
|
+
slots.push({
|
|
18
|
+
index: i,
|
|
19
|
+
placeholder,
|
|
20
|
+
expression: expr,
|
|
21
|
+
startOffset,
|
|
22
|
+
endOffset
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const rawCss = parts.join("");
|
|
26
|
+
return {
|
|
27
|
+
rawCss,
|
|
28
|
+
slots,
|
|
29
|
+
stylisAst: compile(terminateStandaloneInterpolationStatements(rawCss))
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function terminateStandaloneInterpolationStatements(css) {
|
|
33
|
+
let parenDepth = 0;
|
|
34
|
+
const lines = css.split(/(?<=\n)/);
|
|
35
|
+
const depthsBeforeLine = [];
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
depthsBeforeLine.push(parenDepth);
|
|
38
|
+
parenDepth = updateParenDepth(parenDepth, line);
|
|
39
|
+
}
|
|
40
|
+
return lines.map((line, index) => {
|
|
41
|
+
return depthsBeforeLine[index] === 0 && /^\s*__SC_EXPR_\d+__\s*$/.test(line) && isBeforeAtRule(lines, index) ? line.replace(/(\s*)$/, ";$1") : line;
|
|
42
|
+
}).join("");
|
|
43
|
+
}
|
|
44
|
+
function makeInterpolationPlaceholder(index) {
|
|
45
|
+
return `__SC_EXPR_${index}__`;
|
|
46
|
+
}
|
|
47
|
+
function isBeforeAtRule(lines, startIndex) {
|
|
48
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
49
|
+
const trimmed = lines[i].trim();
|
|
50
|
+
if (!trimmed || /^__SC_EXPR_\d+__\s*;?$/.test(trimmed)) continue;
|
|
51
|
+
return trimmed.startsWith("@");
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function updateParenDepth(startDepth, line) {
|
|
56
|
+
let depth = startDepth;
|
|
57
|
+
let inString = false;
|
|
58
|
+
for (let i = 0; i < line.length; i++) {
|
|
59
|
+
const ch = line[i];
|
|
60
|
+
if ((ch === "\"" || ch === "'") && line[i - 1] !== "\\") {
|
|
61
|
+
if (!inString) inString = ch;
|
|
62
|
+
else if (inString === ch) inString = false;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (inString) continue;
|
|
66
|
+
if (ch === "(") depth++;
|
|
67
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
68
|
+
}
|
|
69
|
+
return depth;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/internal/utilities/jsx-static-literal.ts
|
|
73
|
+
function readStaticJsxLiteral(attr) {
|
|
74
|
+
if (!isObjectRecord(attr) || attr.type !== "JSXAttribute") return;
|
|
75
|
+
if (!("value" in attr) || attr.value == null) return true;
|
|
76
|
+
const directLiteral = readLiteralNodeValue(attr.value);
|
|
77
|
+
if (directLiteral !== void 0) return directLiteral;
|
|
78
|
+
if (!isObjectRecord(attr.value) || attr.value.type !== "JSXExpressionContainer") return;
|
|
79
|
+
return readLiteralNodeValue(attr.value.expression);
|
|
80
|
+
}
|
|
81
|
+
function readLiteralNodeValue(node) {
|
|
82
|
+
if (!isObjectRecord(node)) return;
|
|
83
|
+
if (node.type === "StringLiteral" || node.type === "NumericLiteral" || node.type === "BooleanLiteral") return isStaticLiteral(node.value) ? node.value : void 0;
|
|
84
|
+
if (node.type === "Literal") return isStaticLiteral(node.value) ? node.value : void 0;
|
|
85
|
+
if (node.type === "UnaryExpression" && node.operator === "-") {
|
|
86
|
+
const value = readLiteralNodeValue(node.argument);
|
|
87
|
+
return typeof value === "number" ? -value : void 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isObjectRecord(value) {
|
|
91
|
+
return typeof value === "object" && value !== null;
|
|
92
|
+
}
|
|
93
|
+
function isStaticLiteral(value) {
|
|
94
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/internal/utilities/prop-usage.ts
|
|
98
|
+
const KNOWN_NON_ELEMENT_PROPS = new Set([
|
|
99
|
+
"className",
|
|
100
|
+
"style",
|
|
101
|
+
"as",
|
|
102
|
+
"ref",
|
|
103
|
+
"forwardedAs",
|
|
104
|
+
"key",
|
|
105
|
+
"children"
|
|
106
|
+
]);
|
|
107
|
+
function createComponentPropUsageInfo(name) {
|
|
108
|
+
return {
|
|
109
|
+
componentName: name,
|
|
110
|
+
usageCount: 0,
|
|
111
|
+
hasUnknownUsage: false,
|
|
112
|
+
props: {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function mergeComponentPropUsage(info, usage) {
|
|
116
|
+
info.usageCount += 1;
|
|
117
|
+
if (usage.hasSpread) info.hasUnknownUsage = true;
|
|
118
|
+
const presentProps = new Set(Object.keys(usage.props));
|
|
119
|
+
for (const [propName, propInfo] of Object.entries(info.props)) if (!presentProps.has(propName)) propInfo.omittedCount += 1;
|
|
120
|
+
for (const [propName, value] of Object.entries(usage.props)) {
|
|
121
|
+
const propInfo = info.props[propName] ?? (info.props[propName] = {
|
|
122
|
+
values: [],
|
|
123
|
+
hasUnknown: false,
|
|
124
|
+
usageCount: 0,
|
|
125
|
+
omittedCount: info.usageCount - 1
|
|
126
|
+
});
|
|
127
|
+
propInfo.usageCount += 1;
|
|
128
|
+
if (value.kind === "unknown") {
|
|
129
|
+
propInfo.hasUnknown = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!propInfo.values.some((existing) => existing === value.value)) propInfo.values.push(value.value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { PLACEHOLDER_RE as a, readStaticJsxLiteral as i, createComponentPropUsageInfo as n, parseStyledTemplateLiteral as o, mergeComponentPropUsage as r, terminateStandaloneInterpolationStatements as s, KNOWN_NON_ELEMENT_PROPS as t };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { a as Logger, i as resolveBarrelReExport, n as fileImportsFrom, r as findImportSource, t as fileExports } from "./extract-external-interface-
|
|
2
|
-
import { r as
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
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";
|
|
3
|
+
import { r as escapeRegex } from "./string-utils-DD9wdRHW.mjs";
|
|
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
|
+
import { t as isSelectorContext } from "./selector-context-heuristic-6_jSRGkZ.mjs";
|
|
5
6
|
import { relative, resolve } from "node:path";
|
|
6
7
|
import { readFileSync, realpathSync } from "node:fs";
|
|
7
8
|
import { execSync } from "node:child_process";
|
|
@@ -383,243 +384,6 @@ function deduplicateAndResolve(filesToTransform, consumerPaths) {
|
|
|
383
384
|
return result;
|
|
384
385
|
}
|
|
385
386
|
//#endregion
|
|
386
|
-
//#region src/internal/prepass/compute-leaf-set.ts
|
|
387
|
-
/**
|
|
388
|
-
* Computes which styled-component bindings are "leaves" for leaves-only mode:
|
|
389
|
-
* intrinsic bases (`styled.div`) or transitive wrappers around other leaf styled
|
|
390
|
-
* components in the transform set. Uses AST extraction (primary), regex fallback,
|
|
391
|
-
* fixed-point + import resolution (same helpers as consumer analysis).
|
|
392
|
-
*/
|
|
393
|
-
const RX_EXPORT_DECL = String.raw`(?:export\s+)?(?:const|let|var)\s+`;
|
|
394
|
-
/** `const Name = styled.tag` — intrinsic HTML/SVG tag member. */
|
|
395
|
-
const STYLED_INTRINSIC_MEMBER_RE = new RegExp(String.raw`\b${RX_EXPORT_DECL}([A-Z][A-Za-z0-9]*)\b[^=]*=\s*styled\.([a-z][a-zA-Z0-9]*)\b`, "g");
|
|
396
|
-
/** `const Name = styled("tag")` — intrinsic string tag. */
|
|
397
|
-
const STYLED_INTRINSIC_STRING_RE = new RegExp(String.raw`\b${RX_EXPORT_DECL}([A-Z][A-Za-z0-9]*)\b[^=]*=\s*styled\s*\(\s*["']([^"']+)["']`, "g");
|
|
398
|
-
/** `const Name = styled(Component)` — wraps another component identifier. */
|
|
399
|
-
const STYLED_COMPONENT_RE = new RegExp(String.raw`\b${RX_EXPORT_DECL}([A-Z][A-Za-z0-9]*)\b[^=]*=\s*styled\s*\(\s*([A-Z][A-Za-z0-9]*)\s*\)`, "g");
|
|
400
|
-
/**
|
|
401
|
-
* Regex-derived styled definition bases for files in the transform set.
|
|
402
|
-
* Later entries for the same component name overwrite earlier ones (rare).
|
|
403
|
-
*/
|
|
404
|
-
function extractStyledDefBasesFromSource(filePath, source, into) {
|
|
405
|
-
let map = into.get(filePath);
|
|
406
|
-
if (!map) {
|
|
407
|
-
map = /* @__PURE__ */ new Map();
|
|
408
|
-
into.set(filePath, map);
|
|
409
|
-
}
|
|
410
|
-
STYLED_INTRINSIC_MEMBER_RE.lastIndex = 0;
|
|
411
|
-
for (const m of source.matchAll(STYLED_INTRINSIC_MEMBER_RE)) {
|
|
412
|
-
const name = m[1];
|
|
413
|
-
if (name) map.set(name, { kind: "intrinsic" });
|
|
414
|
-
}
|
|
415
|
-
STYLED_INTRINSIC_STRING_RE.lastIndex = 0;
|
|
416
|
-
for (const m of source.matchAll(STYLED_INTRINSIC_STRING_RE)) {
|
|
417
|
-
const name = m[1];
|
|
418
|
-
if (name) map.set(name, { kind: "intrinsic" });
|
|
419
|
-
}
|
|
420
|
-
STYLED_COMPONENT_RE.lastIndex = 0;
|
|
421
|
-
for (const m of source.matchAll(STYLED_COMPONENT_RE)) {
|
|
422
|
-
const name = m[1];
|
|
423
|
-
const ident = m[2];
|
|
424
|
-
if (name && ident) map.set(name, {
|
|
425
|
-
kind: "component",
|
|
426
|
-
ident
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* AST-based extraction: understands `let`/`var`, export blocks, named `styled` imports,
|
|
432
|
-
* and `.attrs` / `.withConfig` chains before the tagged template.
|
|
433
|
-
* Results merge into `into`; bindings found here override regex entries for the same name.
|
|
434
|
-
*/
|
|
435
|
-
function extractStyledDefBasesFromAstProgram(filePath, program, styledLocalNames, into) {
|
|
436
|
-
if (styledLocalNames.size === 0) return;
|
|
437
|
-
let map = into.get(filePath);
|
|
438
|
-
if (!map) {
|
|
439
|
-
map = /* @__PURE__ */ new Map();
|
|
440
|
-
into.set(filePath, map);
|
|
441
|
-
}
|
|
442
|
-
const body = program.body;
|
|
443
|
-
if (!body) return;
|
|
444
|
-
for (const stmt of body) walkStatement(stmt);
|
|
445
|
-
function walkStatement(stmt) {
|
|
446
|
-
if (stmt.type === "VariableDeclaration") {
|
|
447
|
-
for (const d of stmt.declarations ?? []) processDeclarator(d);
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) walkStatement(stmt.declaration);
|
|
451
|
-
}
|
|
452
|
-
function processDeclarator(decl) {
|
|
453
|
-
if (decl.type !== "VariableDeclarator") return;
|
|
454
|
-
const id = decl.id;
|
|
455
|
-
if (id.type !== "Identifier" || typeof id.name !== "string") return;
|
|
456
|
-
const tpl = findTaggedTemplate(unwrapInitializer(decl.init));
|
|
457
|
-
if (!tpl || tpl.type !== "TaggedTemplateExpression") return;
|
|
458
|
-
const base = classifyStyledTemplateTag(tpl.tag, styledLocalNames);
|
|
459
|
-
if (base) map.set(id.name, base);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function unwrapInitializer(node) {
|
|
463
|
-
let cur = node ?? void 0;
|
|
464
|
-
while (cur) {
|
|
465
|
-
if (cur.type === "TSAsExpression" || cur.type === "AsExpression") {
|
|
466
|
-
cur = cur.expression;
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (cur.type === "ParenthesizedExpression") {
|
|
470
|
-
cur = cur.expression;
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
return cur;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
function findTaggedTemplate(node) {
|
|
477
|
-
const n = unwrapInitializer(node);
|
|
478
|
-
if (!n) return;
|
|
479
|
-
if (n.type === "TaggedTemplateExpression") return n;
|
|
480
|
-
}
|
|
481
|
-
/** Peel `.attrs` / `.withConfig` / nested calls down to `styled.div` or `styled(X)`. */
|
|
482
|
-
function peelStyledApplication(tag, styledNames) {
|
|
483
|
-
let cur = tag;
|
|
484
|
-
while (cur) {
|
|
485
|
-
if (cur.type === "CallExpression") {
|
|
486
|
-
const callee = cur.callee;
|
|
487
|
-
if (callee?.type === "MemberExpression") {
|
|
488
|
-
cur = callee;
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
if (callee?.type === "Identifier" && typeof callee.name === "string" && styledNames.has(callee.name)) return cur;
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
if (cur.type === "MemberExpression") {
|
|
495
|
-
const obj = cur.object;
|
|
496
|
-
if (obj?.type === "Identifier" && typeof obj.name === "string" && styledNames.has(obj.name)) return cur;
|
|
497
|
-
cur = obj;
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
return null;
|
|
503
|
-
}
|
|
504
|
-
function classifyStyledTemplateTag(tag, styledNames) {
|
|
505
|
-
const root = peelStyledApplication(tag, styledNames);
|
|
506
|
-
if (!root) return null;
|
|
507
|
-
if (root.type === "MemberExpression") {
|
|
508
|
-
const obj = root.object;
|
|
509
|
-
const prop = root.property;
|
|
510
|
-
const objName = obj?.type === "Identifier" ? obj.name : void 0;
|
|
511
|
-
if (obj?.type !== "Identifier" || typeof objName !== "string" || !styledNames.has(objName)) return null;
|
|
512
|
-
const isComputed = Boolean(root.computed);
|
|
513
|
-
if (isComputed && prop?.type === "StringLiteral" && typeof prop.value === "string") return { kind: "intrinsic" };
|
|
514
|
-
if (!isComputed && prop?.type === "Identifier" && typeof prop.name === "string") return { kind: "intrinsic" };
|
|
515
|
-
return null;
|
|
516
|
-
}
|
|
517
|
-
if (root.type === "CallExpression") {
|
|
518
|
-
const callee = root.callee;
|
|
519
|
-
const arg0 = root.arguments?.[0];
|
|
520
|
-
const calleeName = callee?.type === "Identifier" ? callee.name : void 0;
|
|
521
|
-
if (callee?.type !== "Identifier" || typeof calleeName !== "string" || !styledNames.has(calleeName) || !arg0) return null;
|
|
522
|
-
if (arg0.type === "Identifier" && typeof arg0.name === "string") return {
|
|
523
|
-
kind: "component",
|
|
524
|
-
ident: arg0.name
|
|
525
|
-
};
|
|
526
|
-
if (arg0.type === "StringLiteral" && typeof arg0.value === "string") return { kind: "intrinsic" };
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Fixed-point: a styled binding is a leaf if its base is intrinsic, or its base
|
|
533
|
-
* component resolves (same-file or import) to another leaf binding in the transform set.
|
|
534
|
-
*
|
|
535
|
-
* @param transformSet - Absolute realpaths of files being transformed
|
|
536
|
-
* @param styledDefBases - From {@link extractStyledDefBasesFromSource}
|
|
537
|
-
* @param resolve - Module path resolver
|
|
538
|
-
* @param cachedRead - Read file source for import resolution
|
|
539
|
-
*/
|
|
540
|
-
function computeGlobalLeafKeys(args) {
|
|
541
|
-
const { transformSet, styledDefBases, resolve, cachedRead, toRealPath, resolveBaseComponent } = args;
|
|
542
|
-
/** fileRealPath → Set of local binding names that are leaves */
|
|
543
|
-
const globalLeaves = /* @__PURE__ */ new Map();
|
|
544
|
-
const ensureSet = (file) => {
|
|
545
|
-
let s = globalLeaves.get(file);
|
|
546
|
-
if (!s) {
|
|
547
|
-
s = /* @__PURE__ */ new Set();
|
|
548
|
-
globalLeaves.set(file, s);
|
|
549
|
-
}
|
|
550
|
-
return s;
|
|
551
|
-
};
|
|
552
|
-
const isLeaf = (file, name) => globalLeaves.get(file)?.has(name) ?? false;
|
|
553
|
-
const tryResolveImportedLeaf = (file, ident) => {
|
|
554
|
-
const importInfo = findImportSource(cachedRead(file), ident);
|
|
555
|
-
if (!importInfo) return false;
|
|
556
|
-
const initialDefFile = resolve(importInfo.source, file);
|
|
557
|
-
if (!initialDefFile) return false;
|
|
558
|
-
const defReal = toRealPath(resolveBarrelReExport(initialDefFile, importInfo.isDefault ? "default" : importInfo.exportedName, resolve, cachedRead) ?? initialDefFile);
|
|
559
|
-
if (!transformSet.has(defReal)) return false;
|
|
560
|
-
return leafKeyExists(defReal, importInfo.exportedName, importInfo.isDefault, cachedRead, globalLeaves);
|
|
561
|
-
};
|
|
562
|
-
const tryResolveAdapterIntrinsic = (file, ident) => {
|
|
563
|
-
if (!resolveBaseComponent) return false;
|
|
564
|
-
const importInfo = findImportSource(cachedRead(file), ident);
|
|
565
|
-
if (!importInfo) return false;
|
|
566
|
-
try {
|
|
567
|
-
const result = resolveBaseComponent({
|
|
568
|
-
importSource: importInfo.source,
|
|
569
|
-
importedName: importInfo.exportedName,
|
|
570
|
-
staticProps: {},
|
|
571
|
-
filePath: file
|
|
572
|
-
});
|
|
573
|
-
return typeof result?.tagName === "string" && result.tagName.trim() !== "";
|
|
574
|
-
} catch {
|
|
575
|
-
return false;
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
let changed = true;
|
|
579
|
-
while (changed) {
|
|
580
|
-
changed = false;
|
|
581
|
-
for (const [filePath, nameMap] of styledDefBases) {
|
|
582
|
-
const fileReal = toRealPath(filePath);
|
|
583
|
-
if (!transformSet.has(fileReal)) continue;
|
|
584
|
-
for (const [name, base] of nameMap) {
|
|
585
|
-
if (isLeaf(fileReal, name)) continue;
|
|
586
|
-
if (base.kind === "intrinsic") {
|
|
587
|
-
ensureSet(fileReal).add(name);
|
|
588
|
-
changed = true;
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
const ident = base.ident;
|
|
592
|
-
if (isLeaf(fileReal, ident)) {
|
|
593
|
-
ensureSet(fileReal).add(name);
|
|
594
|
-
changed = true;
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
if (tryResolveAdapterIntrinsic(filePath, ident)) {
|
|
598
|
-
ensureSet(fileReal).add(name);
|
|
599
|
-
changed = true;
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
if (tryResolveImportedLeaf(filePath, ident)) {
|
|
603
|
-
ensureSet(fileReal).add(name);
|
|
604
|
-
changed = true;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
const globalLeafKeys = /* @__PURE__ */ new Set();
|
|
610
|
-
for (const [file, names] of globalLeaves) for (const name of names) globalLeafKeys.add(`${file}:${name}`);
|
|
611
|
-
return globalLeafKeys;
|
|
612
|
-
}
|
|
613
|
-
function leafKeyExists(defFile, exportedName, allowDefaultFallback, cachedRead, globalLeaves) {
|
|
614
|
-
if (globalLeaves.get(defFile)?.has(exportedName) ?? false) return true;
|
|
615
|
-
if (!allowDefaultFallback) return false;
|
|
616
|
-
const defaultLocalName = findDefaultExportedLocalName(cachedRead(defFile));
|
|
617
|
-
return defaultLocalName ? globalLeaves.get(defFile)?.has(defaultLocalName) ?? false : false;
|
|
618
|
-
}
|
|
619
|
-
function findDefaultExportedLocalName(source) {
|
|
620
|
-
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];
|
|
621
|
-
}
|
|
622
|
-
//#endregion
|
|
623
387
|
//#region src/internal/prepass/run-prepass.ts
|
|
624
388
|
/**
|
|
625
389
|
* Unified prepass: single pass for both cross-file selector scanning
|
|
@@ -693,6 +457,7 @@ async function runPrepass(options) {
|
|
|
693
457
|
const styleUsages = /* @__PURE__ */ new Map();
|
|
694
458
|
const elementPropUsages = /* @__PURE__ */ new Map();
|
|
695
459
|
const spreadPropUsages = /* @__PURE__ */ new Map();
|
|
460
|
+
const propUsageCandidates = /* @__PURE__ */ new Map();
|
|
696
461
|
const styledWrapperUsages = [];
|
|
697
462
|
const fileContents = /* @__PURE__ */ new Map();
|
|
698
463
|
const cachedRead = (filePath) => {
|
|
@@ -727,14 +492,16 @@ async function runPrepass(options) {
|
|
|
727
492
|
categorizeSelectorUsages(usages, componentsNeedingMarkerSidecar, componentsNeedingGlobalSelectorBridge);
|
|
728
493
|
}
|
|
729
494
|
}
|
|
495
|
+
if (hasStyled) {
|
|
496
|
+
STYLED_DEF_RE.lastIndex = 0;
|
|
497
|
+
for (const m of source.matchAll(STYLED_DEF_RE)) if (m[1]) addToSetMap(styledDefFiles, filePath, m[1]);
|
|
498
|
+
}
|
|
730
499
|
if (createExternalInterface && hasStyled) {
|
|
731
500
|
STYLED_CALL_RE.lastIndex = 0;
|
|
732
501
|
for (const m of source.matchAll(STYLED_CALL_RE)) if (m[1]) styledCallUsages.push({
|
|
733
502
|
file: filePath,
|
|
734
503
|
name: m[1]
|
|
735
504
|
});
|
|
736
|
-
STYLED_DEF_RE.lastIndex = 0;
|
|
737
|
-
for (const m of source.matchAll(STYLED_DEF_RE)) if (m[1]) addToSetMap(styledDefFiles, filePath, m[1]);
|
|
738
505
|
STYLED_COMPONENT_WRAPPER_RE.lastIndex = 0;
|
|
739
506
|
for (const m of source.matchAll(STYLED_COMPONENT_WRAPPER_RE)) if (m[1] && m[2]) styledWrapperUsages.push({
|
|
740
507
|
file: filePath,
|
|
@@ -759,22 +526,37 @@ async function runPrepass(options) {
|
|
|
759
526
|
}
|
|
760
527
|
}
|
|
761
528
|
const styledFileCount = fileContents.size;
|
|
762
|
-
if (
|
|
529
|
+
if (styledDefFiles.size > 0) {
|
|
763
530
|
const allStyledNames = /* @__PURE__ */ new Set();
|
|
764
531
|
for (const names of styledDefFiles.values()) for (const name of names) allStyledNames.add(name);
|
|
765
532
|
if (allStyledNames.size > 0) {
|
|
766
|
-
const rgHits = rgClassNameStyleFilter(uniqueAllFiles);
|
|
533
|
+
const rgHits = createExternalInterface ? rgClassNameStyleFilter(uniqueAllFiles) : void 0;
|
|
534
|
+
const jsxHits = rgJsxComponentFilter(uniqueAllFiles);
|
|
767
535
|
const scanAndRecord = (filePath, source) => {
|
|
768
|
-
for (const result of scanConsumerProps(source, allStyledNames)) {
|
|
536
|
+
if (createExternalInterface) for (const result of scanConsumerProps(source, allStyledNames)) {
|
|
769
537
|
addToSetMap(classNameStyleUsages, result.name, filePath);
|
|
770
538
|
if (result.className) addToSetMap(classNameUsages, result.name, filePath);
|
|
771
539
|
if (result.style) addToSetMap(styleUsages, result.name, filePath);
|
|
772
540
|
if (result.elementProps) addToSetMap(elementPropUsages, result.name, filePath);
|
|
773
541
|
if (result.spreadProps) addToSetMap(spreadPropUsages, result.name, filePath);
|
|
774
542
|
}
|
|
543
|
+
for (const usage of scanConsumerStaticPropUsages(filePath, source, allStyledNames, parser)) {
|
|
544
|
+
const entries = propUsageCandidates.get(usage.name) ?? [];
|
|
545
|
+
entries.push(usage);
|
|
546
|
+
propUsageCandidates.set(usage.name, entries);
|
|
547
|
+
}
|
|
775
548
|
};
|
|
776
|
-
for (const [filePath, source] of fileContents)
|
|
777
|
-
|
|
549
|
+
for (const [filePath, source] of fileContents) {
|
|
550
|
+
if (jsxHits && !jsxHits.has(filePath) && (!createExternalInterface || !rgHits?.has(filePath))) continue;
|
|
551
|
+
scanAndRecord(filePath, source);
|
|
552
|
+
}
|
|
553
|
+
const filesToScan = (() => {
|
|
554
|
+
const hits = /* @__PURE__ */ new Set();
|
|
555
|
+
const hasAnyFilter = createExternalInterface && rgHits !== void 0 || jsxHits !== void 0;
|
|
556
|
+
if (createExternalInterface && rgHits) for (const f of rgHits) hits.add(f);
|
|
557
|
+
if (jsxHits) for (const f of jsxHits) hits.add(f);
|
|
558
|
+
return hasAnyFilter ? [...hits].filter((f) => allFilesSet.has(f) && !fileContents.has(f)) : uniqueAllFiles.filter((f) => !fileContents.has(f));
|
|
559
|
+
})();
|
|
778
560
|
for (const filePath of filesToScan) {
|
|
779
561
|
const source = cachedRead(filePath);
|
|
780
562
|
if (!source) continue;
|
|
@@ -876,6 +658,13 @@ async function runPrepass(options) {
|
|
|
876
658
|
targetPath: defFile
|
|
877
659
|
});
|
|
878
660
|
}
|
|
661
|
+
const propUsageByFile = buildPropUsageByFile({
|
|
662
|
+
styledDefFiles,
|
|
663
|
+
propUsageCandidates,
|
|
664
|
+
cachedRead,
|
|
665
|
+
resolve: resolve$1,
|
|
666
|
+
toRealPath
|
|
667
|
+
});
|
|
879
668
|
let globalLeafKeys;
|
|
880
669
|
if (leavesOnly) {
|
|
881
670
|
const styledDefBases = /* @__PURE__ */ new Map();
|
|
@@ -893,6 +682,7 @@ async function runPrepass(options) {
|
|
|
893
682
|
selectorUsages,
|
|
894
683
|
componentsNeedingMarkerSidecar,
|
|
895
684
|
componentsNeedingGlobalSelectorBridge,
|
|
685
|
+
propUsageByFile,
|
|
896
686
|
styledDefFiles: createExternalInterface ? styledDefFiles : void 0,
|
|
897
687
|
globalLeafKeys
|
|
898
688
|
};
|
|
@@ -901,7 +691,10 @@ async function runPrepass(options) {
|
|
|
901
691
|
const reStyled = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.styles).length : 0;
|
|
902
692
|
const asProp = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.as).length : 0;
|
|
903
693
|
const refProp = consumerAnalysis ? [...consumerAnalysis.values()].filter((v) => v.ref).length : 0;
|
|
904
|
-
|
|
694
|
+
const propUsageCount = [...propUsageByFile.values()].reduce((sum, byComponent) => {
|
|
695
|
+
return sum + byComponent.size;
|
|
696
|
+
}, 0);
|
|
697
|
+
Logger.info(`Prepass: scanned ${uniqueAllFiles.length} files in ${elapsed}s — ${styledFileCount} with styled-components, ${selectorUsages.size} cross-file selectors, ${reStyled} re-styled, ${asProp} as-prop, ${refProp} ref-prop, ${classNameStyleUsages.size} className/style, ${propUsageCount} prop-usage, ${forwardedAsConsumers.size} forwardedAs\n`);
|
|
905
698
|
}
|
|
906
699
|
if (process.env.DEBUG_CODEMOD) logPrepassDebug(uniqueAllFiles, crossFileInfo, consumerAnalysis);
|
|
907
700
|
return {
|
|
@@ -987,16 +780,6 @@ function buildLocalToImportedMap(source) {
|
|
|
987
780
|
}
|
|
988
781
|
return map;
|
|
989
782
|
}
|
|
990
|
-
/** Props that don't indicate element-specific usage (non-element props). */
|
|
991
|
-
const KNOWN_NON_ELEMENT_PROPS = new Set([
|
|
992
|
-
"className",
|
|
993
|
-
"style",
|
|
994
|
-
"as",
|
|
995
|
-
"ref",
|
|
996
|
-
"forwardedAs",
|
|
997
|
-
"key",
|
|
998
|
-
"children"
|
|
999
|
-
]);
|
|
1000
783
|
/**
|
|
1001
784
|
* Scan source for JSX usage of specific components with className, style,
|
|
1002
785
|
* element-specific props, or JSX spread.
|
|
@@ -1047,6 +830,114 @@ function scanConsumerProps(source, componentNames) {
|
|
|
1047
830
|
}
|
|
1048
831
|
return [...resultMap.values()];
|
|
1049
832
|
}
|
|
833
|
+
function scanConsumerStaticPropUsages(filePath, source, componentNames, parser) {
|
|
834
|
+
if (!/<[A-Z]/.test(source)) return [];
|
|
835
|
+
let ast;
|
|
836
|
+
try {
|
|
837
|
+
ast = parser.parse(source);
|
|
838
|
+
} catch {
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
841
|
+
const importNodes = [];
|
|
842
|
+
const jsxOpenings = [];
|
|
843
|
+
walkForImportsAndJsxOpenings(ast.program ?? ast, importNodes, jsxOpenings);
|
|
844
|
+
const importMap = buildImportMapFromNodes(importNodes);
|
|
845
|
+
const usages = [];
|
|
846
|
+
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;
|
|
859
|
+
}
|
|
860
|
+
if (attr.type !== "JSXAttribute") continue;
|
|
861
|
+
const propName = getJsxAttributeName(attr.name);
|
|
862
|
+
if (!propName || KNOWN_NON_ELEMENT_PROPS.has(propName)) continue;
|
|
863
|
+
const value = readStaticJsxLiteral(attr);
|
|
864
|
+
props[propName] = value === void 0 ? { kind: "unknown" } : {
|
|
865
|
+
kind: "static",
|
|
866
|
+
value
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
usages.push({
|
|
870
|
+
name: resolvedName,
|
|
871
|
+
filePath,
|
|
872
|
+
usage: {
|
|
873
|
+
props,
|
|
874
|
+
hasSpread
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
return usages;
|
|
879
|
+
}
|
|
880
|
+
function buildPropUsageByFile(args) {
|
|
881
|
+
const { styledDefFiles, propUsageCandidates, cachedRead, resolve, toRealPath } = args;
|
|
882
|
+
const propUsageByFile = /* @__PURE__ */ new Map();
|
|
883
|
+
for (const [defFile, names] of styledDefFiles) {
|
|
884
|
+
const defSrc = cachedRead(defFile);
|
|
885
|
+
for (const name of names) {
|
|
886
|
+
const candidates = propUsageCandidates.get(name);
|
|
887
|
+
if (!candidates || !fileExports(defSrc, name)) continue;
|
|
888
|
+
for (const candidate of candidates) {
|
|
889
|
+
const usageFile = candidate.filePath;
|
|
890
|
+
if (usageFile !== defFile && !fileImportsFrom(cachedRead(usageFile), usageFile, name, defFile, resolve)) continue;
|
|
891
|
+
mergeComponentPropUsage(getOrCreateComponentPropUsage(getOrCreatePropUsageFileMap(propUsageByFile, toRealPath(defFile)), name), candidate.usage);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return propUsageByFile;
|
|
896
|
+
}
|
|
897
|
+
function getOrCreatePropUsageFileMap(propUsageByFile, filePath) {
|
|
898
|
+
let byComponent = propUsageByFile.get(filePath);
|
|
899
|
+
if (!byComponent) {
|
|
900
|
+
byComponent = /* @__PURE__ */ new Map();
|
|
901
|
+
propUsageByFile.set(filePath, byComponent);
|
|
902
|
+
}
|
|
903
|
+
return byComponent;
|
|
904
|
+
}
|
|
905
|
+
function getOrCreateComponentPropUsage(byComponent, name) {
|
|
906
|
+
let info = byComponent.get(name);
|
|
907
|
+
if (!info) {
|
|
908
|
+
info = createComponentPropUsageInfo(name);
|
|
909
|
+
byComponent.set(name, info);
|
|
910
|
+
}
|
|
911
|
+
return info;
|
|
912
|
+
}
|
|
913
|
+
function walkForImportsAndJsxOpenings(node, imports, jsxOpenings) {
|
|
914
|
+
if (!node || typeof node !== "object") return;
|
|
915
|
+
const n = node;
|
|
916
|
+
if (n.type === "ImportDeclaration") {
|
|
917
|
+
imports.push(n);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (n.type === "JSXOpeningElement") {
|
|
921
|
+
jsxOpenings.push(n);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
for (const key of Object.keys(n)) {
|
|
925
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
|
|
926
|
+
const val = n[key];
|
|
927
|
+
if (Array.isArray(val)) for (const child of val) walkForImportsAndJsxOpenings(child, imports, jsxOpenings);
|
|
928
|
+
else if (val && typeof val === "object" && val.type) walkForImportsAndJsxOpenings(val, imports, jsxOpenings);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
function getJsxOpeningIdentifierName(name) {
|
|
932
|
+
if (!name) return null;
|
|
933
|
+
if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
function getJsxAttributeName(name) {
|
|
937
|
+
if (!name) return null;
|
|
938
|
+
if (name.type === "JSXIdentifier" && typeof name.name === "string") return name.name;
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
1050
941
|
/**
|
|
1051
942
|
* Use ripgrep to find files containing `className` or `style` props.
|
|
1052
943
|
* Searches for the prop keywords (not component names) to keep the pattern
|
|
@@ -1076,6 +967,31 @@ function rgClassNameStyleFilter(files) {
|
|
|
1076
967
|
return;
|
|
1077
968
|
}
|
|
1078
969
|
}
|
|
970
|
+
/** Use ripgrep to find files with PascalCase JSX tags. */
|
|
971
|
+
function rgJsxComponentFilter(files) {
|
|
972
|
+
const dirs = deduplicateParentDirs(files);
|
|
973
|
+
if (dirs.length === 0) return;
|
|
974
|
+
try {
|
|
975
|
+
const globArgs = [
|
|
976
|
+
"*.tsx",
|
|
977
|
+
"*.jsx",
|
|
978
|
+
"*.ts",
|
|
979
|
+
"*.js",
|
|
980
|
+
"*.mts",
|
|
981
|
+
"*.cts",
|
|
982
|
+
"*.mjs",
|
|
983
|
+
"*.cjs"
|
|
984
|
+
].map((glob) => `--glob ${shellQuote(glob)}`).join(" ");
|
|
985
|
+
const output = execSync(`rg -l ${shellQuote(String.raw`<[A-Z]`)} ${globArgs} ${dirs.map(shellQuote).join(" ")}`, {
|
|
986
|
+
encoding: "utf-8",
|
|
987
|
+
maxBuffer: 10 * 1024 * 1024
|
|
988
|
+
});
|
|
989
|
+
return new Set(output.trim().split("\n").filter(Boolean).map((f) => resolve(f)));
|
|
990
|
+
} catch (err) {
|
|
991
|
+
if (err instanceof Error && "status" in err && err.status === 1) return /* @__PURE__ */ new Set();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
1079
995
|
/** Scan a single file for cross-file selector usages using AST parsing. */
|
|
1080
996
|
function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parser, toRealPath, readFile, cache, failOnParseError) {
|
|
1081
997
|
const hash = cache ? createHash("md5").update(source).digest("hex") : void 0;
|
|
@@ -1123,7 +1039,8 @@ function scanFileForSelectorsAst(filePath, source, transformSet, resolver, parse
|
|
|
1123
1039
|
if (!imp || imp.source === "styled-components") continue;
|
|
1124
1040
|
const resolvedPath = resolver.resolve(filePath, imp.source);
|
|
1125
1041
|
if (!resolvedPath) continue;
|
|
1126
|
-
const
|
|
1042
|
+
const initialResolved = toRealPath(resolvedPath);
|
|
1043
|
+
const realResolved = toRealPath(resolveBarrelReExport(initialResolved, imp.importedName, (specifier, fromFile) => resolver.resolve(fromFile, specifier) ?? null, readFile) ?? initialResolved);
|
|
1127
1044
|
if (realResolved === filePath) continue;
|
|
1128
1045
|
const usage = {
|
|
1129
1046
|
localName,
|