Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/core-compound-ref-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@styleframe/core": patch
"styleframe": patch
---

Validate variable references inside compound `@`-shorthand values. `resolvePropertyValue` already threw `Variable "X" is not defined` for a single exact ref (`padding: "@0.5"`), but the embedded/compound path (`padding: "@0.25 @0.5"`, `border: "1px solid @undefined"`) only recorded usage and skipped `validateReference`, so a compound ref to an undefined variable silently emitted a dead `var(--…)`. The embedded branch now calls `validateReference` for each ref part, matching the single-ref path. Compound refs whose variables are all defined are unchanged (byte-for-byte identical output); only compound refs to undefined variables change — from a silent dead `var(--…)` to a thrown error.
2 changes: 1 addition & 1 deletion engine/core/src/tokens/atRule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ describe("createMediaFunction", () => {
const result = media(
"(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)",
{
backgroundImage: 'url("image@2x.png")',
backgroundImage: 'url("image-2x.png")',
},
);

Expand Down
22 changes: 22 additions & 0 deletions engine/core/src/tokens/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,13 @@ describe("createCSSFunction", () => {
});

it("should resolve embedded @reference to a CSS object", () => {
root.variables.push({
type: "variable",
id: "color-primary",
name: "color.primary",
value: "#006cff",
});

const result = resolvePropertyValue("1px solid @color.primary");

expect(result).toEqual({
Expand All @@ -630,6 +637,21 @@ describe("createCSSFunction", () => {
});

it("should resolve multiple embedded @references to a CSS object", () => {
root.variables.push(
{
type: "variable",
id: "spacing-sm",
name: "spacing.sm",
value: "0.5rem",
},
{
type: "variable",
id: "spacing-md",
name: "spacing.md",
value: "1rem",
},
);

const result = resolvePropertyValue("@spacing.sm @spacing.md");

expect(result).toEqual({
Expand Down
22 changes: 22 additions & 0 deletions engine/core/src/tokens/declarations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,13 @@ describe("parseDeclarationsBlock", () => {
});

it("should resolve embedded @references to a CSS object", () => {
root.variables.push({
type: "variable",
id: "color-primary",
name: "color.primary",
value: "#006cff",
});

const declarations: any = {
border: "1px solid @color.primary",
};
Expand All @@ -391,6 +398,21 @@ describe("parseDeclarationsBlock", () => {
});

it("should resolve multiple embedded @references to a CSS object", () => {
root.variables.push(
{
type: "variable",
id: "spacing-sm",
name: "spacing.sm",
value: "0.5rem",
},
{
type: "variable",
id: "spacing-md",
name: "spacing.md",
value: "1rem",
},
);

const declarations: any = {
padding: "@spacing.sm @spacing.md",
};
Expand Down
77 changes: 77 additions & 0 deletions engine/core/src/tokens/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,13 @@ describe("createPropertyValueResolver", () => {

describe("embedded @ references", () => {
it("should resolve embedded @variable to a CSS object", () => {
root.variables.push({
type: "variable",
id: "color-primary",
name: "color.primary",
value: "#006cff",
});

const result = resolvePropertyValue("1px solid @color.primary");

expect(result).toEqual({
Expand All @@ -329,6 +336,21 @@ describe("createPropertyValueResolver", () => {
});

it("should resolve multiple embedded @variables to a CSS object", () => {
root.variables.push(
{
type: "variable",
id: "border-width",
name: "border.width",
value: "1px",
},
{
type: "variable",
id: "color-primary",
name: "color.primary",
value: "#006cff",
},
);

const result = resolvePropertyValue("@border.width solid @color.primary");

expect(result).toEqual({
Expand All @@ -348,5 +370,60 @@ describe("createPropertyValueResolver", () => {
],
});
});

it("should throw when an embedded @variable is not defined", () => {
expect(() => resolvePropertyValue("@0.25 @0.5")).toThrow(
'Variable "0.25" is not defined',
);
});

it("should throw when only one of several embedded refs is undefined", () => {
root.variables.push({
type: "variable",
id: "border-width",
name: "border.width",
value: "1px",
});

expect(() =>
resolvePropertyValue("@border.width solid @color.primary"),
).toThrow('Variable "color.primary" is not defined');
});

it("should still compile embedded refs when every variable is defined", () => {
root.variables.push(
{
type: "variable",
id: "spacing-sm",
name: "spacing.sm",
value: "0.5rem",
},
{
type: "variable",
id: "spacing-md",
name: "spacing.md",
value: "1rem",
},
);

const result = resolvePropertyValue("@spacing.sm @spacing.md");

expect(result).toEqual({
type: "css",
value: [
{
type: "reference",
name: "spacing.sm",
fallback: undefined,
},
" ",
{
type: "reference",
name: "spacing.md",
fallback: undefined,
},
],
});
});
});
});
1 change: 1 addition & 0 deletions engine/core/src/tokens/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export function createPropertyValueResolver(parent: Container, root: Root) {
if (hasReferences) {
for (const part of parts) {
if (isRef(part)) {
validateReference(part.name, parent, root);
root._usage.variables.add(part.name);
}
}
Expand Down
Loading