Skip to content

Commit cb41e46

Browse files
committed
feat: Add support for interpolations
1 parent d77a65a commit cb41e46

9 files changed

Lines changed: 163 additions & 22 deletions

File tree

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,26 @@ whether the first character in the argument is a quotation mark.
183183
] }
184184
```
185185

186+
### interpolation
187+
188+
An interpolation of value, e.g. Sass interpolation `#{rgb(0,0,0)}`.
189+
190+
Interpolation nodes have nodes all other nodes nested within them.
191+
192+
Additional properties:
193+
194+
- **value**: Interpolation prefix, e.g. `#` in `#{rgb(0,0,0)}`.
195+
- **before**: Whitespace after the opening curly bracket and before the first value,
196+
e.g. ` ` in `#{ rgb(0,0,0)}`.
197+
- **after**: Whitespace before the closing curly bracket and after the last value,
198+
e.g. ` ` in `#{rgb(0,0,0) }`.
199+
- **nodes**: More nodes representing the arguments to the interpolation.
200+
- **unclosed**: `true` if the curly bracket was not closed properly. e.g. `#{ unclosed-interpolation `.
201+
186202
### unicode-range
187203

188-
The unicode-range CSS descriptor sets the specific range of characters to be
189-
used from a font defined by @font-face and made available
204+
The unicode-range CSS descriptor sets the specific range of characters to be
205+
used from a font defined by @font-face and made available
190206
for use on the current page (`unicode-range: U+0025-00FF`).
191207

192208
Node-specific properties:

lib/index.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ declare namespace postcssValueParser {
4747
nodes: Node[];
4848
}
4949

