@codefast/cli 0.3.7-canary.0 → 0.3.12
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 +60 -299
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +5 -0
- package/dist/commands/arrange.d.ts +3 -0
- package/dist/commands/arrange.d.ts.map +1 -0
- package/dist/commands/arrange.js +114 -0
- package/dist/commands/mirror.d.ts +4 -0
- package/dist/commands/mirror.d.ts.map +1 -0
- package/dist/commands/mirror.js +63 -0
- package/dist/lib/arrange/analyze.d.ts +4 -0
- package/dist/lib/arrange/analyze.d.ts.map +1 -0
- package/dist/lib/arrange/analyze.js +105 -0
- package/dist/lib/arrange/ast/collectors-cn.d.ts +10 -0
- package/dist/lib/arrange/ast/collectors-cn.d.ts.map +1 -0
- package/dist/lib/arrange/ast/collectors-cn.js +84 -0
- package/dist/lib/arrange/ast/collectors-jsx.d.ts +4 -0
- package/dist/lib/arrange/ast/collectors-jsx.d.ts.map +1 -0
- package/dist/lib/arrange/ast/collectors-jsx.js +18 -0
- package/dist/lib/arrange/ast/collectors-tv.d.ts +13 -0
- package/dist/lib/arrange/ast/collectors-tv.d.ts.map +1 -0
- package/dist/lib/arrange/ast/collectors-tv.js +273 -0
- package/dist/lib/arrange/ast/targets.d.ts +8 -0
- package/dist/lib/arrange/ast/targets.d.ts.map +1 -0
- package/dist/lib/arrange/ast/targets.js +143 -0
- package/dist/lib/arrange/ast/utils.d.ts +36 -0
- package/dist/lib/arrange/ast/utils.d.ts.map +1 -0
- package/dist/lib/arrange/ast/utils.js +138 -0
- package/dist/lib/arrange/constants.d.ts +68 -0
- package/dist/lib/arrange/constants.d.ts.map +1 -0
- package/dist/lib/arrange/constants.js +179 -0
- package/dist/lib/arrange/errors.d.ts +14 -0
- package/dist/lib/arrange/errors.d.ts.map +1 -0
- package/dist/lib/arrange/errors.js +16 -0
- package/dist/lib/arrange/formatters.d.ts +16 -0
- package/dist/lib/arrange/formatters.d.ts.map +1 -0
- package/dist/lib/arrange/formatters.js +57 -0
- package/dist/lib/arrange/group-file.d.ts +4 -0
- package/dist/lib/arrange/group-file.d.ts.map +1 -0
- package/dist/lib/arrange/group-file.js +115 -0
- package/dist/lib/arrange/grouping.d.ts +41 -0
- package/dist/lib/arrange/grouping.d.ts.map +1 -0
- package/dist/lib/arrange/grouping.js +280 -0
- package/dist/lib/arrange/imports.d.ts +6 -0
- package/dist/lib/arrange/imports.d.ts.map +1 -0
- package/dist/lib/arrange/imports.js +74 -0
- package/dist/lib/arrange/report.d.ts +4 -0
- package/dist/lib/arrange/report.d.ts.map +1 -0
- package/dist/lib/arrange/report.js +37 -0
- package/dist/lib/arrange/run-target.d.ts +4 -0
- package/dist/lib/arrange/run-target.d.ts.map +1 -0
- package/dist/lib/arrange/run-target.js +28 -0
- package/dist/lib/arrange/tokenizer.d.ts +37 -0
- package/dist/lib/arrange/tokenizer.d.ts.map +1 -0
- package/dist/lib/arrange/tokenizer.js +390 -0
- package/dist/lib/arrange/types.d.ts +104 -0
- package/dist/lib/arrange/types.d.ts.map +1 -0
- package/dist/lib/arrange/types.js +7 -0
- package/dist/lib/arrange/walk.d.ts +4 -0
- package/dist/lib/arrange/walk.d.ts.map +1 -0
- package/dist/lib/arrange/walk.js +34 -0
- package/dist/lib/arrange.d.ts +41 -0
- package/dist/lib/arrange.d.ts.map +1 -0
- package/dist/lib/arrange.js +38 -0
- package/dist/lib/infra/fs-contract.d.ts +28 -0
- package/dist/lib/infra/fs-contract.d.ts.map +1 -0
- package/dist/lib/infra/fs-contract.js +1 -0
- package/dist/lib/infra/node-io.d.ts +4 -0
- package/dist/lib/infra/node-io.d.ts.map +1 -0
- package/dist/lib/infra/node-io.js +27 -0
- package/dist/lib/mirror/config.d.ts +7 -0
- package/dist/lib/mirror/config.d.ts.map +1 -0
- package/dist/lib/mirror/config.js +146 -0
- package/dist/lib/mirror/constants.d.ts +10 -0
- package/dist/lib/mirror/constants.d.ts.map +1 -0
- package/dist/lib/mirror/constants.js +13 -0
- package/dist/lib/mirror/engine.d.ts +12 -0
- package/dist/lib/mirror/engine.d.ts.map +1 -0
- package/dist/lib/mirror/engine.js +248 -0
- package/dist/lib/mirror/errors.d.ts +10 -0
- package/dist/lib/mirror/errors.d.ts.map +1 -0
- package/dist/lib/mirror/errors.js +12 -0
- package/dist/lib/mirror/package-filter.d.ts +8 -0
- package/dist/lib/mirror/package-filter.d.ts.map +1 -0
- package/dist/lib/mirror/package-filter.js +33 -0
- package/dist/lib/mirror/reporter.d.ts +24 -0
- package/dist/lib/mirror/reporter.d.ts.map +1 -0
- package/dist/lib/mirror/reporter.js +126 -0
- package/dist/lib/mirror/sync.d.ts +4 -0
- package/dist/lib/mirror/sync.d.ts.map +1 -0
- package/dist/lib/mirror/sync.js +135 -0
- package/dist/lib/mirror/types.d.ts +81 -0
- package/dist/lib/mirror/types.d.ts.map +1 -0
- package/dist/lib/mirror/types.js +1 -0
- package/dist/lib/mirror/update-pkg.d.ts +13 -0
- package/dist/lib/mirror/update-pkg.d.ts.map +1 -0
- package/dist/lib/mirror/update-pkg.js +39 -0
- package/dist/lib/mirror/workspace-packages.d.ts +26 -0
- package/dist/lib/mirror/workspace-packages.d.ts.map +1 -0
- package/dist/lib/mirror/workspace-packages.js +160 -0
- package/dist/lib/mirror.d.ts +7 -0
- package/dist/lib/mirror.d.ts.map +1 -0
- package/dist/lib/mirror.js +5 -0
- package/dist/lib/repo-root.d.ts +7 -0
- package/dist/lib/repo-root.d.ts.map +1 -0
- package/dist/lib/repo-root.js +23 -0
- package/dist/lib/shared/utils.d.ts +9 -0
- package/dist/lib/shared/utils.d.ts.map +1 -0
- package/dist/lib/shared/utils.js +12 -0
- package/dist/program.d.ts +4 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +38 -0
- package/package.json +42 -53
- package/CHANGELOG.md +0 -15
- package/dist/cjs/index.cjs +0 -2
- package/dist/cjs/src/index.d.ts +0 -3
- package/dist/cjs/src/index.d.ts.map +0 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/src/index.d.ts +0 -3
- package/dist/esm/src/index.d.ts.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `staticLiteralTexts` carries the **same partition** of Tailwind tokens as
|
|
3
|
+
* `suggestedGroups` from {@link suggestCnGroups}, ignoring order of arguments and
|
|
4
|
+
* order of tokens within each chunk.
|
|
5
|
+
*/
|
|
6
|
+
export declare function areCnTailwindPartitionsEquivalent(staticLiteralTexts: string[], suggestedGroups: string[]): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Merge singleton groups (< MIN_GROUP_TOKENS tokens) into their nearest
|
|
9
|
+
* neighbour. Prefers merging toward a bucket-compatible neighbor.
|
|
10
|
+
*
|
|
11
|
+
* Loop termination: each iteration either splices one element (shrinking the
|
|
12
|
+
* array, guaranteeing progress) or finds no singleton that can merge and exits
|
|
13
|
+
* via `changed = false`. Two mutually-incompatible singletons both hit
|
|
14
|
+
* `continue` and `changed` stays false, so the loop exits without spinning.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mergeSingletons(groups: string[]): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Merge adjacent groups until total count ≤ maxGroups.
|
|
19
|
+
* Only merges pairs allowed by {@link bucketsMergeCompatible} — never glues incompatible
|
|
20
|
+
* buckets (which previously defaulted to penalty 0 and merged layout+state when over cap).
|
|
21
|
+
*
|
|
22
|
+
* Complexity: O(n²) in the number of groups — each merge pass scans all remaining adjacent
|
|
23
|
+
* pairs. In practice `n` is bounded by {@link MAX_GROUPS_CAP} (24) and typical class strings
|
|
24
|
+
* are well below that, so this is not a concern for the current usage pattern. If this ever
|
|
25
|
+
* runs in a high-throughput batch mode, consider a priority-queue approach.
|
|
26
|
+
*/
|
|
27
|
+
export declare function capGroups(groups: string[], maxGroups: number): string[];
|
|
28
|
+
/**
|
|
29
|
+
* If a chunk is **only** bare `ease-*` timing (Motion), buffer it and prepend to the **first**
|
|
30
|
+
* **later** chunk that is predominantly `state` **and** contains some `animate-*` utility —
|
|
31
|
+
* skipping intermediate `state` chunks (`sm:…`, `group-data-[…]:…`, etc.) that sit between
|
|
32
|
+
* `ease-ui` and `data-open:animate-*`. If no such chunk exists, the ease chunk is left as-is.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mergeEaseTimingIntoFollowingAnimatedState(groups: string[]): string[];
|
|
35
|
+
export declare function suggestCnGroups(classString: string): string[];
|
|
36
|
+
/**
|
|
37
|
+
* One label per suggested group: a single bucket name, or `mixed:a+b` when that chunk
|
|
38
|
+
* spans multiple Tailwind buckets (matches `codefast arrange group` stdout).
|
|
39
|
+
*/
|
|
40
|
+
export declare function summarizeGroupBucketLabels(groups: string[]): string[];
|
|
41
|
+
//# sourceMappingURL=grouping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/grouping.ts"],"names":[],"mappings":"AAsBA;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,kBAAkB,EAAE,MAAM,EAAE,EAC5B,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAiBT;AA+BD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAyC1D;AASD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAmCvE;AAED;;;;;GAKG;AACH,wBAAgB,yCAAyC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAwBpF;AAQD,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CA8E7D;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKrE"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { BUCKET_ORDER, MAX_GROUPS_BASE, MAX_GROUPS_CAP, MAX_GROUPS_HEADROOM, MIN_GROUP_TOKENS, } from "#lib/arrange/constants";
|
|
2
|
+
import { bucketsCompatible, bucketsMergeCompatible, classifyToken, compositeSecondaryOrder, stateKey, stripVariants, tokenizeClassString, } from "#lib/arrange/tokenizer";
|
|
3
|
+
function isVariantKeyedBucket(bucket) {
|
|
4
|
+
return bucket === "state" || bucket === "starting";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* True when `staticLiteralTexts` carries the **same partition** of Tailwind tokens as
|
|
8
|
+
* `suggestedGroups` from {@link suggestCnGroups}, ignoring order of arguments and
|
|
9
|
+
* order of tokens within each chunk.
|
|
10
|
+
*/
|
|
11
|
+
export function areCnTailwindPartitionsEquivalent(staticLiteralTexts, suggestedGroups) {
|
|
12
|
+
const partitionSignatures = (chunks) => chunks
|
|
13
|
+
.map((chunk) => {
|
|
14
|
+
const toks = tokenizeClassString(chunk);
|
|
15
|
+
return toks.length === 0 ? "" : [...toks].sort().join(" ");
|
|
16
|
+
})
|
|
17
|
+
.filter((s) => s.length > 0)
|
|
18
|
+
.sort((a, b) => a.localeCompare(b));
|
|
19
|
+
const staticPartitionSigs = partitionSignatures(staticLiteralTexts);
|
|
20
|
+
const suggestedPartitionSigs = partitionSignatures(suggestedGroups);
|
|
21
|
+
if (staticPartitionSigs.length !== suggestedPartitionSigs.length)
|
|
22
|
+
return false;
|
|
23
|
+
for (let i = 0; i < staticPartitionSigs.length; i++) {
|
|
24
|
+
if (staticPartitionSigs[i] !== suggestedPartitionSigs[i])
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
/** Dominant bucket of a whitespace-delimited class group (for merge heuristics). */
|
|
30
|
+
function dominantBucketOfGroup(groupStr) {
|
|
31
|
+
const counts = new Map();
|
|
32
|
+
for (const tok of tokenizeClassString(groupStr)) {
|
|
33
|
+
const tokenBucket = classifyToken(tok);
|
|
34
|
+
counts.set(tokenBucket, (counts.get(tokenBucket) ?? 0) + 1);
|
|
35
|
+
}
|
|
36
|
+
let best = "other";
|
|
37
|
+
let bestN = 0;
|
|
38
|
+
for (const [bucket, count] of counts) {
|
|
39
|
+
if (count > bestN) {
|
|
40
|
+
best = bucket;
|
|
41
|
+
bestN = count;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Deterministic tie-break: prefer earlier render-pipeline bucket.
|
|
45
|
+
if (count === bestN && BUCKET_ORDER[bucket] < BUCKET_ORDER[best]) {
|
|
46
|
+
best = bucket;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return best;
|
|
50
|
+
}
|
|
51
|
+
/** Dynamic cap: more tokens → allow more groups, within [BASE, CAP]. */
|
|
52
|
+
function dynamicMaxGroups(tokenCount) {
|
|
53
|
+
const byTokens = Math.ceil(tokenCount / 2) + MAX_GROUPS_HEADROOM;
|
|
54
|
+
return Math.max(MAX_GROUPS_BASE, Math.min(MAX_GROUPS_CAP, byTokens));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Merge singleton groups (< MIN_GROUP_TOKENS tokens) into their nearest
|
|
58
|
+
* neighbour. Prefers merging toward a bucket-compatible neighbor.
|
|
59
|
+
*
|
|
60
|
+
* Loop termination: each iteration either splices one element (shrinking the
|
|
61
|
+
* array, guaranteeing progress) or finds no singleton that can merge and exits
|
|
62
|
+
* via `changed = false`. Two mutually-incompatible singletons both hit
|
|
63
|
+
* `continue` and `changed` stays false, so the loop exits without spinning.
|
|
64
|
+
*/
|
|
65
|
+
export function mergeSingletons(groups) {
|
|
66
|
+
if (groups.length <= 1)
|
|
67
|
+
return groups;
|
|
68
|
+
const result = [...groups];
|
|
69
|
+
let changed = true;
|
|
70
|
+
while (changed) {
|
|
71
|
+
changed = false;
|
|
72
|
+
for (let i = 0; i < result.length; i++) {
|
|
73
|
+
if (tokenizeClassString(result[i]).length < MIN_GROUP_TOKENS) {
|
|
74
|
+
if (result.length === 1)
|
|
75
|
+
break;
|
|
76
|
+
const myBucket = dominantBucketOfGroup(result[i]);
|
|
77
|
+
const prevCompat = i > 0 && bucketsMergeCompatible(myBucket, dominantBucketOfGroup(result[i - 1]));
|
|
78
|
+
const nextCompat = i < result.length - 1 &&
|
|
79
|
+
bucketsMergeCompatible(myBucket, dominantBucketOfGroup(result[i + 1]));
|
|
80
|
+
let mergeDir;
|
|
81
|
+
if (nextCompat && !prevCompat) {
|
|
82
|
+
mergeDir = "forward";
|
|
83
|
+
}
|
|
84
|
+
else if (prevCompat && !nextCompat) {
|
|
85
|
+
mergeDir = "backward";
|
|
86
|
+
}
|
|
87
|
+
else if (prevCompat && nextCompat) {
|
|
88
|
+
mergeDir = i < result.length - 1 ? "forward" : "backward";
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (mergeDir === "forward") {
|
|
94
|
+
result[i + 1] = result[i] + " " + result[i + 1];
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
result[i - 1] = result[i - 1] + " " + result[i];
|
|
98
|
+
}
|
|
99
|
+
result.splice(i, 1);
|
|
100
|
+
changed = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/** Tie-break when two merge candidates are both {@link bucketsMergeCompatible} (same bucket or COMPATIBLE_BUCKET_SETS). */
|
|
108
|
+
function capMergePenalty(leftBucket, rightBucket) {
|
|
109
|
+
if (leftBucket === rightBucket)
|
|
110
|
+
return 0;
|
|
111
|
+
if (bucketsCompatible(leftBucket, rightBucket))
|
|
112
|
+
return 0;
|
|
113
|
+
return 500;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Merge adjacent groups until total count ≤ maxGroups.
|
|
117
|
+
* Only merges pairs allowed by {@link bucketsMergeCompatible} — never glues incompatible
|
|
118
|
+
* buckets (which previously defaulted to penalty 0 and merged layout+state when over cap).
|
|
119
|
+
*
|
|
120
|
+
* Complexity: O(n²) in the number of groups — each merge pass scans all remaining adjacent
|
|
121
|
+
* pairs. In practice `n` is bounded by {@link MAX_GROUPS_CAP} (24) and typical class strings
|
|
122
|
+
* are well below that, so this is not a concern for the current usage pattern. If this ever
|
|
123
|
+
* runs in a high-throughput batch mode, consider a priority-queue approach.
|
|
124
|
+
*/
|
|
125
|
+
export function capGroups(groups, maxGroups) {
|
|
126
|
+
const result = [...groups];
|
|
127
|
+
const lengths = result.map((groupStr) => tokenizeClassString(groupStr).length);
|
|
128
|
+
while (result.length > maxGroups) {
|
|
129
|
+
const cands = [];
|
|
130
|
+
for (let i = 0; i < result.length - 1; i++) {
|
|
131
|
+
const dominantLeft = dominantBucketOfGroup(result[i]);
|
|
132
|
+
const dominantRight = dominantBucketOfGroup(result[i + 1]);
|
|
133
|
+
if (!bucketsMergeCompatible(dominantLeft, dominantRight))
|
|
134
|
+
continue;
|
|
135
|
+
cands.push({
|
|
136
|
+
i,
|
|
137
|
+
size: lengths[i] + lengths[i + 1],
|
|
138
|
+
penalty: capMergePenalty(dominantLeft, dominantRight),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (cands.length === 0)
|
|
142
|
+
break;
|
|
143
|
+
let best = cands[0];
|
|
144
|
+
for (const candidate of cands) {
|
|
145
|
+
if (candidate.penalty < best.penalty ||
|
|
146
|
+
(candidate.penalty === best.penalty && candidate.size < best.size)) {
|
|
147
|
+
best = candidate;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const bestIdx = best.i;
|
|
151
|
+
result[bestIdx] = result[bestIdx] + " " + result[bestIdx + 1];
|
|
152
|
+
lengths[bestIdx] = lengths[bestIdx] + lengths[bestIdx + 1];
|
|
153
|
+
result.splice(bestIdx + 1, 1);
|
|
154
|
+
lengths.splice(bestIdx + 1, 1);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* If a chunk is **only** bare `ease-*` timing (Motion), buffer it and prepend to the **first**
|
|
160
|
+
* **later** chunk that is predominantly `state` **and** contains some `animate-*` utility —
|
|
161
|
+
* skipping intermediate `state` chunks (`sm:…`, `group-data-[…]:…`, etc.) that sit between
|
|
162
|
+
* `ease-ui` and `data-open:animate-*`. If no such chunk exists, the ease chunk is left as-is.
|
|
163
|
+
*/
|
|
164
|
+
export function mergeEaseTimingIntoFollowingAnimatedState(groups) {
|
|
165
|
+
const result = [...groups];
|
|
166
|
+
let changed = true;
|
|
167
|
+
while (changed) {
|
|
168
|
+
changed = false;
|
|
169
|
+
for (let i = 0; i < result.length; i++) {
|
|
170
|
+
if (!chunkIsOnlyEaseTimingMotion(result[i]))
|
|
171
|
+
continue;
|
|
172
|
+
const easeStr = result[i];
|
|
173
|
+
let target = -1;
|
|
174
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
175
|
+
if (dominantBucketOfGroup(result[j]) !== "state")
|
|
176
|
+
continue;
|
|
177
|
+
const toks = tokenizeClassString(result[j]);
|
|
178
|
+
if (!toks.some((t) => /^animate/.test(stripVariants(t))))
|
|
179
|
+
continue;
|
|
180
|
+
target = j;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
if (target === -1)
|
|
184
|
+
continue;
|
|
185
|
+
result[target] = `${easeStr} ${result[target]}`.trim();
|
|
186
|
+
result.splice(i, 1);
|
|
187
|
+
changed = true;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
function chunkIsOnlyEaseTimingMotion(groupStr) {
|
|
194
|
+
const toks = tokenizeClassString(groupStr);
|
|
195
|
+
if (toks.length === 0)
|
|
196
|
+
return false;
|
|
197
|
+
return toks.every((t) => classifyToken(t) === "motion" && /^ease-/.test(stripVariants(t)));
|
|
198
|
+
}
|
|
199
|
+
export function suggestCnGroups(classString) {
|
|
200
|
+
const tokens = tokenizeClassString(classString);
|
|
201
|
+
if (tokens.length === 0)
|
|
202
|
+
return [];
|
|
203
|
+
const classified = tokens.map((tok, index) => ({ tok, bucket: classifyToken(tok), index }));
|
|
204
|
+
classified.sort((left, right) => {
|
|
205
|
+
const bucketOrderDiff = BUCKET_ORDER[left.bucket] - BUCKET_ORDER[right.bucket];
|
|
206
|
+
if (bucketOrderDiff !== 0)
|
|
207
|
+
return bucketOrderDiff;
|
|
208
|
+
if (left.bucket === "composite" && right.bucket === "composite") {
|
|
209
|
+
const c = compositeSecondaryOrder(stripVariants(left.tok)) -
|
|
210
|
+
compositeSecondaryOrder(stripVariants(right.tok));
|
|
211
|
+
if (c !== 0)
|
|
212
|
+
return c;
|
|
213
|
+
}
|
|
214
|
+
return left.index - right.index;
|
|
215
|
+
});
|
|
216
|
+
const rawGroups = [];
|
|
217
|
+
/** Bucket of the last token already placed in the current run (pairwise compat with `COMPATIBLE_BUCKET_SETS`). */
|
|
218
|
+
let lastBucketInRun = null;
|
|
219
|
+
let currentStateKey = null;
|
|
220
|
+
let currentTokens = [];
|
|
221
|
+
const flush = () => {
|
|
222
|
+
if (currentTokens.length > 0) {
|
|
223
|
+
rawGroups.push(currentTokens.join(" "));
|
|
224
|
+
currentTokens = [];
|
|
225
|
+
}
|
|
226
|
+
lastBucketInRun = null;
|
|
227
|
+
currentStateKey = null;
|
|
228
|
+
};
|
|
229
|
+
for (const { tok, bucket: tokenBucket } of classified) {
|
|
230
|
+
if (lastBucketInRun === null) {
|
|
231
|
+
lastBucketInRun = tokenBucket;
|
|
232
|
+
currentStateKey = isVariantKeyedBucket(tokenBucket) ? stateKey(tok) : null;
|
|
233
|
+
currentTokens.push(tok);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (isVariantKeyedBucket(tokenBucket)) {
|
|
237
|
+
const key = stateKey(tok);
|
|
238
|
+
if (currentStateKey !== null && key !== currentStateKey) {
|
|
239
|
+
flush();
|
|
240
|
+
lastBucketInRun = tokenBucket;
|
|
241
|
+
currentStateKey = key;
|
|
242
|
+
currentTokens.push(tok);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (!bucketsCompatible(tokenBucket, lastBucketInRun)) {
|
|
247
|
+
flush();
|
|
248
|
+
lastBucketInRun = tokenBucket;
|
|
249
|
+
currentStateKey = isVariantKeyedBucket(tokenBucket) ? stateKey(tok) : null;
|
|
250
|
+
currentTokens.push(tok);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (isVariantKeyedBucket(tokenBucket)) {
|
|
254
|
+
currentStateKey = stateKey(tok);
|
|
255
|
+
}
|
|
256
|
+
else if (currentStateKey !== null) {
|
|
257
|
+
// Defensive reset: keeps state-key logic explicit if bucket rules evolve.
|
|
258
|
+
currentStateKey = null;
|
|
259
|
+
}
|
|
260
|
+
lastBucketInRun = tokenBucket;
|
|
261
|
+
currentTokens.push(tok);
|
|
262
|
+
}
|
|
263
|
+
flush();
|
|
264
|
+
const merged = mergeSingletons(rawGroups);
|
|
265
|
+
const withEaseMerged = mergeEaseTimingIntoFollowingAnimatedState(merged);
|
|
266
|
+
// Never cap-merge below the semantic chunk count from the tokenizer (fixes layout+state glued
|
|
267
|
+
// when `preferred` compat list was empty and unrelated pairs used penalty 0).
|
|
268
|
+
const maxAllowed = Math.max(dynamicMaxGroups(tokens.length), withEaseMerged.length);
|
|
269
|
+
return capGroups(withEaseMerged, maxAllowed);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* One label per suggested group: a single bucket name, or `mixed:a+b` when that chunk
|
|
273
|
+
* spans multiple Tailwind buckets (matches `codefast arrange group` stdout).
|
|
274
|
+
*/
|
|
275
|
+
export function summarizeGroupBucketLabels(groups) {
|
|
276
|
+
return groups.map((g) => {
|
|
277
|
+
const uniq = new Set(tokenizeClassString(g).map(classifyToken));
|
|
278
|
+
return uniq.size === 1 ? [...uniq][0] : `mixed:${[...uniq].sort().join("+")}`;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the module specifier used when injecting a `cn` import into a file.
|
|
3
|
+
*/
|
|
4
|
+
export declare function cnModuleSpecifierForFile(filePath: string, override?: string): string;
|
|
5
|
+
export declare function ensureCnImport(sourceText: string, filePath: string, cnImportOverride?: string): string;
|
|
6
|
+
//# sourceMappingURL=imports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/imports.ts"],"names":[],"mappings":"AAgBA;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAIpF;AAgBD,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,CAAC,EAAE,MAAM,GACxB,MAAM,CA+CR"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
function sourceFileImportsCn(sf) {
|
|
3
|
+
for (const st of sf.statements) {
|
|
4
|
+
if (!ts.isImportDeclaration(st) || !st.importClause)
|
|
5
|
+
continue;
|
|
6
|
+
const clause = st.importClause;
|
|
7
|
+
if (clause.name?.text === "cn")
|
|
8
|
+
return true;
|
|
9
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
10
|
+
for (const el of clause.namedBindings.elements) {
|
|
11
|
+
if (el.name.text === "cn")
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the module specifier used when injecting a `cn` import into a file.
|
|
20
|
+
*/
|
|
21
|
+
export function cnModuleSpecifierForFile(filePath, override) {
|
|
22
|
+
void filePath;
|
|
23
|
+
if (override)
|
|
24
|
+
return override;
|
|
25
|
+
return "@codefast/tailwind-variants";
|
|
26
|
+
}
|
|
27
|
+
function findImportDeclarationFromModule(sf, moduleSpecifier) {
|
|
28
|
+
for (const st of sf.statements) {
|
|
29
|
+
if (!ts.isImportDeclaration(st))
|
|
30
|
+
continue;
|
|
31
|
+
const spec = st.moduleSpecifier;
|
|
32
|
+
if (ts.isStringLiteral(spec) && spec.text === moduleSpecifier) {
|
|
33
|
+
return st;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
export function ensureCnImport(sourceText, filePath, cnImportOverride) {
|
|
39
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
40
|
+
if (sourceFileImportsCn(sf))
|
|
41
|
+
return sourceText;
|
|
42
|
+
const moduleSpecifier = cnModuleSpecifierForFile(filePath, cnImportOverride);
|
|
43
|
+
const decl = findImportDeclarationFromModule(sf, moduleSpecifier);
|
|
44
|
+
if (decl?.importClause &&
|
|
45
|
+
!decl.importClause.isTypeOnly &&
|
|
46
|
+
decl.importClause.namedBindings &&
|
|
47
|
+
ts.isNamedImports(decl.importClause.namedBindings)) {
|
|
48
|
+
const elements = decl.importClause.namedBindings.elements;
|
|
49
|
+
if (elements.length > 0) {
|
|
50
|
+
const pos = elements[0].getStart(sf);
|
|
51
|
+
return `${sourceText.slice(0, pos)}cn, ${sourceText.slice(pos)}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const importLine = `import { cn } from "${moduleSpecifier}";`;
|
|
55
|
+
let firstImport = -1;
|
|
56
|
+
for (const st of sf.statements) {
|
|
57
|
+
if (ts.isImportDeclaration(st)) {
|
|
58
|
+
firstImport = st.getStart(sf);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const useClient = /^["']use client["'];?\s*\r?\n/.exec(sourceText);
|
|
63
|
+
let insertAt;
|
|
64
|
+
if (useClient) {
|
|
65
|
+
insertAt = useClient[0].length;
|
|
66
|
+
}
|
|
67
|
+
else if (firstImport !== -1) {
|
|
68
|
+
insertAt = firstImport;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
insertAt = 0;
|
|
72
|
+
}
|
|
73
|
+
return `${sourceText.slice(0, insertAt)}${importLine}\n${sourceText.slice(insertAt)}`;
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/report.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CA0CzF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { LONG_STRING_TOKEN_THRESHOLD, MAX_REPORT_LINES } from "#lib/arrange/constants";
|
|
3
|
+
export function printAnalyzeReport(dir, r, logger) {
|
|
4
|
+
const { out } = logger;
|
|
5
|
+
out(`Path: ${path.resolve(dir)}`);
|
|
6
|
+
out(`.ts/.tsx files: ${r.files}`);
|
|
7
|
+
out(`cn(...) call sites: ${r.cnCallExpressions}`);
|
|
8
|
+
out(`tv(...) call sites: ${r.tvCallExpressions}`);
|
|
9
|
+
out(`\nLong cn(...) string literals (≥${LONG_STRING_TOKEN_THRESHOLD} tokens — consider splitting arguments): ${r.longCnStringLiterals.length}`);
|
|
10
|
+
for (const x of r.longCnStringLiterals.slice(0, MAX_REPORT_LINES)) {
|
|
11
|
+
out(` ${x.file}:${x.line} (${x.tokenCount} tokens) ${x.preview}`);
|
|
12
|
+
}
|
|
13
|
+
if (r.longCnStringLiterals.length > MAX_REPORT_LINES) {
|
|
14
|
+
out(` … and ${r.longCnStringLiterals.length - MAX_REPORT_LINES} more`);
|
|
15
|
+
}
|
|
16
|
+
out(`\nLong tv({...}) string literals in base/variants/… (≥${LONG_STRING_TOKEN_THRESHOLD} tokens): ${r.longTvStringLiterals.length}`);
|
|
17
|
+
for (const x of r.longTvStringLiterals.slice(0, MAX_REPORT_LINES)) {
|
|
18
|
+
out(` ${x.file}:${x.line} (${x.tokenCount} tokens) ${x.preview}`);
|
|
19
|
+
}
|
|
20
|
+
if (r.longTvStringLiterals.length > MAX_REPORT_LINES) {
|
|
21
|
+
out(` … and ${r.longTvStringLiterals.length - MAX_REPORT_LINES} more`);
|
|
22
|
+
}
|
|
23
|
+
out(`\nJSX className="..." or className={'...'} (static strings, ≥${LONG_STRING_TOKEN_THRESHOLD} tokens): ${r.longJsxClassNameLiterals.length}`);
|
|
24
|
+
for (const x of r.longJsxClassNameLiterals.slice(0, MAX_REPORT_LINES)) {
|
|
25
|
+
out(` ${x.file}:${x.line} (${x.tokenCount} tokens) ${x.preview}`);
|
|
26
|
+
}
|
|
27
|
+
if (r.longJsxClassNameLiterals.length > MAX_REPORT_LINES) {
|
|
28
|
+
out(` … and ${r.longJsxClassNameLiterals.length - MAX_REPORT_LINES} more`);
|
|
29
|
+
}
|
|
30
|
+
out(`\ncn(...) nested inside tv({...}) (prefer a string or array — preview/apply can rewrite): ${r.cnInsideTvCalls.length}`);
|
|
31
|
+
for (const x of r.cnInsideTvCalls.slice(0, MAX_REPORT_LINES)) {
|
|
32
|
+
out(` ${x.file}:${x.line} (${x.argCount} args) ${x.preview}`);
|
|
33
|
+
}
|
|
34
|
+
if (r.cnInsideTvCalls.length > MAX_REPORT_LINES) {
|
|
35
|
+
out(` … and ${r.cnInsideTvCalls.length - MAX_REPORT_LINES} more`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CliFs, CliLogger } from "#lib/infra/fs-contract";
|
|
2
|
+
import type { ArrangeRunOnTargetOptions } from "#lib/arrange/types";
|
|
3
|
+
export declare function runOnTarget(target: string, options: ArrangeRunOnTargetOptions, fs: CliFs, logger: CliLogger): void;
|
|
4
|
+
//# sourceMappingURL=run-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-target.d.ts","sourceRoot":"","sources":["../../../src/lib/arrange/run-target.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAIpE,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,yBAAyB,EAClC,EAAE,EAAE,KAAK,EACT,MAAM,EAAE,SAAS,GAChB,IAAI,CAkCN"}
|