diff --git a/src/__tests__/__snapshots__/babel.test.js.snap b/src/__tests__/__snapshots__/babel.test.js.snap index 7c5974469..3aa60581f 100644 --- a/src/__tests__/__snapshots__/babel.test.js.snap +++ b/src/__tests__/__snapshots__/babel.test.js.snap @@ -190,6 +190,34 @@ Dependencies: NA `; +exports[`inlines array styles as CSS string 1`] = ` +"import { styled } from 'linaria/react'; +const styles = [{ + flex: 1 +}, { + display: 'block', + height: 24 +}]; +export const Title = +/*#__PURE__*/ +styled(\\"h1\\")({ + name: \\"Title\\", + class: \\"th6xni0\\" +});" +`; + +exports[`inlines array styles as CSS string 2`] = ` + +CSS: + +.th6xni0 { + flex: 1; display: block; height: 24px; +} + +Dependencies: NA + +`; + exports[`inlines object styles as CSS string 1`] = ` "import { styled } from 'linaria/react'; const cover = { diff --git a/src/__tests__/__snapshots__/preval.test.js.snap b/src/__tests__/__snapshots__/preval.test.js.snap index c64a807ba..596fb5688 100644 --- a/src/__tests__/__snapshots__/preval.test.js.snap +++ b/src/__tests__/__snapshots__/preval.test.js.snap @@ -532,6 +532,38 @@ Dependencies: NA `; +exports[`inlines array styles as CSS string 1`] = ` +"import { styled } from 'linaria/react'; + +const fill = (top = 0, left = 0, right = 0, bottom = 0) => [{ + position: 'absolute' +}, { + top, + right, + bottom, + left +}]; + +export const Title = +/*#__PURE__*/ +styled(\\"h1\\")({ + name: \\"Title\\", + class: \\"Title_t1xha7dm\\" +});" +`; + +exports[`inlines array styles as CSS string 2`] = ` + +CSS: + +.Title_t1xha7dm { + position: absolute; top: 0; right: 0; bottom: 0; left: 0; +} + +Dependencies: NA + +`; + exports[`inlines object styles as CSS string 1`] = ` "import { styled } from 'linaria/react'; @@ -595,15 +627,6 @@ exports[`throws when interpolation evaluates to NaN 1`] = ` 7 | \`;" `; -exports[`throws when interpolation evaluates to an array 1`] = ` -"<>/source.js: The expression evaluated to '[\\"2px\\",\\"0\\",\\"2px\\"]', which is probably a mistake. If you want it to be inserted into CSS, explicitly cast or transform the value to a string, e.g. - 'String(borderRadius)'. - 4 | - 5 | export const Title = styled.h1\` -> 6 | border-radius: \${borderRadius}px; - | ^ - 7 | \`;" -`; - exports[`throws when interpolation evaluates to null 1`] = ` "<>/source.js: The expression evaluated to 'null', which is probably a mistake. If you want it to be inserted into CSS, explicitly cast or transform the value to a string, e.g. - 'String(color)'. 4 | diff --git a/src/__tests__/babel.test.js b/src/__tests__/babel.test.js index 616f2d0e6..c8772a5c0 100644 --- a/src/__tests__/babel.test.js +++ b/src/__tests__/babel.test.js @@ -108,7 +108,7 @@ it('inlines object styles as CSS string', async () => { left: 0, opacity: 1, minHeight: 420, - + '&.shouldNotBeChanged': { borderColor: '#fff', }, @@ -139,6 +139,26 @@ it('inlines object styles as CSS string', async () => { expect(metadata).toMatchSnapshot(); }); +it('inlines array styles as CSS string', async () => { + const { code, metadata } = await transpile( + dedent` + import { styled } from 'linaria/react'; + + const styles = [ + { flex: 1 }, + { display: 'block', height: 24 }, + ]; + + export const Title = styled.h1\` + ${'${styles}'} + \`; + ` + ); + + expect(code).toMatchSnapshot(); + expect(metadata).toMatchSnapshot(); +}); + it('replaces unknown expressions with CSS custom properties', async () => { const { code, metadata } = await transpile( dedent` diff --git a/src/__tests__/preval.test.js b/src/__tests__/preval.test.js index f91459895..a3c88d0de 100644 --- a/src/__tests__/preval.test.js +++ b/src/__tests__/preval.test.js @@ -271,28 +271,6 @@ it('throws when interpolation evaluates to NaN', async () => { } }); -it('throws when interpolation evaluates to an array', async () => { - expect.assertions(1); - - try { - await transpile( - dedent` - const { styled } = require('../react'); - - const borderRadius = ['2px', '0', '2px']; - - export const Title = styled.h1\` - border-radius: ${'${borderRadius}'}px; - \`; - ` - ); - } catch (e) { - expect( - stripAnsi(e.message.replace(__dirname, '<>')) - ).toMatchSnapshot(); - } -}); - it('handles wrapping another styled component', async () => { const { code, metadata } = await transpile( dedent` @@ -336,6 +314,31 @@ it('inlines object styles as CSS string', async () => { expect(metadata).toMatchSnapshot(); }); +it('inlines array styles as CSS string', async () => { + const { code, metadata } = await transpile( + dedent` + import { styled } from 'linaria/react'; + + const fill = (top = 0, left = 0, right = 0, bottom = 0) => [ + { position: 'absolute' }, + { + top, + right, + bottom, + left, + } + ]; + + export const Title = styled.h1\` + ${'${fill(0, 0)}'} + \`; + ` + ); + + expect(code).toMatchSnapshot(); + expect(metadata).toMatchSnapshot(); +}); + it('ignores inline arrow function expressions', async () => { const { code, metadata } = await transpile( dedent` diff --git a/src/babel/extract.js b/src/babel/extract.js index bf22c4fc8..1c2b28aa4 100644 --- a/src/babel/extract.js +++ b/src/babel/extract.js @@ -16,22 +16,27 @@ const hyphenate = s => // Special case for `-ms` because in JS it starts with `ms` unlike `Webkit` .replace(/^ms-/, '-ms-'); -const isPlainObject = o => - typeof o === 'object' && o != null && o.constructor.name === 'Object'; +const isSerializable = o => + Array.isArray(o) || + (typeof o === 'object' && o != null && o.constructor.name === 'Object'); const toValidCSSIdentifier = s => s.replace(/[^_0-9a-z]/gi, '_'); // Some tools such as polished.js output JS objects // To support them transparently, we convert JS objects to CSS strings -const toCSS = o => - Object.entries(o) +const toCSS = o => { + if (Array.isArray(o)) { + return o.map(toCSS).join('\n'); + } + + return Object.entries(o) .filter( ([, value]) => // Ignore all falsy values except numbers typeof value === 'number' || value ) .map(([key, value]) => { - if (isPlainObject(value)) { + if (isSerializable(value)) { return `${key} { ${toCSS(value)} }`; } @@ -51,6 +56,7 @@ const toCSS = o => };`; }) .join(' '); +}; // Stripping away the new lines ensures that we preserve line numbers // This is useful in case of tools such as the stylelint pre-processor @@ -140,7 +146,7 @@ const throwIfInvalid = (value: any, ex: any) => { typeof value === 'function' || typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value)) || - isPlainObject(value) + isSerializable(value) ) { return; } @@ -400,7 +406,7 @@ module.exports = function extract(babel: any, options: Options) { if (result.confident) { throwIfInvalid(result.value, ex); - if (isPlainObject(result.value)) { + if (isSerializable(result.value)) { // If it's a plain object, convert it to a CSS string cssText += stripLines(loc, toCSS(result.value)); } else { @@ -447,7 +453,7 @@ module.exports = function extract(babel: any, options: Options) { // If it's an React component wrapped in styled, get the class name // Useful for interpolating components cssText += `.${value.__linaria.className}`; - } else if (isPlainObject(value)) { + } else if (isSerializable(value)) { cssText += stripLines(loc, toCSS(value)); } else { // For anything else, assume it'll be stringified