Skip to content
This repository was archived by the owner on Feb 9, 2023. It is now read-only.

Add experimental 'substitute' option, that will subtitute all template literals #46

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions substitute-javascript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use strict';

const literalsKey = Symbol('Literals');
const replacementsKey = Symbol('Replaced properties');

function addSubstitution(item, key, literals) {
item[replacementsKey] = item[replacementsKey] || [];

literals.forEach((literal) => {
const position = item[key].indexOf(literal);

if (position !== -1) {
const substitution = '$dummyValue' + position;

item[replacementsKey].push({
key,
original: literal,
substitution,
});

item[key] = item[key].replace(literal, substitution);
}
});
}

function addSubstitutions(node, css, opts) {
if (typeof node.walk !== 'function') return;

if (css && opts) {
const offset = opts.quasis[0].start;

node[literalsKey] = [];

opts.expressions.forEach(({ start, end }) => {
node[literalsKey].push('${' + css.substring(start - offset, end - offset) + '}');
});
}

if (!node[literalsKey]) return;

node.walk((item) => {
if (item.type === 'atrule') {
addSubstitution(item, 'name', node[literalsKey]);
addSubstitution(item, 'params', node[literalsKey]);
} else if (item.type === 'rule') {
addSubstitution(item, 'selector', node[literalsKey]);
} else if (item.type === 'decl') {
addSubstitution(item, 'prop', node[literalsKey]);
addSubstitution(item, 'value', node[literalsKey]);
} else if (item.type === 'root') {
addSubstitutions(item);
}
});
}

exports.addSubstitutions = addSubstitutions;

exports.removeSubstitutions = (node) => {
if (typeof node.walk !== 'function') return;

node.walk((item) => {
if (!item[replacementsKey]) {
return;
}

item[replacementsKey].forEach((replacement) => {
item[replacement.key] = item[replacement.key].replace(
replacement.substitution,
replacement.original,
);
});

delete item[replacementsKey];
});
};
6 changes: 6 additions & 0 deletions template-parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

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

function templateParse(css, opts) {
Expand All @@ -13,6 +14,11 @@ function templateParse(css, opts) {

parser.parse();

if (((opts.syntax.config.jsx || {}).config || opts.syntax.config).unstable_substitute) {
parser.root.unstable_substitute = true;
substitutions.addSubstitutions(parser.root, css, opts);
}

return parser.root;
}

Expand Down
6 changes: 6 additions & 0 deletions template-safe-parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

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

function templateSafeParse(css, opts) {
Expand All @@ -13,6 +14,11 @@ function templateSafeParse(css, opts) {

parser.parse();

if (opts.syntax.config.unstable_substitute) {
parser.root.unstable_substitute = true;
substitutions.addSubstitutions(parser.root, css, opts);
}

return parser.root;
}

Expand Down
9 changes: 9 additions & 0 deletions template-stringify.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
'use strict';

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

module.exports = function TemplateStringify(node, builder) {
if (node.unstable_substitute) {
substitutions.removeSubstitutions(node);
}

const str = new TemplateStringifier(builder);

str.stringify(node);

if (node.unstable_substitute) {
substitutions.addSubstitutions(node);
}
};
86 changes: 86 additions & 0 deletions test/styled-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,90 @@ describe('styled-components', () => {
expect(document.source).toHaveProperty('lang', 'jsx');
expect(document.nodes).toHaveLength(3);
});

it('re-writes various stuff to substitutes', () => {
const code = `
import styled from 'styled-components';

const greenColor = 'green';
const returnGreenColor = () => 'green'

const C = styled.div\`
@media (min-width: \${t => t.test}) {
color: \${p => p.width};
\${prop}: green;
background-\${prop}: dark\${greenColor};
\${decl};
\${css\`
color: light\${returnGreenColor()}
\`}
}
\`;
`;

const expectation = {
nodes: [
{
nodes: [
{
type: 'atrule',
params: '(min-width: $dummyValue12)',
nodes: [
{
type: 'decl',
prop: 'color',
value: '$dummyValue0',
},
{
type: 'decl',
prop: '$dummyValue0',
value: 'green',
},
{
type: 'decl',
prop: 'background-$dummyValue11',
value: 'dark$dummyValue4',
},
{
type: 'literal',
},
{
type: 'literal',
nodes: [
{
type: 'root',
nodes: [
{
type: 'decl',
prop: 'color',
value: 'light$dummyValue5',
},
],
},
],
},
],
},
],
},
],
};

const document = syntax({ jsx: { config: { unstable_substitute: true } } }).parse(code);

// The document should have the right properties.
expect(document.source).toHaveProperty('lang', 'jsx');

// The document should, without stringifying first, have the right values.
expect(document).toMatchObject(expectation);

// If we stringify the document, it should not have changed in any way.
expect(document.toString()).toBe(code);

// During stringifying, the substitutions are removed and added. After that they should still have the right values.
expect(document).toMatchObject(expectation);

// And one last check - the first time the document is stringified, should not affect subsequent stringifications.
expect(document.toString()).toBe(code);
});
});