@codefast/cli 0.3.14 → 0.3.16-canary.0
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 +8 -3
- package/dist/arrange/analyze.mjs +24 -0
- package/dist/{lib/arrange/presentation/arrange-cli.schema.mjs → arrange/cli-schema.mjs} +10 -1
- package/dist/arrange/command.mjs +106 -0
- package/dist/{lib/arrange/domain/arrange-analyze.service.mjs → arrange/domain/analyze-service.mjs} +17 -10
- package/dist/{lib/arrange/domain/ast/ast-node.model.mjs → arrange/domain/ast/ast-node.mjs} +57 -3
- package/dist/{lib/arrange/domain/ast/collectors-cn.collector.mjs → arrange/domain/ast/collectors-cn.mjs} +21 -9
- package/dist/{lib/arrange/domain/ast/collectors-jsx.collector.mjs → arrange/domain/ast/collectors-jsx.mjs} +5 -2
- package/dist/{lib/arrange/domain/ast/collectors-tv.collector.mjs → arrange/domain/ast/collectors-tv.mjs} +21 -6
- package/dist/{lib/arrange/domain/ast/ast-helpers.helper.mjs → arrange/domain/ast/helpers.mjs} +26 -9
- package/dist/{lib/arrange/domain/ast/targets.model.mjs → arrange/domain/ast/targets.mjs} +19 -10
- package/dist/{lib/arrange/domain/constants.domain.mjs → arrange/domain/constants.mjs} +63 -18
- package/dist/{lib/arrange/domain/arrange-grouping.service.mjs → arrange/domain/grouping-service.mjs} +31 -10
- package/dist/{lib/arrange/domain/grouping.domain.mjs → arrange/domain/grouping.mjs} +39 -18
- package/dist/{lib/arrange/domain/imports.domain.mjs → arrange/domain/imports.mjs} +7 -3
- package/dist/{lib/arrange/domain/source-text-formatters.formatter.mjs → arrange/domain/source-text-formatters.mjs} +22 -5
- package/dist/{lib/arrange/domain/tailwind-token.value-object.mjs → arrange/domain/tailwind-token.mjs} +12 -3
- package/dist/{lib/arrange/domain/tailwind-token-classifier.domain-service.mjs → arrange/domain/token-classifier.mjs} +40 -15
- package/dist/arrange/output.mjs +79 -0
- package/dist/arrange/process-file.mjs +42 -0
- package/dist/arrange/resolve-target.mjs +24 -0
- package/dist/arrange/scan-target.mjs +20 -0
- package/dist/arrange/source-parse.mjs +18 -0
- package/dist/arrange/suggest.mjs +15 -0
- package/dist/arrange/sync.mjs +51 -0
- package/dist/arrange/typescript-ast-translator.mjs +354 -0
- package/dist/arrange/workspace.mjs +31 -0
- package/dist/bin.mjs +1 -1
- package/dist/{program.mjs → cli.mjs} +14 -19
- package/dist/core/cli/format-error.mjs +15 -0
- package/dist/core/cli/global-options.mjs +10 -0
- package/dist/core/cli/positional.mjs +11 -0
- package/dist/core/cli/result-handle.mjs +51 -0
- package/dist/core/config/loader.mjs +79 -0
- package/dist/{lib/config/infrastructure/config-schema.adapter.mjs → core/config/schema.mjs} +20 -6
- package/dist/core/config/warnings.mjs +12 -0
- package/dist/core/config.mjs +19 -0
- package/dist/{lib/core/domain/caught-unknown-message.value-object.mjs → core/errors.mjs} +20 -4
- package/dist/core/exit-codes.mjs +17 -0
- package/dist/core/filesystem/node.mjs +32 -0
- package/dist/core/logger.mjs +15 -0
- package/dist/{lib/core/domain/result.model.mjs → core/result.mjs} +7 -3
- package/dist/{lib/core/application/services/schema-validator.service.mjs → core/schema-parse.mjs} +6 -5
- package/dist/{lib/shared/source-code/domain/text-edit.model.mjs → core/source-text-edit.mjs} +15 -14
- package/dist/core/verbose-diagnostics.mjs +11 -0
- package/dist/core/workspace/resolver.mjs +177 -0
- package/dist/core/workspace/typescript-walk.mjs +36 -0
- package/dist/mirror/cli-result.mjs +21 -0
- package/dist/{lib/mirror/presentation/mirror-cli.schema.mjs → mirror/cli-schema.mjs} +4 -4
- package/dist/mirror/command.mjs +57 -0
- package/dist/mirror/dist-filesystem-impl.mjs +42 -0
- package/dist/{lib/mirror/domain/constants.domain.mjs → mirror/domain/constants.mjs} +16 -1
- package/dist/{lib/mirror/infrastructure/dirent-list.guard.mjs → mirror/domain/dirent-guard.mjs} +5 -2
- package/dist/{lib/mirror/domain/errors.domain.mjs → mirror/domain/errors.mjs} +7 -1
- package/dist/{lib/mirror/application/services/generate-mirror-exports.service.mjs → mirror/domain/exports.mjs} +17 -10
- package/dist/{lib/mirror/domain/package-display-name.policy.mjs → mirror/domain/package-display-name.mjs} +5 -2
- package/dist/{lib/mirror/domain/path-normalizer.value-object.mjs → mirror/domain/path-normalizer.mjs} +4 -1
- package/dist/mirror/output.mjs +45 -0
- package/dist/mirror/package-path.mjs +32 -0
- package/dist/mirror/prepare.mjs +33 -0
- package/dist/mirror/sync-reporter.mjs +104 -0
- package/dist/mirror/sync-workspace-package.mjs +103 -0
- package/dist/mirror/sync.mjs +87 -0
- package/dist/{lib/mirror/infrastructure/package-json-exports.repository.mjs → mirror/write-exports.mjs} +35 -31
- package/dist/tag/cli-result.mjs +11 -0
- package/dist/{lib/tag/presentation/tag-cli.schema.mjs → tag/cli-schema.mjs} +4 -1
- package/dist/tag/command.mjs +56 -0
- package/dist/{lib/tag/presentation/tag-sync.presenter.mjs → tag/output.mjs} +54 -28
- package/dist/tag/prepare.mjs +29 -0
- package/dist/tag/resolve-target-path.mjs +12 -0
- package/dist/tag/since-writer.mjs +130 -0
- package/dist/tag/sync.mjs +141 -0
- package/dist/tag/target-candidates.mjs +68 -0
- package/dist/tag/target-runner.mjs +26 -0
- package/dist/tag/version-resolver.mjs +25 -0
- package/package.json +8 -9
- package/dist/lib/arrange/adapters/primary/cli/arrange.command.mjs +0 -324
- package/dist/lib/arrange/adapters/primary/cli/arrange.presentation.module.mjs +0 -14
- package/dist/lib/arrange/adapters/primary/cli/presentation.tokens.mjs +0 -5
- package/dist/lib/arrange/adapters/secondary/domain-source-parser.adapter.mjs +0 -239
- package/dist/lib/arrange/adapters/secondary/file-walker.adapter.mjs +0 -241
- package/dist/lib/arrange/adapters/secondary/workspace-resolver.adapter.mjs +0 -233
- package/dist/lib/arrange/application/requests/suggest-groups.request.mjs +0 -1
- package/dist/lib/arrange/application/services/arrange-file-processor.service.mjs +0 -267
- package/dist/lib/arrange/application/services/arrange-target-path-resolver.service.mjs +0 -21
- package/dist/lib/arrange/application/services/arrange-target-scanner.service.mjs +0 -243
- package/dist/lib/arrange/application/use-cases/analyze-directory.use-case.mjs +0 -255
- package/dist/lib/arrange/application/use-cases/prepare-arrange-workspace.use-case.mjs +0 -263
- package/dist/lib/arrange/application/use-cases/run-arrange-sync.use-case.mjs +0 -274
- package/dist/lib/arrange/application/use-cases/suggest-cn-groups.use-case.mjs +0 -237
- package/dist/lib/arrange/arrange.module.mjs +0 -31
- package/dist/lib/arrange/contracts/analyze-report-presenter.contract.mjs +0 -1
- package/dist/lib/arrange/contracts/models.mjs +0 -1
- package/dist/lib/arrange/contracts/services.contract.mjs +0 -1
- package/dist/lib/arrange/contracts/tokens.mjs +0 -15
- package/dist/lib/arrange/domain/tailwind-grouping.service.mjs +0 -231
- package/dist/lib/arrange/domain/types.domain.mjs +0 -1
- package/dist/lib/arrange/infrastructure/typescript-ast-translator.adapter.mjs +0 -355
- package/dist/lib/arrange/presentation/arrange-analyze.presenter.mjs +0 -259
- package/dist/lib/arrange/presentation/arrange-sync.presenter.mjs +0 -33
- package/dist/lib/arrange/presentation/group-file-preview.presenter.mjs +0 -271
- package/dist/lib/bootstrap/composition-root.mjs +0 -33
- package/dist/lib/config/application/ports/config-loader.port.mjs +0 -1
- package/dist/lib/config/application/ports/config-warning-reporter.port.mjs +0 -1
- package/dist/lib/config/application/services/load-config.service.mjs +0 -17
- package/dist/lib/config/contracts/tokens.mjs +0 -6
- package/dist/lib/config/domain/schema.domain.mjs +0 -1
- package/dist/lib/config/infrastructure/config-loader.adapter.mjs +0 -306
- package/dist/lib/config/infrastructure/config-warning-reporter.adapter.mjs +0 -234
- package/dist/lib/core/application/load-codefast-config.use-case.mjs +0 -245
- package/dist/lib/core/application/ports/cli-io.port.mjs +0 -1
- package/dist/lib/core/application/ports/path.port.mjs +0 -1
- package/dist/lib/core/application/ports/repo-root-resolver.port.mjs +0 -1
- package/dist/lib/core/application/ports/runtime.port.mjs +0 -1
- package/dist/lib/core/application/services/global-cli-options-parser.service.mjs +0 -9
- package/dist/lib/core/contracts/tokens.mjs +0 -10
- package/dist/lib/core/core.module.mjs +0 -9
- package/dist/lib/core/domain/cli-exit-codes.domain.mjs +0 -11
- package/dist/lib/core/domain/errors.domain.mjs +0 -17
- package/dist/lib/core/infrastructure/infrastructure.module.mjs +0 -30
- package/dist/lib/core/infrastructure/path.adapter.mjs +0 -246
- package/dist/lib/core/infrastructure/port-telemetry.decorator.mjs +0 -62
- package/dist/lib/core/infrastructure/repo-root-resolver.adapter.mjs +0 -233
- package/dist/lib/core/infrastructure/verbose-diagnostics.policy.mjs +0 -9
- package/dist/lib/core/presentation/cli-executor.presenter.mjs +0 -42
- package/dist/lib/core/presentation/cli-schema.parser.mjs +0 -7
- package/dist/lib/core/presentation/format-app-error.presenter.mjs +0 -17
- package/dist/lib/core/presentation/presentation.module.mjs +0 -5
- package/dist/lib/infrastructure/node-io.adapter.mjs +0 -285
- package/dist/lib/infrastructure/workspace/repo-root-resolver.service.mjs +0 -21
- package/dist/lib/kernel/contracts/cli-command.contract.mjs +0 -1
- package/dist/lib/kernel/contracts/tokens.mjs +0 -5
- package/dist/lib/mirror/adapters/primary/cli/mirror.command.mjs +0 -272
- package/dist/lib/mirror/adapters/secondary/file-system-service.adapter.mjs +0 -261
- package/dist/lib/mirror/adapters/secondary/mirror-sync-reporter.adapter.mjs +0 -243
- package/dist/lib/mirror/adapters/secondary/package-repository.adapter.mjs +0 -241
- package/dist/lib/mirror/adapters/secondary/sync-workspace-package.adapter.mjs +0 -351
- package/dist/lib/mirror/adapters/secondary/workspace-service.adapter.mjs +0 -237
- package/dist/lib/mirror/application/mirror-sync-json.format.mjs +0 -12
- package/dist/lib/mirror/application/ports/file-system-service.port.mjs +0 -1
- package/dist/lib/mirror/application/ports/mirror-sync-reporter.port.mjs +0 -1
- package/dist/lib/mirror/application/ports/package-repository.port.mjs +0 -1
- package/dist/lib/mirror/application/ports/sync-workspace-package.port.mjs +0 -1
- package/dist/lib/mirror/application/ports/workspace-service.port.mjs +0 -1
- package/dist/lib/mirror/application/requests/mirror-sync.request.mjs +0 -1
- package/dist/lib/mirror/application/services/mirror-package-arg-resolver.service.mjs +0 -20
- package/dist/lib/mirror/application/use-cases/prepare-mirror-sync.use-case.mjs +0 -264
- package/dist/lib/mirror/application/use-cases/run-mirror-sync.use-case.mjs +0 -302
- package/dist/lib/mirror/contracts/models.mjs +0 -1
- package/dist/lib/mirror/contracts/tokens.mjs +0 -11
- package/dist/lib/mirror/domain/types.domain.mjs +0 -1
- package/dist/lib/mirror/infrastructure/mirror-console-reporter.presenter.mjs +0 -99
- package/dist/lib/mirror/infrastructure/package-filter-resolver.service.mjs +0 -25
- package/dist/lib/mirror/infrastructure/workspace-packages.service.mjs +0 -158
- package/dist/lib/mirror/mirror.module.mjs +0 -25
- package/dist/lib/shared/source-code/infrastructure/typescript-source-file-walker.service.mjs +0 -33
- package/dist/lib/tag/adapters/primary/cli/presentation.tokens.mjs +0 -6
- package/dist/lib/tag/adapters/primary/cli/tag.command.mjs +0 -285
- package/dist/lib/tag/adapters/primary/cli/tag.presentation.module.mjs +0 -13
- package/dist/lib/tag/adapters/secondary/tag-since-writer.adapter.mjs +0 -348
- package/dist/lib/tag/adapters/secondary/tag-target-resolver.adapter.mjs +0 -233
- package/dist/lib/tag/adapters/secondary/tag-version-resolver.adapter.mjs +0 -249
- package/dist/lib/tag/adapters/secondary/typescript-tree-walk.adapter.mjs +0 -233
- package/dist/lib/tag/application/ports/tag-since-writer.port.mjs +0 -1
- package/dist/lib/tag/application/ports/tag-version-resolver.port.mjs +0 -1
- package/dist/lib/tag/application/ports/target-resolver.port.mjs +0 -1
- package/dist/lib/tag/application/ports/typescript-tree-walk.port.mjs +0 -1
- package/dist/lib/tag/application/requests/tag-sync.request.mjs +0 -1
- package/dist/lib/tag/application/services/tag-cli-target-path-resolver.service.mjs +0 -9
- package/dist/lib/tag/application/services/tag-target-runner.service.mjs +0 -21
- package/dist/lib/tag/application/tag-sync-cli-result.mjs +0 -11
- package/dist/lib/tag/application/tag-sync-json.format.mjs +0 -14
- package/dist/lib/tag/application/use-cases/prepare-tag-sync.use-case.mjs +0 -264
- package/dist/lib/tag/application/use-cases/run-tag-sync.use-case.mjs +0 -381
- package/dist/lib/tag/contracts/models.mjs +0 -1
- package/dist/lib/tag/contracts/tag-sync-result-presenter.contract.mjs +0 -1
- package/dist/lib/tag/contracts/tokens.mjs +0 -10
- package/dist/lib/tag/domain/types.domain.mjs +0 -1
- package/dist/lib/tag/infrastructure/tag-target-candidates.adapter.mjs +0 -170
- package/dist/lib/tag/presentation/colors.presenter.mjs +0 -12
- package/dist/lib/tag/presentation/present-tag-sync-result.presenter.mjs +0 -233
- package/dist/lib/tag/presentation/tag-sync-progress-listener.presenter.mjs +0 -243
- package/dist/lib/tag/tag.module.mjs +0 -23
- /package/dist/{lib/arrange/application/ports/domain-source-parser.port.mjs → arrange/domain/types.mjs} +0 -0
- /package/dist/{lib/arrange/application/ports/file-walker.port.mjs → core/filesystem/port.mjs} +0 -0
- /package/dist/{lib/arrange/application/ports/group-file-preview.port.mjs → mirror/domain/dist-filesystem.mjs} +0 -0
- /package/dist/{lib/arrange/application/ports/workspace-resolver.port.mjs → mirror/domain/types.mjs} +0 -0
- /package/dist/{lib/arrange/application/requests/analyze-directory.request.mjs → mirror/sync-types.mjs} +0 -0
- /package/dist/{lib/arrange/application/requests/arrange-sync.request.mjs → tag/domain/types.mjs} +0 -0
|
@@ -1,51 +1,87 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/arrange/domain/constants.ts
|
|
2
2
|
/**
|
|
3
3
|
* Analyze report: long literal threshold (token count).
|
|
4
|
-
|
|
4
|
+
*
|
|
5
|
+
* @since 0.3.16-canary.0
|
|
6
|
+
*/
|
|
7
|
+
const LONG_STRING_TOKEN_THRESHOLD = 18;
|
|
5
8
|
/**
|
|
6
9
|
* Minimum token count for a string to be considered a candidate for grouping
|
|
7
10
|
* in the apply/preview pipeline. Intentionally much lower than
|
|
8
11
|
* {@link LONG_STRING_TOKEN_THRESHOLD} (analyze only).
|
|
9
|
-
|
|
12
|
+
*
|
|
13
|
+
* @since 0.3.16-canary.0
|
|
14
|
+
*/
|
|
15
|
+
const APPLY_MIN_TOKENS = 2;
|
|
10
16
|
/**
|
|
11
17
|
* Minimum tokens a group must have to stand alone before singleton-merging.
|
|
12
18
|
* Set to 2 so single-token groups are not collapsed into unrelated buckets by the merger.
|
|
13
|
-
|
|
19
|
+
*
|
|
20
|
+
* @since 0.3.16-canary.0
|
|
21
|
+
*/
|
|
22
|
+
const MIN_GROUP_TOKENS = 2;
|
|
14
23
|
/**
|
|
15
24
|
* Dynamic max groups clamp: base bound.
|
|
16
|
-
|
|
25
|
+
*
|
|
26
|
+
* @since 0.3.16-canary.0
|
|
27
|
+
*/
|
|
28
|
+
const MAX_GROUPS_BASE = 4;
|
|
17
29
|
/**
|
|
18
30
|
* Dynamic max groups clamp: upper cap.
|
|
19
|
-
|
|
31
|
+
*
|
|
32
|
+
* @since 0.3.16-canary.0
|
|
33
|
+
*/
|
|
34
|
+
const MAX_GROUPS_CAP = 24;
|
|
20
35
|
/**
|
|
21
36
|
* Extra slots so a few state / aria groups do not force `capGroups` to merge
|
|
22
37
|
* unrelated buckets (e.g. `bg-border` + `outline-hidden`).
|
|
23
|
-
|
|
38
|
+
*
|
|
39
|
+
* @since 0.3.16-canary.0
|
|
40
|
+
*/
|
|
41
|
+
const MAX_GROUPS_HEADROOM = 2;
|
|
24
42
|
/**
|
|
25
43
|
* Maximum findings printed per category in the analyze report.
|
|
26
|
-
|
|
44
|
+
*
|
|
45
|
+
* @since 0.3.16-canary.0
|
|
46
|
+
*/
|
|
47
|
+
const MAX_REPORT_LINES = 40;
|
|
27
48
|
/**
|
|
28
49
|
* Maximum recursion depth when traversing `tv()` object literals.
|
|
29
|
-
|
|
50
|
+
*
|
|
51
|
+
* @since 0.3.16-canary.0
|
|
52
|
+
*/
|
|
53
|
+
const MAX_OBJECT_DEPTH = 12;
|
|
30
54
|
/**
|
|
31
55
|
* Maximum depth when peeling conditional / parens / arrays inside `cn(...)` args.
|
|
32
|
-
|
|
56
|
+
*
|
|
57
|
+
* @since 0.3.16-canary.0
|
|
58
|
+
*/
|
|
59
|
+
const MAX_CLASS_EXPR_DEPTH = 12;
|
|
33
60
|
/**
|
|
34
61
|
* Maximum variant-stripping passes in {@link stripVariants}.
|
|
35
62
|
* Real-world Tailwind stacks rarely exceed 4–5 segments (e.g. `@md/sidebar:group-hover:dark:focus-visible:`);
|
|
36
63
|
* 12 is a conservative safety cap that prevents runaway loops on malformed input.
|
|
37
|
-
|
|
64
|
+
*
|
|
65
|
+
* @since 0.3.16-canary.0
|
|
66
|
+
*/
|
|
67
|
+
const MAX_STRIP_VARIANT_PASSES = 12;
|
|
38
68
|
/**
|
|
39
69
|
* Passed when optional `knownBindings` is missing — size 0 disables matching
|
|
40
70
|
* for `cn` / `tv` identifier resolution.
|
|
41
|
-
|
|
71
|
+
*
|
|
72
|
+
* @since 0.3.16-canary.0
|
|
73
|
+
*/
|
|
74
|
+
const EMPTY_CN_TV_BINDINGS = /* @__PURE__ */ new Set();
|
|
42
75
|
/**
|
|
43
76
|
* Bucket sort order — **render pipeline** (lower → earlier in `cn()` output).
|
|
44
|
-
*
|
|
77
|
+
*
|
|
45
78
|
* Matches README: Existence → Position → Layout → Sizing → Spacing → Shape → Background
|
|
46
79
|
* → Shadow → Typography → Composite → Motion → Starting → Behavior → State → Selector,
|
|
47
80
|
* then `other` and `arbitrary` as sort tails for unknown utilities and arbitrary properties.
|
|
48
|
-
|
|
81
|
+
*
|
|
82
|
+
* @since 0.3.16-canary.0
|
|
83
|
+
*/
|
|
84
|
+
const BUCKET_ORDER = {
|
|
49
85
|
existence: 0,
|
|
50
86
|
position: 1,
|
|
51
87
|
layout: 2,
|
|
@@ -72,7 +108,10 @@
|
|
|
72
108
|
* `position↔layout` and `layout↔sizing` each justify one hop and the whole “box in flow”
|
|
73
109
|
* (place + tracks + scrollport) stays in one chunk — co-located because they jointly define
|
|
74
110
|
* stacking and scroll behavior for the same surface.
|
|
75
|
-
|
|
111
|
+
*
|
|
112
|
+
* @since 0.3.16-canary.0
|
|
113
|
+
*/
|
|
114
|
+
const COMPATIBLE_BUCKET_SETS = [
|
|
76
115
|
new Set(["existence", "position"]),
|
|
77
116
|
new Set(["existence", "layout"]),
|
|
78
117
|
new Set(["position", "layout"]),
|
|
@@ -90,13 +129,19 @@
|
|
|
90
129
|
];
|
|
91
130
|
/**
|
|
92
131
|
* Responsive / variant prefix — Tailwind v4 aware.
|
|
93
|
-
*
|
|
132
|
+
*
|
|
94
133
|
* v3: sm: md: … — v4: @sm:, @min-[600px]:, @[480px]:, named @md/sidebar:,
|
|
95
134
|
* viewport md/sidebar:, min-[100px]: / max-[100px]:, …
|
|
96
|
-
|
|
135
|
+
*
|
|
136
|
+
* @since 0.3.16-canary.0
|
|
137
|
+
*/
|
|
138
|
+
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-)\[[^\]]+\]:)/;
|
|
97
139
|
/**
|
|
98
140
|
* State variant stems — hoisted to module scope (not recreated per call).
|
|
99
|
-
|
|
141
|
+
*
|
|
142
|
+
* @since 0.3.16-canary.0
|
|
143
|
+
*/
|
|
144
|
+
const STATE_PREFIXES = new Set([
|
|
100
145
|
"hover",
|
|
101
146
|
"focus",
|
|
102
147
|
"focus-within",
|
package/dist/{lib/arrange/domain/arrange-grouping.service.mjs → arrange/domain/grouping-service.mjs}
RENAMED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { applyEditsDescending } from "../../
|
|
2
|
-
import { buildKnownCnTvBindings, unwrapCnInsideTvCallReplacement } from "./ast/
|
|
3
|
-
import { listAllCnCallsInsideTvInSourceFile } from "./ast/collectors-tv.
|
|
4
|
-
import { collectGroupTargets, planGroupEditForTarget, targetReplaceStart } from "./ast/targets.
|
|
5
|
-
//#region src/
|
|
6
|
-
|
|
7
|
-
* Rich domain: plan Tailwind class grouping / cn-in-tv unwrap for a single file.
|
|
8
|
-
* Pure: only DomainSourceFile, source strings, and options — no I/O.
|
|
9
|
-
*/ function toUnwrapPlans(cnInTvCalls, sourceText) {
|
|
1
|
+
import { applyEditsDescending } from "../../core/source-text-edit.mjs";
|
|
2
|
+
import { buildKnownCnTvBindings, unwrapCnInsideTvCallReplacement } from "./ast/helpers.mjs";
|
|
3
|
+
import { listAllCnCallsInsideTvInSourceFile } from "./ast/collectors-tv.mjs";
|
|
4
|
+
import { collectGroupTargets, planGroupEditForTarget, targetReplaceStart } from "./ast/targets.mjs";
|
|
5
|
+
//#region src/arrange/domain/grouping-service.ts
|
|
6
|
+
function toUnwrapPlans(cnInTvCalls, sourceText) {
|
|
10
7
|
return cnInTvCalls.map((call) => {
|
|
11
8
|
const replacement = unwrapCnInsideTvCallReplacement(call, sourceText);
|
|
12
9
|
if (replacement === void 0) return;
|
|
@@ -18,6 +15,9 @@ import { collectGroupTargets, planGroupEditForTarget, targetReplaceStart } from
|
|
|
18
15
|
};
|
|
19
16
|
}).filter((plan) => plan !== void 0);
|
|
20
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* @since 0.3.16-canary.0
|
|
20
|
+
*/
|
|
21
21
|
function buildGroupFileUnwrapState(domainSfInitial, sourceText) {
|
|
22
22
|
const cnInTvCalls = listAllCnCallsInsideTvInSourceFile(domainSfInitial, buildKnownCnTvBindings(domainSfInitial));
|
|
23
23
|
const unwrapPlans = toUnwrapPlans(cnInTvCalls, sourceText);
|
|
@@ -34,7 +34,10 @@ function buildGroupFileUnwrapState(domainSfInitial, sourceText) {
|
|
|
34
34
|
/**
|
|
35
35
|
* `domainSfGrouped` must be a parse of `unwrap.textAfterUnwrap` (same `text` as source).
|
|
36
36
|
* Returns `null` when there is no cn-in-tv and no grouping targets.
|
|
37
|
-
|
|
37
|
+
*
|
|
38
|
+
* @since 0.3.16-canary.0
|
|
39
|
+
*/
|
|
40
|
+
function tryBuildGroupFileWorkPlan(input) {
|
|
38
41
|
const { filePath, sourceText, domainSfInitial, domainSfGrouped, withClassName, unwrap } = input;
|
|
39
42
|
if (domainSfGrouped.text !== unwrap.textAfterUnwrap) throw new Error("Domain invariant: domainSfGrouped.text must match unwrap phase textAfterUnwrap");
|
|
40
43
|
const groupTargets = collectGroupTargets(domainSfGrouped, filePath);
|
|
@@ -64,6 +67,9 @@ function buildGroupFileUnwrapState(domainSfInitial, sourceText) {
|
|
|
64
67
|
editSitesCount
|
|
65
68
|
};
|
|
66
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* @since 0.3.16-canary.0
|
|
72
|
+
*/
|
|
67
73
|
function groupFileDryRunNoEdits(filePath) {
|
|
68
74
|
return {
|
|
69
75
|
filePath,
|
|
@@ -71,6 +77,9 @@ function groupFileDryRunNoEdits(filePath) {
|
|
|
71
77
|
changed: 0
|
|
72
78
|
};
|
|
73
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* @since 0.3.16-canary.0
|
|
82
|
+
*/
|
|
74
83
|
function groupFilePreviewTotals(work) {
|
|
75
84
|
return {
|
|
76
85
|
filePath: work.filePath,
|
|
@@ -78,6 +87,9 @@ function groupFilePreviewTotals(work) {
|
|
|
78
87
|
changed: 0
|
|
79
88
|
};
|
|
80
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* @since 0.3.16-canary.0
|
|
92
|
+
*/
|
|
81
93
|
function mergeGroupFileBodyText(work) {
|
|
82
94
|
const groupEdits = work.plannedGroupEdits.map((plannedEdit) => ({
|
|
83
95
|
start: plannedEdit.start,
|
|
@@ -87,12 +99,21 @@ function mergeGroupFileBodyText(work) {
|
|
|
87
99
|
}));
|
|
88
100
|
return groupEdits.length > 0 ? applyEditsDescending(work.textAfterUnwrap, groupEdits) : work.textAfterUnwrap;
|
|
89
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* @since 0.3.16-canary.0
|
|
104
|
+
*/
|
|
90
105
|
function groupFileEditsTouchJsxCn(work) {
|
|
91
106
|
return work.plannedGroupEdits.some((plannedEdit) => plannedEdit.jsxCn);
|
|
92
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* @since 0.3.16-canary.0
|
|
110
|
+
*/
|
|
93
111
|
function countPersistedGroupFileEdits(work) {
|
|
94
112
|
return work.unwrapEdits.length + work.plannedGroupEdits.length;
|
|
95
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* @since 0.3.16-canary.0
|
|
116
|
+
*/
|
|
96
117
|
function groupFileWorkHasNothingToReport(work) {
|
|
97
118
|
return work.editSitesCount === 0 && work.cnInTvNoReplacement === 0;
|
|
98
119
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { BUCKET_ORDER } from "./constants.
|
|
2
|
-
import { stripVariants, tokenizeClassString } from "./tailwind-token.
|
|
3
|
-
import { bucketsCompatible, bucketsMergeCompatible, classifyToken, compositeSecondaryOrder, selectorKey, stateKey } from "./
|
|
4
|
-
//#region src/
|
|
1
|
+
import { BUCKET_ORDER } from "./constants.mjs";
|
|
2
|
+
import { stripVariants, tokenizeClassString } from "./tailwind-token.mjs";
|
|
3
|
+
import { bucketsCompatible, bucketsMergeCompatible, classifyToken, compositeSecondaryOrder, selectorKey, stateKey } from "./token-classifier.mjs";
|
|
4
|
+
//#region src/arrange/domain/grouping.ts
|
|
5
5
|
/**
|
|
6
6
|
* `cn()` grouping: bucket sequence is {@link BUCKET_ORDER} only; tokens are classified with
|
|
7
7
|
* {@link classifyToken}. Comparators here (`compareClassifiedTailwindTokensForCnGrouping`, …)
|
|
8
8
|
* are the single place for variant-aware sort — do not reintroduce parallel bucket ordering.
|
|
9
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
10
11
|
* Separates bucket id from variant key in {@link buildFirstVariantKeySourceIndex} map keys.
|
|
11
|
-
*/
|
|
12
|
+
*/
|
|
13
|
+
const VARIANT_BUCKET_KEY_SEP = "\0";
|
|
12
14
|
function isVariantKeyedBucket(bucket) {
|
|
13
15
|
return bucket === "selector" || bucket === "state" || bucket === "starting";
|
|
14
16
|
}
|
|
@@ -21,13 +23,15 @@ function variantKeyedBlockMapKey(bucket, classToken) {
|
|
|
21
23
|
/**
|
|
22
24
|
* Deterministic lexicographic order for Tailwind class tokens (and token-space signatures).
|
|
23
25
|
* Used wherever we need stable, locale-agnostic ASCII ordering of utility strings.
|
|
24
|
-
*/
|
|
26
|
+
*/
|
|
27
|
+
function compareClassTokensCanonically(left, right) {
|
|
25
28
|
return left.localeCompare(right);
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* First source index where each `(bucket, variantGroupKey)` appears — drives stable clustering
|
|
29
32
|
* in {@link suggestCnGroups} without reordering unrelated variant blocks by alphabet alone.
|
|
30
|
-
*/
|
|
33
|
+
*/
|
|
34
|
+
function buildFirstVariantKeySourceIndex(classified) {
|
|
31
35
|
const firstVariantKeySourceIndex = /* @__PURE__ */ new Map();
|
|
32
36
|
for (const item of classified) {
|
|
33
37
|
if (!isVariantKeyedBucket(item.bucket)) continue;
|
|
@@ -62,7 +66,10 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
62
66
|
* True when `staticLiteralTexts` carries the **same partition** of Tailwind tokens as
|
|
63
67
|
* `suggestedGroups` from {@link suggestCnGroups}, ignoring order of arguments and
|
|
64
68
|
* order of tokens within each chunk.
|
|
65
|
-
|
|
69
|
+
*
|
|
70
|
+
* @since 0.3.16-canary.0
|
|
71
|
+
*/
|
|
72
|
+
function areCnTailwindPartitionsEquivalent(staticLiteralTexts, suggestedGroups) {
|
|
66
73
|
const partitionSignatures = (chunks) => chunks.map((chunk) => {
|
|
67
74
|
const classTokens = tokenizeClassString(chunk);
|
|
68
75
|
return classTokens.length === 0 ? "" : [...classTokens].sort(compareClassTokensCanonically).join(" ");
|
|
@@ -75,7 +82,8 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
75
82
|
}
|
|
76
83
|
/**
|
|
77
84
|
* Dominant bucket of a whitespace-delimited class group (for merge heuristics).
|
|
78
|
-
*/
|
|
85
|
+
*/
|
|
86
|
+
function dominantBucketOfGroup(groupStr) {
|
|
79
87
|
const counts = /* @__PURE__ */ new Map();
|
|
80
88
|
for (const classToken of tokenizeClassString(groupStr)) {
|
|
81
89
|
const tokenBucket = classifyToken(classToken);
|
|
@@ -95,7 +103,8 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
95
103
|
}
|
|
96
104
|
/**
|
|
97
105
|
* Dynamic cap: more tokens → allow more groups, within [BASE, CAP].
|
|
98
|
-
*/
|
|
106
|
+
*/
|
|
107
|
+
function dynamicMaxGroups(tokenCount) {
|
|
99
108
|
const byTokens = Math.ceil(tokenCount / 2) + 2;
|
|
100
109
|
return Math.max(4, Math.min(24, byTokens));
|
|
101
110
|
}
|
|
@@ -107,7 +116,8 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
107
116
|
* array, guaranteeing progress) or finds no singleton that can merge and exits
|
|
108
117
|
* via `changed = false`. Two mutually-incompatible singletons both hit
|
|
109
118
|
* `continue` and `changed` stays false, so the loop exits without spinning.
|
|
110
|
-
*/
|
|
119
|
+
*/
|
|
120
|
+
function mergeSingletons(groups) {
|
|
111
121
|
if (groups.length <= 1) return groups;
|
|
112
122
|
const result = [...groups];
|
|
113
123
|
let changed = true;
|
|
@@ -145,7 +155,8 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
145
155
|
}
|
|
146
156
|
/**
|
|
147
157
|
* Tie-break when two merge candidates are both {@link bucketsMergeCompatible} (same bucket or COMPATIBLE_BUCKET_SETS).
|
|
148
|
-
*/
|
|
158
|
+
*/
|
|
159
|
+
function capMergePenalty(leftBucket, rightBucket) {
|
|
149
160
|
if (leftBucket === rightBucket) return 0;
|
|
150
161
|
if (bucketsCompatible(leftBucket, rightBucket)) return 0;
|
|
151
162
|
return 500;
|
|
@@ -159,7 +170,8 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
159
170
|
* pairs. In practice `n` is bounded by {@link MAX_GROUPS_CAP} (24) and typical class strings
|
|
160
171
|
* are well below that, so this is not a concern for the current usage pattern. If this ever
|
|
161
172
|
* runs in a high-throughput batch mode, consider a priority-queue approach.
|
|
162
|
-
*/
|
|
173
|
+
*/
|
|
174
|
+
function capGroups(groups, maxGroups) {
|
|
163
175
|
const result = [...groups];
|
|
164
176
|
const lengths = result.map((groupStr) => tokenizeClassString(groupStr).length);
|
|
165
177
|
while (result.length > maxGroups) {
|
|
@@ -202,7 +214,8 @@ function compareClassifiedTailwindTokensForCnGrouping(left, right, firstVariantK
|
|
|
202
214
|
* **later** chunk that is predominantly `state` **and** contains some `animate-*` utility —
|
|
203
215
|
* skipping intermediate `state` chunks (`sm:…`, `group-data-[…]:…`, etc.) that sit between
|
|
204
216
|
* `ease-ui` and `data-open:animate-*`. If no such chunk exists, the ease chunk is left as-is.
|
|
205
|
-
*/
|
|
217
|
+
*/
|
|
218
|
+
function mergeEaseTimingIntoFollowingAnimatedState(groups) {
|
|
206
219
|
const result = [...groups];
|
|
207
220
|
let changed = true;
|
|
208
221
|
while (changed) {
|
|
@@ -237,6 +250,9 @@ function chunkIsOnlyEaseTimingMotion(groupStr) {
|
|
|
237
250
|
if (classTokens.length === 0) return false;
|
|
238
251
|
return classTokens.every((classToken) => classifyToken(classToken) === "motion" && /^ease-/.test(stripVariants(classToken)));
|
|
239
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* @since 0.3.16-canary.0
|
|
255
|
+
*/
|
|
240
256
|
function suggestCnGroups(classString) {
|
|
241
257
|
const tokens = tokenizeClassString(classString);
|
|
242
258
|
if (tokens.length === 0) return [];
|
|
@@ -247,7 +263,8 @@ function suggestCnGroups(classString) {
|
|
|
247
263
|
* variants. Block order follows the **first source index** of each key so unrelated
|
|
248
264
|
* states keep document order; within a key, `classToken` order is lexicographic for
|
|
249
265
|
* idempotency.
|
|
250
|
-
*/
|
|
266
|
+
*/
|
|
267
|
+
const classified = tokens.map((classToken, index) => ({
|
|
251
268
|
classToken,
|
|
252
269
|
bucket: classifyToken(classToken),
|
|
253
270
|
index
|
|
@@ -257,7 +274,8 @@ function suggestCnGroups(classString) {
|
|
|
257
274
|
const rawGroups = [];
|
|
258
275
|
/**
|
|
259
276
|
* Bucket of the last token already placed in the current run (pairwise compat with `COMPATIBLE_BUCKET_SETS`).
|
|
260
|
-
*/
|
|
277
|
+
*/
|
|
278
|
+
let lastBucketInRun = null;
|
|
261
279
|
let currentStateKey = null;
|
|
262
280
|
let currentTokens = [];
|
|
263
281
|
const flush = () => {
|
|
@@ -304,7 +322,10 @@ function suggestCnGroups(classString) {
|
|
|
304
322
|
/**
|
|
305
323
|
* One label per suggested group: a single bucket name, or `mixed:a+b` when that chunk
|
|
306
324
|
* spans multiple Tailwind buckets (matches `codefast arrange group` stdout).
|
|
307
|
-
|
|
325
|
+
*
|
|
326
|
+
* @since 0.3.16-canary.0
|
|
327
|
+
*/
|
|
328
|
+
function summarizeGroupBucketLabels(groups) {
|
|
308
329
|
return groups.map((g) => {
|
|
309
330
|
const uniq = new Set(tokenizeClassString(g).map(classifyToken));
|
|
310
331
|
if (uniq.size !== 1) return `mixed:${[...uniq].sort(compareClassTokensCanonically).join("+")}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isDomainImportDeclaration, isDomainNamedImports, isDomainStringLiteral } from "./ast/ast-node.
|
|
2
|
-
//#region src/
|
|
1
|
+
import { isDomainImportDeclaration, isDomainNamedImports, isDomainStringLiteral } from "./ast/ast-node.mjs";
|
|
2
|
+
//#region src/arrange/domain/imports.ts
|
|
3
3
|
function sourceFileImportsCn(sourceFile) {
|
|
4
4
|
for (const statement of sourceFile.statements) {
|
|
5
5
|
if (!isDomainImportDeclaration(statement) || !statement.importClause) continue;
|
|
@@ -13,7 +13,8 @@ function sourceFileImportsCn(sourceFile) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Resolve the module specifier used when injecting a `cn` import into a file.
|
|
16
|
-
*/
|
|
16
|
+
*/
|
|
17
|
+
function cnModuleSpecifierForFile(filePath, override) {
|
|
17
18
|
if (override) return override;
|
|
18
19
|
return "@codefast/tailwind-variants";
|
|
19
20
|
}
|
|
@@ -24,6 +25,9 @@ function findImportDeclarationFromModule(sourceFile, moduleSpecifier) {
|
|
|
24
25
|
if (isDomainStringLiteral(spec) && spec.text === moduleSpecifier) return statement;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* @since 0.3.16-canary.0
|
|
30
|
+
*/
|
|
27
31
|
function ensureCnImport(sourceFile, cnImportOverride) {
|
|
28
32
|
const sourceText = sourceFile.text;
|
|
29
33
|
if (sourceFileImportsCn(sourceFile)) return sourceText;
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { indentOfLineContaining } from "../../
|
|
2
|
-
//#region src/
|
|
1
|
+
import { indentOfLineContaining } from "../../core/source-text-edit.mjs";
|
|
2
|
+
//#region src/arrange/domain/source-text-formatters.ts
|
|
3
|
+
/**
|
|
4
|
+
* @since 0.3.16-canary.0
|
|
5
|
+
*/
|
|
3
6
|
function escapeTsStringLiteralContent(group) {
|
|
4
7
|
return group.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
5
8
|
}
|
|
6
|
-
/** Trailing comma on multiline Tailwind group lists (matches prior `cn` / `tv` array style). */
|
|
9
|
+
/** Trailing comma on multiline Tailwind group lists (matches prior `cn` / `tv` array style). */
|
|
10
|
+
function commaAfterTailwindGroupLine(groupIndex, groupCount) {
|
|
7
11
|
return groupIndex < groupCount - 1 || groupCount > 1 ? "," : "";
|
|
8
12
|
}
|
|
9
13
|
/**
|
|
10
14
|
* Multiline fragment for use *inside* an existing `cn(...)` — replaces one
|
|
11
15
|
* string argument with several, without producing `cn(cn(...))`.
|
|
12
|
-
*/
|
|
16
|
+
*/
|
|
17
|
+
function formatCnArguments(groups, options) {
|
|
13
18
|
const indent = options?.indent ?? " ";
|
|
14
19
|
const commaAfterLast = options?.commaAfterLastGroup ?? options?.trailingClassName ?? false;
|
|
15
20
|
const lines = [];
|
|
@@ -22,6 +27,9 @@ function escapeTsStringLiteralContent(group) {
|
|
|
22
27
|
if (options?.trailingClassName) lines.push(`${indent}className,`);
|
|
23
28
|
return lines.join("\n");
|
|
24
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* @since 0.3.16-canary.0
|
|
32
|
+
*/
|
|
25
33
|
function formatCnCall(groups, options) {
|
|
26
34
|
const lines = ["cn("];
|
|
27
35
|
const commaOnEachStringLine = groups.length > 1 || Boolean(options?.trailingClassName);
|
|
@@ -35,6 +43,9 @@ function formatCnCall(groups, options) {
|
|
|
35
43
|
lines.push(")");
|
|
36
44
|
return lines.join("\n");
|
|
37
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* @since 0.3.16-canary.0
|
|
48
|
+
*/
|
|
38
49
|
function formatArray(groups) {
|
|
39
50
|
const lines = ["["];
|
|
40
51
|
for (let i = 0; i < groups.length; i++) {
|
|
@@ -49,7 +60,10 @@ function formatArray(groups) {
|
|
|
49
60
|
/**
|
|
50
61
|
* Multiple string literals as sequential lines for insertion **inside** an existing array
|
|
51
62
|
* (e.g. `tv({ base: [ … ] })` — avoids `[[ "a", "b" ]]` when replacing one long string).
|
|
52
|
-
|
|
63
|
+
*
|
|
64
|
+
* @since 0.3.16-canary.0
|
|
65
|
+
*/
|
|
66
|
+
function formatArrayElementsAsSiblingLines(groups, continuationPrefix) {
|
|
53
67
|
const segments = [];
|
|
54
68
|
for (let i = 0; i < groups.length; i++) {
|
|
55
69
|
const group = groups[i];
|
|
@@ -60,6 +74,9 @@ function formatArray(groups) {
|
|
|
60
74
|
}
|
|
61
75
|
return segments.join("");
|
|
62
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* @since 0.3.16-canary.0
|
|
79
|
+
*/
|
|
63
80
|
function formatJsxCnAttributeValue(groups, source, valueNodeStart) {
|
|
64
81
|
const baseIndent = indentOfLineContaining(source, valueNodeStart);
|
|
65
82
|
return `{cn(\n${formatCnArguments(groups, {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import "./constants.
|
|
2
|
-
//#region src/
|
|
1
|
+
import "./constants.mjs";
|
|
2
|
+
//#region src/arrange/domain/tailwind-token.ts
|
|
3
|
+
/**
|
|
4
|
+
* @since 0.3.16-canary.0
|
|
5
|
+
*/
|
|
3
6
|
function tokenizeClassString(classString) {
|
|
4
7
|
return classString.trim().split(/\s+/).filter(Boolean);
|
|
5
8
|
}
|
|
@@ -7,7 +10,10 @@ function tokenizeClassString(classString) {
|
|
|
7
10
|
* Index of the first `:` that separates a Tailwind variant segment from the rest.
|
|
8
11
|
* Colons inside `[...]` (at positive bracket depth) are ignored so selectors like
|
|
9
12
|
* `[&_a:hover]:text-red-500` split as `[&_a:hover]:` + `text-red-500`.
|
|
10
|
-
|
|
13
|
+
*
|
|
14
|
+
* @since 0.3.16-canary.0
|
|
15
|
+
*/
|
|
16
|
+
function indexOfFirstVariantColon(text) {
|
|
11
17
|
let depth = 0;
|
|
12
18
|
for (let i = 0; i < text.length; i++) {
|
|
13
19
|
const ch = text[i];
|
|
@@ -17,6 +23,9 @@ function tokenizeClassString(classString) {
|
|
|
17
23
|
}
|
|
18
24
|
return -1;
|
|
19
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* @since 0.3.16-canary.0
|
|
28
|
+
*/
|
|
20
29
|
function stripVariants(token) {
|
|
21
30
|
let withoutVariants = token;
|
|
22
31
|
const maxStripVariantPasses = 12;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { COMPATIBLE_BUCKET_SETS, RESPONSIVE_PREFIX, STATE_PREFIXES } from "./constants.
|
|
2
|
-
import { indexOfFirstVariantColon, stripVariants } from "./tailwind-token.
|
|
3
|
-
//#region src/
|
|
1
|
+
import { COMPATIBLE_BUCKET_SETS, RESPONSIVE_PREFIX, STATE_PREFIXES } from "./constants.mjs";
|
|
2
|
+
import { indexOfFirstVariantColon, stripVariants } from "./tailwind-token.mjs";
|
|
3
|
+
//#region src/arrange/domain/token-classifier.ts
|
|
4
4
|
/**
|
|
5
5
|
* Bare-token classification is **total** (always returns a {@link Bucket}). The `Result` pattern
|
|
6
6
|
* (`isOk` / `isErr`) belongs at arrange use-case / I/O boundaries, not on this hot path.
|
|
@@ -9,7 +9,8 @@ import { indexOfFirstVariantColon, stripVariants } from "./tailwind-token.value-
|
|
|
9
9
|
* numbered `nth-*`, media features, v4 `in-[…]`, child selectors `*` / `**`, …).
|
|
10
10
|
* When these are missed, `isStateToken` is false and the variant is stripped — the
|
|
11
11
|
* utility is then bucketed as if it were unconditional, which breaks preview grouping.
|
|
12
|
-
*/
|
|
12
|
+
*/
|
|
13
|
+
function isCompoundOrMediaVariantPrefix(prefix) {
|
|
13
14
|
if (prefix === "*" || prefix === "**") return true;
|
|
14
15
|
if (prefix === "inert") return true;
|
|
15
16
|
if (prefix.startsWith("has-")) return true;
|
|
@@ -22,7 +23,8 @@ import { indexOfFirstVariantColon, stripVariants } from "./tailwind-token.value-
|
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Outermost variant segment: first `:` at bracket depth 0 → text before it.
|
|
25
|
-
*/
|
|
26
|
+
*/
|
|
27
|
+
function firstLeadingVariantPrefix(token) {
|
|
26
28
|
const colonIdx = indexOfFirstVariantColon(token);
|
|
27
29
|
if (colonIdx === -1) return;
|
|
28
30
|
return token.slice(0, colonIdx);
|
|
@@ -58,7 +60,10 @@ function isStateToken(token) {
|
|
|
58
60
|
/**
|
|
59
61
|
* Secondary sort inside the **composite** bucket: opacity / blend / isolation →
|
|
60
62
|
* 3D context → 3D transforms → 2D transforms → filters → will-change.
|
|
61
|
-
|
|
63
|
+
*
|
|
64
|
+
* @since 0.3.16-canary.0
|
|
65
|
+
*/
|
|
66
|
+
function compositeSecondaryOrder(bareUtility) {
|
|
62
67
|
const b = bareUtility;
|
|
63
68
|
if (/^opacity(?:-|$)/.test(b) || /^mix-blend-/.test(b) || /^isolation(?:-|$)/.test(b) || b === "isolate") return 0;
|
|
64
69
|
if (b === "transform-3d" || /^perspective(?:-|$)/.test(b)) return 10;
|
|
@@ -71,7 +76,8 @@ function isStateToken(token) {
|
|
|
71
76
|
}
|
|
72
77
|
/**
|
|
73
78
|
* Classify a **bare** utility (no `hover:` / `md:` / … prefixes).
|
|
74
|
-
*/
|
|
79
|
+
*/
|
|
80
|
+
function classifyBareUtility(bareUtility) {
|
|
75
81
|
const b = bareUtility;
|
|
76
82
|
if (/^@container(?:\/[a-z][a-z0-9-]*)?$/i.test(b)) return "existence";
|
|
77
83
|
if (/^(?:hidden|contents|sr-only|not-sr-only|list-item|flow-root)$/.test(b) || /^(?:block|inline-block|inline)$/.test(b)) return "existence";
|
|
@@ -98,13 +104,17 @@ function isStateToken(token) {
|
|
|
98
104
|
* scope utilities to descendants — bucket as `selector` so they chunk apart from base layout/typography
|
|
99
105
|
* and are labeled distinctly from interactive `state` variants (`hover:`, `data-[…]:`, …).
|
|
100
106
|
* Pure arbitrary **properties** (`[--x]:`, `[color:red]`) have no `&` in the leading `[…]` segment.
|
|
101
|
-
*/
|
|
107
|
+
*/
|
|
108
|
+
function isArbitraryParentSelectorStateToken(token) {
|
|
102
109
|
if (/^\[&/.test(token)) return true;
|
|
103
110
|
if (!token.startsWith("[")) return false;
|
|
104
111
|
const colonIdx = indexOfFirstVariantColon(token);
|
|
105
112
|
if (colonIdx === -1 || colonIdx === 0 || token[colonIdx - 1] !== "]") return false;
|
|
106
113
|
return token.slice(0, colonIdx).includes("&");
|
|
107
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* @since 0.3.16-canary.0
|
|
117
|
+
*/
|
|
108
118
|
function classifyToken(token) {
|
|
109
119
|
if (isArbitraryParentSelectorStateToken(token)) return "selector";
|
|
110
120
|
if (isSelectorVariantToken(token)) return "selector";
|
|
@@ -122,19 +132,22 @@ function classifyToken(token) {
|
|
|
122
132
|
* Stable key for `data-[…]` variants so unrelated selectors stay split, while
|
|
123
133
|
* **branching on the full bracket** (e.g. `data-[vaul-drawer-direction=bottom]`
|
|
124
134
|
* vs `=left`) keeps direction-specific utilities in separate state groups.
|
|
125
|
-
*/
|
|
135
|
+
*/
|
|
136
|
+
function dataAttributeStem(token) {
|
|
126
137
|
const match = token.match(/^data-\[([^\]]*)\]/);
|
|
127
138
|
return match ? `data-[${match[1]}]` : "data";
|
|
128
139
|
}
|
|
129
140
|
/**
|
|
130
141
|
* Same idea as {@link dataAttributeStem} for `aria-[…]:` variants.
|
|
131
|
-
*/
|
|
142
|
+
*/
|
|
143
|
+
function ariaAttributeStem(token) {
|
|
132
144
|
const match = token.match(/^aria-\[([^\]]*)\]/);
|
|
133
145
|
return match ? `aria-[${match[1]}]` : "aria";
|
|
134
146
|
}
|
|
135
147
|
/**
|
|
136
148
|
* `in-data-[…]:` — normalize on the full bracket expression like {@link dataAttributeStem}.
|
|
137
|
-
*/
|
|
149
|
+
*/
|
|
150
|
+
function inDataAttributeStem(token) {
|
|
138
151
|
const match = token.match(/^in-data-\[([^\]]*)\]/);
|
|
139
152
|
return match ? `in-data-[${match[1]}]` : "in-data";
|
|
140
153
|
}
|
|
@@ -144,10 +157,13 @@ function classifyToken(token) {
|
|
|
144
157
|
* Uses the **full variant stack** (every `:` segment outside `[…]`), not only the
|
|
145
158
|
* outermost prefix, so `@md/foo:[&>*]:w-auto` and `@md/foo:has-[…]:mt-px` stay in
|
|
146
159
|
* separate groups while `hover:opacity` still keys as `hover` + `opacity`…
|
|
147
|
-
*
|
|
160
|
+
*
|
|
148
161
|
* `data-[…]` / `aria-[…]` normalize the first segment via {@link dataAttributeStem} /
|
|
149
162
|
* {@link ariaAttributeStem} (full token required for bracket capture).
|
|
150
|
-
|
|
163
|
+
*
|
|
164
|
+
* @since 0.3.16-canary.0
|
|
165
|
+
*/
|
|
166
|
+
function stateKey(token) {
|
|
151
167
|
const layers = [];
|
|
152
168
|
let rest = token;
|
|
153
169
|
while (rest.length > 0) {
|
|
@@ -169,20 +185,29 @@ const SELECTOR_KEY_SEP = "";
|
|
|
169
185
|
* Variant key for {@link Bucket} `"selector"` tokens in {@link suggestCnGroups}.
|
|
170
186
|
* Reuses {@link stateKey} layer splitting, then normalizes common shadcn/Radix SVG patterns so
|
|
171
187
|
* `[&_svg]:…` and `[&_svg:not([class*='size-'])]:…` stay in one chunk.
|
|
172
|
-
|
|
188
|
+
*
|
|
189
|
+
* @since 0.3.16-canary.0
|
|
190
|
+
*/
|
|
191
|
+
function selectorKey(token) {
|
|
173
192
|
return stateKey(token).split(SELECTOR_KEY_SEP).map(normalizeSelectorVariantLayer).join(SELECTOR_KEY_SEP);
|
|
174
193
|
}
|
|
175
194
|
function normalizeSelectorVariantLayer(layer) {
|
|
176
195
|
if (layer === "[&_svg:not([class*='size-'])]") return "[&_svg]";
|
|
177
196
|
return layer;
|
|
178
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* @since 0.3.16-canary.0
|
|
200
|
+
*/
|
|
179
201
|
function bucketsCompatible(a, b) {
|
|
180
202
|
if (a === b) return true;
|
|
181
203
|
return COMPATIBLE_BUCKET_SETS.some((bucketSet) => bucketSet.has(a) && bucketSet.has(b));
|
|
182
204
|
}
|
|
183
205
|
/**
|
|
184
206
|
* Like {@link bucketsCompatible}, but never merge two distinct state / starting / selector variant blobs.
|
|
185
|
-
|
|
207
|
+
*
|
|
208
|
+
* @since 0.3.16-canary.0
|
|
209
|
+
*/
|
|
210
|
+
function bucketsMergeCompatible(a, b) {
|
|
186
211
|
if (a === "state" && b === "state") return false;
|
|
187
212
|
if (a === "starting" && b === "starting") return false;
|
|
188
213
|
if (a === "selector" && b === "selector") return false;
|