diff --git a/lib/__tests__/parse.test.js b/lib/__tests__/parse.test.js index bd7663a..543cacf 100644 --- a/lib/__tests__/parse.test.js +++ b/lib/__tests__/parse.test.js @@ -328,7 +328,11 @@ describe('simple interpolations', () => { let firstNode = document.first.first; expect(firstNode.prop).toBe('color'); - expect(firstNode.value).toBe('${red}'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: '${red}', + }); }); test('property value with symbol right before interpolation', () => { @@ -372,7 +376,11 @@ describe('simple interpolations', () => { let firstNode = document.first.first; expect(firstNode.prop).toBe('color'); - expect(firstNode.value).toBe('${red}'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: '${red}', + }); }); test('property value and !important (no semicolon)', () => { @@ -394,7 +402,11 @@ describe('simple interpolations', () => { let firstNode = document.first.first; expect(firstNode.prop).toBe('color'); - expect(firstNode.value).toBe('${red}'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: '${red}', + }); expect(firstNode.important).toBe(true); }); @@ -417,7 +429,11 @@ describe('simple interpolations', () => { let firstNode = document.first.first; expect(firstNode.prop).toBe('color'); - expect(firstNode.value).toBe('${red}'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: '${red}', + }); expect(firstNode.important).toBe(true); }); @@ -442,7 +458,52 @@ describe('simple interpolations', () => { let firstNode = document.first.first; expect(firstNode.prop).toBe('box-shadow'); - expect(firstNode.value).toBe('${elevation1}, ${elevation2}'); + expect(firstNode.value).toBe('$pssInterpolation0, $pssInterpolation1'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0, $pssInterpolation1', + raw: '${elevation1}, ${elevation2}', + }); + }); + + test('property value with interpolated string concatenation', () => { + let document = parse( + "let Component = styled.div`border-color: ${secondaryColor + '40'}`;", + ); + + let firstNode = document.first.first; + + expect(firstNode.prop).toBe('border-color'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: "${secondaryColor + '40'}", + }); + }); + + test('property value with interpolated ternary', () => { + let document = parse("let Component = styled.div`padding: ${large ? '16px' : '8px'}`;"); + + let firstNode = document.first.first; + + expect(firstNode.prop).toBe('padding'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: "${large ? '16px' : '8px'}", + }); + }); + + test('property value with static value and interpolation stays multi-value', () => { + let document = parse('let Component = styled.div`margin: 4px ${gap}`;'); + + let firstNode = document.first.first; + + expect(firstNode.prop).toBe('margin'); + expect(firstNode.value).toBe('4px $pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '4px $pssInterpolation0', + raw: '4px ${gap}', + }); }); test('property value with props interpolation', () => { @@ -466,7 +527,11 @@ describe('simple interpolations', () => { let firstNode = document.first.first; expect(firstNode.prop).toBe('background-image'); - expect(firstNode.value).toBe('${(props) => props.backgroundImage}'); + expect(firstNode.value).toBe('$pssInterpolation0'); + expect(firstNode.raws.value).toEqual({ + value: '$pssInterpolation0', + raw: '${(props) => props.backgroundImage}', + }); }); test('property value with interpolation inside url function', () => { diff --git a/lib/__tests__/stringify.test.js b/lib/__tests__/stringify.test.js index 104361a..1375b50 100644 --- a/lib/__tests__/stringify.test.js +++ b/lib/__tests__/stringify.test.js @@ -12,6 +12,9 @@ let tests = [ 'let Component = styled.div`color: ${red} !important`;', 'let Component = styled.div`color: ${red} !important;`;', 'let Component = styled.div`box-shadow: ${elevation1}, ${elevation2}`;', + "let Component = styled.div`border-color: ${secondaryColor + '40'}`;", + "let Component = styled.div`padding: ${large ? '16px' : '8px'}`;", + 'let Component = styled.div`margin: 4px ${gap}`;', 'let Component = styled.div`${color}: red`;', 'let Component = styled.div`display: flex; ${color}: red`;', 'let Component = styled.div`${Component} { color: red; }`;', diff --git a/lib/parser.js b/lib/parser.js index ded6061..a83e2df 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -332,13 +332,48 @@ export class Parser extends PostCSSParser { firstSpaces = []; } - this.raw(node, 'value', firstSpaces.concat(tokens), customProperty); // eslint-disable-line unicorn/prefer-spread + let valueTokens = [...firstSpaces, ...tokens]; + + this.raw(node, 'value', valueTokens, customProperty); + + this.opaqueInterpolationsInValue(node, valueTokens); if (node.value.includes(':') && !customProperty) { this.checkMissedSemicolon(tokens); } } + opaqueInterpolationsInValue(node, tokens) { + let hasInterpolation = tokens.some((token) => this.isInterpolation(token)); + + if (!hasInterpolation) { + return; + } + + let cleanValue = ''; + let placeholderIndex = 0; + + for (let token of tokens) { + if (this.isInterpolation(token)) { + // Opaque, single-token placeholder: no spaces, colons or quotes, so + // any CSS value parser treats it as exactly one value. + cleanValue += `$pssInterpolation${placeholderIndex}`; + placeholderIndex++; + } else { + cleanValue += token[1]; + } + } + + if (cleanValue === node.value) { + return; + } + + let raw = node.raws.value ? node.raws.value.raw : node.value; + + node.raws.value = { value: cleanValue, raw }; + node.value = cleanValue; + } + // Helpers fixLine(line) {