@cassida/compiler 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/config.schema.json +88 -0
  3. package/dist/canonicalizer.d.ts +42 -0
  4. package/dist/canonicalizer.d.ts.map +1 -0
  5. package/dist/canonicalizer.js +185 -0
  6. package/dist/canonicalizer.js.map +1 -0
  7. package/dist/compile.d.ts +46 -0
  8. package/dist/compile.d.ts.map +1 -0
  9. package/dist/compile.js +84 -0
  10. package/dist/compile.js.map +1 -0
  11. package/dist/config.d.ts +182 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +150 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/emitter.d.ts +44 -0
  16. package/dist/emitter.d.ts.map +1 -0
  17. package/dist/emitter.js +189 -0
  18. package/dist/emitter.js.map +1 -0
  19. package/dist/generated-property-specs.d.ts +3193 -0
  20. package/dist/generated-property-specs.d.ts.map +1 -0
  21. package/dist/generated-property-specs.js +472 -0
  22. package/dist/generated-property-specs.js.map +1 -0
  23. package/dist/hasher.d.ts +8 -0
  24. package/dist/hasher.d.ts.map +1 -0
  25. package/dist/hasher.js +10 -0
  26. package/dist/hasher.js.map +1 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +13 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/modifier-spec.d.ts +104 -0
  32. package/dist/modifier-spec.d.ts.map +1 -0
  33. package/dist/modifier-spec.js +52 -0
  34. package/dist/modifier-spec.js.map +1 -0
  35. package/dist/plugin.d.ts +66 -0
  36. package/dist/plugin.d.ts.map +1 -0
  37. package/dist/plugin.js +65 -0
  38. package/dist/plugin.js.map +1 -0
  39. package/dist/property-spec.d.ts +321 -0
  40. package/dist/property-spec.d.ts.map +1 -0
  41. package/dist/property-spec.js +382 -0
  42. package/dist/property-spec.js.map +1 -0
  43. package/dist/registry.d.ts +71 -0
  44. package/dist/registry.d.ts.map +1 -0
  45. package/dist/registry.js +123 -0
  46. package/dist/registry.js.map +1 -0
  47. package/dist/types.d.ts +135 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +28 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/util.d.ts +6 -0
  52. package/dist/util.d.ts.map +1 -0
  53. package/dist/util.js +6 -0
  54. package/dist/util.js.map +1 -0
  55. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FSS contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,88 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "CassConfig",
