Skip to content

Commit c812ff4

Browse files
committed
add pure ignore comment for css modules
1 parent f1c05a5 commit c812ff4

File tree

3 files changed

+181
-3
lines changed

3 files changed

+181
-3
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ Transformation examples:
2828
```
2929
<!-- prettier-ignore-end -->
3030

31+
## Pure Mode
32+
33+
In pure mode, all selectors must contain at least one local class or id
34+
selector
35+
36+
To ignore this rule for a specific selector, add the following comment in front
37+
of the selector:
38+
39+
```css
40+
/* cssmodules-pure-ignore */
41+
:global(#modal-backdrop) {
42+
...;
43+
}
44+
```
45+
3146
## Building
3247

3348
```bash

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

@@ -514,7 +532,7 @@ module.exports = (options = {}) => {
514532
let globalKeyframes = globalMode;
515533

516534
if (globalMatch) {
517-
if (pureMode) {
535+
if (pureMode && !hasIgnoreComment(atRule)) {
518536
throw atRule.error(
519537
"@keyframes :global(...) is not allowed in pure mode"
520538
);
@@ -554,7 +572,11 @@ module.exports = (options = {}) => {
554572
context.options = options;
555573
context.localAliasMap = localAliasMap;
556574

557-
if (pureMode && context.hasPureGlobals) {
575+
if (
576+
pureMode &&
577+
context.hasPureGlobals &&
578+
!hasIgnoreComment(atRule)
579+
) {
558580
throw atRule.error(
559581
'Selector in at-rule"' +
560582
selector +
@@ -605,7 +627,7 @@ module.exports = (options = {}) => {
605627
context.options = options;
606628
context.localAliasMap = localAliasMap;
607629

608-
if (pureMode && context.hasPureGlobals) {
630+
if (pureMode && context.hasPureGlobals && !hasIgnoreComment(rule)) {
609631
throw rule.error(
610632
'Selector "' +
611633
rule.selector +

test/index.test.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,147 @@ const tests = [
876876
options: { mode: "pure" },
877877
error: /is not pure/,
878878
},
879+
{
880+
name: "should suppress errors for global selectors after ignore comment",
881+
options: { mode: "pure" },
882+
input: `/* cssmodules-pure-ignore */
883+
:global(.foo) { color: blue; }`,
884+
expected: `/* cssmodules-pure-ignore */
885+
.foo { color: blue; }`,
886+
},
887+
{
888+
name: "should allow additional text in ignore comment",
889+
options: { mode: "pure" },
890+
input: `/* cssmodules-pure-ignore - needed for third party integration */
891+
:global(#foo) { color: blue; }`,
892+
expected: `/* cssmodules-pure-ignore - needed for third party integration */
893+
#foo { color: blue; }`,
894+
},
895+
{
896+
name: "should not affect rules after the ignored block",
897+
options: { mode: "pure" },
898+
input: `/* cssmodules-pure-ignore */
899+
:global(.foo) { color: blue; }
900+
:global(.bar) { color: red; }`,
901+
error: /is not pure/,
902+
},
903+
{
904+
name: "should work with nested global selectors in ignored block",
905+
options: { mode: "pure" },
906+
input: `/* cssmodules-pure-ignore */
907+
:global(.foo) {
908+
:global(.bar) { color: blue; }
909+
}`,
910+
error: /is not pure/,
911+
},
912+
{
913+
name: "should work with ignored nested global selectors in ignored block",
914+
options: { mode: "pure" },
915+
input: `/* cssmodules-pure-ignore */
916+
:global(.foo) {
917+
/* cssmodules-pure-ignore */
918+
:global(.bar) { color: blue; }
919+
}`,
920+
expected: `/* cssmodules-pure-ignore */
921+
.foo {
922+
/* cssmodules-pure-ignore */
923+
.bar { color: blue; }
924+
}`,
925+
},
926+
{
927+
name: "should work with view transitions in ignored block",
928+
options: { mode: "pure" },
929+
input: `/* cssmodules-pure-ignore */
930+
::view-transition-group(modal) {
931+
animation-duration: 300ms;
932+
}`,
933+
expected: `/* cssmodules-pure-ignore */
934+
::view-transition-group(modal) {
935+
animation-duration: 300ms;
936+
}`,
937+
},
938+
{
939+
name: "should work with keyframes in ignored block",
940+
options: { mode: "pure" },
941+
input: `/* cssmodules-pure-ignore */
942+
@keyframes :global(fadeOut) {
943+
from { opacity: 1; }
944+
to { opacity: 0; }
945+
}`,
946+
expected: `/* cssmodules-pure-ignore */
947+
@keyframes fadeOut {
948+
from { opacity: 1; }
949+
to { opacity: 0; }
950+
}`,
951+
},
952+
{
953+
name: "should work in media queries",
954+
options: { mode: "pure" },
955+
input: `@media (min-width: 768px) {
956+
/* cssmodules-pure-ignore */
957+
:global(.foo) { color: blue; }
958+
}`,
959+
expected: `@media (min-width: 768px) {
960+
/* cssmodules-pure-ignore */
961+
.foo { color: blue; }
962+
}`,
963+
},
964+
{
965+
name: "should handle multiple ignore comments",
966+
options: { mode: "pure" },
967+
input: `/* cssmodules-pure-ignore */
968+
:global(.foo) { color: blue; }
969+
.local { color: green; }
970+
/* cssmodules-pure-ignore */
971+
:global(.bar) { color: red; }`,
972+
expected: `/* cssmodules-pure-ignore */
973+
.foo { color: blue; }
974+
:local(.local) { color: green; }
975+
/* cssmodules-pure-ignore */
976+
.bar { color: red; }`,
977+
},
978+
{
979+
name: "should work with complex selectors in ignored block",
980+
options: { mode: "pure" },
981+
input: `/* cssmodules-pure-ignore */
982+
:global(.foo):hover > :global(.bar) + :global(.baz) {
983+
color: blue;
984+
}`,
985+
expected: `/* cssmodules-pure-ignore */
986+
.foo:hover > .bar + .baz {
987+
color: blue;
988+
}`,
989+
},
990+
{
991+
name: "should work with multiple selectors in ignored block",
992+
options: { mode: "pure" },
993+
input: `/* cssmodules-pure-ignore */
994+
:global(.foo),
995+
:global(.bar),
996+
:global(.baz) {
997+
color: blue;
998+
}`,
999+
expected: `/* cssmodules-pure-ignore */
1000+
.foo,
1001+
.bar,
1002+
.baz {
1003+
color: blue;
1004+
}`,
1005+
},
1006+
{
1007+
name: "should work with pseudo-elements in ignored block",
1008+
options: { mode: "pure" },
1009+
input: `/* cssmodules-pure-ignore */
1010+
:global(.foo)::before,
1011+
:global(.foo)::after {
1012+
content: '';
1013+
}`,
1014+
expected: `/* cssmodules-pure-ignore */
1015+
.foo::before,
1016+
.foo::after {
1017+
content: '';
1018+
}`,
1019+
},
8791020
{
8801021
name: "css nesting",
8811022
input: `

0 commit comments

Comments
 (0)