diff --git a/changelog_unreleased/css/13577.md b/changelog_unreleased/css/13577.md new file mode 100644 index 000000000..e19fe8491 --- /dev/null +++ b/changelog_unreleased/css/13577.md @@ -0,0 +1,32 @@ +#### Fix formatting of long `:is`, `:where`, and `:not` selectors (#13577 by @j-f1) + +Pseudo-selectors like `:is`, `:where`, and `:not` that can take multiple selectors as arguments are now formatted like function calls are in other languages. Previously, no special significance was attached to the commas between their “arguments,” leading to confusing wrapping behavior. There are likely still improvements to be made here — please open an issue with some example code if you find something that doesn’t look as expected. + + +```css +/* Input */ +:where( + label > input:valid, + label > textarea:not(:empty), + label > button[disabled] +) ~ .errors > .error { display: none; } + +/* Prettier stable */ +:where(label > input:valid, label > textarea:not(:empty), label + > button[disabled]) + ~ .errors + > .error { + display: none; +} + +/* Prettier main */ +:where( + label > input:valid, + label > textarea:not(:empty), + label > button[disabled] + ) + ~ .errors + > .error { + display: none; +} +``` diff --git a/src/language-css/printer-postcss.js b/src/language-css/printer-postcss.js index 7b9a89af4..20165c5fa 100644 --- a/src/language-css/printer-postcss.js +++ b/src/language-css/printer-postcss.js @@ -461,7 +461,12 @@ function genericPrint(path, options, print) { return [ maybeToLowerCase(node.value), isNonEmptyArray(node.nodes) - ? ["(", join(", ", path.map(print, "nodes")), ")"] + ? group([ + "(", + indent([softline, join([",", line], path.map(print, "nodes"))]), + softline, + ")", + ]) : "", ]; diff --git a/tests/format/css/indent/__snapshots__/jsfmt.spec.js.snap b/tests/format/css/indent/__snapshots__/jsfmt.spec.js.snap index e7457a685..b00a54f5c 100644 --- a/tests/format/css/indent/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/css/indent/__snapshots__/jsfmt.spec.js.snap @@ -42,7 +42,9 @@ a { =====================================output===================================== a { - ~ .Pagination-itemWrapper:not(.is-separator):not([data-priority^="#{$priority}"]) + ~ .Pagination-itemWrapper:not(.is-separator):not( + [data-priority^="#{$priority}"] + ) ~ .Pagination-itemWrapper.is-separator[data-priority^="#{$priority}"] { display: flex; } diff --git a/tests/format/css/pseudo-call/__snapshots__/jsfmt.spec.js.snap b/tests/format/css/pseudo-call/__snapshots__/jsfmt.spec.js.snap index 50ebc77cb..5a3dbabd4 100644 --- a/tests/format/css/pseudo-call/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/css/pseudo-call/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,86 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`is.css format 1`] = ` +====================================options===================================== +parsers: ["css"] +printWidth: 80 + | printWidth +=====================================input====================================== +:is(ol, ul, menu:unsupported) :is(ol, ul) { + color: green; +} + +:is(ol, ul) :is(ol, ul) ol { + list-style-type: lower-greek; + color: chocolate; +} + +:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) { + list-style-type: square; +} + +/* Level 0 */ +h1 { + font-size: 30px; +} +/* Level 1 */ +:is(section, article, aside, nav) h1 { + font-size: 25px; +} +/* Level 2 */ +:is(section, article, aside, nav) :is(section, article, aside, nav) h1 { + font-size: 20px; +} +/* Level 3 */ +:is(section, article, aside, nav) :is(section, article, aside, nav) :is(section, article, aside, nav) h1 { + font-size: 15px; +} + +some-element:is(::before, ::after) { + display: block; +} + +=====================================output===================================== +:is(ol, ul, menu:unsupported) :is(ol, ul) { + color: green; +} + +:is(ol, ul) :is(ol, ul) ol { + list-style-type: lower-greek; + color: chocolate; +} + +:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) { + list-style-type: square; +} + +/* Level 0 */ +h1 { + font-size: 30px; +} +/* Level 1 */ +:is(section, article, aside, nav) h1 { + font-size: 25px; +} +/* Level 2 */ +:is(section, article, aside, nav) :is(section, article, aside, nav) h1 { + font-size: 20px; +} +/* Level 3 */ +:is(section, article, aside, nav) + :is(section, article, aside, nav) + :is(section, article, aside, nav) + h1 { + font-size: 15px; +} + +some-element:is(::before, ::after) { + display: block; +} + +================================================================================ +`; + exports[`pseudo_call.css format 1`] = ` ====================================options===================================== parsers: ["css"] @@ -15,3 +96,60 @@ div:not(:last-child) { ================================================================================ `; + +exports[`where.css format 1`] = ` +====================================options===================================== +parsers: ["css"] +printWidth: 80 + | printWidth +=====================================input====================================== +:where(#p0:checked ~ #play:checked ~ #c1:checked, #p1:checked ~ #play:checked ~ #c2:checked, #p2:checked ~ #play:checked ~ #cO:checked) ~ #result > +#c { display: block; } + +:where(ol, ul, menu:unsupported) :where(ol, ul) { + color: green; +} + +:where(ol, ul) :where(ol, ul) ol { + list-style-type: lower-greek; + color: chocolate; +} + +:is(section.is-styling, aside.is-styling, footer.is-styling) a { + color: red; +} + +:where(section.where-styling, aside.where-styling, footer.where-styling) a { + color: orange; +} + +=====================================output===================================== +:where( + #p0:checked ~ #play:checked ~ #c1:checked, + #p1:checked ~ #play:checked ~ #c2:checked, + #p2:checked ~ #play:checked ~ #cO:checked + ) + ~ #result + > #c { + display: block; +} + +:where(ol, ul, menu:unsupported) :where(ol, ul) { + color: green; +} + +:where(ol, ul) :where(ol, ul) ol { + list-style-type: lower-greek; + color: chocolate; +} + +:is(section.is-styling, aside.is-styling, footer.is-styling) a { + color: red; +} + +:where(section.where-styling, aside.where-styling, footer.where-styling) a { + color: orange; +} + +================================================================================ +`; diff --git a/tests/format/css/pseudo-call/is.css b/tests/format/css/pseudo-call/is.css new file mode 100644 index 000000000..4b26a4aa1 --- /dev/null +++ b/tests/format/css/pseudo-call/is.css @@ -0,0 +1,33 @@ +:is(ol, ul, menu:unsupported) :is(ol, ul) { + color: green; +} + +:is(ol, ul) :is(ol, ul) ol { + list-style-type: lower-greek; + color: chocolate; +} + +:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, dir) { + list-style-type: square; +} + +/* Level 0 */ +h1 { + font-size: 30px; +} +/* Level 1 */ +:is(section, article, aside, nav) h1 { + font-size: 25px; +} +/* Level 2 */ +:is(section, article, aside, nav) :is(section, article, aside, nav) h1 { + font-size: 20px; +} +/* Level 3 */ +:is(section, article, aside, nav) :is(section, article, aside, nav) :is(section, article, aside, nav) h1 { + font-size: 15px; +} + +some-element:is(::before, ::after) { + display: block; +} diff --git a/tests/format/css/pseudo-call/where.css b/tests/format/css/pseudo-call/where.css new file mode 100644 index 000000000..a52b9af46 --- /dev/null +++ b/tests/format/css/pseudo-call/where.css @@ -0,0 +1,19 @@ +:where(#p0:checked ~ #play:checked ~ #c1:checked, #p1:checked ~ #play:checked ~ #c2:checked, #p2:checked ~ #play:checked ~ #cO:checked) ~ #result > +#c { display: block; } + +:where(ol, ul, menu:unsupported) :where(ol, ul) { + color: green; +} + +:where(ol, ul) :where(ol, ul) ol { + list-style-type: lower-greek; + color: chocolate; +} + +:is(section.is-styling, aside.is-styling, footer.is-styling) a { + color: red; +} + +:where(section.where-styling, aside.where-styling, footer.where-styling) a { + color: orange; +}