diff --git a/src/core/sass.ts b/src/core/sass.ts index a94305f220d..5adfeef0077 100644 --- a/src/core/sass.ts +++ b/src/core/sass.ts @@ -371,7 +371,6 @@ export async function compileWithCache( console.warn( "This is likely a Quarto bug.\nPlease consider reporting it at https://github.com/quarto-dev/quarto-cli,\nalong with the _quarto_internal_scss_error.scss file that can be found in the current working directory.", ); - throw e; } return input; }; diff --git a/src/core/sass/analyzer/parse.ts b/src/core/sass/analyzer/parse.ts index 84680a5ffba..34183bd7830 100644 --- a/src/core/sass/analyzer/parse.ts +++ b/src/core/sass/analyzer/parse.ts @@ -14,6 +14,16 @@ export const makeParserModule = ( // it also doesn't like some valid ways to do '@import url' contents = contents.replaceAll("@import url", "//@import url"); + // https://github.com/quarto-dev/quarto-cli/issues/11121 + // It also doesn't like empty rules + + // that long character class rule matches everything in \s except for \n + // using the explanation from regex101.com as a reference + contents = contents.replaceAll( + /^[\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*([^\n{]+)([{])\s*([}])$/mg, + "$1$2 /* empty rule */ $3", + ); + // it also really doesn't like statements that don't end in a semicolon // so, in case you are reading this code to understand why the parser is failing, // ensure that your SCSS has semicolons at the end of every statement. diff --git a/tests/docs/smoke-all/2024/10/22/issue-11121.qmd b/tests/docs/smoke-all/2024/10/22/issue-11121.qmd new file mode 100644 index 00000000000..36d8b636065 --- /dev/null +++ b/tests/docs/smoke-all/2024/10/22/issue-11121.qmd @@ -0,0 +1,9 @@ +--- +format: + html: + theme: + - cerulean + - test.scss +--- + +## This will crash \ No newline at end of file diff --git a/tests/docs/smoke-all/2024/10/22/test.scss b/tests/docs/smoke-all/2024/10/22/test.scss new file mode 100644 index 00000000000..7d6972d01a8 --- /dev/null +++ b/tests/docs/smoke-all/2024/10/22/test.scss @@ -0,0 +1,4 @@ +/*-- scss:rules --*/ + +.agenda { +} diff --git a/tools/sass-variable-explainer/parse.ts b/tools/sass-variable-explainer/parse.ts index 554826c8268..34183bd7830 100644 --- a/tools/sass-variable-explainer/parse.ts +++ b/tools/sass-variable-explainer/parse.ts @@ -2,22 +2,44 @@ // that works in Deno and on the web import { walk } from "./ast-utils.ts"; - +let counter = 1; export const makeParserModule = ( parse: any, - prettierFormat: any ) => { return { - getSassAst: async (contents: string) => { + getSassAst: (contents: string) => { // scss-parser doesn't support the `...` operator and it breaks their parser oO, so we remove it. // our analysis doesn't need to know about it. contents = contents.replaceAll("...", "_dot_dot_dot"); // it also doesn't like some valid ways to do '@import url' contents = contents.replaceAll("@import url", "//@import url"); - // the scss-parser also apparently breaks on Quarto's SCSS unless it's - // been prettified first :shrug: - contents = await prettierFormat(contents, { parser: "scss" }); + // https://github.com/quarto-dev/quarto-cli/issues/11121 + // It also doesn't like empty rules + + // that long character class rule matches everything in \s except for \n + // using the explanation from regex101.com as a reference + contents = contents.replaceAll( + /^[\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*([^\n{]+)([{])\s*([}])$/mg, + "$1$2 /* empty rule */ $3", + ); + + // it also really doesn't like statements that don't end in a semicolon + // so, in case you are reading this code to understand why the parser is failing, + // ensure that your SCSS has semicolons at the end of every statement. + // we try to work around this by adding semicolons at the end of declarations that don't have them + contents = contents.replaceAll( + /^(?!(?=\/\/)|(?=\s*[@#$]))(.*[^}/\s\n;])([\s\n]*)}(\n|$)/mg, + "$1;$2}$3", + ); + // It also doesn't like values that follow a colon directly without a space + contents = contents.replaceAll( + /(^\s*[A-Za-z0-9-]+):([^ \n])/mg, + "$1: $2", + ); + + // This is relatively painful, because unfortunately the error message of scss-parser + // is not helpful. // Create an AST from a string of SCSS // and convert it to a plain JSON object @@ -25,7 +47,7 @@ export const makeParserModule = ( if (!(ast.type === "stylesheet")) { throw new Error("Expected AST to have type 'stylesheet'"); - }; + } if (!Array.isArray(ast.value)) { throw new Error("Expected AST to have an array value"); } @@ -53,6 +75,6 @@ export const makeParserModule = ( }); return ast; - } - } -} + }, + }; +};