Skip to content

Commit

Permalink
Merge pull request gucong3000#17 from ota-meshi/issue-orig-53
Browse files Browse the repository at this point in the history
Fixed incorrect parsing/stringifying for nested tagged template literals
  • Loading branch information
jeddy3 committed Apr 3, 2020
2 parents 9d729fe + 2d60dbd commit 2f174f3
Show file tree
Hide file tree
Showing 18 changed files with 2,002 additions and 11 deletions.
46 changes: 38 additions & 8 deletions extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ function literalParser (source, opts, styles) {
traverse(ast, visitor);
jobs.forEach(job => job());

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

tplLiteral = Array.from(tplLiteral).filter(node => (
objLiteral.every(style => (
node.start > style.endIndex || node.end < style.startIndex
))
)).map(node => {
const tplLiteralStyles = [];
Array.from(tplLiteral).forEach(node => {
if (objLiteralStyles.some(style => (
style.startIndex <= node.end && node.start < style.endIndex
))) {
return
}

const quasis = node.quasis.map(node => ({
start: node.start,
end: node.end,
Expand All @@ -376,18 +379,45 @@ function literalParser (source, opts, styles) {
content: getTemplate(node, source),
};
if (node.expressions.length) {
const expressions = node.expressions.map(node => ({
start: node.start,
end: node.end,
}));
style.syntax = loadSyntax(opts, __dirname);
style.lang = "template-literal";
style.opts = {
quasis: quasis,
expressions: expressions,
};
} else {
style.lang = "css";
}
return style;

let parent = null;
let targetStyles = tplLiteralStyles;
while (targetStyles) {
const target = targetStyles.find(targetStyle =>
targetStyle.opts && targetStyle.opts.expressions.some(expr =>
expr.start <= style.startIndex && style.endIndex < expr.end
)
);
if (target) {
parent = target;
targetStyles = target.opts.templateLiteralStyles;
} else {
break
}
}

if (parent) {
const templateLiteralStyles = parent.opts.templateLiteralStyles || (parent.opts.templateLiteralStyles = []);
templateLiteralStyles.push(style);
} else {
tplLiteralStyles.push(style);
}
});

return (styles || []).concat(objLiteral).concat(tplLiteral);
return (styles || []).concat(objLiteralStyles).concat(tplLiteralStyles);
};

module.exports = literalParser;
4 changes: 2 additions & 2 deletions literal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

"use strict";
const Node = require("postcss/lib/node");
const Container = require("postcss/lib/container");

/**
* Represents a JS literal
Expand All @@ -13,7 +13,7 @@ const Node = require("postcss/lib/node");
* literal.type //=> 'literal'
* literal.toString() //=> 'a{}'
*/
class Literal extends Node {
class Literal extends Container {
constructor (defaults) {
super(defaults);
this.type = "literal";
Expand Down
2 changes: 2 additions & 0 deletions template-parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const Input = require("postcss/lib/input");
function templateParse (css, opts) {
const input = new Input(css, opts);
input.quasis = opts.quasis;
input.templateLiteralStyles = opts.templateLiteralStyles;
input.parseOptions = opts
const parser = new TemplateParser(input);
parser.parse();

Expand Down
160 changes: 160 additions & 0 deletions template-parser-helper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"use strict";
const Literal = require("./literal");
const postcssParse = require("postcss/lib/parse");
const Input = require("postcss/lib/input");
const reNewLine = /(?:\r?\n|\r)/gm;
const isLiteral = token => token[0] === "word" && /^\$\{[\s\S]*\}$/.test(token[1]);
function literal (start) {
if (!isLiteral(start)) {
Expand Down Expand Up @@ -33,6 +36,28 @@ function literal (start) {

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

const input = this.input;
if (input.templateLiteralStyles) {
const offset = input.quasis[0].start;
const nodeIndex = getNodeIndex(node, input);
const start = offset + nodeIndex;
const end = start + node.text.length;
const templateLiteralStyles = input.templateLiteralStyles.filter(style =>
style.startIndex <= end && start < style.endIndex
);
if (templateLiteralStyles.length) {
const nodes = parseTemplateLiteralStyles(
templateLiteralStyles,
input,
[nodeIndex, nodeIndex + node.text.length]
);
if (nodes.length) {
node.nodes = nodes;
nodes.forEach(n => n.parent = node);
}
}
}

return node;
}

Expand All @@ -50,3 +75,138 @@ module.exports = {
freeSemicolon: freeSemicolon,
literal: literal,
};

function parseTemplateLiteralStyles(styles, input, range) {
const offset = input.quasis[0].start;
const source = input.css;
const parseStyle = docFixer(offset, source, input.parseOptions);

const nodes = [];
let index = range[0];
styles.sort((a, b) => (
a.startIndex - b.startIndex
)).forEach(style => {
const root = parseStyle(style);
if (!root || !root.nodes.length) {
return
}
root.raws.beforeStart = source.slice(index, style.startIndex - offset);
root.raws.afterEnd = '';
if (style.endIndex) {
index = style.endIndex - offset;
} else {
index = style.startIndex - offset + (style.content || root.source.input.css).length;
}
nodes.push(root);
})
if (nodes.length) {
nodes[nodes.length - 1].raws.afterEnd = source.slice(index, range[1]);
}
return nodes;
}

class LocalFixer {
constructor (offset, lines, style, templateParse) {
const startIndex = style.startIndex - offset
let line = 0;
let column = startIndex;
lines.some((lineEndIndex, lineNumber) => {
if (lineEndIndex >= startIndex) {
line = lineNumber--;
if (lineNumber in lines) {
column = startIndex - lines[lineNumber] - 1;
}
return true;
}
});

this.line = line;
this.column = column;
this.style = style;
this.templateParse = templateParse
}
object (object) {
if (object) {
if (object.line === 1) {
object.column += this.column;
}
object.line += this.line;
}
}
node (node) {
this.object(node.source.start);
this.object(node.source.end);
}
root (root) {
this.node(root);
root.walk(node => {
this.node(node);
});
}
error (error) {
if (error && error.name === "CssSyntaxError") {
this.object(error);
this.object(error.input);
error.message = error.message.replace(/:\d+:\d+:/, ":" + error.line + ":" + error.column + ":");
}
return error;
}
parse (opts) {
const style = this.style;
const syntax = style.syntax;
let root = style.root;
try {
root = this.templateParse(style.content, Object.assign({}, opts, {
map: false,
}, style.opts));
} catch (error) {
if (style.ignoreErrors) {
return;
} else if (!style.skipConvert) {
this.error(error);
}
throw error;
}
if (!style.skipConvert) {
this.root(root);
}

root.source.inline = Boolean(style.inline);
root.source.lang = style.lang;
root.source.syntax = syntax;
return root;
}
}

function docFixer (offset, source, opts) {
let match;
const lines = [];
reNewLine.lastIndex = 0;
while ((match = reNewLine.exec(source))) {
lines.push(match.index);
}
lines.push(source.length);
return function parseStyle (style) {
const parse = style.syntax ? style.syntax.parse : postcssParse
return new LocalFixer(offset, lines, style, parse).parse(opts);
};
}

function getNodeIndex(node, input) {
const source = input.css
let match;
let line = 1
let lastIndex = -1
reNewLine.lastIndex = 0;
while ((match = reNewLine.exec(source))) {
if (line === node.source.start.line) {
return lastIndex + node.source.start.column
}
lastIndex = match.index
line++
}
if (line === node.source.start.line) {
return lastIndex + node.source.start.column
}
return source.length
}
2 changes: 2 additions & 0 deletions template-safe-parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const Input = require("postcss/lib/input");
function templateSafeParse (css, opts) {
const input = new Input(css, opts);
input.quasis = opts.quasis;
input.templateLiteralStyles = opts.templateLiteralStyles;
input.parseOptions = opts
const parser = new TemplateSafeParser(input);
parser.parse();

Expand Down
10 changes: 9 additions & 1 deletion template-stringifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ const Stringifier = require("postcss/lib/stringifier");

class TemplateStringifier extends Stringifier {
literal (node) {
this.builder(node.text, node);
if (node.nodes && node.nodes.length) {
node.nodes.forEach(root => {
this.builder(root.raws.beforeStart, root, "beforeStart");
this.stringify(root)
this.builder(root.raws.afterEnd, root, "afterEnd");
})
} else {
this.builder(node.text, node);
}
if (node.raws.ownSemicolon) {
this.builder(node.raws.ownSemicolon, node, "end");
}
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/styled-components-nesting-nesting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled, { css } from 'styled-components';
const Message1 = styled.p`
padding: 10px;
${css`
color: #b02d00;
${css`
background: white;
`}
`}
`;
Loading

0 comments on commit 2f174f3

Please sign in to comment.