Skip to content

Commit be59c25

Browse files
fix: unicode characters
1 parent a0128ed commit be59c25

File tree

3 files changed

+75
-54
lines changed

3 files changed

+75
-54
lines changed

index.js

Lines changed: 47 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const postcss = require('postcss');
44
const Tokenizer = require('css-selector-tokenizer');
5+
const valueParser = require('postcss-value-parser');
56

67
function normalizeNodeArray(nodes) {
78
const array = [];
@@ -162,45 +163,48 @@ function localizeNode(node, context) {
162163
}
163164

164165
function localizeDeclNode(node, context) {
165-
let newNode;
166166
switch (node.type) {
167-
case 'item':
167+
case 'word':
168168
if (context.localizeNextItem) {
169-
newNode = Object.create(node);
170-
newNode.name = ':local(' + newNode.name + ')';
169+
node.value = ':local(' + node.value + ')';
171170
context.localizeNextItem = false;
172-
return newNode;
173171
}
174172
break;
175173

176-
case 'nested-item':
177-
const newNodes = node.nodes.map(function(n) {
178-
return localizeDeclValue(n, context);
179-
});
180-
node = Object.create(node);
181-
node.nodes = newNodes;
182-
break;
174+
case 'function':
175+
if (context.options && context.options.rewriteUrl && node.value.toLowerCase() === 'url') {
176+
node.nodes.map((nestedNode) => {
177+
if (nestedNode.type !== 'string' && nestedNode.type !== 'word') {
178+
return;
179+
}
180+
181+
let newUrl = context.options.rewriteUrl(context.global, nestedNode.value);
182+
183+
switch (nestedNode.type) {
184+
case 'string':
185+
if (nestedNode.quote === '\'') {
186+
newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/'/g, '\\\'')
187+
}
188+
189+
if (nestedNode.quote === '"') {
190+
newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/"/g, '\\"')
191+
}
192+
193+
break;
194+
case 'word':
195+
newUrl = newUrl.replace(/("|'|\)|\\)/g, '\\$1');
196+
break;
197+
}
183198

184-
case 'url':
185-
if (context.options && context.options.rewriteUrl) {
186-
newNode = Object.create(node);
187-
newNode.url = context.options.rewriteUrl(context.global, node.url);
188-
return newNode;
199+
nestedNode.value = newUrl;
200+
});
189201
}
190202
break;
191203
}
192204
return node;
193205
}
194206

195-
function localizeDeclValue(valueNode, context) {
196-
const newValueNode = Object.create(valueNode);
197-
newValueNode.nodes = valueNode.nodes.map(function(node) {
198-
return localizeDeclNode(node, context);
199-
});
200-
return newValueNode;
201-
}
202-
203-
function localizeAnimationShorthandDeclValueNodes(nodes, context) {
207+
function localizeAnimationShorthandDeclValues(decl, context) {
204208
const validIdent = /^-?[_a-z][_a-z0-9-]*$/i;
205209

206210
/*
@@ -240,9 +244,9 @@ function localizeAnimationShorthandDeclValueNodes(nodes, context) {
240244

241245
const didParseAnimationName = false;
242246
const parsedAnimationKeywords = {};
243-
return nodes.map(function(valueNode) {
247+
const valueNodes = valueParser(decl.value).walk((node) => {
244248
const value =
245-
valueNode.type === 'item' ? valueNode.name.toLowerCase() : null;
249+
node.type === 'word' ? node.value.toLowerCase() : null;
246250

247251
let shouldParseAnimationName = false;
248252

@@ -266,52 +270,43 @@ function localizeAnimationShorthandDeclValueNodes(nodes, context) {
266270
global: context.global,
267271
localizeNextItem: shouldParseAnimationName && !context.global
268272
};
269-
return localizeDeclNode(valueNode, subContext);
273+
return localizeDeclNode(node, subContext);
270274
});
271-
}
272275

273-
function localizeAnimationShorthandDeclValues(valuesNode, decl, context) {
274-
const newValuesNode = Object.create(valuesNode);
275-
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode, index) {
276-
const newValueNode = Object.create(valueNode);
277-
newValueNode.nodes = localizeAnimationShorthandDeclValueNodes(
278-
valueNode.nodes,
279-
context
280-
);
281-
return newValueNode;
282-
});
283-
decl.value = Tokenizer.stringifyValues(newValuesNode);
276+
decl.value = valueNodes.toString();
284277
}
285278

286-
function localizeDeclValues(localize, valuesNode, decl, context) {
287-
const newValuesNode = Object.create(valuesNode);
288-
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode) {
279+
function localizeDeclValues(localize, decl, context) {
280+
const valueNodes = valueParser(decl.value);
281+
valueNodes.walk((node, index, nodes) => {
289282
const subContext = {
290283
options: context.options,
291284
global: context.global,
292285
localizeNextItem: localize && !context.global
293286
};
294-
return localizeDeclValue(valueNode, subContext);
287+
nodes[index] = localizeDeclNode(node, subContext);
295288
});
296-
decl.value = Tokenizer.stringifyValues(newValuesNode);
289+
decl.value = valueNodes.toString();
297290
}
298291

299292
function localizeDecl(decl, context) {
300-
const valuesNode = Tokenizer.parseValues(decl.value);
301-
302-
const isAnimation = /animation?$/i.test(decl.prop);
293+
const isAnimation = /animation$/i.test(decl.prop);
303294

304295
if (isAnimation) {
305-
return localizeAnimationShorthandDeclValues(valuesNode, decl, context);
296+
return localizeAnimationShorthandDeclValues(decl, context);
306297
}
307298

308299
const isAnimationName = /animation(-name)?$/i.test(decl.prop);
309300

310301
if (isAnimationName) {
311-
return localizeDeclValues(true, valuesNode, decl, context);
302+
return localizeDeclValues(true, decl, context);
312303
}
313304

314-
return localizeDeclValues(false, valuesNode, decl, context);
305+
const hasUrl = /url\(/i.test(decl.value);
306+
307+
if (hasUrl) {
308+
return localizeDeclValues(false, decl, context);
309+
}
315310
}
316311

317312
module.exports = postcss.plugin('postcss-modules-local-by-default', function(

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
},
2121
"dependencies": {
2222
"css-selector-tokenizer": "^0.7.0",
23-
"postcss": "^7.0.6"
23+
"postcss": "^7.0.6",
24+
"postcss-value-parser": "^3.3.1"
2425
},
2526
"devDependencies": {
2627
"chokidar-cli": "^1.0.1",

test.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,32 @@ const tests = [
449449
})(),
450450
// postcss-less's stringify would honor `ruleWithoutBody` and omit the trailing `{}`
451451
expected: ':local(.a) {\n :local(.b) {}\n}'
452-
}
452+
},
453+
{
454+
should: 'not break unicode characters',
455+
input: '.a { content: "\\2193" }',
456+
expected: ':local(.a) { content: "\\2193" }'
457+
},
458+
{
459+
should: 'not break unicode characters',
460+
input: '.a { content: "\\2193\\2193" }',
461+
expected: ':local(.a) { content: "\\2193\\2193" }'
462+
},
463+
{
464+
should: 'not break unicode characters',
465+
input: '.a { content: "\\2193 \\2193" }',
466+
expected: ':local(.a) { content: "\\2193 \\2193" }'
467+
},
468+
{
469+
should: 'not break unicode characters',
470+
input: '.a { content: "\\2193\\2193\\2193" }',
471+
expected: ':local(.a) { content: "\\2193\\2193\\2193" }'
472+
},
473+
{
474+
should: 'not break unicode characters',
475+
input: '.a { content: "\\2193 \\2193 \\2193" }',
476+
expected: ':local(.a) { content: "\\2193 \\2193 \\2193" }'
477+
},
453478
];
454479

455480
function process(css, options) {

0 commit comments

Comments
 (0)