Repository: prettier/prettier (JavaScript)
Date: 2022-08-30 14:33:51+00:00
Task type: Bug Fix

Issue:
[CSS] Prettier removes trailing comma inside `var` function 
**Prettier 2.7.1**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEA6AZhCACYAdKbbAWmICMBDAJyWwDdqAKUygLwBoBKAbgIF8Q7EBAAOMAJbQAzslDUqEAO4AFaghkoKAG0UUAnjKFkqFMAGs4MAMoUAtnAAy4qHGTptUuEZPnLVkabOAObIMFQArl4gcLZkcAAm8QkOFFBB4RRBcABiEFS2FDASacggFOEwEIIgABYwtloA6jXi8FIBYHBW6q3idK16pWBShiDOnlQwyiZBBW4eUQBWUgAeVsFacACK4RDw81qeQgFUE0Mj1SJUzjCN4vEwNcgAHAAMxwqejSYipVdwEzoriEAEddvBpqINGUpMQXAkEtUqHAweJkdNMnMkO5DlFPLZxKEIniNttwa5sQshDAKGQ7g8nkgAEzUkziLTBADCEFsWOiUgArNVwp4ACq0jQ4o4gOiRACSUCSsCsYGuYgAgoqrDA9JsDp4+HwgA)
<!-- prettier-ignore -->
```sh
--parser css
```

**Input:**
<!-- prettier-ignore -->
```css
.foo {
  --bar: var(--baz,);
}
```

**Output:**
<!-- prettier-ignore -->
```css
.foo {
  --bar: var(--baz);
}

```

**Expected behavior:**

The bare comma, which indicates "fallback to nothing," remains.

<!-- prettier-ignore -->
```css
.foo {
  --bar: var(--baz,);
}
```

## Spec

https://www.w3.org/TR/css-variables-1/#using-variables

> In an exception to the usual [comma elision rules](https://www.w3.org/TR/css-values-4/#comb-comma), which require commas to be omitted when they’re not separating values, a bare comma, with nothing following it, must be treated as valid in [var()](https://www.w3.org/TR/css-variables-1/#funcdef-var), indicating an empty fallback value.
> Tests
> 
> Note: That is, var(--a,) is a valid function, specifying that if the --a custom property is invalid or missing, the [var()](https://www.w3.org/TR/css-variables-1/#funcdef-var) should be replaced with nothing.


Issue discussion:

Solution patch:
diff --git a/changelog_unreleased/css/13402.md b/changelog_unreleased/css/13402.md
new file mode 100644
index 000000000000..c50ac315c49e
--- /dev/null
+++ b/changelog_unreleased/css/13402.md
@@ -0,0 +1,19 @@
+#### Keep trailing-comma for `var` function (#13402 by @sosukesuzuki)
+
+<!-- prettier-ignore -->
+```css
+/* Input */
+.foo {
+  --bar: var(--baz,);
+}
+
+/* Prettier stable */
+.foo {
+  --bar: var(--baz);
+}
+
+/* Prettier main */
+.foo {
+  --bar: var(--baz,);
+}
+```
diff --git a/src/language-css/printer-postcss.js b/src/language-css/printer-postcss.js
index 033c506d725f..6315704323aa 100644
--- a/src/language-css/printer-postcss.js
+++ b/src/language-css/printer-postcss.js
@@ -72,6 +72,7 @@ import {
   isAtWordPlaceholderNode,
   isConfigurationNode,
   isParenGroupNode,
+  isVarFunctionNode,
 } from "./utils/index.js";
 import { locStart, locEnd } from "./loc.js";
 import printUnit from "./utils/print-unit.js";
@@ -901,6 +902,7 @@ function genericPrint(path, options, print) {
       const isLastItemComment = lastItem && lastItem.type === "value-comment";
       const isKey = isKeyInValuePairNode(node, parentNode);
       const isConfiguration = isConfigurationNode(node, parentNode);
+      const isVarFunction = isVarFunctionNode(parentNode);
 
       const shouldBreak = isConfiguration || (isSCSSMapItem && !isKey);
       const shouldDedent = isConfiguration || isKey;
@@ -915,7 +917,17 @@ function genericPrint(path, options, print) {
               path.map((childPath, index) => {
                 const child = childPath.getValue();
                 const isLast = index === node.groups.length - 1;
-                const printed = [print(), isLast ? "" : ","];
+                const hasComma = () =>
+                  Boolean(
+                    child.source &&
+                      options.originalText
+                        .slice(locStart(child), locStart(node.close))
+                        .trimEnd()
+                        .endsWith(",")
+                  );
+                const shouldPrintComma =
+                  !isLast || (isVarFunction && hasComma());
+                const printed = [print(), shouldPrintComma ? "," : ""];
 
                 // Key/Value pair in open paren already indented
                 if (
diff --git a/src/language-css/utils/index.js b/src/language-css/utils/index.js
index 22a2ccc41c18..1a8763844d59 100644
--- a/src/language-css/utils/index.js
+++ b/src/language-css/utils/index.js
@@ -120,6 +120,10 @@ function isURLFunctionNode(node) {
   return node.type === "value-func" && node.value.toLowerCase() === "url";
 }
 
+function isVarFunctionNode(node) {
+  return node.type === "value-func" && node.value.toLowerCase() === "var";
+}
+
 function isLastNode(path, node) {
   const nodes = path.getParentNode()?.nodes;
   return nodes && nodes.indexOf(node) === nodes.length - 1;
@@ -444,4 +448,5 @@ export {
   isAtWordPlaceholderNode,
   isConfigurationNode,
   isParenGroupNode,
+  isVarFunctionNode,
 };


Test patch:
diff --git a/tests/format/css/trailing-comma/__snapshots__/jsfmt.spec.js.snap b/tests/format/css/trailing-comma/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..867f8c99505c
--- /dev/null
+++ b/tests/format/css/trailing-comma/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`var-func.css format 1`] = `
+====================================options=====================================
+parsers: ["css"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+.foo {
+	--bar: var(--baz,);
+  --bar: var(--baz     ,);
+  --bar: var(--baz     ,    );
+  --bar: var(--baz,);
+	--bar: var(   --baz1, --baz2    , );
+}
+
+=====================================output=====================================
+.foo {
+  --bar: var(--baz,);
+  --bar: var(--baz,);
+  --bar: var(--baz,);
+  --bar: var(--baz,);
+  --bar: var(--baz1, --baz2,);
+}
+
+================================================================================
+`;
diff --git a/tests/format/css/trailing-comma/jsfmt.spec.js b/tests/format/css/trailing-comma/jsfmt.spec.js
new file mode 100644
index 000000000000..f232bbc6ca54
--- /dev/null
+++ b/tests/format/css/trailing-comma/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(import.meta, ["css"]);
diff --git a/tests/format/css/trailing-comma/var-func.css b/tests/format/css/trailing-comma/var-func.css
new file mode 100644
index 000000000000..e3e6f6b512ed
--- /dev/null
+++ b/tests/format/css/trailing-comma/var-func.css
@@ -0,0 +1,7 @@
+.foo {
+	--bar: var(--baz,);
+  --bar: var(--baz     ,);
+  --bar: var(--baz     ,    );
+  --bar: var(--baz,);
+	--bar: var(   --baz1, --baz2    , );
+}