Skip to content

Conversation

jgerigmeyer
Copy link
Member

@jgerigmeyer jgerigmeyer commented May 14, 2025

Notes

  • This is very work-in-progress. The current state ignores less and css parsing, and is just focused on using sass-parser to parse Sass/SCSS.
  • The quickest way to test this locally is to create a test.scss file and run yarn debug test.scss -- you can then compare the output to the Prettier playground if useful.
  • Tests are mostly failing, but can reveal aspects that are still missing: yarn test tests/format/scss

Description

Uses sass-parser for parsing Sass (instead of postcss-scss, postcss-media-query-parser, postcss-selector-parser, and postcss-values-parser).

Checklist

  • I’ve added tests to confirm my change works.
  • (If changing the API or CLI) I’ve documented the changes I’ve made (in the docs/ directory).
  • (If the change is user-facing) I’ve added my changes to changelog_unreleased/*/XXXX.md file following changelog_unreleased/TEMPLATE.md.
  • I’ve read the contributing guidelines.

Copy link
Member Author

@jgerigmeyer jgerigmeyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nex3 @jamesnw This is still very WIP, but I'd appreciate some initial eyes on it to see if I'm going in the right direction. There's a lot of custom logic in the way Prettier currently handles values and comma-groups (everything in between two commas in a list) that I haven't really tackled yet -- I'm cautiously hopeful that we can ignore/remove a lot of it.

}

node.selector = parseSelector(selector);
// TODO: node.selector cannot be overwritten
node.selectorTemp = parseSelector(selector);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still here because overriding node.selector raises an error, but postcss-selector-parser is still need until sass-parser exposes selector parsing.

Comment on lines +525 to +526
// TODO: Every node needs a `type`
node.type = "list";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prettier throws an error if any node is missing a type -- should sass-parser always set type internally?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that Prettier demands that every parser for every language conforms to a certain interface? That seems needlessly onerous.

The reason we don't set type for value-level nodes is that type represents the PostCSS notion of a node's type, rather than the Sass parser's more detailed notion. Since PostCSS doesn't have a notion of the type of expression-level constructs, we only provide sassType.

Honestly probably the easiest thing to do here would be to monkey-patch Expression.prototype to set a type property that just forwards to sassType if that's what PostCSS requires.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can tell Prettier to consistently use sassType for this parser 👍

Comment on lines +534 to +546
// TODO: It looks like `url()` is just parsed as a `string`
if (
parentNode &&
isURLFunctionNode(parentNode) &&
(node.groups.length === 1 ||
(node.groups.length > 0 &&
node.groups[0].type === "value-comma_group" &&
node.groups[0].groups.length > 0 &&
node.groups[0].groups[0].type === "value-word" &&
node.groups[0].groups[0].value.startsWith("data:")))
) {
return [hasParens ? "(" : "", join(",", nodes), hasParens ? ")" : ""];
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prettier currently has special-handling for items inside a url() function, but it looks like sass-parser just treats urls as one big StringExpression.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a number of function-esque expressions that Sass just parses as strings because they don't conform to the normal CSS expression-level conventions. For all intents and purposes in Sass these are string literals, and it's technically not safe to reformat them in most circumstances because even the whitespace is observable. The exception is URLs, where for historical reasons Sass will eliminate the leading and trailing whitespace. See this playground for an example.

There are also some cases where Sass parses URLs as actual Sass functions, but you won't need to worry about those since they'll have a different AST type entirely and can be handled by the normal function logic.

Comment on lines +550 to +553
// TODO: The original `shouldBreakList()` checks for comma-groups
node.some((node) =>
["list", "binary-operation"].includes(node.sassType),
),
Copy link
Member Author

@jgerigmeyer jgerigmeyer May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

postcss-values-parser breaks every value between two commas into its own "comma-group" if it's more than a single node, and uses that to determine line-breaks and indentation. With sass-parser each item in a list is parsed individually, so this is an initial attempt to track a list of any sass-parser node types that postcss-values-parser would have considered more than one node (and therefore a "comma-group").

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you in the end, but it's also possible that it's worth just rethinking the underlying logic of this formatting in the first place. Might be worth looking into how e.g. JS formats code in lists.

@@ -123,24 +120,25 @@ function genericPrint(path, options, print) {
}

return [
node.raws.before.replaceAll(/[\s;]/gu, ""),
node.raws.before?.replaceAll(/[\s;]/gu, "") ?? "",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because raws are not yet implemented in sass-parser, some inline comments are currently being inadvertently removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants