Skip to content

Commit d565062

Browse files
authored
Merge pull request #12537 from quarto-dev/bugfix/11665
brand - warn on invalid color name usage
2 parents 71d7413 + 280e7ee commit d565062

File tree

4 files changed

+210
-13
lines changed

4 files changed

+210
-13
lines changed

src/core/brand/brand.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
import { InternalError } from "../lib/error.ts";
1919

2020
import { join, relative } from "../../deno_ral/path.ts";
21+
import { warnOnce } from "../log.ts";
22+
import { isCssColorName } from "../css/color-names.ts";
2123

2224
// we can't programmatically convert typescript types to string arrays,
2325
// so we have to define this manually. They should match `BrandNamedThemeColor` in schema-types.ts
@@ -176,7 +178,7 @@ export class Brand {
176178
// - if the name is in the "palette" key, use that value as they key for a recursive call (so color names can be aliased or redefined away from scss defaults)
177179
// - if the name is a default color name, call getColor recursively (so defaults can use named values)
178180
// - otherwise, assume it's a color value and return it
179-
getColor(name: string): string {
181+
getColor(name: string, quiet = false): string {
180182
const seenValues = new Set<string>();
181183

182184
do {
@@ -198,6 +200,12 @@ export class Brand {
198200
) {
199201
name = this.data.color[name as BrandNamedThemeColor]!;
200202
} else {
203+
// if the name is not a default color name, assume it's a color value
204+
if (!isCssColorName(name) && !quiet) {
205+
warnOnce(
206+
`"${name}" is not a valid CSS color name.\nThis might cause SCSS compilation to fail, or the color to have no effect.`,
207+
);
208+
}
201209
return name;
202210
}
203211
} while (seenValues.size < 100); // 100 ought to be enough for anyone, with apologies to Bill Gates

src/core/css/color-names.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* color-names.ts
3+
*
4+
* Copyright (C) 2025 Posit Software, PBC
5+
*/
6+
7+
// https://www.w3.org/TR/css-color-3/#svg-color
8+
export const isCssColorName = (name: string): boolean => {
9+
if (name.startsWith("#")) return true;
10+
if (name.startsWith("rgb")) return true;
11+
if (name.startsWith("hsl")) return true;
12+
if (name.startsWith("hwb")) return true;
13+
14+
return [
15+
"aliceblue",
16+
"antiquewhite",
17+
"aqua",
18+
"aquamarine",
19+
"azure",
20+
"beige",
21+
"bisque",
22+
"black",
23+
"blanchedalmond",
24+
"blue",
25+
"blueviolet",
26+
"brown",
27+
"burlywood",
28+
"cadetblue",
29+
"chartreuse",
30+
"chocolate",
31+
"coral",
32+
"cornflowerblue",
33+
"cornsilk",
34+
"crimson",
35+
"cyan",
36+
"darkblue",
37+
"darkcyan",
38+
"darkgoldenrod",
39+
"darkgray",
40+
"darkgreen",
41+
"darkgrey",
42+
"darkkhaki",
43+
"darkmagenta",
44+
"darkolivegreen",
45+
"darkorange",
46+
"darkorchid",
47+
"darkred",
48+
"darksalmon",
49+
"darkseagreen",
50+
"darkslateblue",
51+
"darkslategray",
52+
"darkslategrey",
53+
"darkturquoise",
54+
"darkviolet",
55+
"deeppink",
56+
"deepskyblue",
57+
"dimgray",
58+
"dimgrey",
59+
"dodgerblue",
60+
"firebrick",
61+
"floralwhite",
62+
"forestgreen",
63+
"fuchsia",
64+
"gainsboro",
65+
"ghostwhite",
66+
"gold",
67+
"goldenrod",
68+
"gray",
69+
"green",
70+
"greenyellow",
71+
"grey",
72+
"honeydew",
73+
"hotpink",
74+
"indianred",
75+
"indigo",
76+
"ivory",
77+
"khaki",
78+
"lavender",
79+
"lavenderblush",
80+
"lawngreen",
81+
"lemonchiffon",
82+
"lightblue",
83+
"lightcoral",
84+
"lightcyan",
85+
"lightgoldenrodyellow",
86+
"lightgray",
87+
"lightgreen",
88+
"lightgrey",
89+
"lightpink",
90+
"lightsalmon",
91+
"lightseagreen",
92+
"lightskyblue",
93+
"lightslategray",
94+
"lightslategrey",
95+
"lightsteelblue",
96+
"lightyellow",
97+
"lime",
98+
"limegreen",
99+
"linen",
100+
"magenta",
101+
"maroon",
102+
"mediumaquamarine",
103+
"mediumblue",
104+
"mediumorchid",
105+
"mediumpurple",
106+
"mediumseagreen",
107+
"mediumslateblue",
108+
"mediumspringgreen",
109+
"mediumturquoise",
110+
"mediumvioletred",
111+
"midnightblue",
112+
"mintcream",
113+
"mistyrose",
114+
"moccasin",
115+
"navajowhite",
116+
"navy",
117+
"oldlace",
118+
"olive",
119+
"olivedrab",
120+
"orange",
121+
"orangered",
122+
"orchid",
123+
"palegoldenrod",
124+
"palegreen",
125+
"paleturquoise",
126+
"palevioletred",
127+
"papayawhip",
128+
"peachpuff",
129+
"peru",
130+
"pink",
131+
"plum",
132+
"powderblue",
133+
"purple",
134+
"red",
135+
"rosybrown",
136+
"royalblue",
137+
"saddlebrown",
138+
"salmon",
139+
"sandybrown",
140+
"seagreen",
141+
"seashell",
142+
"sienna",
143+
"silver",
144+
"skyblue",
145+
"slateblue",
146+
"slategray",
147+
"slategrey",
148+
"snow",
149+
"springgreen",
150+
"steelblue",
151+
"tan",
152+
"teal",
153+
"thistle",
154+
"tomato",
155+
"turquoise",
156+
"violet",
157+
"wheat",
158+
"white",
159+
"whitesmoke",
160+
"yellow",
161+
"yellowgreen",
162+
].includes(name);
163+
};

src/core/sass/brand.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ import {
2222
BrandFontWeight,
2323
} from "../../resources/types/schema-types.ts";
2424
import { Brand } from "../brand/brand.ts";
25-
import {
26-
darkModeDefault,
27-
} from "../../format/html/format-html-info.ts";
28-
25+
import { darkModeDefault } from "../../format/html/format-html-info.ts";
2926

3027
const defaultColorNameMap: Record<string, string> = {
3128
"link-color": "link",
@@ -81,8 +78,8 @@ export async function brandBootstrapSassBundles(
8178
dependency: "bootstrap",
8279
user: layers.light,
8380
dark: {
84-
user: layers.dark
85-
}
81+
user: layers.dark,
82+
},
8683
}];
8784
}
8885

@@ -187,7 +184,7 @@ const brandColorLayer = (
187184

188185
// format-specific name mapping
189186
for (const [key, value] of Object.entries(nameMap)) {
190-
const resolvedValue = brand.getColor(value);
187+
const resolvedValue = brand.getColor(value, true);
191188
if (resolvedValue !== value) {
192189
colorVariables.push(
193190
`$${key}: ${resolvedValue} !default;`,
@@ -579,7 +576,7 @@ export async function brandSassLayers(
579576
const brand = await project.resolveBrand(fileName);
580577
const sassLayers: LightDarkSassLayers = {
581578
light: [],
582-
dark: []
579+
dark: [],
583580
};
584581

585582
for (const mode of ["light", "dark"] as Array<"dark" | "light">) {
@@ -660,10 +657,12 @@ export async function brandSassFormatExtras(
660657
key: "brand",
661658
dependency: "bootstrap",
662659
user: htmlSassBundleLayers.light,
663-
dark: htmlSassBundleLayers.dark.length ? {
664-
user: htmlSassBundleLayers.dark,
665-
default: darkModeDefault(format.metadata)
666-
} : undefined
660+
dark: htmlSassBundleLayers.dark.length
661+
? {
662+
user: htmlSassBundleLayers.dark,
663+
default: darkModeDefault(format.metadata),
664+
}
665+
: undefined,
667666
},
668667
],
669668
},
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: Hello, `_brand.yml`!
3+
brand:
4+
typography:
5+
monospace:
6+
color: foreground
7+
format: html
8+
_quarto:
9+
tests:
10+
html:
11+
noErrors: default
12+
printsMessage:
13+
level: WARN
14+
regex: 'foreground.*valid CSS color name'
15+
---
16+
17+
## Links
18+
19+
Download the Positron Beta here: <https://positron.posit.co/download.html>
20+
21+
Docs for `_brand.yml`: <https://posit-dev.github.io/brand-yml/>, <https://quarto.org/docs/authoring/brand.html>
22+
23+
## Placeholder text
24+
25+
This is an example document with a [number](https://quarto.org) of `features` to help you come with a nicely branded document.
26+
27+
Duis ornare ex ac iaculis pretium. Maecenas sagittis odio id erat pharetra, sit amet consectetur quam sollicitudin. Vivamus pharetra quam purus, nec sagittis risus pretium at. Nullam feugiat, turpis ac accumsan interdum, sem tellus blandit neque, id vulputate diam quam semper nisl. Donec sit amet enim at neque porttitor aliquet. Phasellus facilisis nulla eget placerat eleifend. Vestibulum non egestas eros, eget lobortis ipsum. Nulla rutrum massa eget enim aliquam, id porttitor erat luctus. Nunc sagittis quis eros eu sagittis. Pellentesque dictum, erat at pellentesque sollicitudin, justo augue pulvinar metus, quis rutrum est mi nec felis. Vestibulum efficitur mi lorem, at elementum purus tincidunt a. Aliquam finibus enim magna, vitae pellentesque erat faucibus at. Nulla mauris tellus, imperdiet id lobortis et, dignissim condimentum ipsum. Morbi nulla orci, varius at aliquet sed, facilisis id tortor. Donec ut urna nisi.

0 commit comments

Comments
 (0)