Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tools/sass-variable-explainer/declaration-types.ts
6438 views
1
export const propagateDeclarationTypes = (ast: any) => {
2
3
const declarationsToTrack = new Map<string, any>();
4
5
const typesToSkip = new Set([
6
"identifier", "boolean",
7
"number", "dimension", "percentage",
8
"string", "string_double", "string_single"]);
9
10
const namesToIgnore: Set<string> = new Set([
11
"title-banner-image", // with this hack, we can assume all other instances of 'null' are color
12
"theme",
13
"theme-name",
14
"enable-grid-classes",
15
"enable-cssgrid",
16
"nav-tabs-link-active-border-color",
17
"navbar-light-bg",
18
"navbar-dark-bg",
19
"navbar-light-color",
20
"navbar-light-hover-color",
21
"navbar-light-active-color",
22
"navbar-light-disabled-color",
23
"navbar-light-toggler-icon-bg",
24
"navbar-light-toggler-border-color",
25
"navbar-light-brand-color",
26
"navbar-light-brand-hover-color",
27
"navbar-dark-color",
28
"navbar-dark-hover-color",
29
"navbar-dark-active-color",
30
"navbar-dark-disabled-color",
31
"navbar-dark-toggler-icon-bg",
32
"navbar-dark-toggler-border-color",
33
"navbar-dark-brand-color",
34
"navbar-dark-brand-hover-color",
35
]);
36
37
const speciallyKnownTypes: Record<string, string> = {
38
"link-color": "color",
39
"input-border-color": "color",
40
"title-banner-color": "color",
41
"theme": "string",
42
};
43
44
for (const node of ast.children) {
45
if (node?.type !== "declaration") {
46
continue;
47
}
48
49
// ignore declarations that have documented
50
// non-standard !default rules because of the
51
// way we set if() conditions in our SCSS
52
const varName = node?.property?.variable?.value;
53
if (namesToIgnore.has(varName)) {
54
continue;
55
}
56
const varValue = node?.value?.value;
57
if (declarationsToTrack.has(varName)) {
58
const prevDeclaration = declarationsToTrack.get(varName);
59
if (prevDeclaration?.value?.isDefault &&
60
node?.value?.isDefault) {
61
// pass
62
} else if (!prevDeclaration?.value?.isDefault &&
63
!node?.value?.isDefault) {
64
declarationsToTrack.set(varName, node);
65
} else {
66
// are these special cases?
67
if (speciallyKnownTypes[varName]) {
68
node.valueType = speciallyKnownTypes[varName];
69
declarationsToTrack.set(varName, node);
70
} else {
71
console.log("Warning: variable redeclaration with conflicting default settings");
72
console.log("variable: ", varName);
73
console.log("lines ", prevDeclaration?.line, node?.line);
74
}
75
}
76
} else {
77
declarationsToTrack.set(varName, node);
78
}
79
}
80
81
const valueTypeMap: Record<string, string> = {
82
"number": "number",
83
"boolean": "boolean",
84
"string": "string",
85
"dimension": "dimension",
86
"percentage": "percentage",
87
"identifier": "identifier",
88
"color_hex": "color",
89
"named_color": "color",
90
"string_double": "string",
91
"string_single": "string",
92
"null": "color", // This is specific to our themes, and requires excluding 'title-banner-image' above
93
};
94
95
const functionTypeResolver: Record<string, (node: any) => string | undefined> = {
96
"theme-contrast": (_: any) => "color",
97
"quote": (_: any) => "string",
98
"url": (_: any) => "string",
99
"tint-color": (_: any) => "color",
100
"shade-color": (_: any) => "color",
101
"lighten": (_: any) => "color",
102
"darken": (_: any) => "color",
103
"mix": (_: any) => "color",
104
"shift-color": (_: any) => "color",
105
"linear-gradient": (_: any) => "image",
106
"color-contrast": (_: any) => "color",
107
"translate3d": (_: any) => "transform-function",
108
"rotate": (_: any) => "transform-function",
109
"translate": (_: any) => "transform-function",
110
"scale": (_: any) => "transform-function",
111
"calc": (_: any) => "calc-value", // this can be a number, percentage, or dimension, but we don't presently care
112
"add": (_: any) => "__unimplemented__",
113
"subtract": (_: any) => "__unimplemented__",
114
"brightness": (_: any) => "filter",
115
"minmax": (_: any) => "grid-template",
116
"var": (valueNode: any) => "__unimplemented__",
117
118
// This is used in bslib as an instance of the hack described here:
119
// https://css-tricks.com/when-sass-and-new-css-features-collide/
120
// it's truly atrocious and we'll never be able to track this kind of thing properly,
121
// but we can at least make sure it doesn't break the rest of the analysis
122
"Min": (_: any) => "__unimplemented__",
123
124
"theme-override-value": (valueNode: any) => {
125
const defaultValue = valueNode?.arguments?.children[2];
126
if (defaultValue && typeForValue(defaultValue)) {
127
return typeForValue(defaultValue);
128
} else {
129
return undefined;
130
}
131
},
132
"if": (valueNode: any) => {
133
const _condition = valueNode?.arguments?.children[0];
134
const trueValue = valueNode?.arguments?.children[1];
135
const falseValue = valueNode?.arguments?.children[2];
136
// we will assume type consistency for now
137
if (trueValue) {
138
const trueType = typeForValue(trueValue);
139
if (trueType) {
140
return trueType;
141
}
142
}
143
if (falseValue) {
144
const falseType = typeForValue(falseValue);
145
if (falseType) {
146
return falseType;
147
}
148
}
149
return undefined;
150
}
151
}
152
153
const typeForValue = (valueNode: any): string | undefined => {
154
const nodeValueType = valueNode?.type;
155
if (valueTypeMap[nodeValueType]) {
156
return valueTypeMap[nodeValueType];
157
}
158
if (nodeValueType === "variable") {
159
const nodeVariableName = valueNode?.value;
160
if (!declarationsToTrack.has(nodeVariableName) && !namesToIgnore.has(nodeVariableName)) {
161
console.log("Warning: variable used before declaration");
162
console.log("variable: ", nodeVariableName, valueNode.line);
163
return undefined;
164
} else {
165
const valueType = declarationsToTrack.get(nodeVariableName)?.valueType;
166
if (valueType) {
167
return valueType;
168
}
169
}
170
}
171
if (nodeValueType === "function") {
172
const functionName = valueNode?.identifier?.value;
173
if (functionTypeResolver[functionName]) {
174
return functionTypeResolver[functionName](valueNode);
175
}
176
}
177
}
178
179
// tag all declarations with values of known types
180
for (const [name, node] of declarationsToTrack) {
181
if (node?.value?.type === "value") {
182
const valueType = node.value.value[0].valueType || typeForValue(node.value.value[0]);
183
if (valueType) {
184
node.valueType = valueType;
185
}
186
}
187
}
188
return declarationsToTrack;
189
190
// // now warn about variables with unknown types
191
// for (const [name, node] of declarationsToTrack) {
192
// if (node.valueType === "color") {
193
// console.log(name, node.line)
194
// }
195
// if (!node.valueType && node.value.value.length === 1) {
196
// // ignore unknown types for multi-value declarations, assume they're arrays which we don't care about.
197
// if (node.value.value[0]?.type === "parentheses") {
198
// continue;
199
// }
200
// console.log("Warning: variable with unknown type");
201
// console.log("variable: ", name, node);
202
// }
203
// }
204
}
205
206