Skip to content

Commit 5e963f4

Browse files
committed
Add an unstable_subtitute option that replaces all literal expressions with a sass-like dummy value
1 parent e37d564 commit 5e963f4

File tree

5 files changed

+182
-0
lines changed

5 files changed

+182
-0
lines changed

substitute-javascript.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
const literalsKey = Symbol('Literals');
4+
const replacementsKey = Symbol('Replaced properties');
5+
6+
function addSubstitution(item, key, literals) {
7+
item[replacementsKey] = item[replacementsKey] || [];
8+
9+
literals.forEach((literal) => {
10+
const position = item[key].indexOf(literal);
11+
12+
if (position !== -1) {
13+
const substitution = '$dummyValue' + position;
14+
15+
item[replacementsKey].push({
16+
key,
17+
original: literal,
18+
substitution,
19+
});
20+
21+
item[key] = item[key].replace(literal, substitution);
22+
}
23+
});
24+
}
25+
26+
function addSubstitutions(node, css, opts) {
27+
if (typeof node.walk !== 'function') return;
28+
29+
if (css && opts) {
30+
const offset = opts.quasis[0].start;
31+
32+
node[literalsKey] = [];
33+
34+
opts.expressions.forEach(({ start, end }) => {
35+
node[literalsKey].push('${' + css.substring(start - offset, end - offset) + '}');
36+
});
37+
}
38+
39+
if (!node[literalsKey]) return;
40+
41+
node.walk((item) => {
42+
if (item.type === 'atrule') {
43+
addSubstitution(item, 'name', node[literalsKey]);
44+
addSubstitution(item, 'params', node[literalsKey]);
45+
} else if (item.type === 'rule') {
46+
addSubstitution(item, 'selector', node[literalsKey]);
47+
} else if (item.type === 'decl') {
48+
addSubstitution(item, 'prop', node[literalsKey]);
49+
addSubstitution(item, 'value', node[literalsKey]);
50+
} else if (item.type === 'root') {
51+
addSubstitutions(item);
52+
}
53+
});
54+
}
55+
56+
exports.addSubstitutions = addSubstitutions;
57+
58+
exports.removeSubstitutions = (node) => {
59+
if (typeof node.walk !== 'function') return;
60+
61+
node.walk((item) => {
62+
if (!item[replacementsKey]) {
63+
return;
64+
}
65+
66+
item[replacementsKey].forEach((replacement) => {
67+
item[replacement.key] = item[replacement.key].replace(
68+
replacement.substitution,
69+
replacement.original,
70+
);
71+
});
72+
73+
delete item[replacementsKey];
74+
});
75+
};

template-parse.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const Input = require('postcss/lib/input');
4+
const substitutions = require('./substitute-javascript');
45
const TemplateParser = require('./template-parser');
56

67
function templateParse(css, opts) {
@@ -13,6 +14,11 @@ function templateParse(css, opts) {
1314

1415
parser.parse();
1516

17+
if (((opts.syntax.config.jsx || {}).config || opts.syntax.config).unstable_substitute) {
18+
parser.root.unstable_substitute = true;
19+
substitutions.addSubstitutions(parser.root, css, opts);
20+
}
21+
1622
return parser.root;
1723
}
1824

template-safe-parse.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const Input = require('postcss/lib/input');
4+
const substitutions = require('./substitute-javascript');
45
const TemplateSafeParser = require('./template-safe-parser');
56

67
function templateSafeParse(css, opts) {
@@ -13,6 +14,11 @@ function templateSafeParse(css, opts) {
1314

1415
parser.parse();
1516

17+
if (opts.syntax.config.unstable_substitute) {
18+
parser.root.unstable_substitute = true;
19+
substitutions.addSubstitutions(parser.root, css, opts);
20+
}
21+
1622
return parser.root;
1723
}
1824

template-stringify.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
'use strict';
22

3+
const substitutions = require('./substitute-javascript');
34
const TemplateStringifier = require('./template-stringifier');
45

56
module.exports = function TemplateStringify(node, builder) {
7+
if (node.unstable_substitute) {
8+
substitutions.removeSubstitutions(node);
9+
}
10+
611
const str = new TemplateStringifier(builder);
712

813
str.stringify(node);
14+
15+
if (node.unstable_substitute) {
16+
substitutions.addSubstitutions(node);
17+
}
918
};

test/styled-components.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,90 @@ describe('styled-components', () => {
323323
expect(document.source).toHaveProperty('lang', 'jsx');
324324
expect(document.nodes).toHaveLength(3);
325325
});
326+
327+
it('re-writes various stuff to substitutes', () => {
328+
const code = `
329+
import styled from 'styled-components';
330+
331+
const greenColor = 'green';
332+
const returnGreenColor = () => 'green'
333+
334+
const C = styled.div\`
335+
@media (min-width: \${t => t.test}) {
336+
color: \${p => p.width};
337+
\${prop}: green;
338+
background-\${prop}: dark\${greenColor};
339+
\${decl};
340+
\${css\`
341+
color: light\${returnGreenColor()}
342+
\`}
343+
}
344+
\`;
345+
`;
346+
347+
const expectation = {
348+
nodes: [
349+
{
350+
nodes: [
351+
{
352+
type: 'atrule',
353+
params: '(min-width: $dummyValue12)',
354+
nodes: [
355+
{
356+
type: 'decl',
357+
prop: 'color',
358+
value: '$dummyValue0',
359+
},
360+
{
361+
type: 'decl',
362+
prop: '$dummyValue0',
363+
value: 'green',
364+
},
365+
{
366+
type: 'decl',
367+
prop: 'background-$dummyValue11',
368+
value: 'dark$dummyValue4',
369+
},
370+
{
371+
type: 'literal',
372+
},
373+
{
374+
type: 'literal',
375+
nodes: [
376+
{
377+
type: 'root',
378+
nodes: [
379+
{
380+
type: 'decl',
381+
prop: 'color',
382+
value: 'light$dummyValue5',
383+
},
384+
],
385+
},
386+
],
387+
},
388+
],
389+
},
390+
],
391+
},
392+
],
393+
};
394+
395+
const document = syntax({ jsx: { config: { unstable_substitute: true } } }).parse(code);
396+
397+
// The document should have the right properties.
398+
expect(document.source).toHaveProperty('lang', 'jsx');
399+
400+
// The document should, without stringifying first, have the right values.
401+
expect(document).toMatchObject(expectation);
402+
403+
// If we stringify the document, it should not have changed in any way.
404+
expect(document.toString()).toBe(code);
405+
406+
// During stringifying, the substitutions are removed and added. After that they should still have the right values.
407+
expect(document).toMatchObject(expectation);
408+
409+
// And one last check - the first time the document is stringified, should not affect subsequent stringifications.
410+
expect(document.toString()).toBe(code);
411+
});
326412
});

0 commit comments

Comments
 (0)