Skip to content

Commit 5376be1

Browse files
committed
chore(dev-utils): updated variables command to work with sass
1 parent a8e8df3 commit 5376be1

8 files changed

Lines changed: 256 additions & 192 deletions

File tree

packages/dev-utils/src/utils/styles/createScssVariables.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,20 @@ function toJSONValue(value: VariableValue): JSONValue {
3333

3434
export async function createScssVariables(): Promise<void> {
3535
const items = await getSassdoc();
36-
const variables = items.reduce<PackageVariables>((collection, variable) => {
37-
if (isVariableItem(variable) && isPublic(variable)) {
38-
const group = variable.group[0].replace(/^(form)-.+$/, "$1");
39-
collection[group] = collection[group] || {};
40-
41-
const { name, value } = getCompiledValue(variable);
42-
collection[group][name] = toJSONValue(value);
43-
}
44-
return collection;
45-
}, {});
36+
const variables = items.reduce<PackageVariables>(
37+
(collection, variable, i) => {
38+
if (isVariableItem(variable) && isPublic(variable)) {
39+
const group = variable.group[0].replace(/^(form)-.+$/, "$1");
40+
collection[group] = collection[group] || {};
41+
42+
const { name, value } = getCompiledValue(variable, i);
43+
collection[group][name] = toJSONValue(value);
44+
}
45+
46+
return collection;
47+
},
48+
{}
49+
);
4650

4751
const promises = Object.entries(variables).map(([packageName, json]) => {
4852
const contents = format(

packages/dev-utils/src/utils/styles/variable.ts

Lines changed: 144 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -48,126 +48,186 @@ function parseValue(value: VariableValue): VariableValue {
4848
return number;
4949
}
5050

51-
return value;
51+
// remove additional quotes around strings
52+
if (/^('|").+\1$/.test(value)) {
53+
value = value.substring(1, value.length - 1);
54+
}
55+
56+
// remove additional spaces that get added to css functions somtimes
57+
return value.replace(/((\()\s+)|(\s+(\)))/g, "$2$4");
5258
}
5359

54-
function matchParen(s: string, count = 0): string {
60+
function consumeMatchingParens(s: string, totalParens = 0): string {
5561
const match = s.match(/\(|\)/);
5662
if (!match || typeof match.index === "undefined") {
5763
return s;
5864
}
5965

6066
const i = match.index + 1;
67+
let addition = 1;
6168
if (match[0] === ")") {
62-
if (count === 1) {
69+
if (totalParens === 1) {
6370
return s.substring(0, i);
6471
}
6572

66-
return s.substring(0, i) + matchParen(s.substring(i), count - 1);
73+
addition = -1;
6774
}
6875

69-
return s.substring(0, i) + matchParen(s.substring(i), count + 1);
76+
return (
77+
s.substring(0, i) +
78+
consumeMatchingParens(s.substring(i), totalParens + addition)
79+
);
80+
}
81+
82+
function isCssFunction(remaining: string, endIndex: number): boolean {
83+
return /(var|calc|rgba?|rotate(3d)?|translate(X|Y|3d)?|cubic-bezier)\(/.test(
84+
remaining.substring(0, endIndex + 1)
85+
);
7086
}
7187

72-
function parseMap(mapValue: string): ValuedVariable[] {
73-
let remaining = mapValue.substring(1, mapValue.length - 1);
88+
const VALUE_SEP = ": ";
89+
90+
function parseMap(mapString: string): ValuedVariable[] {
7491
const values: ValuedVariable[] = [];
92+
93+
if (mapString[0] !== "(" || mapString[mapString.length - 1] !== ")") {
94+
throw new Error("INVALID MAP FORMAT");
95+
}
96+
97+
let remaining = mapString.substring(1, mapString.length - 1);
7598
while (remaining.length) {
76-
const i = remaining.indexOf(": ");
77-
if (i === -1) {
78-
log.error(
79-
"Unable to hack a css variable correctly since no valid key/value split was found."
80-
);
81-
log.error("Original Map Value: ", mapValue);
82-
log.error("Remaining string: ", remaining);
83-
process.exit(1);
99+
const sepIndex = remaining.indexOf(VALUE_SEP);
100+
if (sepIndex === -1) {
101+
throw new Error("Unable to find a name");
84102
}
85103

86-
const name = remaining.substring(0, i);
87-
remaining = remaining.substring(i + 2);
88-
let j =
89-
name === "font-family"
90-
? remaining.search(/, [-a-z]+: /)
91-
: remaining.indexOf(",");
92-
if (j === -1) {
93-
j = remaining.length;
104+
const name = remaining.substring(0, sepIndex);
105+
remaining = remaining.substring(sepIndex + VALUE_SEP.length);
106+
if (name === "font-family") {
107+
const tokenIndex = remaining.search(/, [-a-z]+: /);
108+
if (tokenIndex !== -1) {
109+
const value = remaining.substring(0, tokenIndex);
110+
values.push({
111+
name,
112+
value: parseValue(value),
113+
});
114+
remaining = remaining.substring(tokenIndex + 1).trim();
115+
continue;
116+
} else {
117+
throw new Error("unsupported font-family value");
118+
}
94119
}
95120

96-
const remainingString = remaining.substring(0, j);
97-
let value: VariableValue = remainingString;
98-
if (remainingString.startsWith("(")) {
99-
const mapString = matchParen(remaining);
100-
j = mapString.length;
101-
value = parseMap(mapString);
102-
} else if (remainingString.includes("(")) {
103-
const matched = matchParen(remaining);
104-
j = matched.length;
105-
value = matched;
121+
const nextMatch = remaining.match(/\(|\)|,/);
122+
if (!nextMatch) {
123+
values.push({
124+
name,
125+
value: parseValue(remaining),
126+
});
127+
break;
106128
}
107129

108-
value = parseValue(value);
109-
remaining = remaining.substring(j + 1).trim();
110-
values.push({ name, value });
130+
const nextMatchIndex = nextMatch.index;
131+
if (typeof nextMatchIndex !== "number") {
132+
throw new Error("NO MATCHINDEX");
133+
}
134+
135+
const token = remaining[nextMatchIndex];
136+
if (token === ",") {
137+
values.push({
138+
name,
139+
value: parseValue(remaining.substring(0, nextMatchIndex)),
140+
});
141+
142+
remaining = remaining.substring(nextMatchIndex + 1);
143+
} else if (token === ")") {
144+
throw new Error(`token: "${token}"`);
145+
} else if (isCssFunction(remaining, nextMatchIndex)) {
146+
const variableValue = consumeMatchingParens(remaining);
147+
values.push({
148+
name,
149+
value: parseValue(variableValue),
150+
});
151+
remaining = remaining.substring(variableValue.length + 1);
152+
} else {
153+
const nextMap = consumeMatchingParens(remaining);
154+
values.push({
155+
name,
156+
value: parseMap(nextMap),
157+
});
158+
159+
remaining = remaining.substring(nextMap.length).replace(/^,\s+/, "");
160+
}
161+
162+
remaining = remaining.trim();
111163
}
112164

113165
return values;
114166
}
115167

116-
export function getCompiledValue(variable: VariableItem): ValuedVariable {
168+
export function getCompiledValue(
169+
variable: VariableItem,
170+
index?: number
171+
): ValuedVariable {
117172
const {
118173
file: { path },
119-
context: { name, value },
174+
context: { name, value: originalValue },
120175
type,
121176
} = variable;
122177

123-
const prefix = `$${name}: `;
124-
const data = `@import '${path}';
125-
@error '${prefix}#{${value}}'`;
126-
127-
try {
128-
renderSync({
129-
data,
130-
outputStyle: "expanded",
131-
includePaths: [tempStylesDir],
132-
});
133-
throw new Error("Should never happen");
134-
} catch (e) {
135-
const { message } = e as Error;
136-
if (/Undefined variable |File to import not found/.test(message)) {
137-
log.error(`Unable to hackily generate the value for "${name}"`);
138-
log.error();
139-
log.error(message);
140-
process.exit(1);
141-
}
178+
// sanity check for typos since sassdoc doesn't have any validation for this
179+
if (!isValidType(type || "")) {
180+
log.error(`${name} variable has an invalid @type declaration: "${type}"`);
181+
log.error(`index is: "${index}"`);
182+
log.error();
183+
process.exit(1);
184+
}
142185

143-
let value: VariableValue = message.substring(prefix.length);
144-
switch (type) {
145-
case "List": {
146-
const parsed = value
147-
.split(/\s|,/)
148-
.map((part) => parseValue(part)) as SimplePrimative[];
186+
const output = renderSync({
187+
data: `@use 'sass:meta';
188+
@use 'sass:math';
149189
150-
value = parsed;
151-
break;
152-
}
153-
case "Map":
154-
value = parseMap(value);
155-
break;
156-
default:
157-
// sanity check for typos since sassdoc doesn't have any validation for this
158-
if (!isValidType(type || "")) {
159-
log.error(
160-
`${name} variable has an invalid @type declaration: "${type}"`
161-
);
162-
log.error();
163-
process.exit(1);
164-
}
165-
value = parseValue(value);
166-
}
190+
@import '${path}';
167191
168-
return {
169-
name,
170-
value,
171-
};
192+
.output {
193+
--value: #{meta.inspect(${originalValue})};
194+
}
195+
`,
196+
outputStyle: "expanded",
197+
includePaths: [tempStylesDir],
198+
}).css.toString();
199+
// since the `rmd-option-selected-content` is unicode, an `@charset` value
200+
// might also be rendered in the output
201+
const compiledValue = output
202+
.substring(output.indexOf("--value") + "--value: ".length)
203+
.replace(";\n}", "");
204+
205+
let value: VariableValue;
206+
switch (type) {
207+
case "List":
208+
value = compiledValue
209+
.split(/\s|,/)
210+
.map((part) => parseValue(part)) as SimplePrimative[];
211+
break;
212+
case "Map":
213+
try {
214+
value = parseMap(compiledValue);
215+
} catch (e) {
216+
log.error(`Unable to parse the map: ${name}`);
217+
log.error(`index is: "${index}"`);
218+
log.error(compiledValue);
219+
if ("message" in e) {
220+
log.error(e.message);
221+
}
222+
process.exit(1);
223+
}
224+
break;
225+
default:
226+
value = parseValue(compiledValue);
172227
}
228+
229+
return {
230+
name,
231+
value,
232+
};
173233
}

0 commit comments

Comments
 (0)