Skip to content

Commit a16c07d

Browse files
committed
Merge pull request #7 from css-modules/localize-update
Major localization update
2 parents 64ed497 + 7a30a7b commit a16c07d

File tree

2 files changed

+310
-33
lines changed

2 files changed

+310
-33
lines changed

index.js

Lines changed: 132 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,156 @@
11
var postcss = require('postcss');
22
var Tokenizer = require('css-selector-tokenizer');
33

4-
function isGlobal(node) {
5-
return node.type === 'pseudo-class' && node.name === 'global';
4+
function normalizeNodeArray(nodes) {
5+
var array = [];
6+
nodes.forEach(function(x) {
7+
if(Array.isArray(x)) {
8+
normalizeNodeArray(x).forEach(function(item) {
9+
array.push(item);
10+
});
11+
} else if(x) {
12+
array.push(x);
13+
}
14+
});
15+
if(array.length > 0 && array[array.length - 1].type === "spacing") {
16+
array.pop();
17+
}
18+
return array;
619
}
720

8-
function isNestedGlobal(node) {
9-
return node.type === 'nested-pseudo-class' && node.name === 'global';
10-
}
11-
12-
function isNestedLocal(node) {
13-
return node.type === 'nested-pseudo-class' && node.name === 'local';
14-
}
21+
function localizeNode(node, context) {
22+
if(context.ignoreNextSpacing && node.type !== "spacing") {
23+
throw new Error("Missing whitespace after :" + context.ignoreNextSpacing);
24+
}
25+
if(context.enforceNoSpacing && node.type === "spacing") {
26+
throw new Error("Missing whitespace before :" + context.enforceNoSpacing);
27+
}
1528

16-
function localizeNodes(nodes) {
17-
var isGlobalContext = false;
29+
var newNodes;
30+
switch(node.type) {
31+
case "selectors":
32+
var resultingGlobal;
33+
newNodes = node.nodes.map(function(n) {
34+
var nContext = { global: context.global, lastWasSpacing: true };
35+
n = localizeNode(n, nContext);
36+
if(typeof resultingGlobal === "undefined") {
37+
resultingGlobal = nContext.global;
38+
} else if(resultingGlobal !== nContext.global) {
39+
throw new Error("Inconsistent rule global/local result in rule '" +
40+
Tokenizer.stringify(node) + "' (multiple selectors must result in the same mode for the rule)");
41+
}
42+
return n;
43+
});
44+
context.global = resultingGlobal;
45+
node = Object.create(node);
46+
node.nodes = normalizeNodeArray(newNodes);
47+
break;
1848

19-
return nodes
20-
.map(function(node, i) {
21-
var newNode = node;
49+
case "selector":
50+
newNodes = node.nodes.map(function(n) {
51+
return localizeNode(n, context);
52+
});
53+
node = Object.create(node);
54+
node.nodes = normalizeNodeArray(newNodes);
55+
break;
2256

23-
if (isGlobal(newNode)) {
24-
isGlobalContext = true;
57+
case "spacing":
58+
if(context.ignoreNextSpacing) {
59+
context.ignoreNextSpacing = false;
60+
context.lastWasSpacing = false;
61+
context.enforceNoSpacing = false;
2562
return null;
2663
}
64+
context.lastWasSpacing = true;
65+
return node;
2766

28-
if (newNode.type === 'spacing' && isGlobal(nodes[i - 1])) {
67+
case "pseudo-class":
68+
if(node.name === "local" || node.name === "global") {
69+
if(context.inside) {
70+
throw new Error("A :" + node.name + " is not allowed inside of a :" + context.inside + "(...)");
71+
}
72+
context.ignoreNextSpacing = context.lastWasSpacing ? node.name : false;
73+
context.enforceNoSpacing = context.lastWasSpacing ? false : node.name;
74+
context.global = (node.name === "global");
2975
return null;
3076
}
77+
break;
3178

32-
if (!isGlobalContext && node.type === 'class') {
33-
newNode = { type: 'nested-pseudo-class', name: 'local', nodes: [node] };
34-
} else if (isNestedGlobal(newNode)) {
35-
newNode = node.nodes[0];
36-
} else if (!isNestedLocal(newNode) && newNode.nodes) {
37-
newNode.nodes = localizeNodes(newNode.nodes);
79+
case "nested-pseudo-class":
80+
var subContext;
81+
if(node.name === "local" || node.name === "global") {
82+
if(context.inside) {
83+
throw new Error("A :" + node.name + "(...) is not allowed inside of a :" + context.inside + "(...)");
84+
}
85+
subContext = { global: (node.name === "global"), inside: node.name };
86+
node = node.nodes.map(function(n) {
87+
return localizeNode(n, subContext);
88+
});
89+
// don't leak spacing
90+
node[0].before = undefined;
91+
node[node.length - 1].after = undefined;
92+
} else {
93+
subContext = { global: context.global, inside: context.inside, lastWasSpacing: true };
94+
newNodes = node.nodes.map(function(n) {
95+
return localizeNode(n, subContext);
96+
});
97+
node = Object.create(node);
98+
node.nodes = normalizeNodeArray(newNodes);
3899
}
100+
break;
39101

40-
return newNode;
41-
}).filter(function(node) {
42-
return node !== null;
43-
});
102+
case "class":
103+
if(!context.global) {
104+
node = {
105+
type: "nested-pseudo-class",
106+
name: "local",
107+
nodes: [node]
108+
};
109+
}
110+
break;
111+
}
112+
113+
// reset context
114+
context.lastWasSpacing = false;
115+
context.ignoreNextSpacing = false;
116+
context.enforceNoSpacing = false;
117+
return node;
44118
}
45119

46-
module.exports = postcss.plugin('postcss-modules-local-by-default', function () {
120+
function localizeDecl(decl) {
121+
if(typeof decl.prop === "string" && /animation(-name)?/.test(decl.prop)) {
122+
decl.value = decl.value.replace(/(^|,)(\s*)(\w+)/g, "$1$2:local($3)"); // TODO
123+
}
124+
}
125+
126+
module.exports = postcss.plugin('postcss-modules-local-by-default', function (options) {
127+
if(options && options.mode) {
128+
if(options.mode !== "global" && options.mode !== "local") {
129+
throw new Error("options.mode must be either 'global' or 'local' (default 'local')");
130+
}
131+
}
47132
return function(css) {
133+
var initialGlobal = options && options.mode === "global";
134+
css.eachAtRule(function(atrule) {
135+
if(/keyframes$/.test(atrule.name)) {
136+
var globalMatch = /^\s*:global\s*\((.+)\)\s*$/.exec(atrule.params);
137+
var localMatch = /^\s*:local\s*\((.+)\)\s*$/.exec(atrule.params);
138+
if(globalMatch) {
139+
atrule.params = globalMatch[1];
140+
} else if(localMatch) {
141+
atrule.params = localMatch[0];
142+
} else if(!initialGlobal) {
143+
atrule.params = ":local(" + atrule.params + ")";
144+
}
145+
}
146+
});
48147
css.eachRule(function(rule) {
49148
var selector = Tokenizer.parse(rule.selector);
50-
selector.nodes = localizeNodes(selector.nodes);
149+
var context = { global: initialGlobal };
150+
selector = localizeNode(selector, context);
151+
if(!context.global) {
152+
rule.nodes.forEach(localizeDecl);
153+
}
51154
rule.selector = Tokenizer.stringify(selector);
52155
});
53156
};

0 commit comments

Comments
 (0)