Skip to content

Commit ba7e8ef

Browse files
authored
feat: added support a cssmodules-pure-no-check comment
1 parent 39a2f78 commit ba7e8ef

File tree

3 files changed

+161
-11
lines changed

3 files changed

+161
-11
lines changed

Diff for: README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Declarations (mode `local`, by default):
5959
In pure mode, all selectors must contain at least one local class or id
6060
selector
6161

62-
To ignore this rule for a specific selector, add the following comment in front
62+
To ignore this rule for a specific selector, add the a `/* cssmodules-pure-ignore */` comment in front
6363
of the selector:
6464

6565
```css
@@ -69,6 +69,20 @@ of the selector:
6969
}
7070
```
7171

72+
or by adding a `/* cssmodules-pure-no-check */` comment at the top of a file to disable this check for the whole file:
73+
74+
```css
75+
/* cssmodules-pure-no-check */
76+
77+
:global(#modal-backdrop) {
78+
...;
79+
}
80+
81+
:global(#my-id) {
82+
...;
83+
}
84+
```
85+
7286
## Building
7387

7488
```bash

Diff for: src/index.js

+26-10
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 &&

Diff for: test/index.test.js

+120
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,126 @@ 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 ignore no-check comment if not at root level #2",
1218+
options: { mode: "pure" },
1219+
input: `/* Some file description */
1220+
.class { color: red; }
1221+
/* cssmodules-pure-no-check */
1222+
:global(.foo) { color: blue }`,
1223+
error: /is not pure/,
1224+
},
1225+
{
1226+
name: "should allow other comments before no-check comment",
1227+
options: { mode: "pure" },
1228+
input: `/* Some file description */
1229+
/* cssmodules-pure-no-check */
1230+
:global(.foo) { color: blue }`,
1231+
expected: `/* Some file description */
1232+
/* cssmodules-pure-no-check */
1233+
.foo { color: blue }`,
1234+
},
1235+
{
1236+
name: "should disable pure mode checks for deep nested selectors",
1237+
options: { mode: "pure" },
1238+
input: `/* cssmodules-pure-no-check */
1239+
:global(.foo) { max-width: 600px }
1240+
:global(.bar) { background: #fafafa }
1241+
:global(.baz) {
1242+
:global(.foobar) {
1243+
&::-webkit-scrollbar { width: 8px }
1244+
}
1245+
}`,
1246+
expected: `/* cssmodules-pure-no-check */
1247+
.foo { max-width: 600px }
1248+
.bar { background: #fafafa }
1249+
.baz {
1250+
.foobar {
1251+
&::-webkit-scrollbar { width: 8px }
1252+
}
1253+
}`,
1254+
},
1255+
{
1256+
name: "should work with keyframes when no-check is enabled",
1257+
options: { mode: "pure" },
1258+
input: `/* cssmodules-pure-no-check */
1259+
@keyframes :global(fadeIn) {
1260+
from { opacity: 0 }
1261+
to { opacity: 1 }
1262+
}
1263+
:global(.animate) { animation: global(fadeIn) 0.3s }`,
1264+
expected: `/* cssmodules-pure-no-check */
1265+
@keyframes fadeIn {
1266+
from { opacity: 0 }
1267+
to { opacity: 1 }
1268+
}
1269+
.animate { animation: fadeIn 0.3s }`,
1270+
},
1271+
{
1272+
name: "should allow multiline no-check comment",
1273+
options: { mode: "pure" },
1274+
input: `/*
1275+
cssmodules-pure-no-check
1276+
*/
1277+
:global(.foo) { color: blue }`,
1278+
expected: `/*
1279+
cssmodules-pure-no-check
1280+
*/
1281+
.foo { color: blue }`,
1282+
},
1283+
{
1284+
name: "should allow additional text in no-check comment",
1285+
options: { mode: "pure" },
1286+
input: `/* cssmodules-pure-no-check - needed for styling third-party components */
1287+
:global(.foo) { color: blue }`,
1288+
expected: `/* cssmodules-pure-no-check - needed for styling third-party components */
1289+
.foo { color: blue }`,
1290+
},
1291+
{
1292+
name: "should work with media queries when no-check is enabled",
1293+
options: { mode: "pure" },
1294+
input: `/* cssmodules-pure-no-check */
1295+
@media (max-width: 768px) {
1296+
:global(.foo) { position: fixed }
1297+
}`,
1298+
expected: `/* cssmodules-pure-no-check */
1299+
@media (max-width: 768px) {
1300+
.foo { position: fixed }
1301+
}`,
1302+
},
11831303
{
11841304
name: "css nesting",
11851305
input: `

0 commit comments

Comments
 (0)