Skip to content

Commit

Permalink
feat: handle arrays in interpolations (#337)
Browse files Browse the repository at this point in the history
closes #332
  • Loading branch information
satya164 authored and zamotany committed Mar 6, 2019
1 parent 76677f1 commit ba45d81
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 40 deletions.
28 changes: 28 additions & 0 deletions src/__tests__/__snapshots__/babel.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
41 changes: 32 additions & 9 deletions src/__tests__/__snapshots__/preval.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -595,15 +627,6 @@ exports[`throws when interpolation evaluates to NaN 1`] = `
7 | \`;"
`;
exports[`throws when interpolation evaluates to an array 1`] = `
"<<DIRNAME>>/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`] = `
"<<DIRNAME>>/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 |
Expand Down
22 changes: 21 additions & 1 deletion src/__tests__/babel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ it('inlines object styles as CSS string', async () => {
left: 0,
opacity: 1,
minHeight: 420,
'&.shouldNotBeChanged': {
borderColor: '#fff',
},
Expand Down Expand Up @@ -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`
Expand Down
47 changes: 25 additions & 22 deletions src/__tests__/preval.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, '<<DIRNAME>>'))
).toMatchSnapshot();
}
});

it('handles wrapping another styled component', async () => {
const { code, metadata } = await transpile(
dedent`
Expand Down Expand Up @@ -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`
Expand Down
22 changes: 14 additions & 8 deletions src/babel/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)} }`;
}

Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ba45d81

Please sign in to comment.