Skip to content

fix: unicode characters #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 47 additions & 52 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const postcss = require('postcss');
const Tokenizer = require('css-selector-tokenizer');
const valueParser = require('postcss-value-parser');

function normalizeNodeArray(nodes) {
const array = [];
Expand Down Expand Up @@ -162,45 +163,48 @@ function localizeNode(node, context) {
}

function localizeDeclNode(node, context) {
let newNode;
switch (node.type) {
case 'item':
case 'word':
if (context.localizeNextItem) {
newNode = Object.create(node);
newNode.name = ':local(' + newNode.name + ')';
node.value = ':local(' + node.value + ')';
context.localizeNextItem = false;
return newNode;
}
break;

case 'nested-item':
const newNodes = node.nodes.map(function(n) {
return localizeDeclValue(n, context);
});
node = Object.create(node);
node.nodes = newNodes;
break;
case 'function':
if (context.options && context.options.rewriteUrl && node.value.toLowerCase() === 'url') {
node.nodes.map((nestedNode) => {
if (nestedNode.type !== 'string' && nestedNode.type !== 'word') {
return;
}

let newUrl = context.options.rewriteUrl(context.global, nestedNode.value);

switch (nestedNode.type) {
case 'string':
if (nestedNode.quote === '\'') {
newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/'/g, '\\\'')
}

if (nestedNode.quote === '"') {
newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/"/g, '\\"')
}

break;
case 'word':
newUrl = newUrl.replace(/("|'|\)|\\)/g, '\\$1');
break;
}

case 'url':
if (context.options && context.options.rewriteUrl) {
newNode = Object.create(node);
newNode.url = context.options.rewriteUrl(context.global, node.url);
return newNode;
nestedNode.value = newUrl;
});
}
break;
}
return node;
}

function localizeDeclValue(valueNode, context) {
const newValueNode = Object.create(valueNode);
newValueNode.nodes = valueNode.nodes.map(function(node) {
return localizeDeclNode(node, context);
});
return newValueNode;
}

function localizeAnimationShorthandDeclValueNodes(nodes, context) {
function localizeAnimationShorthandDeclValues(decl, context) {
const validIdent = /^-?[_a-z][_a-z0-9-]*$/i;

/*
Expand Down Expand Up @@ -240,9 +244,9 @@ function localizeAnimationShorthandDeclValueNodes(nodes, context) {

const didParseAnimationName = false;
const parsedAnimationKeywords = {};
return nodes.map(function(valueNode) {
const valueNodes = valueParser(decl.value).walk((node) => {
const value =
valueNode.type === 'item' ? valueNode.name.toLowerCase() : null;
node.type === 'word' ? node.value.toLowerCase() : null;

let shouldParseAnimationName = false;

Expand All @@ -266,52 +270,43 @@ function localizeAnimationShorthandDeclValueNodes(nodes, context) {
global: context.global,
localizeNextItem: shouldParseAnimationName && !context.global
};
return localizeDeclNode(valueNode, subContext);
return localizeDeclNode(node, subContext);
});
}

function localizeAnimationShorthandDeclValues(valuesNode, decl, context) {
const newValuesNode = Object.create(valuesNode);
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode, index) {
const newValueNode = Object.create(valueNode);
newValueNode.nodes = localizeAnimationShorthandDeclValueNodes(
valueNode.nodes,
context
);
return newValueNode;
});
decl.value = Tokenizer.stringifyValues(newValuesNode);
decl.value = valueNodes.toString();
}

function localizeDeclValues(localize, valuesNode, decl, context) {
const newValuesNode = Object.create(valuesNode);
newValuesNode.nodes = valuesNode.nodes.map(function(valueNode) {
function localizeDeclValues(localize, decl, context) {
const valueNodes = valueParser(decl.value);
valueNodes.walk((node, index, nodes) => {
const subContext = {
options: context.options,
global: context.global,
localizeNextItem: localize && !context.global
};
return localizeDeclValue(valueNode, subContext);
nodes[index] = localizeDeclNode(node, subContext);
});
decl.value = Tokenizer.stringifyValues(newValuesNode);
decl.value = valueNodes.toString();
}

function localizeDecl(decl, context) {
const valuesNode = Tokenizer.parseValues(decl.value);

const isAnimation = /animation?$/i.test(decl.prop);
const isAnimation = /animation$/i.test(decl.prop);

if (isAnimation) {
return localizeAnimationShorthandDeclValues(valuesNode, decl, context);
return localizeAnimationShorthandDeclValues(decl, context);
}

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

if (isAnimationName) {
return localizeDeclValues(true, valuesNode, decl, context);
return localizeDeclValues(true, decl, context);
}

return localizeDeclValues(false, valuesNode, decl, context);
const hasUrl = /url\(/i.test(decl.value);

if (hasUrl) {
return localizeDeclValues(false, decl, context);
}
}

module.exports = postcss.plugin('postcss-modules-local-by-default', function(
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
},
"dependencies": {
"css-selector-tokenizer": "^0.7.0",
"postcss": "^7.0.6"
"postcss": "^7.0.6",
"postcss-value-parser": "^3.3.1"
},
"devDependencies": {
"chokidar-cli": "^1.0.1",
Expand Down
35 changes: 32 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,14 @@ const tests = [
'.a { background: url(./image.png); }\n' +
':global .b { background: url(image.png); }\n' +
'.c { background: url("./image.png"); }\n' +
'.c { background: url(\'./image.png\'); }\n' +
'.d { background: -webkit-image-set(url("./image.png") 1x, url("./image2x.png") 2x); }\n' +
'@font-face { src: url("./font.woff"); }\n' +
'@-webkit-font-face { src: url("./font.woff"); }\n' +
'@media screen { .a { src: url("./image.png"); } }\n' +
'@keyframes :global(ani1) { 0% { src: url("image.png"); } }\n' +
'@keyframes ani2 { 0% { src: url("./image.png"); } }',
'@keyframes ani2 { 0% { src: url("./image.png"); } }\n' +
'foo { background: end-with-url(something); }',
options: {
rewriteUrl: function(global, url) {
const mode = global ? 'global' : 'local';
Expand All @@ -426,12 +428,14 @@ const tests = [
':local(.a) { background: url((local\\)./image.png\\"local\\"); }\n' +
'.b { background: url((global\\)image.png\\"global\\"); }\n' +
':local(.c) { background: url("(local)./image.png\\"local\\""); }\n' +
':local(.c) { background: url(\'(local)./image.png"local"\'); }\n' +
':local(.d) { background: -webkit-image-set(url("(local)./image.png\\"local\\"") 1x, url("(local)./image2x.png\\"local\\"") 2x); }\n' +
'@font-face { src: url("(local)./font.woff\\"local\\""); }\n' +
'@-webkit-font-face { src: url("(local)./font.woff\\"local\\""); }\n' +
'@media screen { :local(.a) { src: url("(local)./image.png\\"local\\""); } }\n' +
'@keyframes ani1 { 0% { src: url("(global)image.png\\"global\\""); } }\n' +
'@keyframes :local(ani2) { 0% { src: url("(local)./image.png\\"local\\""); } }'
'@keyframes :local(ani2) { 0% { src: url("(local)./image.png\\"local\\""); } }\n' +
'foo { background: end-with-url(something); }',
},
{
should: 'not crash on atrule without nodes',
Expand All @@ -449,7 +453,32 @@ const tests = [
})(),
// postcss-less's stringify would honor `ruleWithoutBody` and omit the trailing `{}`
expected: ':local(.a) {\n :local(.b) {}\n}'
}
},
{
should: 'not break unicode characters',
input: '.a { content: "\\2193" }',
expected: ':local(.a) { content: "\\2193" }'
},
{
should: 'not break unicode characters',
input: '.a { content: "\\2193\\2193" }',
expected: ':local(.a) { content: "\\2193\\2193" }'
},
{
should: 'not break unicode characters',
input: '.a { content: "\\2193 \\2193" }',
expected: ':local(.a) { content: "\\2193 \\2193" }'
},
{
should: 'not break unicode characters',
input: '.a { content: "\\2193\\2193\\2193" }',
expected: ':local(.a) { content: "\\2193\\2193\\2193" }'
},
{
should: 'not break unicode characters',
input: '.a { content: "\\2193 \\2193 \\2193" }',
expected: ':local(.a) { content: "\\2193 \\2193 \\2193" }'
},
];

function process(css, options) {
Expand Down