Skip to content

Commit 7b6f630

Browse files
committed
Merge pull request #11 from css-modules/warn-implicit
Warn implicit
2 parents e3c94d4 + fc08e3f commit 7b6f630

File tree

2 files changed

+79
-21
lines changed

2 files changed

+79
-21
lines changed

index.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@ function localizeNode(node, context) {
3030
switch(node.type) {
3131
case "selectors":
3232
var resultingGlobal;
33-
context.locals = true;
33+
context.hasPureGlobals = false;
34+
context.hasPureImplicitGlobals = false;
3435
newNodes = node.nodes.map(function(n) {
3536
var nContext = {
3637
global: context.global,
3738
lastWasSpacing: true,
38-
locals: false
39+
hasLocals: false,
40+
hasImplicitGlobals: false,
41+
explicit: false
3942
};
4043
n = localizeNode(n, nContext);
4144
if(typeof resultingGlobal === "undefined") {
@@ -44,8 +47,11 @@ function localizeNode(node, context) {
4447
throw new Error("Inconsistent rule global/local result in rule '" +
4548
Tokenizer.stringify(node) + "' (multiple selectors must result in the same mode for the rule)");
4649
}
47-
if(!nContext.locals) {
48-
context.locals = false;
50+
if(!nContext.hasLocals) {
51+
context.hasPureGlobals = true;
52+
if(nContext.hasImplicitGlobals) {
53+
context.hasPureImplicitGlobals = true;
54+
}
4955
}
5056
return n;
5157
});
@@ -80,6 +86,7 @@ function localizeNode(node, context) {
8086
context.ignoreNextSpacing = context.lastWasSpacing ? node.name : false;
8187
context.enforceNoSpacing = context.lastWasSpacing ? false : node.name;
8288
context.global = (node.name === "global");
89+
context.explicit = true;
8390
return null;
8491
}
8592
break;
@@ -93,32 +100,43 @@ function localizeNode(node, context) {
93100
subContext = {
94101
global: (node.name === "global"),
95102
inside: node.name,
96-
locals: false
103+
hasLocals: false,
104+
hasImplicitGlobals: false,
105+
explicit: true
97106
};
98107
node = node.nodes.map(function(n) {
99108
return localizeNode(n, subContext);
100109
});
101110
// don't leak spacing
102111
node[0].before = undefined;
103112
node[node.length - 1].after = undefined;
104-
if(subContext.locals) {
105-
context.locals = true;
106-
}
107113
} else {
108114
subContext = {
109115
global: context.global,
110116
inside: context.inside,
111117
lastWasSpacing: true,
112-
locals: false
118+
hasLocals: false,
119+
hasImplicitGlobals: false,
120+
explicit: context.explicit
113121
};
114122
newNodes = node.nodes.map(function(n) {
115123
return localizeNode(n, subContext);
116124
});
117125
node = Object.create(node);
118126
node.nodes = normalizeNodeArray(newNodes);
119-
if(subContext.locals) {
120-
context.locals = true;
121-
}
127+
}
128+
if(subContext.hasLocals) {
129+
context.hasLocals = true;
130+
}
131+
if(subContext.hasImplicitGlobals) {
132+
context.hasImplicitGlobals = true;
133+
}
134+
break;
135+
136+
case "attribute":
137+
case "element":
138+
if(!context.global && !context.explicit) {
139+
context.hasImplicitGlobals = true;
122140
}
123141
break;
124142

@@ -130,7 +148,7 @@ function localizeNode(node, context) {
130148
name: "local",
131149
nodes: [node]
132150
};
133-
context.locals = true;
151+
context.hasLocals = true;
134152
}
135153
break;
136154
}
@@ -177,13 +195,18 @@ module.exports = postcss.plugin('postcss-modules-local-by-default', function (op
177195
var selector = Tokenizer.parse(rule.selector);
178196
var context = {
179197
global: globalMode,
180-
locals: false
198+
hasPureGlobals: false,
199+
hasPureImplicitGlobals: false
181200
};
182201
var newSelector = localizeNode(selector, context);
183-
if(pureMode && !context.locals) {
202+
if(pureMode && context.hasPureGlobals) {
184203
throw new Error("Selector '" + Tokenizer.stringify(selector) + "' is not pure " +
185204
"(pure selectors must contain at least one local class or id)");
186205
}
206+
if(!globalMode && context.hasPureImplicitGlobals) {
207+
throw new Error("Selector '" + Tokenizer.stringify(selector) + "' must be explicit flagged :global " +
208+
"(elsewise it would leak globally)");
209+
}
187210
if(!context.global) {
188211
rule.nodes.forEach(localizeDecl);
189212
}

test.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,22 @@ var tests = [
235235
input: ':import("~/lol.css") { foo: __foo; }',
236236
expected: ':import("~/lol.css") { foo: __foo; }'
237237
},
238+
{
239+
should: 'compile in pure mode',
240+
input: ':global(.foo).bar, [type="radio"] ~ .label, :not(.foo), #bar {}',
241+
options: { mode: "pure" },
242+
expected: '.foo:local(.bar), [type="radio"] ~ :local(.label), :not(:local(.foo)), :local(#bar) {}'
243+
},
244+
{
245+
should: 'compile explict global element',
246+
input: ':global(input) {}',
247+
expected: 'input {}'
248+
},
249+
{
250+
should: 'compile explict global attribute',
251+
input: ':global([type="radio"]), :not(:global [type="radio"]) {}',
252+
expected: '[type="radio"], :not([type="radio"]) {}'
253+
},
238254

239255
{
240256
should: 'throw on invalid mode',
@@ -283,12 +299,6 @@ var tests = [
283299
options: { mode: "pure" },
284300
error: /':global\(\.foo\)' is not pure/
285301
},
286-
{
287-
should: 'compile in pure mode',
288-
input: ':global(.foo).bar, [type="radio"] ~ .label, :not(.foo), #bar {}',
289-
options: { mode: "pure" },
290-
expected: '.foo:local(.bar), [type="radio"] ~ :local(.label), :not(:local(.foo)), :local(#bar) {}'
291-
},
292302
{
293303
should: 'throw on not pure selector (with multiple 1)',
294304
input: '.foo, :global(.bar) {}',
@@ -318,6 +328,31 @@ var tests = [
318328
input: '@keyframes :global(foo) {}',
319329
options: { mode: "pure" },
320330
error: /@keyframes :global\(\.\.\.\) is not allowed in pure mode/
331+
},
332+
{
333+
should: 'throw on implicit global element',
334+
input: 'input {}',
335+
error: /'input' must be explicit flagged :global/
336+
},
337+
{
338+
should: 'throw on implicit global element (with multiple 1)',
339+
input: 'input, .foo {}',
340+
error: /'input, \.foo' must be explicit flagged :global/
341+
},
342+
{
343+
should: 'throw on implicit global element (with multiple 2)',
344+
input: '.foo, input {}',
345+
error: /'\.foo, input' must be explicit flagged :global/
346+
},
347+
{
348+
should: 'throw on implicit global attribute',
349+
input: '[type="radio"] {}',
350+
error: /'\[type="radio"\]' must be explicit flagged :global/
351+
},
352+
{
353+
should: 'throw on implicit global attribute in nested',
354+
input: ':not([type="radio"]) {}',
355+
error: /':not\(\[type="radio"\]\)' must be explicit flagged :global/
321356
}
322357
];
323358

0 commit comments

Comments
 (0)