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

Commit 2f174f3

Browse files
authored
Merge pull request #17 from ota-meshi/issue-orig-53
Fixed incorrect parsing/stringifying for nested tagged template literals
2 parents 9d729fe + 2d60dbd commit 2f174f3

18 files changed

+2002
-11
lines changed

extract.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ function literalParser (source, opts, styles) {
337337
traverse(ast, visitor);
338338
jobs.forEach(job => job());
339339

340-
objLiteral = Array.from(objLiteral).map(endNode => {
340+
const objLiteralStyles = Array.from(objLiteral).map(endNode => {
341341
const objectSyntax = require("./object-syntax");
342342
let startNode = endNode;
343343
if (startNode.leadingComments && startNode.leadingComments.length) {
@@ -361,11 +361,14 @@ function literalParser (source, opts, styles) {
361361
};
362362
});
363363

364-
tplLiteral = Array.from(tplLiteral).filter(node => (
365-
objLiteral.every(style => (
366-
node.start > style.endIndex || node.end < style.startIndex
367-
))
368-
)).map(node => {
364+
const tplLiteralStyles = [];
365+
Array.from(tplLiteral).forEach(node => {
366+
if (objLiteralStyles.some(style => (
367+
style.startIndex <= node.end && node.start < style.endIndex
368+
))) {
369+
return
370+
}
371+
369372
const quasis = node.quasis.map(node => ({
370373
start: node.start,
371374
end: node.end,
@@ -376,18 +379,45 @@ function literalParser (source, opts, styles) {
376379
content: getTemplate(node, source),
377380
};
378381
if (node.expressions.length) {
382+
const expressions = node.expressions.map(node => ({
383+
start: node.start,
384+
end: node.end,
385+
}));
379386
style.syntax = loadSyntax(opts, __dirname);
380387
style.lang = "template-literal";
381388
style.opts = {
382389
quasis: quasis,
390+
expressions: expressions,
383391
};
384392
} else {
385393
style.lang = "css";
386394
}
387-
return style;
395+
396+
let parent = null;
397+
let targetStyles = tplLiteralStyles;
398+
while (targetStyles) {
399+
const target = targetStyles.find(targetStyle =>
400+
targetStyle.opts && targetStyle.opts.expressions.some(expr =>
401+
expr.start <= style.startIndex && style.endIndex < expr.end
402+
)
403+
);
404+
if (target) {
405+
parent = target;
406+
targetStyles = target.opts.templateLiteralStyles;
407+
} else {
408+
break
409+
}
410+
}
411+
412+
if (parent) {
413+
const templateLiteralStyles = parent.opts.templateLiteralStyles || (parent.opts.templateLiteralStyles = []);
414+
templateLiteralStyles.push(style);
415+
} else {
416+
tplLiteralStyles.push(style);
417+
}
388418
});
389419

390-
return (styles || []).concat(objLiteral).concat(tplLiteral);
420+
return (styles || []).concat(objLiteralStyles).concat(tplLiteralStyles);
391421
};
392422

393423
module.exports = literalParser;

literal.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
"use strict";
3-
const Node = require("postcss/lib/node");
3+
const Container = require("postcss/lib/container");
44

55
/**
66
* Represents a JS literal
@@ -13,7 +13,7 @@ const Node = require("postcss/lib/node");
1313
* literal.type //=> 'literal'
1414
* literal.toString() //=> 'a{}'
1515
*/
16-
class Literal extends Node {
16+
class Literal extends Container {
1717
constructor (defaults) {
1818
super(defaults);
1919
this.type = "literal";

template-parse.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const Input = require("postcss/lib/input");
66
function templateParse (css, opts) {
77
const input = new Input(css, opts);
88
input.quasis = opts.quasis;
9+
input.templateLiteralStyles = opts.templateLiteralStyles;
10+
input.parseOptions = opts
911
const parser = new TemplateParser(input);
1012
parser.parse();
1113

template-parser-helper.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"use strict";
22
const Literal = require("./literal");
3+
const postcssParse = require("postcss/lib/parse");
4+
const Input = require("postcss/lib/input");
5+
const reNewLine = /(?:\r?\n|\r)/gm;
36
const isLiteral = token => token[0] === "word" && /^\$\{[\s\S]*\}$/.test(token[1]);
47
function literal (start) {
58
if (!isLiteral(start)) {
@@ -33,6 +36,28 @@ function literal (start) {
3336

3437
this.init(node, start[2], start[3]);
3538

39+
const input = this.input;
40+
if (input.templateLiteralStyles) {
41+
const offset = input.quasis[0].start;
42+
const nodeIndex = getNodeIndex(node, input);
43+
const start = offset + nodeIndex;
44+
const end = start + node.text.length;
45+
const templateLiteralStyles = input.templateLiteralStyles.filter(style =>
46+
style.startIndex <= end && start < style.endIndex
47+
);
48+
if (templateLiteralStyles.length) {
49+
const nodes = parseTemplateLiteralStyles(
50+
templateLiteralStyles,
51+
input,
52+
[nodeIndex, nodeIndex + node.text.length]
53+
);
54+
if (nodes.length) {
55+
node.nodes = nodes;
56+
nodes.forEach(n => n.parent = node);
57+
}
58+
}
59+
}
60+
3661
return node;
3762
}
3863

@@ -50,3 +75,138 @@ module.exports = {
5075
freeSemicolon: freeSemicolon,
5176
literal: literal,
5277
};
78+
79+
function parseTemplateLiteralStyles(styles, input, range) {
80+
const offset = input.quasis[0].start;
81+
const source = input.css;
82+
const parseStyle = docFixer(offset, source, input.parseOptions);
83+
84+
const nodes = [];
85+
let index = range[0];
86+
styles.sort((a, b) => (
87+
a.startIndex - b.startIndex
88+
)).forEach(style => {
89+
const root = parseStyle(style);
90+
if (!root || !root.nodes.length) {
91+
return
92+
}
93+
root.raws.beforeStart = source.slice(index, style.startIndex - offset);
94+
root.raws.afterEnd = '';
95+
if (style.endIndex) {
96+
index = style.endIndex - offset;
97+
} else {
98+
index = style.startIndex - offset + (style.content || root.source.input.css).length;
99+
}
100+
nodes.push(root);
101+
})
102+
if (nodes.length) {
103+
nodes[nodes.length - 1].raws.afterEnd = source.slice(index, range[1]);
104+
}
105+
return nodes;
106+
}
107+
108+
class LocalFixer {
109+
constructor (offset, lines, style, templateParse) {
110+
const startIndex = style.startIndex - offset
111+
let line = 0;
112+
let column = startIndex;
113+
lines.some((lineEndIndex, lineNumber) => {
114+
if (lineEndIndex >= startIndex) {
115+
line = lineNumber--;
116+
if (lineNumber in lines) {
117+
column = startIndex - lines[lineNumber] - 1;
118+
}
119+
return true;
120+
}
121+
});
122+
123+
this.line = line;
124+
this.column = column;
125+
this.style = style;
126+
this.templateParse = templateParse
127+
}
128+
object (object) {
129+
if (object) {
130+
if (object.line === 1) {
131+
object.column += this.column;
132+
}
133+
object.line += this.line;
134+
}
135+
}
136+
node (node) {
137+
this.object(node.source.start);
138+
this.object(node.source.end);
139+
}
140+
root (root) {
141+
this.node(root);
142+
root.walk(node => {
143+
this.node(node);
144+
});
145+
}
146+
error (error) {
147+
if (error && error.name === "CssSyntaxError") {
148+
this.object(error);
149+
this.object(error.input);
150+
error.message = error.message.replace(/:\d+:\d+:/, ":" + error.line + ":" + error.column + ":");
151+
}
152+
return error;
153+
}
154+
parse (opts) {
155+
const style = this.style;
156+
const syntax = style.syntax;
157+
let root = style.root;
158+
try {
159+
root = this.templateParse(style.content, Object.assign({}, opts, {
160+
map: false,
161+
}, style.opts));
162+
} catch (error) {
163+
if (style.ignoreErrors) {
164+
return;
165+
} else if (!style.skipConvert) {
166+
this.error(error);
167+
}
168+
throw error;
169+
}
170+
if (!style.skipConvert) {
171+
this.root(root);
172+
}
173+
174+
root.source.inline = Boolean(style.inline);
175+
root.source.lang = style.lang;
176+
root.source.syntax = syntax;
177+
return root;
178+
}
179+
}
180+
181+
function docFixer (offset, source, opts) {
182+
let match;
183+
const lines = [];
184+
reNewLine.lastIndex = 0;
185+
while ((match = reNewLine.exec(source))) {
186+
lines.push(match.index);
187+
}
188+
lines.push(source.length);
189+
return function parseStyle (style) {
190+
const parse = style.syntax ? style.syntax.parse : postcssParse
191+
return new LocalFixer(offset, lines, style, parse).parse(opts);
192+
};
193+
}
194+
195+
function getNodeIndex(node, input) {
196+
const source = input.css
197+
let match;
198+
let line = 1
199+
let lastIndex = -1
200+
reNewLine.lastIndex = 0;
201+
while ((match = reNewLine.exec(source))) {
202+
if (line === node.source.start.line) {
203+
return lastIndex + node.source.start.column
204+
}
205+
lastIndex = match.index
206+
line++
207+
}
208+
if (line === node.source.start.line) {
209+
return lastIndex + node.source.start.column
210+
}
211+
return source.length
212+
}

template-safe-parse.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const Input = require("postcss/lib/input");
66
function templateSafeParse (css, opts) {
77
const input = new Input(css, opts);
88
input.quasis = opts.quasis;
9+
input.templateLiteralStyles = opts.templateLiteralStyles;
10+
input.parseOptions = opts
911
const parser = new TemplateSafeParser(input);
1012
parser.parse();
1113

template-stringifier.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ const Stringifier = require("postcss/lib/stringifier");
33

44
class TemplateStringifier extends Stringifier {
55
literal (node) {
6-
this.builder(node.text, node);
6+
if (node.nodes && node.nodes.length) {
7+
node.nodes.forEach(root => {
8+
this.builder(root.raws.beforeStart, root, "beforeStart");
9+
this.stringify(root)
10+
this.builder(root.raws.afterEnd, root, "afterEnd");
11+
})
12+
} else {
13+
this.builder(node.text, node);
14+
}
715
if (node.raws.ownSemicolon) {
816
this.builder(node.raws.ownSemicolon, node, "end");
917
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import styled, { css } from 'styled-components';
2+
const Message1 = styled.p`
3+
padding: 10px;
4+
${css`
5+
color: #b02d00;
6+
${css`
7+
background: white;
8+
`}
9+
`}
10+
`;

0 commit comments

Comments
 (0)