Skip to content

Commit d383e7f

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

File tree

9 files changed

+177
-23
lines changed

9 files changed

+177
-23
lines changed

README.md

Lines changed: 25 additions & 3 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:
@@ -242,10 +258,16 @@ The `callback` is invoked with three arguments: `callback(node, index, nodes)`.
242258

243259
Returns the `valueParser` instance.
244260

245-
### var parsed = valueParser(value)
261+
### var parsed = valueParser(value, options)
246262

247263
Returns the parsed node tree.
248264

265+
### options
266+
267+
#### options.interpolationPrefix
268+
269+
Prefix used for interpolation, e.g. `#` for Sass interpolation.
270+
249271
### parsed.nodes
250272

251273
The array of nodes.

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: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,23 @@ 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 !== null &&
20+
interpolation.charCodeAt(0) === code &&
21+
value.charCodeAt(pos + 1) === openInterpolation
22+
);
23+
}
24+
25+
function isCloseInterpolation(code) {
26+
return code === closeInterpolation;
27+
}
28+
29+
module.exports = function(input, options) {
1630
var tokens = [];
1731
var value = input;
1832

@@ -35,6 +49,15 @@ module.exports = function(input) {
3549
var before = "";
3650
var after = "";
3751

52+
options = options || {};
53+
var interpolationPrefix = null;
54+
if (
55+
typeof options.interpolationPrefix !== "undefined" &&
56+
options.interpolationPrefix !== null
57+
) {
58+
interpolationPrefix = options.interpolationPrefix;
59+
}
60+
3861
while (pos < max) {
3962
// Whitespaces
4063
if (code <= 32) {
@@ -46,7 +69,10 @@ module.exports = function(input) {
4669
token = value.slice(pos, next);
4770

4871
prev = tokens[tokens.length - 1];
49-
if (code === closeParentheses && balanced) {
72+
if (
73+
(code === closeParentheses || isCloseInterpolation(code)) &&
74+
balanced
75+
) {
5076
after = token;
5177
} else if (prev && prev.type === "div") {
5278
prev.after = token;
@@ -151,18 +177,22 @@ module.exports = function(input) {
151177
code = value.charCodeAt(pos);
152178

153179
// Open parentheses
154-
} else if (openParentheses === code) {
180+
} else if (
181+
openParentheses === code ||
182+
isOpenInterpolation(code, value, pos, interpolationPrefix)
183+
) {
184+
var isFunction = openParentheses === code;
155185
// Whitespaces after open parentheses
156-
next = pos;
186+
next = isFunction ? pos : pos + 1;
157187
do {
158188
next += 1;
159189
code = value.charCodeAt(next);
160190
} while (code <= 32);
161-
parenthesesOpenPos = pos;
191+
parenthesesOpenPos = isFunction ? pos : pos + 1;
162192
token = {
163-
type: "function",
193+
type: isFunction ? "function" : "interpolation",
164194
sourceIndex: pos - name.length,
165-
value: name,
195+
value: isFunction ? name : interpolationPrefix,
166196
before: value.slice(parenthesesOpenPos + 1, next)
167197
};
168198
pos = next;
@@ -230,7 +260,10 @@ module.exports = function(input) {
230260
name = "";
231261

232262
// Close parentheses
233-
} else if (closeParentheses === code && balanced) {
263+
} else if (
264+
(code === closeParentheses || isCloseInterpolation(code)) &&
265+
balanced
266+
) {
234267
pos += 1;
235268
code = value.charCodeAt(pos);
236269

@@ -260,19 +293,24 @@ module.exports = function(input) {
260293
code === colon ||
261294
code === slash ||
262295
code === openParentheses ||
296+
isOpenInterpolation(code, value, pos, interpolationPrefix) ||
263297
(code === star &&
264298
parent &&
265299
parent.type === "function" &&
266300
parent.value === "calc") ||
267301
(code === slash &&
268302
parent.type === "function" &&
269303
parent.value === "calc") ||
270-
(code === closeParentheses && balanced)
304+
((code === closeParentheses || isCloseInterpolation(code)) &&
305+
balanced)
271306
)
272307
);
273308
token = value.slice(pos, next);
274309

275-
if (openParentheses === code) {
310+
if (
311+
openParentheses === code ||
312+
isOpenInterpolation(code, value, pos, interpolationPrefix)
313+
) {
276314
name = token;
277315
} else if (
278316
(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)