Skip to content

Commit 0559b68

Browse files
committed
add pure ignore comment for css modules
1 parent fde62d7 commit 0559b68

File tree

3 files changed

+180
-3
lines changed

3 files changed

+180
-3
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ Declarations (mode `local`, by default):
5353
}
5454
```
5555
<!-- prettier-ignore-end -->
56+
## Pure Mode
57+
58+
In pure mode, all selectors must contain at least one local class or id
59+
selector
60+
61+
To ignore this rule for a specific selector, add the following comment in front
62+
of the selector:
63+
64+
```css
65+
/* cssmodules-pure-ignore */
66+
:global(#modal-backdrop) {
67+
...;
68+
}
69+
```
5670

5771
## Building
5872

src/index.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,26 @@ const selectorParser = require("postcss-selector-parser");
44
const valueParser = require("postcss-value-parser");
55
const { extractICSS } = require("icss-utils");
66

7+
const IGNORE_MARKER = "cssmodules-pure-ignore";
8+
79
const isSpacing = (node) => node.type === "combinator" && node.value === " ";
810

11+
function hasIgnoreComment(node) {
12+
if (!node.parent) {
13+
return false;
14+
}
15+
const indexInParent = node.parent.index(node);
16+
for (let i = indexInParent - 1; i >= 0; i--) {
17+
const prevNode = node.parent.nodes[i];
18+
if (prevNode.type === "comment") {
19+
return prevNode.text.trimStart().startsWith(IGNORE_MARKER);
20+
} else {
21+
break;
22+
}
23+
}
24+
return false;
25+
}
26+
927
function normalizeNodeArray(nodes) {
1028
const array = [];
1129

@@ -524,7 +542,7 @@ module.exports = (options = {}) => {
524542
let globalKeyframes = globalMode;
525543

526544
if (globalMatch) {
527-
if (pureMode) {
545+
if (pureMode && !hasIgnoreComment(atRule)) {
528546
throw atRule.error(
529547
"@keyframes :global(...) is not allowed in pure mode"
530548
);
@@ -564,7 +582,11 @@ module.exports = (options = {}) => {
564582
context.options = options;
565583
context.localAliasMap = localAliasMap;
566584

567-
if (pureMode && context.hasPureGlobals) {
585+
if (
586+
pureMode &&
587+
context.hasPureGlobals &&
588+
!hasIgnoreComment(atRule)
589+
) {
568590
throw atRule.error(
569591
'Selector in at-rule"' +
570592
selector +
@@ -615,7 +637,7 @@ module.exports = (options = {}) => {
615637
context.options = options;
616638
context.localAliasMap = localAliasMap;
617639

618-
if (pureMode && context.hasPureGlobals) {
640+
if (pureMode && context.hasPureGlobals && !hasIgnoreComment(rule)) {
619641
throw rule.error(
620642
'Selector "' +
621643
rule.selector +

test/index.test.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,147 @@ const tests = [
944944
options: { mode: "pure" },
945945
error: /is not pure/,
946946
},
947+
{
948+
name: "should suppress errors for global selectors after ignore comment",
949+
options: { mode: "pure" },
950+
input: `/* cssmodules-pure-ignore */
951+
:global(.foo) { color: blue; }`,
952+
expected: `/* cssmodules-pure-ignore */
953+
.foo { color: blue; }`,
954+
},
955+
{
956+
name: "should allow additional text in ignore comment",
957+
options: { mode: "pure" },
958+
input: `/* cssmodules-pure-ignore - needed for third party integration */
959+
:global(#foo) { color: blue; }`,
960+
expected: `/* cssmodules-pure-ignore - needed for third party integration */
961+
#foo { color: blue; }`,
962+
},
963+
{
964+
name: "should not affect rules after the ignored block",
965+
options: { mode: "pure" },
966+
input: `/* cssmodules-pure-ignore */
967+
:global(.foo) { color: blue; }
968+
:global(.bar) { color: red; }`,
969+
error: /is not pure/,
970+
},
971+
{
972+
name: "should work with nested global selectors in ignored block",
973+
options: { mode: "pure" },
974+
input: `/* cssmodules-pure-ignore */
975+
:global(.foo) {
976+
:global(.bar) { color: blue; }
977+
}`,
978+
error: /is not pure/,
979+
},
980+
{
981+
name: "should work with ignored nested global selectors in ignored block",
982+
options: { mode: "pure" },
983+
input: `/* cssmodules-pure-ignore */
984+
:global(.foo) {
985+
/* cssmodules-pure-ignore */
986+
:global(.bar) { color: blue; }
987+
}`,
988+
expected: `/* cssmodules-pure-ignore */
989+
.foo {
990+
/* cssmodules-pure-ignore */
991+
.bar { color: blue; }
992+
}`,
993+
},
994+
{
995+
name: "should work with view transitions in ignored block",
996+
options: { mode: "pure" },
997+
input: `/* cssmodules-pure-ignore */
998+
::view-transition-group(modal) {
999+
animation-duration: 300ms;
1000+
}`,
1001+
expected: `/* cssmodules-pure-ignore */
1002+
::view-transition-group(modal) {
1003+
animation-duration: 300ms;
1004+
}`,
1005+
},
1006+
{
1007+
name: "should work with keyframes in ignored block",
1008+
options: { mode: "pure" },
1009+
input: `/* cssmodules-pure-ignore */
1010+
@keyframes :global(fadeOut) {
1011+
from { opacity: 1; }
1012+
to { opacity: 0; }
1013+
}`,
1014+
expected: `/* cssmodules-pure-ignore */
1015+
@keyframes fadeOut {
1016+
from { opacity: 1; }
1017+
to { opacity: 0; }
1018+
}`,
1019+
},
1020+
{
1021+
name: "should work in media queries",
1022+
options: { mode: "pure" },
1023+
input: `@media (min-width: 768px) {
1024+
/* cssmodules-pure-ignore */
1025+
:global(.foo) { color: blue; }
1026+
}`,
1027+
expected: `@media (min-width: 768px) {
1028+
/* cssmodules-pure-ignore */
1029+
.foo { color: blue; }
1030+
}`,
1031+
},
1032+
{
1033+
name: "should handle multiple ignore comments",
1034+
options: { mode: "pure" },
1035+
input: `/* cssmodules-pure-ignore */
1036+
:global(.foo) { color: blue; }
1037+
.local { color: green; }
1038+
/* cssmodules-pure-ignore */
1039+
:global(.bar) { color: red; }`,
1040+
expected: `/* cssmodules-pure-ignore */
1041+
.foo { color: blue; }
1042+
:local(.local) { color: green; }
1043+
/* cssmodules-pure-ignore */
1044+
.bar { color: red; }`,
1045+
},
1046+
{
1047+
name: "should work with complex selectors in ignored block",
1048+
options: { mode: "pure" },
1049+
input: `/* cssmodules-pure-ignore */
1050+
:global(.foo):hover > :global(.bar) + :global(.baz) {
1051+
color: blue;
1052+
}`,
1053+
expected: `/* cssmodules-pure-ignore */
1054+
.foo:hover > .bar + .baz {
1055+
color: blue;
1056+
}`,
1057+
},
1058+
{
1059+
name: "should work with multiple selectors in ignored block",
1060+
options: { mode: "pure" },
1061+
input: `/* cssmodules-pure-ignore */
1062+
:global(.foo),
1063+
:global(.bar),
1064+
:global(.baz) {
1065+
color: blue;
1066+
}`,
1067+
expected: `/* cssmodules-pure-ignore */
1068+
.foo,
1069+
.bar,
1070+
.baz {
1071+
color: blue;
1072+
}`,
1073+
},
1074+
{
1075+
name: "should work with pseudo-elements in ignored block",
1076+
options: { mode: "pure" },
1077+
input: `/* cssmodules-pure-ignore */
1078+
:global(.foo)::before,
1079+
:global(.foo)::after {
1080+
content: '';
1081+
}`,
1082+
expected: `/* cssmodules-pure-ignore */
1083+
.foo::before,
1084+
.foo::after {
1085+
content: '';
1086+
}`,
1087+
},
9471088
{
9481089
name: "css nesting",
9491090
input: `

0 commit comments

Comments
 (0)