4
+ "description": "FSS (Functional Style Sheet) configuration. Place this at the project root as cassida.config.json. Every field is optional; missing fields fall back to FSS defaults.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "JSON Schema URL for editor autocomplete."
11
+ },
12
+ "layer": {
13
+ "type": ["string", "null"],
14
+ "description": "@layer name for the cascade-isolation wrapper. Use null to disable the @layer wrap entirely.",
15
+ "default": "fss"
16
+ },
17
+ "importSource": {
18
+ "type": "string",
19
+ "description": "Module specifier the parser recognizes as the source of `fss`. Renamed imports (`{ fss as ff }`) are honored automatically.",
20
+ "default": "@cassida/core"
21
+ },
22
+ "hash": {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "description": "Class-name hashing parameters.",
26
+ "properties": {
27
+ "prefix": {
28
+ "type": "string",
29
+ "description": "Prefix prepended to every generated class name.",
30
+ "default": "cas-"
31
+ },
32
+ "length": {
33
+ "type": "integer",
34
+ "description": "Hex characters of SHA-1 digest used as the hash. 8 is enough for ~tens of thousands of unique rules; bump to 10–12 for very large apps.",
35
+ "minimum": 4,
36
+ "maximum": 40,
37
+ "default": 8
38
+ }
39
+ }
40
+ },
41
+ "media": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "description": "Media-query handling.",
45
+ "properties": {
46
+ "sort": {
47
+ "type": "string",
48
+ "enum": ["mobile-first", "desktop-first"],
49
+ "description": "Cascade direction for width-based @media queries. Mobile-first sorts min-width ascending and max-width descending; desktop-first reverses both.",
50
+ "default": "mobile-first"
51
+ }
52
+ }
53
+ },
54
+ "css": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "description": "CSS-emission strategy and post-processing.",
58
+ "properties": {
59
+ "mode": {
60
+ "type": "string",
61
+ "enum": ["rule-per-class", "shared-by-declaration"],
62
+ "description": "rule-per-class: one rule per className with declarations baked in. shared-by-declaration: one rule per (property, value) with grouped selectors. Phase 4+ feature; recognized but not yet implemented.",
63
+ "default": "rule-per-class"
64
+ },
65
+ "lightningcss": {
66
+ "type": "object",
67
+ "additionalProperties": false,
68
+ "description": "Optional lightningcss post-processing of the emitted CSS. Phase 5+ feature; recognized but not yet implemented.",
69
+ "properties": {
70
+ "enabled": {
71
+ "type": "boolean",
72
+ "default": false
73
+ },
74
+ "minify": {
75
+ "type": "boolean",
76
+ "default": true
77
+ },
78
+ "targets": {
79
+ "type": "string",
80
+ "description": "Browserslist string passed to lightningcss.",
81
+ "default": "defaults"
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,42 @@
1
+ import { type Op, type Scope, type ScopeBag } from './types.js';
2
+ import type { Registry } from './registry.js';
3
+ import type { ShorthandPolicy } from './config.js';
4
+ /**
5
+ * Walks an `Op[]` chain, LIFO-collapsing each scope's declarations and
6
+ * recursing into nested scoped sub-chains. The output is a deterministic
7
+ * `ScopeBag` tree: the root has `scope: null` and owns "base"
8
+ * declarations; each child carries its modifier (`:hover`, `@media (...)`,
9
+ * raw selector) plus its own bag and further children.
10
+ *
11
+ * Multiple scoped ops at the same scope key (e.g. two `.hover(...)`
12
+ * calls in the same chain) are merged: their inner ops are concatenated,
13
+ * then re-collapsed inside the merged scope. This matches the user's
14
+ * intuition that "`.hover()` adds, doesn't replace" while still
15
+ * applying property-level LIFO inside the merged scope.
16
+ */
17
+ export declare class Canonicalizer {
18
+ private readonly registry;
19
+ private readonly policy;
20
+ constructor(registry: Registry, policy?: ShorthandPolicy);
21
+ collapse(ops: readonly Op[], scope?: Scope | null): ScopeBag;
22
+ /**
23
+ * Apply the configured shorthand-policy check. Throws when the user
24
+ * mixes shorthand and longhand of the same family within a single
25
+ * scope in a way the policy forbids. The error message names both
26
+ * methods and points at the three escape hatches: modifier callback,
27
+ * looser `policy` config, or sticking to one form.
28
+ */
29
+ private checkShorthandPolicy;
30
+ private policyError;
31
+ /**
32
+ * Deterministic key derived from a scope tree.
33
+ *
34
+ * For static-only chains (no modifiers, no nested scopes) the format
35
+ * matches Phase 1 exactly — sorted entry pairs — so existing class
36
+ * hashes are preserved across the Phase 2 upgrade. Trees with any
37
+ * nested scope use the structured form, which encodes the full
38
+ * sorted scope-tree shape.
39
+ */
40
+ canonicalKey(tree: ScopeBag): string;
41
+ }
42
+ //# sourceMappingURL=canonicalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalizer.d.ts","sourceRoot":"","sources":["../src/canonicalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,EAAE,EACP,KAAK,KAAK,EACV,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,QAAQ,EAAE,QAAQ,EAClB,MAAM,GAAE,eAA0B;IAGrD,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,GAAE,KAAK,GAAG,IAAW,GAAG,QAAQ;IA4ElE;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAkC5B,OAAO,CAAC,WAAW;IAiBnB;;;;;;;;OAQG;IACH,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM;CAOrC"}
@@ -0,0 +1,185 @@
1
+ import { DYNAMIC_PLACEHOLDER, isDynamic, isMethodOp, isRawOp, isScopedOp, } from './types.js';
2
+ /**
3
+ * Walks an `Op[]` chain, LIFO-collapsing each scope's declarations and
4
+ * recursing into nested scoped sub-chains. The output is a deterministic
5
+ * `ScopeBag` tree: the root has `scope: null` and owns "base"
6
+ * declarations; each child carries its modifier (`:hover`, `@media (...)`,
7
+ * raw selector) plus its own bag and further children.
8
+ *
9
+ * Multiple scoped ops at the same scope key (e.g. two `.hover(...)`
10
+ * calls in the same chain) are merged: their inner ops are concatenated,
11
+ * then re-collapsed inside the merged scope. This matches the user's
12
+ * intuition that "`.hover()` adds, doesn't replace" while still
13
+ * applying property-level LIFO inside the merged scope.
14
+ */
15
+ export class Canonicalizer {
16
+ registry;
17
+ policy;
18
+ constructor(registry, policy = 'strict') {
19
+ this.registry = registry;
20
+ this.policy = policy;
21
+ }
22
+ collapse(ops, scope = null) {
23
+ const bag = {};
24
+ const slots = {};
25
+ const childOpsByKey = new Map();
26
+ // Per-scope tracking for shorthand ↔ longhand co-occurrence.
27
+ // Recursion into ScopedOps creates a fresh `collapse` invocation
28
+ // with its own local `seenShorthand` / `seenLonghand`, so the
29
+ // `media`/`hover`/`on` scope automatically reset the constraint.
30
+ const seenShorthand = new Map();
31
+ const seenLonghand = new Map();
32
+ for (const op of ops) {
33
+ if (isMethodOp(op)) {
34
+ const entry = this.registry[op.method];
35
+ if (!entry) {
36
+ throw new Error(`[cassida] unknown method "${op.method}". Add it to the registry or check for typos.`);
37
+ }
38
+ this.checkShorthandPolicy(op.method, entry, seenShorthand, seenLonghand);
39
+ const dynamics = op.args.filter(isDynamic);
40
+ if (dynamics.length === 0) {
41
+ bag[entry.property] = entry.format(...op.args);
42
+ delete slots[entry.property];
43
+ continue;
44
+ }
45
+ if (op.args.length !== 1 || dynamics.length !== 1) {
46
+ throw new Error(`[cassida] mixed/multi-dynamic args are not supported in this phase; method "${op.method}"`);
47
+ }
48
+ bag[entry.property] = DYNAMIC_PLACEHOLDER;
49
+ slots[entry.property] = dynamics[0].id;
50
+ continue;
51
+ }
52
+ if (isScopedOp(op)) {
53
+ const key = scopeKey(op.scope);
54
+ const existing = childOpsByKey.get(key);
55
+ if (existing) {
56
+ existing.ops.push(...op.ops);
57
+ }
58
+ else {
59
+ childOpsByKey.set(key, { scope: op.scope, ops: [...op.ops] });
60
+ }
61
+ continue;
62
+ }
63
+ if (isRawOp(op)) {
64
+ // RawOps bypass the registry entirely. They sit in the bag as
65
+ // pre-formatted CSS declarations. Shorthand-policy and family
66
+ // tracking deliberately do NOT apply — the user already opted
67
+ // out of the safety net by routing through `cas.unsafe`.
68
+ bag[op.property] = op.value;
69
+ delete slots[op.property];
70
+ continue;
71
+ }
72
+ }
73
+ const children = [];
74
+ for (const { scope: childScope, ops: childOps } of childOpsByKey.values()) {
75
+ children.push(this.collapse(childOps, childScope));
76
+ }
77
+ return {
78
+ scope,
79
+ bag,
80
+ slots,
81
+ children,
82
+ };
83
+ }
84
+ /**
85
+ * Apply the configured shorthand-policy check. Throws when the user
86
+ * mixes shorthand and longhand of the same family within a single
87
+ * scope in a way the policy forbids. The error message names both
88
+ * methods and points at the three escape hatches: modifier callback,
89
+ * looser `policy` config, or sticking to one form.
90
+ */
91
+ checkShorthandPolicy(methodName, entry, seenShorthand, seenLonghand) {
92
+ if (this.policy === 'lenient') {
93
+ // No-op, but still record so downstream code can introspect if needed.
94
+ if (entry.shorthandFamily)
95
+ seenShorthand.set(entry.shorthandFamily, methodName);
96
+ if (entry.longhandFamily)
97
+ seenLonghand.set(entry.longhandFamily, methodName);
98
+ return;
99
+ }
100
+ // longhand → shorthand check (strict only — the bug-prone direction).
101
+ if (entry.shorthandFamily) {
102
+ const family = entry.shorthandFamily;
103
+ const conflicting = seenLonghand.get(family);
104
+ if (this.policy === 'strict' && conflicting !== undefined) {
105
+ throw new Error(this.policyError('shorthand', methodName, conflicting, family));
106
+ }
107
+ seenShorthand.set(family, methodName);
108
+ }
109
+ // shorthand → longhand check (always — both 'strict' and 'shorthand-first').
110
+ if (entry.longhandFamily) {
111
+ const family = entry.longhandFamily;
112
+ const conflicting = seenShorthand.get(family);
113
+ if (conflicting !== undefined) {
114
+ throw new Error(this.policyError('longhand', methodName, conflicting, family));
115
+ }
116
+ seenLonghand.set(family, methodName);
117
+ }
118
+ }
119
+ policyError(incomingKind, incomingMethod, earlierMethod, family) {
120
+ const prior = incomingKind === 'shorthand' ? 'longhand' : 'shorthand';
121
+ return (`[cassida] ${incomingKind} "${incomingMethod}" cannot follow ${prior} "${earlierMethod}" ` +
122
+ `in the same scope (family: "${family}", policy: "${this.policy}"). ` +
123
+ `Resolve via one of:\n` +
124
+ ` • Use a modifier callback (.media(...), .hover(...), .on(...)) to scope them separately.\n` +
125
+ ` • Set "shorthand.policy" to "shorthand-first" or "lenient" in cassida.config.json.\n` +
126
+ ` • Stick to a single form (only shorthand, or only longhands) in this scope.`);
127
+ }
128
+ /**
129
+ * Deterministic key derived from a scope tree.
130
+ *
131
+ * For static-only chains (no modifiers, no nested scopes) the format
132
+ * matches Phase 1 exactly — sorted entry pairs — so existing class
133
+ * hashes are preserved across the Phase 2 upgrade. Trees with any
134
+ * nested scope use the structured form, which encodes the full
135
+ * sorted scope-tree shape.
136
+ */
137
+ canonicalKey(tree) {
138
+ if (tree.scope === null && tree.children.length === 0) {
139
+ const keys = Object.keys(tree.bag).sort();
140
+ return JSON.stringify(keys.map((k) => [k, tree.bag[k]]));
141
+ }
142
+ return JSON.stringify(serialize(tree));
143
+ }
144
+ }
145
+ function serialize(node) {
146
+ const bag = Object.keys(node.bag)
147
+ .sort()
148
+ .map((k) => [k, node.bag[k]]);
149
+ const sortedChildren = [...node.children].sort((a, b) => scopeOrderKey(a.scope).localeCompare(scopeOrderKey(b.scope)));
150
+ return {
151
+ s: serializeScope(node.scope),
152
+ b: bag,
153
+ c: sortedChildren.map(serialize),
154
+ };
155
+ }
156
+ function serializeScope(scope) {
157
+ if (scope === null)
158
+ return null;
159
+ if (scope.kind === 'pseudo')
160
+ return { k: 'pseudo', v: scope.selector };
161
+ if (scope.kind === 'media')
162
+ return { k: 'media', v: scope.query };
163
+ return { k: 'raw', v: scope.selector };
164
+ }
165
+ /**
166
+ * Stable string used to (a) identify same-scope ops in `collapse`, and
167
+ * (b) sort sibling children deterministically in `canonicalKey`.
168
+ *
169
+ * Pseudo scopes sort before media scopes before raw scopes, with
170
+ * lexicographic order within each group. This is purely a
171
+ * canonicalization device — emission ordering for output CSS is
172
+ * decided by the emitter (which uses the same prefix to keep `:hover`
173
+ * rules before `@media` rules in the output).
174
+ */
175
+ function scopeKey(scope) {
176
+ return scopeOrderKey(scope);
177
+ }
178
+ function scopeOrderKey(scope) {
179
+ if (scope.kind === 'pseudo')
180
+ return `0${scope.selector}`;
181
+ if (scope.kind === 'media')
182
+ return `1${scope.query}`;
183
+ return `2${scope.selector}`;
184
+ }
185
+ //# sourceMappingURL=canonicalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalizer.js","sourceRoot":"","sources":["../src/canonicalizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { type CompiledRule, type Op } from './types.js';
2
+ import type { Registry } from './registry.js';
3
+ import { type HashOptions } from './hasher.js';
4
+ import { type PropertyMeta } from './property-spec.js';
5
+ import type { ShorthandPolicy } from './config.js';
6
+ import { type CassPlugin, type PluginContext } from './plugin.js';
7
+ export interface CompileOptions extends HashOptions {
8
+ readonly registry: Registry;
9
+ /**
10
+ * Override the property → metadata table used to enrich dynamic slots
11
+ * with `animatable` / `syntax` / `initialValue`. Defaults to the
12
+ * canonical spec's own metadata; only override when extending the
13
+ * registry with custom properties that need their own `@property`
14
+ * descriptors.
15
+ */
16
+ readonly propertyMeta?: Readonly<Record<string, PropertyMeta>>;
17
+ /**
18
+ * Policy for shorthand ↔ longhand co-occurrence within a single
19
+ * scope. Defaults to `'strict'`. See `ShorthandPolicy` in `config.ts`.
20
+ */
21
+ readonly shorthandPolicy?: ShorthandPolicy;
22
+ /**
23
+ * Build-time plugins. Each plugin receives the post-collapse
24
+ * `ScopeBag` tree and returns a new tree; the className is derived
25
+ * from the post-plugin form. Plugins run in array order.
26
+ */
27
+ readonly plugins?: readonly CassPlugin[];
28
+ /**
29
+ * Subset of resolved config exposed to plugins through their
30
+ * `PluginContext`. Optional — if omitted, plugins receive a minimal
31
+ * default context.
32
+ */
33
+ readonly pluginContext?: PluginContext;
34
+ }
35
+ /**
36
+ * Pure: an `Op[]` chain in, a deterministic `CompiledRule` out.
37
+ *
38
+ * The canonical key is computed from the *shape* of the scope tree (not
39
+ * concrete dynamic values), so chains with the same set of properties
40
+ * and modifiers collapse to the same className regardless of the
41
+ * dynamic source values. Each dynamic slot ends up in `dynamics` with a
42
+ * fresh `--<className>-<scope-segments>-<prop>` variable name; the
43
+ * parser uses these to populate the element's inline style.
44
+ */
45
+ export declare function compileOps(ops: readonly Op[], options: CompileOptions): CompiledRule;
46
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,EAAE,EAIR,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAQ,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEhF,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IAC/D;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,cAAc,GAAG,YAAY,CA0BpF"}
@@ -0,0 +1,84 @@
1
+ import { DYNAMIC_PLACEHOLDER, } from './types.js';
2
+ import { Canonicalizer } from './canonicalizer.js';
3
+ import { hash } from './hasher.js';
4
+ import { defaultPropertyMeta } from './property-spec.js';
5
+ import { applyPlugins } from './plugin.js';
6
+ /**
7
+ * Pure: an `Op[]` chain in, a deterministic `CompiledRule` out.
8
+ *
9
+ * The canonical key is computed from the *shape* of the scope tree (not
10
+ * concrete dynamic values), so chains with the same set of properties
11
+ * and modifiers collapse to the same className regardless of the
12
+ * dynamic source values. Each dynamic slot ends up in `dynamics` with a
13
+ * fresh `--<className>-<scope-segments>-<prop>` variable name; the
14
+ * parser uses these to populate the element's inline style.
15
+ */
16
+ export function compileOps(ops, options) {
17
+ const canon = new Canonicalizer(options.registry, options.shorthandPolicy ?? 'strict');
18
+ const rawTree = canon.collapse(ops);
19
+ // Plugin pipeline: between collapse and canonicalKey so that any
20
+ // tree shape change (e.g. wrapping :hover in @media (hover: hover))
21
+ // propagates into the className. This is the FSS bijection
22
+ // contract: same hash ⇔ same final-state CSS.
23
+ const ctx = options.pluginContext ?? {
24
+ config: { layer: 'fss', importSource: '@cassida/core' },
25
+ };
26
+ const transformedTree = applyPlugins(rawTree, options.plugins, ctx);
27
+ const canonical = canon.canonicalKey(transformedTree);
28
+ const className = hash(canonical, options);
29
+ const meta = options.propertyMeta ?? defaultPropertyMeta;
30
+ const dynamics = [];
31
+ const tree = substituteVars(transformedTree, className, meta, dynamics, []);
32
+ return {
33
+ className,
34
+ tree,
35
+ canonical,
36
+ dynamics: Object.freeze(dynamics),
37
+ };
38
+ }
39
+ function substituteVars(node, className, meta, dynamics, scopePath) {
40
+ const newBag = {};
41
+ for (const prop of Object.keys(node.bag).sort()) {
42
+ const val = node.bag[prop];
43
+ if (val === DYNAMIC_PLACEHOLDER) {
44
+ const varName = makeVarName(className, scopePath, prop);
45
+ newBag[prop] = `var(${varName})`;
46
+ const m = meta[prop];
47
+ dynamics.push({
48
+ property: prop,
49
+ varName,
50
+ sourceId: node.slots[prop],
51
+ animatable: m?.animatable ?? false,
52
+ syntax: m?.syntax,
53
+ initialValue: m?.initialValue,
54
+ scopePath: [...scopePath],
55
+ });
56
+ }
57
+ else {
58
+ newBag[prop] = val;
59
+ }
60
+ }
61
+ const newChildren = node.children.map((c) => substituteVars(c, className, meta, dynamics, [...scopePath, c.scope]));
62
+ return {
63
+ scope: node.scope,
64
+ bag: Object.freeze(newBag),
65
+ slots: node.slots,
66
+ children: Object.freeze(newChildren),
67
+ };
68
+ }
69
+ function makeVarName(className, scopePath, prop) {
70
+ const parts = [className];
71
+ for (const s of scopePath) {
72
+ parts.push(scopeToVarSegment(s));
73
+ }
74
+ parts.push(prop);
75
+ return '--' + parts.join('-');
76
+ }
77
+ function scopeToVarSegment(s) {
78
+ if (s.kind === 'pseudo')
79
+ return s.selector.replace(/:/g, '_');
80
+ if (s.kind === 'media')
81
+ return 'm-' + s.query.replace(/[^a-zA-Z0-9]/g, '_');
82
+ return 'r-' + s.selector.replace(/[^a-zA-Z0-9]/g, '_');
83
+ }
84
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"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"}