Skip to content

Commit

Permalink
feat: added support a cssmodules-pure-no-check comment
Browse files Browse the repository at this point in the history
  • Loading branch information
jantimon authored Dec 11, 2024
1 parent 39a2f78 commit ba7e8ef
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 11 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Declarations (mode `local`, by default):
In pure mode, all selectors must contain at least one local class or id
selector

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

```css
Expand All @@ -69,6 +69,20 @@ of the selector:
}
```

or by adding a `/* cssmodules-pure-no-check */` comment at the top of a file to disable this check for the whole file:

```css
/* cssmodules-pure-no-check */

:global(#modal-backdrop) {
...;
}

:global(#my-id) {
...;
}
```

## Building

```bash
Expand Down
36 changes: 26 additions & 10 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@ const selectorParser = require("postcss-selector-parser");
const valueParser = require("postcss-value-parser");
const { extractICSS } = require("icss-utils");

const IGNORE_MARKER = "cssmodules-pure-ignore";
const IGNORE_FILE_MARKER = "cssmodules-pure-no-check";
const IGNORE_NEXT_LINE_MARKER = "cssmodules-pure-ignore";

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

const isPureCheckDisabled = (root) => {
for (const node of root.nodes) {
if (node.type !== "comment") {
return false;
}
if (node.text.trim().startsWith(IGNORE_FILE_MARKER)) {
return true;
}
}
return false;
};

function getIgnoreComment(node) {
if (!node.parent) {
return;
}

const indexInParent = node.parent.index(node);

for (let i = indexInParent - 1; i >= 0; i--) {
const prevNode = node.parent.nodes[i];
if (prevNode.type === "comment") {
if (prevNode.text.trimStart().startsWith(IGNORE_MARKER)) {
if (prevNode.text.trimStart().startsWith(IGNORE_NEXT_LINE_MARKER)) {
return prevNode;
}
} else {
Expand Down Expand Up @@ -552,6 +563,7 @@ module.exports = (options = {}) => {
return {
Once(root) {
const { icssImports } = extractICSS(root, false);
const enforcePureMode = pureMode && !isPureCheckDisabled(root);

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

if (globalMatch) {
if (pureMode) {
if (enforcePureMode) {
const ignoreComment = getIgnoreComment(atRule);

if (!ignoreComment) {
throw atRule.error(
"@keyframes :global(...) is not allowed in pure mode"
Expand All @@ -582,7 +593,6 @@ module.exports = (options = {}) => {
ignoreComment.remove();
}
}

atRule.params = globalMatch[1];
globalKeyframes = true;
} else if (localMatch) {
Expand Down Expand Up @@ -626,7 +636,11 @@ module.exports = (options = {}) => {
context.options = options;
context.localAliasMap = localAliasMap;

if (pureMode && context.hasPureGlobals && !ignoreComment) {
if (
enforcePureMode &&
context.hasPureGlobals &&
!ignoreComment
) {
throw atRule.error(
'Selector in at-rule"' +
selector +
Expand Down Expand Up @@ -677,8 +691,10 @@ module.exports = (options = {}) => {
context.options = options;
context.localAliasMap = localAliasMap;

const ignoreComment = pureMode ? getIgnoreComment(rule) : undefined;
const isNotPure = pureMode && !isPureSelector(context, rule);
const ignoreComment = enforcePureMode
? getIgnoreComment(rule)
: undefined;
const isNotPure = enforcePureMode && !isPureSelector(context, rule);

if (
isNotPure &&
Expand Down
120 changes: 120 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,126 @@ const tests = [
content: '';
}`,
},
{
name: "should disable pure mode checks for entire file with no-check comment",
options: { mode: "pure" },
input: `/* cssmodules-pure-no-check */
:global(.foo) { border: 1px solid #e2e8f0 }
:global(.bar) { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) }
:global(.baz) { background: #4299e1 }`,
expected: `/* cssmodules-pure-no-check */
.foo { border: 1px solid #e2e8f0 }
.bar { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) }
.baz { background: #4299e1 }`,
},
{
name: "should disable pure mode checks for nested selectors",
options: { mode: "pure" },
input: `/* cssmodules-pure-no-check */
:global(.foo) {
&:hover { border-color: #cbd5e0 }
& :global(.bar) { color: blue }
}`,
expected: `/* cssmodules-pure-no-check */
.foo {
&:hover { border-color: #cbd5e0 }
& .bar { color: blue }
}`,
},
{
name: "should ignore no-check comment if not at root level",
options: { mode: "pure" },
input: `:global(.bar) { color: blue }
/* cssmodules-pure-no-check */`,
error: /is not pure/,
},
{
name: "should ignore no-check comment if not at root level #2",
options: { mode: "pure" },
input: `/* Some file description */
.class { color: red; }
/* cssmodules-pure-no-check */
:global(.foo) { color: blue }`,
error: /is not pure/,
},
{
name: "should allow other comments before no-check comment",
options: { mode: "pure" },
input: `/* Some file description */
/* cssmodules-pure-no-check */
:global(.foo) { color: blue }`,
expected: `/* Some file description */
/* cssmodules-pure-no-check */
.foo { color: blue }`,
},
{
name: "should disable pure mode checks for deep nested selectors",
options: { mode: "pure" },
input: `/* cssmodules-pure-no-check */
:global(.foo) { max-width: 600px }
:global(.bar) { background: #fafafa }
:global(.baz) {
:global(.foobar) {
&::-webkit-scrollbar { width: 8px }
}
}`,
expected: `/* cssmodules-pure-no-check */
.foo { max-width: 600px }
.bar { background: #fafafa }
.baz {
.foobar {
&::-webkit-scrollbar { width: 8px }
}
}`,
},
{
name: "should work with keyframes when no-check is enabled",
options: { mode: "pure" },
input: `/* cssmodules-pure-no-check */
@keyframes :global(fadeIn) {
from { opacity: 0 }
to { opacity: 1 }
}
:global(.animate) { animation: global(fadeIn) 0.3s }`,
expected: `/* cssmodules-pure-no-check */
@keyframes fadeIn {
from { opacity: 0 }
to { opacity: 1 }
}
.animate { animation: fadeIn 0.3s }`,
},
{
name: "should allow multiline no-check comment",
options: { mode: "pure" },
input: `/*
cssmodules-pure-no-check
*/
:global(.foo) { color: blue }`,
expected: `/*
cssmodules-pure-no-check
*/
.foo { color: blue }`,
},
{
name: "should allow additional text in no-check comment",
options: { mode: "pure" },
input: `/* cssmodules-pure-no-check - needed for styling third-party components */
:global(.foo) { color: blue }`,
expected: `/* cssmodules-pure-no-check - needed for styling third-party components */
.foo { color: blue }`,
},
{
name: "should work with media queries when no-check is enabled",
options: { mode: "pure" },
input: `/* cssmodules-pure-no-check */
@media (max-width: 768px) {
:global(.foo) { position: fixed }
}`,
expected: `/* cssmodules-pure-no-check */
@media (max-width: 768px) {
.foo { position: fixed }
}`,
},
{
name: "css nesting",
input: `
Expand Down

0 comments on commit ba7e8ef

Please sign in to comment.