50+
interface InterpolationNode
51+
extends BaseNode,
52+
ClosableNode,
53+
AdjacentAwareNode {
54+
type: "interpolation";
55+
56+
/**
57+
* Nodes inside the interpolation
58+
*/
59+
nodes: Node[];
60+
}
61+
5062
interface SpaceNode extends BaseNode {
5163
type: "space";
5264
}
@@ -75,6 +87,7 @@ declare namespace postcssValueParser {
7587
| CommentNode
7688
| DivNode
7789
| FunctionNode
90+
| InterpolationNode
7891
| SpaceNode
7992
| StringNode
8093
| UnicodeRangeNode

lib/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ var parse = require("./parse");
22
var walk = require("./walk");
33
var stringify = require("./stringify");
44

5-
function ValueParser(value) {
5+
function ValueParser(value, options) {
66
if (this instanceof ValueParser) {
7-
this.nodes = parse(value);
7+
this.nodes = parse(value, options);
88
return this;
99
}
10-
return new ValueParser(value);
10+
return new ValueParser(value, options);
1111
}
1212

1313
ValueParser.prototype.toString = function() {

lib/parse.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,22 @@ var star = "*".charCodeAt(0);
1010
var uLower = "u".charCodeAt(0);
1111
var uUpper = "U".charCodeAt(0);
1212
var plus = "+".charCodeAt(0);
13+
var openInterpolation = "{".charCodeAt(0);
14+
var closeInterpolation = "}".charCodeAt(0);
1315
var isUnicodeRange = /^[a-f0-9?-]+$/i;
1416

15-
module.exports = function(input) {
17+
function isOpenInterpolation(code, value, pos, interpolation) {
18+
return (
19+
interpolation.charCodeAt(0) === code &&
20+
value.charCodeAt(pos + 1) === openInterpolation
21+
);
22+
}
23+
24+
function isCloseInterpolation(code) {
25+
return code === closeInterpolation;
26+
}
27+
28+
module.exports = function(input, options) {
1629
var tokens = [];
1730
var value = input;
1831

@@ -35,6 +48,9 @@ module.exports = function(input) {
3548
var before = "";
3649
var after = "";
3750

51+
options = options || {};
52+
var interpolationPrefix = options.interpolationPrefix || "";
53+
3854
while (pos < max) {
3955
// Whitespaces
4056
if (code <= 32) {
@@ -46,7 +62,10 @@ module.exports = function(input) {
4662
token = value.slice(pos, next);
4763

4864
prev = tokens[tokens.length - 1];
49-
if (code === closeParentheses && balanced) {
65+
if (
66+
(code === closeParentheses || isCloseInterpolation(code)) &&
67+
balanced
68+
) {
5069
after = token;
5170
} else if (prev && prev.type === "div") {
5271
prev.after = token;
@@ -151,18 +170,22 @@ module.exports = function(input) {
151170
code = value.charCodeAt(pos);
152171

153172
// Open parentheses
154-
} else if (openParentheses === code) {
173+
} else if (
174+
openParentheses === code ||
175+
isOpenInterpolation(code, value, pos, interpolationPrefix)
176+
) {
177+
var isFunction = openParentheses === code;
155178
// Whitespaces after open parentheses
156-
next = pos;
179+
next = isFunction ? pos : pos + 1;
157180
do {
158181
next += 1;
159182
code = value.charCodeAt(next);
160183
} while (code <= 32);
161-
parenthesesOpenPos = pos;
184+
parenthesesOpenPos = isFunction ? pos : pos + 1;
162185
token = {
163-
type: "function",
186+
type: isFunction ? "function" : "interpolation",
164187
sourceIndex: pos - name.length,
165-
value: name,
188+
value: isFunction ? name : interpolationPrefix,
166189
before: value.slice(parenthesesOpenPos + 1, next)
167190
};
168191
pos = next;
@@ -230,7 +253,10 @@ module.exports = function(input) {
230253
name = "";
231254

232255
// Close parentheses
233-
} else if (closeParentheses === code && balanced) {
256+
} else if (
257+
(code === closeParentheses || isCloseInterpolation(code)) &&
258+
balanced
259+
) {
234260
pos += 1;
235261
code = value.charCodeAt(pos);
236262

@@ -260,19 +286,24 @@ module.exports = function(input) {
260286
code === colon ||
261287
code === slash ||
262288
code === openParentheses ||
289+
isOpenInterpolation(code, value, pos, interpolationPrefix) ||
263290
(code === star &&
264291
parent &&
265292
parent.type === "function" &&
266293
parent.value === "calc") ||
267294
(code === slash &&
268295
parent.type === "function" &&
269296
parent.value === "calc") ||
270-
(code === closeParentheses && balanced)
297+
((code === closeParentheses || isCloseInterpolation(code)) &&
298+
balanced)
271299
)
272300
);
273301
token = value.slice(pos, next);
274302

275-
if (openParentheses === code) {
303+
if (
304+
openParentheses === code ||
305+
isOpenInterpolation(code, value, pos, interpolationPrefix)
306+
) {
276307
name = token;
277308
} else if (
278309
(uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) &&

lib/stringify.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ function stringifyNode(node, custom) {
1717
return (node.before || "") + value + (node.after || "");
1818
} else if (Array.isArray(node.nodes)) {
1919
buf = stringify(node.nodes, custom);
20-
if (type !== "function") {
20+
if (type !== "function" && type !== "interpolation") {
2121
return buf;
2222
}
23+
var isFunction = type === "function";
2324
return (
2425
value +
25-
"(" +
26+
(isFunction ? "(" : "{") +
2627
(node.before || "") +
2728
buf +
2829
(node.after || "") +
29-
(node.unclosed ? "" : ")")
30+
(node.unclosed ? "" : isFunction ? ")" : "}")
3031
);
3132
}
3233
return value;

lib/walk.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = function walk(nodes, cb, bubble) {
99

1010
if (
1111
result !== false &&
12-
node.type === "function" &&
12+
(node.type === "function" || node.type === "interpolation") &&
1313
Array.isArray(node.nodes)
1414
) {
1515
walk(node.nodes, cb, bubble);

test/index.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test("ValueParser", function(tp) {
2121
});
2222

2323
tp.test("walk", function(t) {
24-
t.plan(4);
24+
t.plan(5);
2525
var result;
2626

2727
result = [];
@@ -74,6 +74,49 @@ test("ValueParser", function(tp) {
7474

7575
result = [];
7676

77+
parser("fn( ) #{fn2( fn3())}", { interpolationPrefix: "#" }).walk(function(
78+
node
79+
) {
80+
if (node.type === "interpolation") {
81+
result.push(node);
82+
}
83+
});
84+
85+
t.deepEqual(
86+
result,
87+
[
88+
{
89+
type: "interpolation",
90+
sourceIndex: 6,
91+
value: "#",
92+
before: "",
93+
after: "",
94+
nodes: [
95+
{
96+
type: "function",
97+
sourceIndex: 8,
98+
value: "fn2",
99+
before: " ",
100+
after: "",
101+
nodes: [
102+
{
103+
type: "function",
104+
sourceIndex: 13,
105+
value: "fn3",
106+
before: "",
107+
after: "",
108+
nodes: []
109+
}
110+
]
111+
}
112+
]
113+
}
114+
],
115+
"should process all interpolations"
116+
);
117+
118+
result = [];
119+
77120
parser("fn( ) fn2( fn3())").walk(function(node) {
78121
if (node.type === "function") {
79122
result.push(node);

test/parse.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,32 @@ var tests = [
8989
}
9090
]
9191
},
92+
{
93+
message: "should process interpolation",
94+
fixture: "#{name()}",
95+
options: {
96+
interpolationPrefix: "#"
97+
},
98+
expected: [
99+
{
100+
type: "interpolation",
101+
sourceIndex: 0,
102+
value: "#",
103+
before: "",
104+
after: "",
105+
nodes: [
106+
{
107+
type: "function",
108+
sourceIndex: 2,
109+
value: "name",
110+
before: "",
111+
after: "",
112+
nodes: []
113+
}
114+
]
115+
}
116+
]
117+
},
92118
{
93119
message: "should process nested functions",
94120
fixture: "((()))",
@@ -1319,6 +1345,6 @@ test("Parse", function(t) {
13191345
t.plan(tests.length);
13201346

13211347
tests.forEach(function(opts) {
1322-
t.deepEqual(parse(opts.fixture), opts.expected, opts.message);
1348+
t.deepEqual(parse(opts.fixture, opts.options), opts.expected, opts.message);
13231349
});
13241350
});

test/stringify.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ var tests = [
5454
message: "Should correctly process empty url with newline (LF)",
5555
fixture: "url(\n)"
5656
},
57+
{
58+
message: "Should correctly process interpolation",
59+
fixture: "#{url(\n)}",
60+
options: {
61+
interpolationPrefix: "#"
62+
}
63+
},
5764
{
5865
message: "Should correctly process empty url with newline (LF)",
5966
fixture: "url(\n\n\n)"
@@ -92,7 +99,11 @@ test("Stringify", function(t) {
9299
t.plan(tests.length + 4);
93100

94101
tests.forEach(function(opts) {
95-
t.equal(stringify(parse(opts.fixture)), opts.fixture, opts.message);
102+
t.equal(
103+
stringify(parse(opts.fixture, opts.options)),
104+
opts.fixture,
105+
opts.message
106+
);
96107
});
97108

98109
var tokens = parse(" rgba(12, 54, 65 ) ");

0 commit comments

Comments
 (0)