Path: blob/main/tools/sass-variable-explainer/parse.ts
6438 views
// our attempt at factoring out a clean parser1// that works in Deno and on the web23import { walk } from "./ast-utils.ts";4let counter = 1;5export const makeParserModule = (6parse: any,7) => {8return {9getSassAst: (contents: string) => {10// scss-parser doesn't support the `...` operator and it breaks their parser oO, so we remove it.11// our analysis doesn't need to know about it.12contents = contents.replaceAll("...", "_dot_dot_dot");13// it also doesn't like some valid ways to do '@import url'14contents = contents.replaceAll("@import url", "//@import url");1516// https://github.com/quarto-dev/quarto-cli/issues/1112117// It also doesn't like empty rules1819// that long character class rule matches everything in \s except for \n20// using the explanation from regex101.com as a reference21contents = contents.replaceAll(22/^[\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*([^\n{]+)([{])\s*([}])$/mg,23"$1$2 /* empty rule */ $3",24);2526// it also really doesn't like statements that don't end in a semicolon27// so, in case you are reading this code to understand why the parser is failing,28// ensure that your SCSS has semicolons at the end of every statement.29// we try to work around this by adding semicolons at the end of declarations that don't have them30contents = contents.replaceAll(31/^(?!(?=\/\/)|(?=\s*[@#$]))(.*[^}/\s\n;])([\s\n]*)}(\n|$)/mg,32"$1;$2}$3",33);34// It also doesn't like values that follow a colon directly without a space35contents = contents.replaceAll(36/(^\s*[A-Za-z0-9-]+):([^ \n])/mg,37"$1: $2",38);3940// This is relatively painful, because unfortunately the error message of scss-parser41// is not helpful.4243// Create an AST from a string of SCSS44// and convert it to a plain JSON object45const ast = JSON.parse(JSON.stringify(parse(contents)));4647if (!(ast.type === "stylesheet")) {48throw new Error("Expected AST to have type 'stylesheet'");49}50if (!Array.isArray(ast.value)) {51throw new Error("Expected AST to have an array value");52}5354// rename 'value' to 'children'55// because they also use 'value' for the value of a property5657// this is the only place we'll use 'walk' instead of the58// more explicit 'mapDeep' and 'filterValuesDeep' functions59// below, which will then assume 'children'6061walk(ast, (node: any) => {62if (Array.isArray(node)) {63return true;64}65if (["value", "identifier", "operator"].includes(node?.type)) {66return true;67}68if (!node?.value || !Array.isArray(node.value)) {69return true;70}71node.children = node.value;72delete node.value;73return true;74});7576return ast;77},78};79};808182