Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 9d07eeb

Browse files
feat: add pure ignore comment for CSS Modules (#80)
1 parent fde62d7 commit 9d07eeb

File tree

3 files changed

+288
-5
lines changed

3 files changed

+288
-5
lines changed

‎README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ Declarations (mode `local`, by default):
5454
```
5555
<!-- prettier-ignore-end -->
5656

57+
## Pure Mode
58+
59+
In pure mode, all selectors must contain at least one local class or id
60+
selector
61+
62+
To ignore this rule for a specific selector, add the following comment in front
63+
of the selector:
64+
65+
```css
66+
/* cssmodules-pure-ignore */
67+
:global(#modal-backdrop) {
68+
...;
69+
}
70+
```
71+
5772
## Building
5873

5974
```bash

‎src/index.js

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,29 @@ 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 getIgnoreComment(node) {
12+
if (!node.parent) {
13+
return;
14+
}
15+
16+
const indexInParent = node.parent.index(node);
17+
18+
for (let i = indexInParent - 1; i >= 0; i--) {
19+
const prevNode = node.parent.nodes[i];
20+
if (prevNode.type === "comment") {
21+
if (prevNode.text.trimStart().startsWith(IGNORE_MARKER)) {
22+
return prevNode;
23+
}
24+
} else {
25+
break;
26+
}
27+
}
28+
}
29+
930
function normalizeNodeArray(nodes) {
1031
const array = [];
1132

@@ -525,10 +546,17 @@ module.exports = (options = {}) => {
525546

526547
if (globalMatch) {
527548
if (pureMode) {
528-
throw atRule.error(
529-
"@keyframes :global(...) is not allowed in pure mode"
530-
);
549+
const ignoreComment = getIgnoreComment(atRule);
550+
551+
if (!ignoreComment) {
552+
throw atRule.error(
553+
"@keyframes :global(...) is not allowed in pure mode"
554+
);
555+
} else {
556+
ignoreComment.remove();
557+
}
531558
}
559+
532560
atRule.params = globalMatch[1];
533561
globalKeyframes = true;
534562
} else if (localMatch) {
@@ -551,6 +579,14 @@ module.exports = (options = {}) => {
551579
});
552580
} else if (/scope$/i.test(atRule.name)) {
553581
if (atRule.params) {
582+
const ignoreComment = pureMode
583+
? getIgnoreComment(atRule)
584+
: undefined;
585+
586+
if (ignoreComment) {
587+
ignoreComment.remove();
588+
}
589+
554590
atRule.params = atRule.params
555591
.split("to")
556592
.map((item) => {
@@ -564,7 +600,7 @@ module.exports = (options = {}) => {
564600
context.options = options;
565601
context.localAliasMap = localAliasMap;
566602

567-
if (pureMode && context.hasPureGlobals) {
603+
if (pureMode && context.hasPureGlobals&&!ignoreComment) {
568604
throw atRule.error(
569605
'Selector in at-rule"' +
570606
selector +
@@ -615,13 +651,17 @@ module.exports = (options = {}) => {
615651
context.options = options;
616652
context.localAliasMap = localAliasMap;
617653

618-
if (pureMode && context.hasPureGlobals) {
654+
const ignoreComment = pureMode ? getIgnoreComment(rule) : undefined;
655+
656+
if (pureMode && context.hasPureGlobals && !ignoreComment) {
619657
throw rule.error(
620658
'Selector "' +
621659
rule.selector +
622660
'" is not pure ' +
623661
"(pure selectors must contain at least one local class or id)"
624662
);
663+
} else if (ignoreComment) {
664+
ignoreComment.remove();
625665
}
626666

627667
rule.selector = context.selector;

‎test/index.test.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,234 @@ const tests = [
944944
options: { mode: "pure" },
945945
error: /isnotpure/,
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: `.foo { color: blue; }`,
953+
},
954+
{
955+
name: "should suppress errors for global selectors after ignore comment #2",
956+
options: { mode: "pure" },
957+
input: `/* cssmodules-pure-ignore */
958+
/* another comment */
959+
:global(.foo) { color: blue; }`,
960+
expected: `/* another comment */
961+
.foo { color: blue; }`,
962+
},
963+
{
964+
name: "should suppress errors for global selectors after ignore comment #3",
965+
options: { mode: "pure" },
966+
input: `/* another comment */
967+
/* cssmodules-pure-ignore */
968+
:global(.foo) { color: blue; }`,
969+
expected: `/* another comment */
970+
.foo { color: blue; }`,
971+
},
972+
{
973+
name: "should suppress errors for global selectors after ignore comment #4",
974+
options: { mode: "pure" },
975+
input: `/* cssmodules-pure-ignore */ /* another comment */
976+
:global(.foo) { color: blue; }`,
977+
expected: `/* another comment */
978+
.foo { color: blue; }`,
979+
},
980+
{
981+
name: "should suppress errors for global selectors after ignore comment #5",
982+
options: { mode: "pure" },
983+
input: `/* another comment */ /* cssmodules-pure-ignore */
984+
:global(.foo) { color: blue; }`,
985+
expected: `/* another comment */
986+
.foo { color: blue; }`,
987+
},
988+
{
989+
name: "should suppress errors for global selectors after ignore comment #6",
990+
options: { mode: "pure" },
991+
input: `.foo { /* cssmodules-pure-ignore */ :global(.bar) { color: blue }; }`,
992+
expected: `:local(.foo) { .bar { color: blue }; }`,
993+
},
994+
{
995+
name: "should suppress errors for global selectors after ignore comment #7",
996+
options: { mode: "pure" },
997+
input: `/* cssmodules-pure-ignore */ :global(.foo) { /* cssmodules-pure-ignore */ :global(.bar) { color: blue } }`,
998+
expected: `.foo { .bar { color: blue } }`,
999+
},
1000+
{
1001+
name: "should suppress errors for global selectors after ignore comment #8",
1002+
options: { mode: "pure" },
1003+
input: `/* cssmodules-pure-ignore */ :global(.foo) { color: blue; }`,
1004+
expected: `.foo { color: blue; }`,
1005+
},
1006+
{
1007+
name: "should suppress errors for global selectors after ignore comment #9",
1008+
options: { mode: "pure" },
1009+
input: `/*
1010+
cssmodules-pure-ignore
1011+
*/ :global(.foo) { color: blue; }`,
1012+
expected: `.foo { color: blue; }`,
1013+
},
1014+
{
1015+
name: "should allow additional text in ignore comment",
1016+
options: { mode: "pure" },
1017+
input: `/* cssmodules-pure-ignore - needed for third party integration */
1018+
:global(#foo) { color: blue; }`,
1019+
expected: `#foo { color: blue; }`,
1020+
},
1021+
{
1022+
name: "should not affect rules after the ignored block",
1023+
options: { mode: "pure" },
1024+
input: `/* cssmodules-pure-ignore */
1025+
:global(.foo) { color: blue; }
1026+
:global(.bar) { color: red; }`,
1027+
error: /isnotpure/,
1028+
},
1029+
{
1030+
name: "should work with nested global selectors in ignored block",
1031+
options: { mode: "pure" },
1032+
input: `/* cssmodules-pure-ignore */
1033+
:global(.foo) {
1034+
:global(.bar) { color: blue; }
1035+
}`,
1036+
error: /isnotpure/,
1037+
},
1038+
{
1039+
name: "should work with ignored nested global selectors in ignored block",
1040+
options: { mode: "pure" },
1041+
input: `/* cssmodules-pure-ignore */
1042+
:global(.foo) {
1043+
/* cssmodules-pure-ignore */
1044+
:global(.bar) { color: blue; }
1045+
}`,
1046+
expected: `.foo {
1047+
.bar { color: blue; }
1048+
}`,
1049+
},
1050+
{
1051+
name: "should work with view transitions in ignored block",
1052+
options: { mode: "pure" },
1053+
input: `/* cssmodules-pure-ignore */
1054+
::view-transition-group(modal) {
1055+
animation-duration: 300ms;
1056+
}`,
1057+
expected: `::view-transition-group(modal) {
1058+
animation-duration: 300ms;
1059+
}`,
1060+
},
1061+
{
1062+
name: "should work with keyframes in ignored block",
1063+
options: { mode: "pure" },
1064+
input: `/* cssmodules-pure-ignore */
1065+
@keyframes :global(fadeOut) {
1066+
from { opacity: 1; }
1067+
to { opacity: 0; }
1068+
}`,
1069+
expected: `@keyframes fadeOut {
1070+
from { opacity: 1; }
1071+
to { opacity: 0; }
1072+
}`,
1073+
},
1074+
{
1075+
name: "should work with scope in ignored block",
1076+
options: { mode: "pure" },
1077+
input: `
1078+
/* cssmodules-pure-ignore */
1079+
@scope (:global(.foo)) to (:global(.bar)) {
1080+
.article-footer {
1081+
border: 5px solid black;
1082+
}
1083+
}
1084+
`,
1085+
expected: `
1086+
@scope (.foo) to (.bar) {
1087+
:local(.article-footer) {
1088+
border: 5px solid black;
1089+
}
1090+
}
1091+
`,
1092+
},
1093+
{
1094+
name: "should work with scope in ignored block #2",
1095+
options: { mode: "pure" },
1096+
input: `
1097+
/* cssmodules-pure-ignore */
1098+
@scope (:global(.foo))
1099+
to (:global(.bar)) {
1100+
.article-footer {
1101+
border: 5px solid black;
1102+
}
1103+
}
1104+
`,
1105+
expected: `
1106+
@scope (.foo) to (.bar) {
1107+
:local(.article-footer) {
1108+
border: 5px solid black;
1109+
}
1110+
}
1111+
`,
1112+
},
1113+
{
1114+
name: "should work in media queries",
1115+
options: { mode: "pure" },
1116+
input: `@media (min-width: 768px) {
1117+
/* cssmodules-pure-ignore */
1118+
:global(.foo) { color: blue; }
1119+
}`,
1120+
expected: `@media (min-width: 768px) {
1121+
.foo { color: blue; }
1122+
}`,
1123+
},
1124+
{
1125+
name: "should handle multiple ignore comments",
1126+
options: { mode: "pure" },
1127+
input: `/* cssmodules-pure-ignore */
1128+
:global(.foo) { color: blue; }
1129+
.local { color: green; }
1130+
/* cssmodules-pure-ignore */
1131+
:global(.bar) { color: red; }`,
1132+
expected: `.foo { color: blue; }
1133+
:local(.local) { color: green; }
1134+
.bar { color: red; }`,
1135+
},
1136+
{
1137+
name: "should work with complex selectors in ignored block",
1138+
options: { mode: "pure" },
1139+
input: `/* cssmodules-pure-ignore */
1140+
:global(.foo):hover > :global(.bar) + :global(.baz) {
1141+
color: blue;
1142+
}`,
1143+
expected: `.foo:hover > .bar + .baz {
1144+
color: blue;
1145+
}`,
1146+
},
1147+
{
1148+
name: "should work with multiple selectors in ignored block",
1149+
options: { mode: "pure" },
1150+
input: `/* cssmodules-pure-ignore */
1151+
:global(.foo),
1152+
:global(.bar),
1153+
:global(.baz) {
1154+
color: blue;
1155+
}`,
1156+
expected: `.foo,
1157+
.bar,
1158+
.baz {
1159+
color: blue;
1160+
}`,
1161+
},
1162+
{
1163+
name: "should work with pseudo-elements in ignored block",
1164+
options: { mode: "pure" },
1165+
input: `/* cssmodules-pure-ignore */
1166+
:global(.foo)::before,
1167+
:global(.foo)::after {
1168+
content: '';
1169+
}`,
1170+
expected: `.foo::before,
1171+
.foo::after {
1172+
content: '';
1173+
}`,
1174+
},
9471175
{
9481176
name: "css nesting",
9491177
input: `

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /