From 8ec7bc229aeade5ffff20eada625905eb26d02b0 Mon Sep 17 00:00:00 2001 From: Georgii Dolzhykov Date: Mon, 23 Mar 2020 16:59:49 +0200 Subject: [PATCH] fix formatting of pseudo-elements and pseudo-classes in styled-components (#7842) --- changelog_unreleased/javascript/pr-7842.md | 21 ++++++++ src/language-js/embed.js | 48 +++++++++++++++---- .../__snapshots__/jsfmt.spec.js.snap | 45 +++++++++++++++++ .../colons-after-substitutions.js | 16 +++++++ 4 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 changelog_unreleased/javascript/pr-7842.md create mode 100644 tests/multiparser_js_css/colons-after-substitutions.js diff --git a/changelog_unreleased/javascript/pr-7842.md b/changelog_unreleased/javascript/pr-7842.md new file mode 100644 index 000000000000..0b2e68825670 --- /dev/null +++ b/changelog_unreleased/javascript/pr-7842.md @@ -0,0 +1,21 @@ +#### Fix formatting of pseudo-elements and pseudo-classes in styled-components template literals ([#7842](https://github.com/prettier/prettier/pull/7842) by [@thorn0](https://github.com/thorn0)) + + +```jsx +// Input +const Foo = styled.div` + ${media.smallDown}::before {} +`; + +// Prettier stable +const Foo = styled.div` + ${media.smallDown}: : before{ + } +`; + +// Prettier master +const Foo = styled.div` + ${media.smallDown}::before { + } +`; +``` diff --git a/src/language-js/embed.js b/src/language-js/embed.js index 64ef0d39d435..f6a14007b4f5 100644 --- a/src/language-js/embed.js +++ b/src/language-js/embed.js @@ -36,14 +36,35 @@ function embed(path, print, textToDoc, options) { const rawQuasis = node.quasis.map((q) => q.value.raw); let placeholderID = 0; const text = rawQuasis.reduce((prevVal, currVal, idx) => { - return idx === 0 - ? currVal - : prevVal + - "@prettier-placeholder-" + - placeholderID++ + - "-id" + - currVal; + if (idx === 0) { + return currVal; + } + + let specialSuffix = ""; // colons and whitespaces + + const trailingColons = currVal.match(/^(\s*)(:+)(\s*)/); + if (trailingColons) { + const whitespaceBeforeColons = !!trailingColons[1]; + const numberOfColons = trailingColons[2].length; + const whitespaceAfterColons = !!trailingColons[3]; + + if (whitespaceAfterColons) { + // do nothing, it's not a pseudo-element or pseudo-class + } else { + if (whitespaceBeforeColons) { + specialSuffix += "-whitespace"; + } + specialSuffix += "-colon".repeat(numberOfColons); + + currVal = "\uffff" + currVal.slice(trailingColons[0].length); + } + } + + const placeholder = `@prettier-placeholder${specialSuffix}-${placeholderID++}-id`; + + return prevVal + placeholder + currVal; }, ""); + const doc = textToDoc(text, { parser: "css" }); return transformCssDoc(doc, path, print); } @@ -292,14 +313,21 @@ function replacePlaceholders(quasisDoc, expressionDocs) { const placeholder = parts[atPlaceholderIndex]; const rest = parts.slice(atPlaceholderIndex + 1); const placeholderMatch = placeholder.match( - /@prettier-placeholder-(.+)-id([\s\S]*)/ + /@prettier-placeholder((?:-whitespace|-colon)*)-(.+)-id([\s\S]*)/ ); - const placeholderID = placeholderMatch[1]; + const specialSuffix = placeholderMatch[1]; // colons and whitespaces + const placeholderID = placeholderMatch[2]; // When the expression has a suffix appended, like: // animation: linear ${time}s ease-out; - const suffix = placeholderMatch[2]; + let suffix = placeholderMatch[3]; const expression = expressions[placeholderID]; + if (specialSuffix) { + suffix = + specialSuffix.replace(/-whitespace/g, " ").replace(/-colon/g, ":") + + suffix.replace(/^\uffff/g, ""); + } + replaceCounter++; parts = parts .slice(0, atPlaceholderIndex) diff --git a/tests/multiparser_js_css/__snapshots__/jsfmt.spec.js.snap b/tests/multiparser_js_css/__snapshots__/jsfmt.spec.js.snap index 5af6968d153c..ce07a9cb1733 100644 --- a/tests/multiparser_js_css/__snapshots__/jsfmt.spec.js.snap +++ b/tests/multiparser_js_css/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`colons-after-substitutions.js 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +const Icon = styled.div\` + flex: none; + transition: fill 0.25s; + width: 48px; + height: 48px; + + \${Link}:hover { + fill: rebeccapurple; + } + + \${Link} :hover { + fill: yellow; + } + + \${media.smallDown}::before {} +\` + +=====================================output===================================== +const Icon = styled.div\` + flex: none; + transition: fill 0.25s; + width: 48px; + height: 48px; + + \${Link}:hover { + fill: rebeccapurple; + } + + \${Link} :hover { + fill: yellow; + } + + \${media.smallDown}::before { + } +\`; + +================================================================================ +`; + exports[`styled-components.js 1`] = ` ====================================options===================================== parsers: ["babel"] diff --git a/tests/multiparser_js_css/colons-after-substitutions.js b/tests/multiparser_js_css/colons-after-substitutions.js new file mode 100644 index 000000000000..97954db5d54a --- /dev/null +++ b/tests/multiparser_js_css/colons-after-substitutions.js @@ -0,0 +1,16 @@ +const Icon = styled.div` + flex: none; + transition: fill 0.25s; + width: 48px; + height: 48px; + + ${Link}:hover { + fill: rebeccapurple; + } + + ${Link} :hover { + fill: yellow; + } + + ${media.smallDown}::before {} +`