@codefast/cli 0.3.12 → 0.3.13-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -51
- package/dist/{bin.js → bin.mjs} +4 -1
- package/dist/commands/arrange.mjs +107 -0
- package/dist/commands/mirror.mjs +66 -0
- package/dist/commands/tag.mjs +58 -0
- package/dist/lib/arrange/application/analyze.mjs +92 -0
- package/dist/lib/arrange/application/group-file.mjs +113 -0
- package/dist/lib/arrange/application/run-target.mjs +65 -0
- package/dist/lib/arrange/domain/ast/ast-helpers.mjs +115 -0
- package/dist/lib/arrange/domain/ast/collectors-cn.mjs +61 -0
- package/dist/lib/arrange/domain/ast/collectors-jsx.mjs +20 -0
- package/dist/lib/arrange/domain/ast/collectors-tv.mjs +170 -0
- package/dist/lib/arrange/domain/ast/targets.mjs +106 -0
- package/dist/lib/arrange/domain/constants.mjs +169 -0
- package/dist/lib/arrange/domain/errors.mjs +19 -0
- package/dist/lib/arrange/domain/grouping.mjs +235 -0
- package/dist/lib/arrange/domain/imports.mjs +54 -0
- package/dist/lib/arrange/domain/tokenizer.mjs +216 -0
- package/dist/lib/arrange/index.mjs +15 -0
- package/dist/lib/arrange/infra/walk.mjs +33 -0
- package/dist/lib/arrange/presentation/formatters.mjs +52 -0
- package/dist/lib/arrange/presentation/report.mjs +24 -0
- package/dist/lib/config/domain/schema.mjs +25 -0
- package/dist/lib/config/index.mjs +3 -0
- package/dist/lib/config/infra/loader.mjs +78 -0
- package/dist/lib/infra/caught-unknown-message.mjs +20 -0
- package/dist/lib/infra/config-reporter.mjs +10 -0
- package/dist/lib/infra/node-io.mjs +30 -0
- package/dist/lib/infra/workspace/repo-root.mjs +22 -0
- package/dist/lib/mirror/application/engine.mjs +248 -0
- package/dist/lib/mirror/application/sync.mjs +162 -0
- package/dist/lib/mirror/domain/constants.mjs +12 -0
- package/dist/lib/mirror/domain/errors.mjs +15 -0
- package/dist/lib/mirror/domain/types.mjs +1 -0
- package/dist/lib/mirror/index.mjs +2 -0
- package/dist/lib/mirror/infra/dirent-list.mjs +14 -0
- package/dist/lib/mirror/infra/package-filter.mjs +26 -0
- package/dist/lib/mirror/infra/path-normalizer.mjs +7 -0
- package/dist/lib/mirror/infra/update-pkg.mjs +105 -0
- package/dist/lib/mirror/infra/workspace-packages.mjs +159 -0
- package/dist/lib/mirror/presentation/reporter.mjs +111 -0
- package/dist/lib/tag/application/engine.mjs +287 -0
- package/dist/lib/tag/domain/types.mjs +1 -0
- package/dist/lib/tag/index.mjs +4 -0
- package/dist/lib/tag/infra/target-resolver.mjs +170 -0
- package/dist/lib/tag/presentation/colors.mjs +12 -0
- package/dist/lib/tag/presentation/tag-presenter.mjs +63 -0
- package/dist/program.mjs +34 -0
- package/package.json +11 -11
- package/dist/bin.d.ts +0 -3
- package/dist/bin.d.ts.map +0 -1
- package/dist/commands/arrange.d.ts +0 -3
- package/dist/commands/arrange.d.ts.map +0 -1
- package/dist/commands/arrange.js +0 -114
- package/dist/commands/mirror.d.ts +0 -4
- package/dist/commands/mirror.d.ts.map +0 -1
- package/dist/commands/mirror.js +0 -63
- package/dist/lib/arrange/analyze.d.ts +0 -4
- package/dist/lib/arrange/analyze.d.ts.map +0 -1
- package/dist/lib/arrange/analyze.js +0 -105
- package/dist/lib/arrange/ast/collectors-cn.d.ts +0 -10
- package/dist/lib/arrange/ast/collectors-cn.d.ts.map +0 -1
- package/dist/lib/arrange/ast/collectors-cn.js +0 -84
- package/dist/lib/arrange/ast/collectors-jsx.d.ts +0 -4
- package/dist/lib/arrange/ast/collectors-jsx.d.ts.map +0 -1
- package/dist/lib/arrange/ast/collectors-jsx.js +0 -18
- package/dist/lib/arrange/ast/collectors-tv.d.ts +0 -13
- package/dist/lib/arrange/ast/collectors-tv.d.ts.map +0 -1
- package/dist/lib/arrange/ast/collectors-tv.js +0 -273
- package/dist/lib/arrange/ast/targets.d.ts +0 -8
- package/dist/lib/arrange/ast/targets.d.ts.map +0 -1
- package/dist/lib/arrange/ast/targets.js +0 -143
- package/dist/lib/arrange/ast/utils.d.ts +0 -36
- package/dist/lib/arrange/ast/utils.d.ts.map +0 -1
- package/dist/lib/arrange/ast/utils.js +0 -138
- package/dist/lib/arrange/constants.d.ts +0 -68
- package/dist/lib/arrange/constants.d.ts.map +0 -1
- package/dist/lib/arrange/constants.js +0 -179
- package/dist/lib/arrange/errors.d.ts +0 -14
- package/dist/lib/arrange/errors.d.ts.map +0 -1
- package/dist/lib/arrange/errors.js +0 -16
- package/dist/lib/arrange/formatters.d.ts +0 -16
- package/dist/lib/arrange/formatters.d.ts.map +0 -1
- package/dist/lib/arrange/formatters.js +0 -57
- package/dist/lib/arrange/group-file.d.ts +0 -4
- package/dist/lib/arrange/group-file.d.ts.map +0 -1
- package/dist/lib/arrange/group-file.js +0 -115
- package/dist/lib/arrange/grouping.d.ts +0 -41
- package/dist/lib/arrange/grouping.d.ts.map +0 -1
- package/dist/lib/arrange/grouping.js +0 -280
- package/dist/lib/arrange/imports.d.ts +0 -6
- package/dist/lib/arrange/imports.d.ts.map +0 -1
- package/dist/lib/arrange/imports.js +0 -74
- package/dist/lib/arrange/report.d.ts +0 -4
- package/dist/lib/arrange/report.d.ts.map +0 -1
- package/dist/lib/arrange/report.js +0 -37
- package/dist/lib/arrange/run-target.d.ts +0 -4
- package/dist/lib/arrange/run-target.d.ts.map +0 -1
- package/dist/lib/arrange/run-target.js +0 -28
- package/dist/lib/arrange/tokenizer.d.ts +0 -37
- package/dist/lib/arrange/tokenizer.d.ts.map +0 -1
- package/dist/lib/arrange/tokenizer.js +0 -390
- package/dist/lib/arrange/types.d.ts +0 -104
- package/dist/lib/arrange/types.d.ts.map +0 -1
- package/dist/lib/arrange/types.js +0 -7
- package/dist/lib/arrange/walk.d.ts +0 -4
- package/dist/lib/arrange/walk.d.ts.map +0 -1
- package/dist/lib/arrange/walk.js +0 -34
- package/dist/lib/arrange.d.ts +0 -41
- package/dist/lib/arrange.d.ts.map +0 -1
- package/dist/lib/arrange.js +0 -38
- package/dist/lib/infra/fs-contract.d.ts +0 -28
- package/dist/lib/infra/fs-contract.d.ts.map +0 -1
- package/dist/lib/infra/node-io.d.ts +0 -4
- package/dist/lib/infra/node-io.d.ts.map +0 -1
- package/dist/lib/infra/node-io.js +0 -27
- package/dist/lib/mirror/config.d.ts +0 -7
- package/dist/lib/mirror/config.d.ts.map +0 -1
- package/dist/lib/mirror/config.js +0 -146
- package/dist/lib/mirror/constants.d.ts +0 -10
- package/dist/lib/mirror/constants.d.ts.map +0 -1
- package/dist/lib/mirror/constants.js +0 -13
- package/dist/lib/mirror/engine.d.ts +0 -12
- package/dist/lib/mirror/engine.d.ts.map +0 -1
- package/dist/lib/mirror/engine.js +0 -248
- package/dist/lib/mirror/errors.d.ts +0 -10
- package/dist/lib/mirror/errors.d.ts.map +0 -1
- package/dist/lib/mirror/errors.js +0 -12
- package/dist/lib/mirror/package-filter.d.ts +0 -8
- package/dist/lib/mirror/package-filter.d.ts.map +0 -1
- package/dist/lib/mirror/package-filter.js +0 -33
- package/dist/lib/mirror/reporter.d.ts +0 -24
- package/dist/lib/mirror/reporter.d.ts.map +0 -1
- package/dist/lib/mirror/reporter.js +0 -126
- package/dist/lib/mirror/sync.d.ts +0 -4
- package/dist/lib/mirror/sync.d.ts.map +0 -1
- package/dist/lib/mirror/sync.js +0 -135
- package/dist/lib/mirror/types.d.ts +0 -81
- package/dist/lib/mirror/types.d.ts.map +0 -1
- package/dist/lib/mirror/update-pkg.d.ts +0 -13
- package/dist/lib/mirror/update-pkg.d.ts.map +0 -1
- package/dist/lib/mirror/update-pkg.js +0 -39
- package/dist/lib/mirror/workspace-packages.d.ts +0 -26
- package/dist/lib/mirror/workspace-packages.d.ts.map +0 -1
- package/dist/lib/mirror/workspace-packages.js +0 -160
- package/dist/lib/mirror.d.ts +0 -7
- package/dist/lib/mirror.d.ts.map +0 -1
- package/dist/lib/mirror.js +0 -5
- package/dist/lib/repo-root.d.ts +0 -7
- package/dist/lib/repo-root.d.ts.map +0 -1
- package/dist/lib/repo-root.js +0 -23
- package/dist/lib/shared/utils.d.ts +0 -9
- package/dist/lib/shared/utils.d.ts.map +0 -1
- package/dist/lib/shared/utils.js +0 -12
- package/dist/program.d.ts +0 -4
- package/dist/program.d.ts.map +0 -1
- package/dist/program.js +0 -38
- /package/dist/lib/{infra/fs-contract.js → arrange/domain/types.mjs} +0 -0
- /package/dist/lib/{mirror/types.js → infra/fs-contract.mjs} +0 -0
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { EMPTY_CN_TV_BINDINGS } from "#lib/arrange/constants";
|
|
3
|
-
/** Known module specifiers that export `cn` / `tv`. */
|
|
4
|
-
export const KNOWN_CN_TV_MODULES = new Set([
|
|
5
|
-
"@codefast/tailwind-variants",
|
|
6
|
-
"clsx",
|
|
7
|
-
"class-variance-authority",
|
|
8
|
-
// Backward compatibility only: keep detection for existing user code that still imports
|
|
9
|
-
// from "tailwind-variants"; new code should prefer "@codefast/tailwind-variants".
|
|
10
|
-
"tailwind-variants",
|
|
11
|
-
"#lib/utils",
|
|
12
|
-
"~/lib/utils",
|
|
13
|
-
"@/lib/utils",
|
|
14
|
-
]);
|
|
15
|
-
/**
|
|
16
|
-
* True for typical shadcn-style re-exports: `./utils`, `@/lib/utils`, `…/utils/…`,
|
|
17
|
-
* or a dedicated `cn.ts` / `cn.tsx` module.
|
|
18
|
-
*/
|
|
19
|
-
export function moduleLooksLikeCnTvReexport(mod) {
|
|
20
|
-
const norm = mod.replace(/\\/g, "/");
|
|
21
|
-
if (/(?:^|[./])utils(?:\/|$)/.test(norm))
|
|
22
|
-
return true;
|
|
23
|
-
if (/\/utils\//.test(norm) || /\/utils$/.test(norm))
|
|
24
|
-
return true;
|
|
25
|
-
if (/(?:^|\/)cn\.tsx?$/.test(norm))
|
|
26
|
-
return true;
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Build a set of local binding names that are imported from a known cn/tv
|
|
31
|
-
* module in `sf`.
|
|
32
|
-
*/
|
|
33
|
-
export function buildKnownCnTvBindings(sf) {
|
|
34
|
-
const bindings = new Set();
|
|
35
|
-
for (const stmt of sf.statements) {
|
|
36
|
-
if (!ts.isImportDeclaration(stmt) || !stmt.importClause)
|
|
37
|
-
continue;
|
|
38
|
-
if (stmt.importClause.isTypeOnly)
|
|
39
|
-
continue;
|
|
40
|
-
const spec = stmt.moduleSpecifier;
|
|
41
|
-
if (!ts.isStringLiteral(spec))
|
|
42
|
-
continue;
|
|
43
|
-
const mod = spec.text;
|
|
44
|
-
const isKnown = KNOWN_CN_TV_MODULES.has(mod);
|
|
45
|
-
if (!isKnown && !moduleLooksLikeCnTvReexport(mod))
|
|
46
|
-
continue;
|
|
47
|
-
const clause = stmt.importClause;
|
|
48
|
-
if (clause.name)
|
|
49
|
-
bindings.add(clause.name.text);
|
|
50
|
-
if (!clause.namedBindings)
|
|
51
|
-
continue;
|
|
52
|
-
if (ts.isNamedImports(clause.namedBindings)) {
|
|
53
|
-
for (const el of clause.namedBindings.elements)
|
|
54
|
-
bindings.add(el.name.text);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
bindings.add(clause.namedBindings.name.text);
|
|
58
|
-
}
|
|
59
|
-
return bindings;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Returns true when `expr` is an identifier whose name matches `name` AND
|
|
63
|
-
* that name is listed in `knownBindings` from a recognized import.
|
|
64
|
-
*/
|
|
65
|
-
export function isCnOrTvIdentifier(expr, name, knownBindings = EMPTY_CN_TV_BINDINGS) {
|
|
66
|
-
if (knownBindings.size === 0)
|
|
67
|
-
return false;
|
|
68
|
-
if (ts.isIdentifier(expr) && expr.text === name)
|
|
69
|
-
return knownBindings.has(expr.text);
|
|
70
|
-
if (ts.isPropertyAccessExpression(expr) && expr.name.text === name) {
|
|
71
|
-
return ts.isIdentifier(expr.expression) && knownBindings.has(expr.expression.text);
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
export function propertyAssignmentNameText(prop) {
|
|
76
|
-
if (ts.isIdentifier(prop.name))
|
|
77
|
-
return prop.name.text;
|
|
78
|
-
if (ts.isStringLiteral(prop.name))
|
|
79
|
-
return prop.name.text;
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
export function lineOf(sf, node) {
|
|
83
|
-
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Returns only the leading whitespace (tabs / spaces) of the line containing
|
|
87
|
-
* `pos`.
|
|
88
|
-
*/
|
|
89
|
-
export function indentOfLineContaining(source, pos) {
|
|
90
|
-
const searchPos = Math.max(0, Math.min(pos, source.length));
|
|
91
|
-
const prevLineBreak = Math.max(source.lastIndexOf("\n", searchPos - 1), source.lastIndexOf("\r", searchPos - 1));
|
|
92
|
-
const lineStart = prevLineBreak === -1 ? 0 : prevLineBreak + 1;
|
|
93
|
-
let lineEnd = source.length;
|
|
94
|
-
for (let i = lineStart; i < source.length; i++) {
|
|
95
|
-
const ch = source[i];
|
|
96
|
-
if (ch === "\n" || ch === "\r") {
|
|
97
|
-
lineEnd = i;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const line = source.slice(lineStart, lineEnd);
|
|
102
|
-
const indentMatch = /^[\t ]*/.exec(line);
|
|
103
|
-
return indentMatch?.[0] ?? "";
|
|
104
|
-
}
|
|
105
|
-
export function applyEditsDescending(sourceText, edits) {
|
|
106
|
-
const sorted = [...edits].sort((editA, editB) => editB.start - editA.start);
|
|
107
|
-
let out = sourceText;
|
|
108
|
-
for (const edit of sorted) {
|
|
109
|
-
out = out.slice(0, edit.start) + edit.replacement + out.slice(edit.end);
|
|
110
|
-
}
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Replace `cn(...)` nested in `tv({...})` with a plain string (one arg) or a string
|
|
115
|
-
* array (multiple args). Returns undefined when there are zero arguments.
|
|
116
|
-
*/
|
|
117
|
-
export function unwrapCnInsideTvCallReplacement(call, sourceText, sf) {
|
|
118
|
-
const args = call.arguments;
|
|
119
|
-
if (args.length === 0)
|
|
120
|
-
return undefined;
|
|
121
|
-
const baseIndent = indentOfLineContaining(sourceText, call.getStart(sf));
|
|
122
|
-
const innerIndent = `${baseIndent} `;
|
|
123
|
-
if (args.length === 1) {
|
|
124
|
-
const firstArg = args[0];
|
|
125
|
-
return sourceText.slice(firstArg.getStart(sf), firstArg.getEnd());
|
|
126
|
-
}
|
|
127
|
-
const lines = ["["];
|
|
128
|
-
for (let i = 0; i < args.length; i++) {
|
|
129
|
-
const arg = args[i];
|
|
130
|
-
const piece = sourceText.slice(arg.getStart(sf), arg.getEnd());
|
|
131
|
-
// Trailing comma on every element — intentional (Prettier-compatible style;
|
|
132
|
-
// keeps array diffs clean when arguments are later added or removed).
|
|
133
|
-
const comma = i < args.length - 1 || args.length > 1 ? "," : "";
|
|
134
|
-
lines.push(`${innerIndent}${piece}${comma}`);
|
|
135
|
-
}
|
|
136
|
-
lines.push(`${baseIndent}]`);
|
|
137
|
-
return lines.join("\n");
|
|
138
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { Bucket } from "#lib/arrange/types";
|
|
2
|
-
/** Analyze report: long literal threshold (token count). */
|
|
3
|
-
export declare const LONG_STRING_TOKEN_THRESHOLD = 18;
|
|
4
|
-
/**
|
|
5
|
-
* Minimum token count for a string to be considered a candidate for grouping
|
|
6
|
-
* in the apply/preview pipeline. Intentionally much lower than
|
|
7
|
-
* {@link LONG_STRING_TOKEN_THRESHOLD} (analyze only).
|
|
8
|
-
*/
|
|
9
|
-
export declare const APPLY_MIN_TOKENS = 2;
|
|
10
|
-
/**
|
|
11
|
-
* Minimum tokens a group must have to stand alone before singleton-merging.
|
|
12
|
-
* Set to 2 so single-token groups are not collapsed into unrelated buckets by the merger.
|
|
13
|
-
*/
|
|
14
|
-
export declare const MIN_GROUP_TOKENS = 2;
|
|
15
|
-
/** Dynamic max groups clamp: base bound. */
|
|
16
|
-
export declare const MAX_GROUPS_BASE = 4;
|
|
17
|
-
/** Dynamic max groups clamp: upper cap. */
|
|
18
|
-
export declare const MAX_GROUPS_CAP = 24;
|
|
19
|
-
/**
|
|
20
|
-
* Extra slots so a few state / aria groups do not force `capGroups` to merge
|
|
21
|
-
* unrelated buckets (e.g. `bg-border` + `outline-hidden`).
|
|
22
|
-
*/
|
|
23
|
-
export declare const MAX_GROUPS_HEADROOM = 2;
|
|
24
|
-
/** Maximum findings printed per category in the analyze report. */
|
|
25
|
-
export declare const MAX_REPORT_LINES = 40;
|
|
26
|
-
/** Maximum recursion depth when traversing `tv()` object literals. */
|
|
27
|
-
export declare const MAX_OBJECT_DEPTH = 12;
|
|
28
|
-
/** Maximum depth when peeling conditional / parens / arrays inside `cn(...)` args. */
|
|
29
|
-
export declare const MAX_CLASS_EXPR_DEPTH = 12;
|
|
30
|
-
/**
|
|
31
|
-
* Maximum variant-stripping passes in {@link stripVariants}.
|
|
32
|
-
* Real-world Tailwind stacks rarely exceed 4–5 segments (e.g. `@md/sidebar:group-hover:dark:focus-visible:`);
|
|
33
|
-
* 12 is a conservative safety cap that prevents runaway loops on malformed input.
|
|
34
|
-
*/
|
|
35
|
-
export declare const MAX_STRIP_VARIANT_PASSES = 12;
|
|
36
|
-
/**
|
|
37
|
-
* Passed when optional `knownBindings` is missing — size 0 disables matching
|
|
38
|
-
* for `cn` / `tv` identifier resolution.
|
|
39
|
-
*/
|
|
40
|
-
export declare const EMPTY_CN_TV_BINDINGS: Set<string>;
|
|
41
|
-
/**
|
|
42
|
-
* Bucket sort order — **render pipeline** (lower → earlier in output).
|
|
43
|
-
* Existence → Position → Layout → Sizing → Spacing → Shape → Background → Shadow
|
|
44
|
-
* → Typography → Composite → Motion → Starting → Behavior → Conditions (state).
|
|
45
|
-
*/
|
|
46
|
-
export declare const BUCKET_ORDER: Record<Bucket, number>;
|
|
47
|
-
/**
|
|
48
|
-
* Compatible bucket **pairs** — two adjacent tokens whose buckets appear in the same
|
|
49
|
-
* `Set` may stay in one `cn()` string chunk. Compatibility is **not transitive**: there is
|
|
50
|
-
* no `{position, sizing}` pair, so `fixed inset-0` and `overflow-auto` become **separate**
|
|
51
|
-
* literals unless **layout** tokens sit between them in sorted order; then
|
|
52
|
-
* `position↔layout` and `layout↔sizing` each justify one hop and the whole “box in flow”
|
|
53
|
-
* (place + tracks + scrollport) stays in one chunk — co-located because they jointly define
|
|
54
|
-
* stacking and scroll behavior for the same surface.
|
|
55
|
-
*/
|
|
56
|
-
export declare const COMPATIBLE_BUCKET_SETS: ReadonlyArray<ReadonlySet<Bucket>>;
|
|
57
|
-
/**
|
|
58
|
-
* Responsive / variant prefix — Tailwind v4 aware.
|
|
59
|
-
*
|
|
60
|
-
* v3: sm: md: … — v4: @sm:, @min-[600px]:, @[480px]:, named @md/sidebar:,
|
|
61
|
-
* viewport md/sidebar:, min-[100px]: / max-[100px]:, …
|
|
62
|
-
*/
|
|
63
|
-
export declare const RESPONSIVE_PREFIX: RegExp;
|
|
64
|
-
/** State variant stems — hoisted to module scope (not recreated per call). */
|
|
65
|
-
export declare const STATE_PREFIXES: Set<string>;
|
|
66
|
-
/** Default directory for arrange analyze/preview/apply when no target is passed. */
|
|
67
|
-
export declare const DEFAULT_ARRANGE_TARGET = "packages/ui/src/components";
|
|
68
|
-
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,4DAA4D;AAC5D,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,4CAA4C;AAC5C,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,2CAA2C;AAC3C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,sFAAsF;AACtF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,oBAAoB,aAAoB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAiB/C,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,WAAW,CAAC,MAAM,CAAC,CA6BrE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAC2J,CAAC;AAE1L,8EAA8E;AAC9E,eAAO,MAAM,cAAc,aAmEzB,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,sBAAsB,+BAA+B,CAAC"}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/** Analyze report: long literal threshold (token count). */
|
|
2
|
-
export const LONG_STRING_TOKEN_THRESHOLD = 18;
|
|
3
|
-
/**
|
|
4
|
-
* Minimum token count for a string to be considered a candidate for grouping
|
|
5
|
-
* in the apply/preview pipeline. Intentionally much lower than
|
|
6
|
-
* {@link LONG_STRING_TOKEN_THRESHOLD} (analyze only).
|
|
7
|
-
*/
|
|
8
|
-
export const APPLY_MIN_TOKENS = 2;
|
|
9
|
-
/**
|
|
10
|
-
* Minimum tokens a group must have to stand alone before singleton-merging.
|
|
11
|
-
* Set to 2 so single-token groups are not collapsed into unrelated buckets by the merger.
|
|
12
|
-
*/
|
|
13
|
-
export const MIN_GROUP_TOKENS = 2;
|
|
14
|
-
/** Dynamic max groups clamp: base bound. */
|
|
15
|
-
export const MAX_GROUPS_BASE = 4;
|
|
16
|
-
/** Dynamic max groups clamp: upper cap. */
|
|
17
|
-
export const MAX_GROUPS_CAP = 24;
|
|
18
|
-
/**
|
|
19
|
-
* Extra slots so a few state / aria groups do not force `capGroups` to merge
|
|
20
|
-
* unrelated buckets (e.g. `bg-border` + `outline-hidden`).
|
|
21
|
-
*/
|
|
22
|
-
export const MAX_GROUPS_HEADROOM = 2;
|
|
23
|
-
/** Maximum findings printed per category in the analyze report. */
|
|
24
|
-
export const MAX_REPORT_LINES = 40;
|
|
25
|
-
/** Maximum recursion depth when traversing `tv()` object literals. */
|
|
26
|
-
export const MAX_OBJECT_DEPTH = 12;
|
|
27
|
-
/** Maximum depth when peeling conditional / parens / arrays inside `cn(...)` args. */
|
|
28
|
-
export const MAX_CLASS_EXPR_DEPTH = 12;
|
|
29
|
-
/**
|
|
30
|
-
* Maximum variant-stripping passes in {@link stripVariants}.
|
|
31
|
-
* Real-world Tailwind stacks rarely exceed 4–5 segments (e.g. `@md/sidebar:group-hover:dark:focus-visible:`);
|
|
32
|
-
* 12 is a conservative safety cap that prevents runaway loops on malformed input.
|
|
33
|
-
*/
|
|
34
|
-
export const MAX_STRIP_VARIANT_PASSES = 12;
|
|
35
|
-
/**
|
|
36
|
-
* Passed when optional `knownBindings` is missing — size 0 disables matching
|
|
37
|
-
* for `cn` / `tv` identifier resolution.
|
|
38
|
-
*/
|
|
39
|
-
export const EMPTY_CN_TV_BINDINGS = new Set();
|
|
40
|
-
/**
|
|
41
|
-
* Bucket sort order — **render pipeline** (lower → earlier in output).
|
|
42
|
-
* Existence → Position → Layout → Sizing → Spacing → Shape → Background → Shadow
|
|
43
|
-
* → Typography → Composite → Motion → Starting → Behavior → Conditions (state).
|
|
44
|
-
*/
|
|
45
|
-
export const BUCKET_ORDER = {
|
|
46
|
-
existence: 0,
|
|
47
|
-
position: 1,
|
|
48
|
-
layout: 2,
|
|
49
|
-
sizing: 3,
|
|
50
|
-
spacing: 4,
|
|
51
|
-
shape: 5,
|
|
52
|
-
background: 6,
|
|
53
|
-
shadow: 7,
|
|
54
|
-
typography: 8,
|
|
55
|
-
composite: 9,
|
|
56
|
-
motion: 10,
|
|
57
|
-
starting: 11,
|
|
58
|
-
behavior: 12,
|
|
59
|
-
state: 13,
|
|
60
|
-
other: 14,
|
|
61
|
-
arbitrary: 15,
|
|
62
|
-
};
|
|
63
|
-
/**
|
|
64
|
-
* Compatible bucket **pairs** — two adjacent tokens whose buckets appear in the same
|
|
65
|
-
* `Set` may stay in one `cn()` string chunk. Compatibility is **not transitive**: there is
|
|
66
|
-
* no `{position, sizing}` pair, so `fixed inset-0` and `overflow-auto` become **separate**
|
|
67
|
-
* literals unless **layout** tokens sit between them in sorted order; then
|
|
68
|
-
* `position↔layout` and `layout↔sizing` each justify one hop and the whole “box in flow”
|
|
69
|
-
* (place + tracks + scrollport) stays in one chunk — co-located because they jointly define
|
|
70
|
-
* stacking and scroll behavior for the same surface.
|
|
71
|
-
*/
|
|
72
|
-
export const COMPATIBLE_BUCKET_SETS = [
|
|
73
|
-
// existence + position: named container / group / peer context edited next to inset & z-index.
|
|
74
|
-
new Set(["existence", "position"]),
|
|
75
|
-
// existence + layout: `group/cmd` + `flex …` are one template row (context + grid/flex).
|
|
76
|
-
new Set(["existence", "layout"]),
|
|
77
|
-
// position + layout: `fixed inset-0` + `grid` describe the same stacking + track shell.
|
|
78
|
-
new Set(["position", "layout"]),
|
|
79
|
-
// layout + sizing: `grid` + `overflow-auto` / min size are one scrollport / track concern.
|
|
80
|
-
new Set(["layout", "sizing"]),
|
|
81
|
-
// shape + shadow: radius/border + shadow/outline overlay are one “chrome” literal in UIs.
|
|
82
|
-
new Set(["shape", "shadow"]),
|
|
83
|
-
// background + shadow: fill + depth (card surface) without splitting noise across args.
|
|
84
|
-
new Set(["background", "shadow"]),
|
|
85
|
-
// background + typography: destructive buttons etc. (`bg-*` + `text-*` on the same control).
|
|
86
|
-
new Set(["background", "typography"]),
|
|
87
|
-
// layout + typography: title rows (`flex` + `text-sm`) in one literal when adjacent in sort order.
|
|
88
|
-
new Set(["layout", "typography"]),
|
|
89
|
-
// sizing + typography: chart shell (`aspect-video` + `text-xs`) before scoped `[&_…]` state.
|
|
90
|
-
new Set(["sizing", "typography"]),
|
|
91
|
-
// motion + behavior: `transition-*` with `pointer-events-*` / `select-*` on the same surface.
|
|
92
|
-
new Set(["motion", "behavior"]),
|
|
93
|
-
// motion + starting: `transition` beside `starting:*` for coordinated enter/exit motion.
|
|
94
|
-
new Set(["motion", "starting"]),
|
|
95
|
-
// composite + motion: transforms then transitions (`-translate-*` + `transition-*` + `ease-*`).
|
|
96
|
-
new Set(["composite", "motion"]),
|
|
97
|
-
// sizing + composite: width/position utilities with transforms in one interaction block.
|
|
98
|
-
new Set(["sizing", "composite"]),
|
|
99
|
-
// sizing + spacing: width utilities with padding on the same slot (`w-auto` + `px-3`).
|
|
100
|
-
new Set(["sizing", "spacing"]),
|
|
101
|
-
];
|
|
102
|
-
/**
|
|
103
|
-
* Responsive / variant prefix — Tailwind v4 aware.
|
|
104
|
-
*
|
|
105
|
-
* v3: sm: md: … — v4: @sm:, @min-[600px]:, @[480px]:, named @md/sidebar:,
|
|
106
|
-
* viewport md/sidebar:, min-[100px]: / max-[100px]:, …
|
|
107
|
-
*/
|
|
108
|
-
export const RESPONSIVE_PREFIX = /^(?:@(?:min|max)-\[[^\]]+\]:|@\[[^\]]+\]:|@(?:[a-z0-9]+(?:-[a-z0-9]+)*)(?:\/[a-z][a-z0-9-]*)?:|(?:max-|min-)?(?:sm|md|lg|xl|2xl|3xl)(?:\/[a-z][a-z0-9-]*)?:|(?:max-|min-)\[[^\]]+\]:)/;
|
|
109
|
-
/** State variant stems — hoisted to module scope (not recreated per call). */
|
|
110
|
-
export const STATE_PREFIXES = new Set([
|
|
111
|
-
"hover",
|
|
112
|
-
"focus",
|
|
113
|
-
"focus-within",
|
|
114
|
-
"focus-visible",
|
|
115
|
-
"active",
|
|
116
|
-
"visited",
|
|
117
|
-
"link",
|
|
118
|
-
"any-link",
|
|
119
|
-
"disabled",
|
|
120
|
-
"enabled",
|
|
121
|
-
"checked",
|
|
122
|
-
"indeterminate",
|
|
123
|
-
"required",
|
|
124
|
-
"optional",
|
|
125
|
-
"valid",
|
|
126
|
-
"invalid",
|
|
127
|
-
"in-range",
|
|
128
|
-
"out-of-range",
|
|
129
|
-
"read-only",
|
|
130
|
-
"read-write",
|
|
131
|
-
"placeholder-shown",
|
|
132
|
-
"default",
|
|
133
|
-
"autofill",
|
|
134
|
-
"open",
|
|
135
|
-
"closed",
|
|
136
|
-
"first",
|
|
137
|
-
"last",
|
|
138
|
-
"only",
|
|
139
|
-
"odd",
|
|
140
|
-
"even",
|
|
141
|
-
"first-of-type",
|
|
142
|
-
"last-of-type",
|
|
143
|
-
"only-of-type",
|
|
144
|
-
"first-child",
|
|
145
|
-
"last-child",
|
|
146
|
-
"only-child",
|
|
147
|
-
"empty",
|
|
148
|
-
"target",
|
|
149
|
-
"group",
|
|
150
|
-
"peer",
|
|
151
|
-
"aria",
|
|
152
|
-
"data",
|
|
153
|
-
"dark",
|
|
154
|
-
"light",
|
|
155
|
-
"supports",
|
|
156
|
-
"motion-reduce",
|
|
157
|
-
"motion-safe",
|
|
158
|
-
"print",
|
|
159
|
-
"screen",
|
|
160
|
-
"ltr",
|
|
161
|
-
"rtl",
|
|
162
|
-
"not",
|
|
163
|
-
"has",
|
|
164
|
-
"in",
|
|
165
|
-
"nth",
|
|
166
|
-
"file",
|
|
167
|
-
"selection",
|
|
168
|
-
"marker",
|
|
169
|
-
"placeholder",
|
|
170
|
-
"before",
|
|
171
|
-
"after",
|
|
172
|
-
"first-line",
|
|
173
|
-
"first-letter",
|
|
174
|
-
"backdrop",
|
|
175
|
-
"details-content",
|
|
176
|
-
"popover-open",
|
|
177
|
-
]);
|
|
178
|
-
/** Default directory for arrange analyze/preview/apply when no target is passed. */
|
|
179
|
-
export const DEFAULT_ARRANGE_TARGET = "packages/ui/src/components";
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured errors for the arrange CLI library (throw from lib; handle exit in commands).
|
|
3
|
-
*/
|
|
4
|
-
export declare const ArrangeErrorCode: {
|
|
5
|
-
readonly TARGET_NOT_FOUND: "TARGET_NOT_FOUND";
|
|
6
|
-
readonly INVALID_INPUT: "INVALID_INPUT";
|
|
7
|
-
readonly INTERNAL: "INTERNAL";
|
|
8
|
-
};
|
|
9
|
-
export type ArrangeErrorCode = (typeof ArrangeErrorCode)[keyof typeof ArrangeErrorCode];
|
|
10
|
-
export declare class ArrangeError extends Error {
|
|
11
|
-
readonly code: ArrangeErrorCode;
|
|
12
|
-
constructor(code: ArrangeErrorCode, message: string, options?: ErrorOptions);
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,gBAAgB;aAC3B,gBAAgB,EAAE,kBAAkB;aACpC,aAAa,EAAE,eAAe;aAC9B,QAAQ,EAAE,UAAU;CACZ,CAAC;AAGX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAExF,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAEhC,YAAY,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAI1E;CACF"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured errors for the arrange CLI library (throw from lib; handle exit in commands).
|
|
3
|
-
*/
|
|
4
|
-
export const ArrangeErrorCode = {
|
|
5
|
-
TARGET_NOT_FOUND: "TARGET_NOT_FOUND",
|
|
6
|
-
INVALID_INPUT: "INVALID_INPUT",
|
|
7
|
-
INTERNAL: "INTERNAL",
|
|
8
|
-
};
|
|
9
|
-
export class ArrangeError extends Error {
|
|
10
|
-
code;
|
|
11
|
-
constructor(code, message, options) {
|
|
12
|
-
super(message, options);
|
|
13
|
-
this.name = "ArrangeError";
|
|
14
|
-
this.code = code;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export declare function escapeTsStringLiteralContent(group: string): string;
|
|
2
|
-
/**
|
|
3
|
-
* Multiline fragment for use *inside* an existing `cn(...)` — replaces one
|
|
4
|
-
* string argument with several, without producing `cn(cn(...))`.
|
|
5
|
-
*/
|
|
6
|
-
export declare function formatCnArguments(groups: string[], options?: {
|
|
7
|
-
indent?: string;
|
|
8
|
-
commaAfterLastGroup?: boolean;
|
|
9
|
-
trailingClassName?: boolean;
|
|
10
|
-
}): string;
|
|
11
|
-
export declare function formatCnCall(groups: string[], options?: {
|
|
12
|
-
trailingClassName?: boolean;
|
|
13
|
-
}): string;
|
|
14
|
-
export declare function formatArray(groups: string[]): string;
|
|
15
|
-
export declare function formatJsxCnAttributeValue(groups: string[], source: string, valueNodeStart: number): string;
|
|
16
|
-
//# sourceMappingURL=formatters.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/formatters.ts"],"names":[],"mappings":"AAEA,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GACA,MAAM,CAaR;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAahG;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAWpD;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EAAE,EAChB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,MAAM,CAQR"}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { indentOfLineContaining } from "#lib/arrange/ast/utils";
|
|
2
|
-
export function escapeTsStringLiteralContent(group) {
|
|
3
|
-
return group.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Multiline fragment for use *inside* an existing `cn(...)` — replaces one
|
|
7
|
-
* string argument with several, without producing `cn(cn(...))`.
|
|
8
|
-
*/
|
|
9
|
-
export function formatCnArguments(groups, options) {
|
|
10
|
-
const indent = options?.indent ?? " ";
|
|
11
|
-
const commaAfterLast = options?.commaAfterLastGroup ?? options?.trailingClassName ?? false;
|
|
12
|
-
const lines = [];
|
|
13
|
-
for (let i = 0; i < groups.length; i++) {
|
|
14
|
-
const group = groups[i];
|
|
15
|
-
const comma = i < groups.length - 1 || commaAfterLast ? "," : "";
|
|
16
|
-
lines.push(`${indent}"${escapeTsStringLiteralContent(group)}"${comma}`);
|
|
17
|
-
}
|
|
18
|
-
if (options?.trailingClassName) {
|
|
19
|
-
lines.push(`${indent}className,`);
|
|
20
|
-
}
|
|
21
|
-
return lines.join("\n");
|
|
22
|
-
}
|
|
23
|
-
export function formatCnCall(groups, options) {
|
|
24
|
-
const lines = ["cn("];
|
|
25
|
-
const commaOnEachStringLine = groups.length > 1 || Boolean(options?.trailingClassName);
|
|
26
|
-
for (let i = 0; i < groups.length; i++) {
|
|
27
|
-
const group = groups[i];
|
|
28
|
-
const comma = commaOnEachStringLine ? "," : "";
|
|
29
|
-
lines.push(` "${escapeTsStringLiteralContent(group)}"${comma}`);
|
|
30
|
-
}
|
|
31
|
-
if (options?.trailingClassName) {
|
|
32
|
-
lines.push(" className,");
|
|
33
|
-
}
|
|
34
|
-
lines.push(")");
|
|
35
|
-
return lines.join("\n");
|
|
36
|
-
}
|
|
37
|
-
export function formatArray(groups) {
|
|
38
|
-
const lines = ["["];
|
|
39
|
-
for (let i = 0; i < groups.length; i++) {
|
|
40
|
-
const group = groups[i];
|
|
41
|
-
// Trailing comma on every element when there are multiple entries — matches
|
|
42
|
-
// Prettier's default array formatting and keeps diffs minimal on insertion.
|
|
43
|
-
const comma = i < groups.length - 1 || groups.length > 1 ? "," : "";
|
|
44
|
-
lines.push(` "${escapeTsStringLiteralContent(group)}"${comma}`);
|
|
45
|
-
}
|
|
46
|
-
lines.push("]");
|
|
47
|
-
return lines.join("\n");
|
|
48
|
-
}
|
|
49
|
-
export function formatJsxCnAttributeValue(groups, source, valueNodeStart) {
|
|
50
|
-
const baseIndent = indentOfLineContaining(source, valueNodeStart);
|
|
51
|
-
const argIndent = `${baseIndent} `;
|
|
52
|
-
const inner = formatCnArguments(groups, {
|
|
53
|
-
indent: argIndent,
|
|
54
|
-
commaAfterLastGroup: groups.length > 1,
|
|
55
|
-
});
|
|
56
|
-
return `{cn(\n${inner}\n${baseIndent})}`;
|
|
57
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { CliFs, CliLogger } from "#lib/infra/fs-contract";
|
|
2
|
-
import type { ArrangeGroupFileOptions, GroupFileResult } from "#lib/arrange/types";
|
|
3
|
-
export declare function groupFile(filePath: string, options: ArrangeGroupFileOptions, fs: CliFs, logger: CliLogger): GroupFileResult;
|
|
4
|
-
//# sourceMappingURL=group-file.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"group-file.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/group-file.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EACV,uBAAuB,EACvB,eAAe,EAEhB,MAAM,oBAAoB,CAAC;AAiH5B,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,EAChC,EAAE,EAAE,KAAK,EACT,MAAM,EAAE,SAAS,GAChB,eAAe,CA8FjB"}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { applyEditsDescending, buildKnownCnTvBindings, lineOf, unwrapCnInsideTvCallReplacement, } from "#lib/arrange/ast/utils";
|
|
3
|
-
import { listAllCnCallsInsideTvInSourceFile } from "#lib/arrange/ast/collectors-tv";
|
|
4
|
-
import { collectGroupTargets, planGroupEditForTarget, targetReplaceStart, } from "#lib/arrange/ast/targets";
|
|
5
|
-
import { ensureCnImport } from "#lib/arrange/imports";
|
|
6
|
-
function printGroupFilePreview(logger, args) {
|
|
7
|
-
const { out } = logger;
|
|
8
|
-
const { filePath, reportTotal, cnInTvNoReplacement, cnInTvCalls, unwrapReplacementByCall, sourceText, sf, unwrapEdits, plannedGroupEdits, } = args;
|
|
9
|
-
let header = `\n── ${filePath} (${reportTotal} site(s)`;
|
|
10
|
-
if (cnInTvNoReplacement > 0) {
|
|
11
|
-
header += `; plus ${cnInTvNoReplacement} cn() inside tv left unchanged (0 args)`;
|
|
12
|
-
}
|
|
13
|
-
header += `) ──`;
|
|
14
|
-
out(header);
|
|
15
|
-
for (const call of cnInTvCalls) {
|
|
16
|
-
const replacement = unwrapReplacementByCall.get(call);
|
|
17
|
-
if (replacement === undefined) {
|
|
18
|
-
out(` Line ${lineOf(sf, call)} [tv ⊃ cn]: cn(...) has no arguments — skipped`);
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
const start = call.getStart(sf);
|
|
22
|
-
const end = call.getEnd();
|
|
23
|
-
if (sourceText.slice(start, end) === replacement)
|
|
24
|
-
continue;
|
|
25
|
-
out(` Line ${lineOf(sf, call)} [tv ⊃ cn → string/array]:`);
|
|
26
|
-
out(` ${replacement.split("\n").join("\n ")}`);
|
|
27
|
-
}
|
|
28
|
-
if (unwrapEdits.length > 0 && plannedGroupEdits.length > 0) {
|
|
29
|
-
out(" ([cn] / [tv] / [JSX className] lines below reflect content after unwrap of cn inside tv.)");
|
|
30
|
-
}
|
|
31
|
-
for (const plan of plannedGroupEdits) {
|
|
32
|
-
out(` Line ${lineOf(plan.lineSf, plan.reportNode)} [${plan.label}]:`);
|
|
33
|
-
out(` ${plan.replacement.split("\n").join("\n ")}`);
|
|
34
|
-
out(` // Buckets: ${JSON.stringify(plan.bucketSummary)}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function applyGroupFileWrites(filePath, options, fs, logger, unwrapEdits, textAfterUnwrap, plannedGroupEdits, cnInTvNoReplacement) {
|
|
38
|
-
const groupEdits = plannedGroupEdits.map((p) => ({
|
|
39
|
-
start: p.start,
|
|
40
|
-
end: p.end,
|
|
41
|
-
replacement: p.replacement,
|
|
42
|
-
jsxCn: p.jsxCn,
|
|
43
|
-
}));
|
|
44
|
-
const touchedJsxCn = groupEdits.some((e) => e.jsxCn);
|
|
45
|
-
let newText = groupEdits.length > 0 ? applyEditsDescending(textAfterUnwrap, groupEdits) : textAfterUnwrap;
|
|
46
|
-
const changed = unwrapEdits.length + groupEdits.length;
|
|
47
|
-
if (changed > 0) {
|
|
48
|
-
if (touchedJsxCn) {
|
|
49
|
-
newText = ensureCnImport(newText, filePath, options.cnImport);
|
|
50
|
-
}
|
|
51
|
-
fs.writeFileSync(filePath, newText, "utf8");
|
|
52
|
-
}
|
|
53
|
-
return { filePath, totalFound: changed + cnInTvNoReplacement, changed };
|
|
54
|
-
}
|
|
55
|
-
export function groupFile(filePath, options, fs, logger) {
|
|
56
|
-
const sourceText = fs.readFileSync(filePath, "utf8");
|
|
57
|
-
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
58
|
-
const knownBindings = buildKnownCnTvBindings(sf);
|
|
59
|
-
const cnInTvCalls = listAllCnCallsInsideTvInSourceFile(sf, knownBindings);
|
|
60
|
-
const unwrapPlans = cnInTvCalls
|
|
61
|
-
.map((call) => {
|
|
62
|
-
const replacement = unwrapCnInsideTvCallReplacement(call, sourceText, sf);
|
|
63
|
-
if (replacement === undefined)
|
|
64
|
-
return undefined;
|
|
65
|
-
return { start: call.getStart(sf), end: call.getEnd(), replacement, call };
|
|
66
|
-
})
|
|
67
|
-
.filter((e) => e !== undefined);
|
|
68
|
-
const unwrapReplacementByCall = new Map(unwrapPlans.map((p) => [p.call, p.replacement]));
|
|
69
|
-
const unwrapEdits = unwrapPlans.filter((e) => sourceText.slice(e.start, e.end) !== e.replacement);
|
|
70
|
-
const textAfterUnwrap = unwrapEdits.length > 0 ? applyEditsDescending(sourceText, unwrapEdits) : sourceText;
|
|
71
|
-
// The first parse (`sf`) intentionally targets the original source text to discover unwrap edits.
|
|
72
|
-
// After `applyEditsDescending` runs, node offsets from that AST no longer match the updated text.
|
|
73
|
-
// Re-parse `textAfterUnwrap` so group-target traversal uses valid positions; reusing the first AST
|
|
74
|
-
// would produce incorrect start/end offsets for subsequent grouping edits.
|
|
75
|
-
const sfGrouped = ts.createSourceFile(filePath, textAfterUnwrap, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
76
|
-
const groupTargets = collectGroupTargets(sfGrouped, filePath);
|
|
77
|
-
const cnInTvNoReplacement = cnInTvCalls.length - unwrapPlans.length;
|
|
78
|
-
const sortedTargets = [...groupTargets].sort((a, b) => targetReplaceStart(b) - targetReplaceStart(a));
|
|
79
|
-
const plannedGroupEdits = [];
|
|
80
|
-
for (const t of sortedTargets) {
|
|
81
|
-
const plan = planGroupEditForTarget(t, textAfterUnwrap, options.withClassName);
|
|
82
|
-
if (plan === undefined)
|
|
83
|
-
continue;
|
|
84
|
-
if (textAfterUnwrap.slice(plan.start, plan.end) === plan.replacement)
|
|
85
|
-
continue;
|
|
86
|
-
plannedGroupEdits.push(plan);
|
|
87
|
-
}
|
|
88
|
-
const editSitesCount = unwrapEdits.length + plannedGroupEdits.length;
|
|
89
|
-
const reportTotal = editSitesCount + cnInTvNoReplacement;
|
|
90
|
-
if (cnInTvCalls.length === 0 && groupTargets.length === 0) {
|
|
91
|
-
return { filePath, totalFound: 0, changed: 0 };
|
|
92
|
-
}
|
|
93
|
-
if (!options.write) {
|
|
94
|
-
if (editSitesCount === 0 && cnInTvNoReplacement === 0) {
|
|
95
|
-
return { filePath, totalFound: 0, changed: 0 };
|
|
96
|
-
}
|
|
97
|
-
printGroupFilePreview(logger, {
|
|
98
|
-
filePath,
|
|
99
|
-
reportTotal,
|
|
100
|
-
cnInTvNoReplacement,
|
|
101
|
-
cnInTvCalls,
|
|
102
|
-
unwrapReplacementByCall,
|
|
103
|
-
sourceText,
|
|
104
|
-
sf,
|
|
105
|
-
unwrapEdits,
|
|
106
|
-
plannedGroupEdits,
|
|
107
|
-
});
|
|
108
|
-
return {
|
|
109
|
-
filePath,
|
|
110
|
-
totalFound: reportTotal,
|
|
111
|
-
changed: 0,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
return applyGroupFileWrites(filePath, options, fs, logger, unwrapEdits, textAfterUnwrap, plannedGroupEdits, cnInTvNoReplacement);
|
|
115
|
-
}
|