Skip to content

Commit 2024e1b

Browse files
authored
Parser of template (#41)
1 parent d221849 commit 2024e1b

16 files changed

+206
-45
lines changed

extract.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,10 @@ function literalParser (source, opts, styles) {
308308
endIndex: endNode.end,
309309
skipConvert: true,
310310
content: source,
311-
syntax: objectSyntax(endNode),
311+
opts: {
312+
node: endNode,
313+
},
314+
syntax: objectSyntax,
312315
lang: "object-literal",
313316
};
314317
});
@@ -321,18 +324,17 @@ function literalParser (source, opts, styles) {
321324
const quasis = node.quasis;
322325
const value = getTemplate(node, source);
323326

324-
if (value.length === 1 && !value[0].trim()) {
325-
return;
326-
}
327-
328327
const style = {
329328
startIndex: quasis[0].start,
330329
endIndex: quasis[quasis.length - 1].end,
331-
content: value.join(""),
330+
content: value,
332331
};
333-
if (value.length > 1) {
334-
style.syntax = loadSyntax(opts, "postcss-styled");
332+
if (node.expressions.length) {
333+
style.syntax = loadSyntax(opts, __dirname);
335334
style.lang = "template-literal";
335+
style.opts = {
336+
node: node,
337+
};
336338
} else {
337339
style.lang = "css";
338340
}

get-template.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
"use strict";
22
function getTemplate (node, source) {
3-
const result = [];
4-
node.quasis.forEach((node, i) => {
5-
result[i << 1] = node.value.cooked;
6-
});
7-
const quasis = node.quasis;
8-
node.expressions.forEach((node, i) => {
9-
const index = (i << 1) + 1;
10-
result[index] = source.slice(quasis[i].end, quasis[i + 1].start);
11-
});
12-
13-
return result;
3+
return source.slice(node.quasis[0].start, node.quasis[node.quasis.length - 1].end);
144
}
155
module.exports = getTemplate;

literal.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
const Node = require("postcss/lib/node");
44

55
/**
6-
* Represents a JS obj
6+
* Represents a JS literal
77
*
88
* @extends Container
99
*
1010
* @example
1111
* const root = postcss.parse('{}');
12-
* const obj = root.first;
13-
* obj.type //=> 'obj'
14-
* obj.toString() //=> 'a{}'
12+
* const literal = root.first;
13+
* literal.type //=> 'literal'
14+
* literal.toString() //=> 'a{}'
1515
*/
1616
class Literal extends Node {
1717
constructor (defaults) {

object-parse.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
const ObjectParser = require("./object-parser");
44
const Input = require("postcss/lib/input");
55

6-
function objectParse (node, source, opts) {
6+
function objectParse (source, opts) {
77
const input = new Input(source, opts);
88
const parser = new ObjectParser(input);
9-
parser.parse(node);
9+
parser.parse(opts.node);
1010
return parser.root;
1111
}
1212
module.exports = objectParse;

object-parser.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@ class objectParser {
172172
break;
173173
}
174174
case "TemplateLiteral": {
175-
rawValue = source.slice(node.quasis[0].start, node.quasis[node.quasis.length - 1].end);
176-
cookedValue = getTemplate(node, source).join("");
175+
rawValue = getTemplate(node, source);
177176
break;
178177
}
179178
default: {

object-syntax.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
const stringify = require("./object-stringify");
33
const parse = require("./object-parse");
44

5-
module.exports = (node) => {
6-
const syntax = {
7-
stringify,
8-
};
9-
syntax.parse = parse.bind(null, node);
10-
return syntax;
5+
const syntax = {
6+
parse,
7+
stringify,
118
};
9+
10+
module.exports = syntax;

package.json

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "postcss-jsx",
3-
"version": "0.35.0",
3+
"version": "0.36.0",
44
"description": "PostCSS syntax for parsing CSS in JS literals",
55
"repository": {
66
"type": "git",
@@ -45,23 +45,20 @@
4545
"dependencies": {
4646
"@babel/core": "^7.1.2"
4747
},
48-
"optionalDependencies": {
49-
"postcss-styled": ">=0.34.0"
50-
},
5148
"peerDependencies": {
5249
"postcss": ">=5.0.0",
53-
"postcss-syntax": ">=0.34.0"
50+
"postcss-syntax": ">=0.36.0"
5451
},
5552
"devDependencies": {
56-
"autoprefixer": "^9.1.5",
53+
"autoprefixer": "^9.4.3",
5754
"chai": "^4.2.0",
5855
"codecov": "^3.1.0",
5956
"json5": "^2.1.0",
6057
"mocha": "^5.2.0",
61-
"nyc": "^13.0.1",
62-
"postcss": "^7.0.5",
63-
"postcss-parser-tests": "^6.3.0",
58+
"nyc": "^13.1.0",
59+
"postcss": "^7.0.7",
60+
"postcss-parser-tests": "^6.3.1",
6461
"postcss-safe-parser": "^4.0.1",
65-
"postcss-syntax": ">=0.34.0"
62+
"postcss-syntax": ">=0.36.0"
6663
}
6764
}

template-parse.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
3+
const TemplateParser = require("./template-parser");
4+
const Input = require("postcss/lib/input");
5+
6+
function templateParse (css, opts) {
7+
const input = new Input(css, opts);
8+
input.node = opts.node;
9+
const parser = new TemplateParser(input);
10+
parser.parse();
11+
12+
return parser.root;
13+
}
14+
module.exports = templateParse;

template-parser.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"use strict";
2+
const Parser = require("postcss/lib/parser");
3+
const templateTokenize = require("./template-tokenize");
4+
class TemplateParser extends Parser {
5+
createTokenizer () {
6+
this.tokenizer = templateTokenize(this.input);
7+
}
8+
}
9+
module.exports = TemplateParser;

template-safe-parse.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
3+
const TemplateSafeParser = require("./template-safe-parser");
4+
const Input = require("postcss/lib/input");
5+
6+
function templateSafeParse (css, opts) {
7+
const input = new Input(css, opts);
8+
input.node = opts.node;
9+
const parser = new TemplateSafeParser(input);
10+
parser.parse();
11+
12+
return parser.root;
13+
}
14+
module.exports = templateSafeParse;

template-safe-parser.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"use strict";
2+
const SafeParser = require("postcss-safe-parser/lib/safe-parser");
3+
const templateTokenize = require("./template-tokenize");
4+
class TemplateSafeParser extends SafeParser {
5+
createTokenizer () {
6+
this.tokenizer = templateTokenize(this.input, { ignoreErrors: true });
7+
}
8+
}
9+
module.exports = TemplateSafeParser;

template-tokenize.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use strict";
2+
const tokenize = require("postcss/lib/tokenize");
3+
4+
function templateTokenize (input) {
5+
const quasis = input.node.quasis.map((node) => ({
6+
start: node.start,
7+
end: node.end,
8+
})).filter(quasi => quasi.start !== quasi.end);
9+
10+
let pos = input.node.start + 1;
11+
12+
const tokenizer = tokenize.apply(this, arguments);
13+
14+
function tokenInQuasis (start, end) {
15+
return quasis.some(quasi => (start >= quasi.start && end < quasi.end));
16+
}
17+
18+
function back (token) {
19+
pos -= token[1].length;
20+
return tokenizer.back.apply(tokenizer, arguments);
21+
}
22+
23+
function nextToken () {
24+
const args = arguments;
25+
const returned = [];
26+
let token;
27+
while (
28+
(token = tokenizer.nextToken.apply(tokenizer, args)) &&
29+
!tokenInQuasis(pos, (pos += token[1].length)) &&
30+
(returned.length || /\$(?=\{|$)/.test(token[1]))
31+
) {
32+
returned.push(token);
33+
}
34+
if (returned.length) {
35+
const lastToken = returned[returned.length - 1];
36+
if (token && token !== lastToken) {
37+
back(token);
38+
}
39+
token = [
40+
returned[0][0],
41+
returned.map(token => token[1]).join(""),
42+
returned[0][2],
43+
returned[0][3],
44+
lastToken[4] || lastToken[2],
45+
lastToken[5] || lastToken[3],
46+
];
47+
}
48+
49+
return token;
50+
}
51+
return Object.assign({}, tokenizer, {
52+
back,
53+
nextToken,
54+
});
55+
}
56+
57+
module.exports = templateTokenize;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import styled from 'styled-components';
2+
3+
export const StatusText = styled.div`
4+
color: ${(props) =>
5+
(props.status === 'signed' && 'red') ||
6+
'blue'};
7+
`;

test/fixtures/tpl-in-tpl.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import styled from 'styled-components';
2+
3+
const color = '#ddd';
4+
5+
export const Row = styled.div`
6+
border-bottom: ${(props) => (props.border ? `1px solid ${color}` : '0')};
7+
`;

test/literals.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use strict";
2+
const expect = require("chai").expect;
3+
const syntax = require("../");
4+
const fs = require("fs");
5+
6+
describe("template literals", () => {
7+
it("template literals inside template literals", () => {
8+
const file = require.resolve("./fixtures/tpl-in-tpl");
9+
let code = fs.readFileSync(file);
10+
11+
const document = syntax.parse(code, {
12+
from: file,
13+
});
14+
15+
code = code.toString();
16+
expect(document.toString(), code);
17+
expect(document.source).to.haveOwnProperty("lang", "jsx");
18+
19+
expect(document.nodes).to.have.lengthOf(1);
20+
expect(document.first.nodes).to.have.lengthOf(1);
21+
22+
document.first.nodes.forEach((decl) => {
23+
expect(decl).have.property("type", "decl");
24+
expect(decl).have.property("prop", "border-bottom");
25+
expect(decl).have.property("value", "${(props) => (props.border ? `1px solid ${color}` : '0')}");
26+
});
27+
});
28+
29+
it("multiline arrow function", () => {
30+
const file = require.resolve("./fixtures/multiline-arrow-function");
31+
let code = fs.readFileSync(file);
32+
33+
const document = syntax.parse(code, {
34+
from: file,
35+
});
36+
37+
code = code.toString();
38+
expect(document.toString(), code);
39+
expect(document.source).to.haveOwnProperty("lang", "jsx");
40+
41+
expect(document.nodes).to.have.lengthOf(1);
42+
expect(document.first.nodes).to.have.lengthOf(1);
43+
44+
document.first.nodes.forEach((decl) => {
45+
expect(decl).have.property("type", "decl");
46+
expect(decl).have.property("prop", "color");
47+
expect(decl).have.property(
48+
"value",
49+
[
50+
"${(props) =>",
51+
"(props.status === 'signed' && 'red') ||",
52+
"'blue'}",
53+
].join("\n\t")
54+
);
55+
});
56+
});
57+
});

test/styled-components.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe("styled-components", () => {
112112
}).to.throw("css_syntax_error.js:2:12: Unclosed block");
113113
});
114114

115-
it("skip empty template literal", () => {
115+
it("not skip empty template literal", () => {
116116
const code = [
117117
"const styled = require(\"styled-components\");",
118118
"styled.div``;",
@@ -121,7 +121,7 @@ describe("styled-components", () => {
121121
from: "empty_template_literal.js",
122122
});
123123
expect(root.toString()).to.equal(code);
124-
expect(root.nodes).to.have.lengthOf(0);
124+
expect(root.nodes).to.have.lengthOf(1);
125125
});
126126

127127
it("fix CSS syntax error", () => {

0 commit comments

Comments
 (0)