diff --git a/src/language-css/print/misc.js b/src/language-css/print/misc.js index b819dc907..f6aeac6ef 100644 --- a/src/language-css/print/misc.js +++ b/src/language-css/print/misc.js @@ -50,7 +50,7 @@ function printCssNumber(rawNumber) { ); } -function shouldPrintComma(options) { +function shouldPrintTrailingComma(options) { return options.trailingComma === "es5" || options.trailingComma === "all"; } @@ -58,7 +58,7 @@ export { adjustStrings, adjustNumbers, quoteAttributeValue, - shouldPrintComma, + shouldPrintTrailingComma, printUnit, printCssNumber, }; diff --git a/src/language-css/print/parenthesized-value-group.js b/src/language-css/print/parenthesized-value-group.js index 2a88b377b..f101ac9c8 100644 --- a/src/language-css/print/parenthesized-value-group.js +++ b/src/language-css/print/parenthesized-value-group.js @@ -21,19 +21,44 @@ import { isVarFunctionNode, } from "../utils/index.js"; import { locStart, locEnd } from "../loc.js"; -import { shouldPrintComma } from "./misc.js"; +import { shouldPrintTrailingComma } from "./misc.js"; + +function hasComma({ node, parent }, options) { + return Boolean( + node.source && + options.originalText + .slice(locStart(node), locStart(parent.close)) + .trimEnd() + .endsWith(",") + ); +} + +function printTrailingComma(path, options) { + if (isVarFunctionNode(path.grandparent) && hasComma(path, options)) { + return ","; + } + + if ( + path.node.type !== "value-comment" && + shouldPrintTrailingComma(options) && + path.callParent(() => isSCSSMapItemNode(path, options)) + ) { + return ifBreak(","); + } + + return ""; +} function printParenthesizedValueGroup(path, options, print) { - const { node } = path; - const parentNode = path.parent; - const printedGroups = path.map( + const { node, parent } = path; + const groupDocs = path.map( ({ node }) => (typeof node === "string" ? node : print()), "groups" ); if ( - parentNode && - isURLFunctionNode(parentNode) && + parent && + isURLFunctionNode(parent) && (node.groups.length === 1 || (node.groups.length > 0 && node.groups[0].type === "value-comma_group" && @@ -43,92 +68,64 @@ function printParenthesizedValueGroup(path, options, print) { ) { return [ node.open ? print("open") : "", - join(",", printedGroups), + join(",", groupDocs), node.close ? print("close") : "", ]; } if (!node.open) { const forceHardLine = shouldBreakList(path); - const parts = join([",", forceHardLine ? hardline : line], printedGroups); + const parts = join([",", forceHardLine ? hardline : line], groupDocs); return indent(forceHardLine ? [hardline, parts] : group(fill(parts))); } + const parts = path.map(({ node: child, isLast, index }) => { + let doc = groupDocs[index]; + + // Key/Value pair in open paren already indented + if ( + isKeyValuePairNode(child) && + child.type === "value-comma_group" && + child.groups && + child.groups[0].type !== "value-paren_group" && + child.groups[2]?.type === "value-paren_group" + ) { + const parts = getDocParts(doc.contents.contents); + parts[1] = group(parts[1]); + doc = group(dedent(doc)); + } + + const parts = [doc, isLast ? printTrailingComma(path, options) : ","]; + + if ( + !isLast && + child.type === "value-comma_group" && + isNonEmptyArray(child.groups) + ) { + let last = child.groups.at(-1); + + // `value-paren_group` does not have location info, but its closing parenthesis does. + if (!last.source && last.close) { + last = last.close; + } + + if (last.source && isNextLineEmpty(options.originalText, locEnd(last))) { + parts.push(hardline); + } + } + + return parts; + }, "groups"); + const isKey = isKeyInValuePairNode(node, parent); + const isConfiguration = isConfigurationNode(node, parent); const isSCSSMapItem = isSCSSMapItemNode(path, options); - - const lastItem = node.groups.at(-1); - const isLastItemComment = 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; - const printed = group( + const doc = group( [ node.open ? print("open") : "", - indent([ - softline, - join( - [line], - path.map(({ node: child, isLast, index }) => { - const hasComma = () => - Boolean( - child.source && - options.originalText - .slice(locStart(child), locStart(node.close)) - .trimEnd() - .endsWith(",") - ); - const shouldPrintComma = !isLast || (isVarFunction && hasComma()); - let printed = [printedGroups[index], shouldPrintComma ? "," : ""]; - - // Key/Value pair in open paren already indented - if ( - isKeyValuePairNode(child) && - child.type === "value-comma_group" && - child.groups && - child.groups[0].type !== "value-paren_group" && - child.groups[2]?.type === "value-paren_group" - ) { - const parts = getDocParts(printed[0].contents.contents); - parts[1] = group(parts[1]); - printed = [group(dedent(printed))]; - } - - if ( - !isLast && - child.type === "value-comma_group" && - isNonEmptyArray(child.groups) - ) { - let last = child.groups.at(-1); - - // `value-paren_group` does not have location info, but its closing parenthesis does. - if (!last.source && last.close) { - last = last.close; - } - - if ( - last.source && - isNextLineEmpty(options.originalText, locEnd(last)) - ) { - printed.push(hardline); - } - } - - return printed; - }, "groups") - ), - ]), - ifBreak( - !isLastItemComment && - options.parser === "scss" && - isSCSSMapItem && - shouldPrintComma(options) - ? "," - : "" - ), + indent([softline, join(line, parts)]), softline, node.close ? print("close") : "", ], @@ -137,7 +134,7 @@ function printParenthesizedValueGroup(path, options, print) { } ); - return shouldDedent ? dedent(printed) : printed; + return shouldDedent ? dedent(doc) : doc; } function shouldBreakList(path) {