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