Skip to content

Commit 20ffc3d

Browse files
committed
add cssmodules-pure-no-check
1 parent 39a2f78 commit 20ffc3d

File tree

2 files changed

+137
-10
lines changed

2 files changed

+137
-10
lines changed

src/index.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,32 @@ 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";
7+
const IGNORE_FILE_MARKER = "cssmodules-pure-no-check";
8+
const IGNORE_NEXT_LINE_MARKER = "cssmodules-pure-ignore";
89

910
const isSpacing = (node) => node.type === "combinator" && node.value === " ";
1011

12+
const isPureCheckDisabled = (root) => {
13+
for (const node of root.nodes) {
14+
if (node.type !== "comment") {
15+
return false;
16+
}
17+
if (node.text.trim().startsWith(IGNORE_FILE_MARKER)) {
18+
return true;
19+
}
20+
}
21+
return false;
22+
};
23+
1124
function getIgnoreComment(node) {
1225
if (!node.parent) {
1326
return;
1427
}
15-
1628
const indexInParent = node.parent.index(node);
17-
1829
for (let i = indexInParent - 1; i >= 0; i--) {
1930
const prevNode = node.parent.nodes[i];
2031
if (prevNode.type === "comment") {
21-
if (prevNode.text.trimStart().startsWith(IGNORE_MARKER)) {
32+
if (prevNode.text.trimStart().startsWith(IGNORE_NEXT_LINE_MARKER)) {
2233
return prevNode;
2334
}
2435
} else {
@@ -552,6 +563,7 @@ module.exports = (options = {}) => {
552563
return {
553564
Once(root) {
554565
const { icssImports } = extractICSS(root, false);
566+
const enforcePureMode = pureMode && !isPureCheckDisabled(root);
555567

556568
Object.keys(icssImports).forEach((key) => {
557569
Object.keys(icssImports[key]).forEach((prop) => {
@@ -571,9 +583,8 @@ module.exports = (options = {}) => {
571583
let globalKeyframes = globalMode;
572584

573585
if (globalMatch) {
574-
if (pureMode) {
586+
if (enforcePureMode) {
575587
const ignoreComment = getIgnoreComment(atRule);
576-
577588
if (!ignoreComment) {
578589
throw atRule.error(
579590
"@keyframes :global(...) is not allowed in pure mode"
@@ -582,7 +593,6 @@ module.exports = (options = {}) => {
582593
ignoreComment.remove();
583594
}
584595
}
585-
586596
atRule.params = globalMatch[1];
587597
globalKeyframes = true;
588598
} else if (localMatch) {
@@ -626,7 +636,11 @@ module.exports = (options = {}) => {
626636
context.options = options;
627637
context.localAliasMap = localAliasMap;
628638

629-
if (pureMode && context.hasPureGlobals && !ignoreComment) {
639+
if (
640+
enforcePureMode &&
641+
context.hasPureGlobals &&
642+
!ignoreComment
643+
) {
630644
throw atRule.error(
631645
'Selector in at-rule"' +
632646
selector +
@@ -677,8 +691,10 @@ module.exports = (options = {}) => {
677691
context.options = options;
678692
context.localAliasMap = localAliasMap;
679693

680-
const ignoreComment = pureMode ? getIgnoreComment(rule) : undefined;
681-
const isNotPure = pureMode && !isPureSelector(context, rule);
694+
const ignoreComment = enforcePureMode
695+
? getIgnoreComment(rule)
696+
: undefined;
697+
const isNotPure = enforcePureMode && !isPureSelector(context, rule);
682698

683699
if (
684700
isNotPure &&

test/index.test.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,117 @@ const tests = [
11801180
content: '';
11811181
}`,
11821182
},
1183+
{
1184+
name: "should disable pure mode checks for entire file with no-check comment",
1185+
options: { mode: "pure" },
1186+
input: `/* cssmodules-pure-no-check */
1187+
:global(.foo) { border: 1px solid #e2e8f0 }
1188+
:global(.bar) { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) }
1189+
:global(.baz) { background: #4299e1 }`,
1190+
expected: `/* cssmodules-pure-no-check */
1191+
.foo { border: 1px solid #e2e8f0 }
1192+
.bar { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) }
1193+
.baz { background: #4299e1 }`,
1194+
},
1195+
{
1196+
name: "should disable pure mode checks for nested selectors",
1197+
options: { mode: "pure" },
1198+
input: `/* cssmodules-pure-no-check */
1199+
:global(.foo) {
1200+
&:hover { border-color: #cbd5e0 }
1201+
& :global(.bar) { color: blue }
1202+
}`,
1203+
expected: `/* cssmodules-pure-no-check */
1204+
.foo {
1205+
&:hover { border-color: #cbd5e0 }
1206+
& .bar { color: blue }
1207+
}`,
1208+
},
1209+
{
1210+
name: "should ignore no-check comment if not at root level",
1211+
options: { mode: "pure" },
1212+
input: `:global(.bar) { color: blue }
1213+
/* cssmodules-pure-no-check */`,
1214+
error: /is not pure/,
1215+
},
1216+
{
1217+
name: "should allow other comments before no-check comment",
1218+
options: { mode: "pure" },
1219+
input: `/* Some file description */
1220+
/* cssmodules-pure-no-check */
1221+
:global(.foo) { color: blue }`,
1222+
expected: `/* Some file description */
1223+
/* cssmodules-pure-no-check */
1224+
.foo { color: blue }`,
1225+
},
1226+
{
1227+
name: "should disable pure mode checks for deep nested selectors",
1228+
options: { mode: "pure" },
1229+
input: `/* cssmodules-pure-no-check */
1230+
:global(.foo) { max-width: 600px }
1231+
:global(.bar) { background: #fafafa }
1232+
:global(.baz) {
1233+
:global(.foobar) {
1234+
&::-webkit-scrollbar { width: 8px }
1235+
}
1236+
}`,
1237+
expected: `/* cssmodules-pure-no-check */
1238+
.foo { max-width: 600px }
1239+
.bar { background: #fafafa }
1240+
.baz {
1241+
.foobar {
1242+
&::-webkit-scrollbar { width: 8px }
1243+
}
1244+
}`,
1245+
},
1246+
{
1247+
name: "should work with keyframes when no-check is enabled",
1248+
options: { mode: "pure" },
1249+
input: `/* cssmodules-pure-no-check */
1250+
@keyframes :global(fadeIn) {
1251+
from { opacity: 0 }
1252+
to { opacity: 1 }
1253+
}
1254+
:global(.animate) { animation: global(fadeIn) 0.3s }`,
1255+
expected: `/* cssmodules-pure-no-check */
1256+
@keyframes fadeIn {
1257+
from { opacity: 0 }
1258+
to { opacity: 1 }
1259+
}
1260+
.animate { animation: fadeIn 0.3s }`,
1261+
},
1262+
{
1263+
name: "should allow multiline no-check comment",
1264+
options: { mode: "pure" },
1265+
input: `/*
1266+
cssmodules-pure-no-check
1267+
*/
1268+
:global(.foo) { color: blue }`,
1269+
expected: `/*
1270+
cssmodules-pure-no-check
1271+
*/
1272+
.foo { color: blue }`,
1273+
},
1274+
{
1275+
name: "should allow additional text in no-check comment",
1276+
options: { mode: "pure" },
1277+
input: `/* cssmodules-pure-no-check - needed for styling third-party components */
1278+
:global(.foo) { color: blue }`,
1279+
expected: `/* cssmodules-pure-no-check - needed for styling third-party components */
1280+
.foo { color: blue }`,
1281+
},
1282+
{
1283+
name: "should work with media queries when no-check is enabled",
1284+
options: { mode: "pure" },
1285+
input: `/* cssmodules-pure-no-check */
1286+
@media (max-width: 768px) {
1287+
:global(.foo) { position: fixed }
1288+
}`,
1289+
expected: `/* cssmodules-pure-no-check */
1290+
@media (max-width: 768px) {
1291+
.foo { position: fixed }
1292+
}`,
1293+
},
11831294
{
11841295
name: "css nesting",
11851296
input: `

0 commit comments

Comments
 (0)