From 90c16fe06ec4cf5cfd1c50e794e4af9e224e5d3d Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Thu, 31 Oct 2024 21:17:42 +0100 Subject: [PATCH] add pure ignore comment for css modules --- README.md | 15 +++++ src/index.js | 28 ++++++++- test/index.test.js | 141 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5501230..842f9b0 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,21 @@ Declarations (mode `local`, by default): ``` +## Pure Mode + +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 +of the selector: + +```css +/* cssmodules-pure-ignore */ +:global(#modal-backdrop) { + ...; +} +``` + ## Building ```bash diff --git a/src/index.js b/src/index.js index 5bad19a..899d6ae 100644 --- a/src/index.js +++ b/src/index.js @@ -4,8 +4,26 @@ const selectorParser = require("postcss-selector-parser"); const valueParser = require("postcss-value-parser"); const { extractICSS } = require("icss-utils"); +const IGNORE_MARKER = "cssmodules-pure-ignore"; + const isSpacing = (node) => node.type === "combinator" && node.value === " "; +function hasIgnoreComment(node) { + if (!node.parent) { + return false; + } + const indexInParent = node.parent.index(node); + for (let i = indexInParent - 1; i >= 0; i--) { + const prevNode = node.parent.nodes[i]; + if (prevNode.type === "comment") { + return prevNode.text.trimStart().startsWith(IGNORE_MARKER); + } else { + break; + } + } + return false; +} + function normalizeNodeArray(nodes) { const array = []; @@ -524,7 +542,7 @@ module.exports = (options = {}) => { let globalKeyframes = globalMode; if (globalMatch) { - if (pureMode) { + if (pureMode && !hasIgnoreComment(atRule)) { throw atRule.error( "@keyframes :global(...) is not allowed in pure mode" ); @@ -564,7 +582,11 @@ module.exports = (options = {}) => { context.options = options; context.localAliasMap = localAliasMap; - if (pureMode && context.hasPureGlobals) { + if ( + pureMode && + context.hasPureGlobals && + !hasIgnoreComment(atRule) + ) { throw atRule.error( 'Selector in at-rule"' + selector + @@ -615,7 +637,7 @@ module.exports = (options = {}) => { context.options = options; context.localAliasMap = localAliasMap; - if (pureMode && context.hasPureGlobals) { + if (pureMode && context.hasPureGlobals && !hasIgnoreComment(rule)) { throw rule.error( 'Selector "' + rule.selector + diff --git a/test/index.test.js b/test/index.test.js index 874634b..2e9bd68 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -944,6 +944,147 @@ const tests = [ options: { mode: "pure" }, error: /is not pure/, }, + { + name: "should suppress errors for global selectors after ignore comment", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo) { color: blue; }`, + expected: `/* cssmodules-pure-ignore */ + .foo { color: blue; }`, + }, + { + name: "should allow additional text in ignore comment", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore - needed for third party integration */ + :global(#foo) { color: blue; }`, + expected: `/* cssmodules-pure-ignore - needed for third party integration */ + #foo { color: blue; }`, + }, + { + name: "should not affect rules after the ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo) { color: blue; } + :global(.bar) { color: red; }`, + error: /is not pure/, + }, + { + name: "should work with nested global selectors in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo) { + :global(.bar) { color: blue; } + }`, + error: /is not pure/, + }, + { + name: "should work with ignored nested global selectors in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo) { + /* cssmodules-pure-ignore */ + :global(.bar) { color: blue; } + }`, + expected: `/* cssmodules-pure-ignore */ + .foo { + /* cssmodules-pure-ignore */ + .bar { color: blue; } + }`, + }, + { + name: "should work with view transitions in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + ::view-transition-group(modal) { + animation-duration: 300ms; + }`, + expected: `/* cssmodules-pure-ignore */ + ::view-transition-group(modal) { + animation-duration: 300ms; + }`, + }, + { + name: "should work with keyframes in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + @keyframes :global(fadeOut) { + from { opacity: 1; } + to { opacity: 0; } + }`, + expected: `/* cssmodules-pure-ignore */ + @keyframes fadeOut { + from { opacity: 1; } + to { opacity: 0; } + }`, + }, + { + name: "should work in media queries", + options: { mode: "pure" }, + input: `@media (min-width: 768px) { + /* cssmodules-pure-ignore */ + :global(.foo) { color: blue; } + }`, + expected: `@media (min-width: 768px) { + /* cssmodules-pure-ignore */ + .foo { color: blue; } + }`, + }, + { + name: "should handle multiple ignore comments", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo) { color: blue; } + .local { color: green; } + /* cssmodules-pure-ignore */ + :global(.bar) { color: red; }`, + expected: `/* cssmodules-pure-ignore */ + .foo { color: blue; } + :local(.local) { color: green; } + /* cssmodules-pure-ignore */ + .bar { color: red; }`, + }, + { + name: "should work with complex selectors in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo):hover > :global(.bar) + :global(.baz) { + color: blue; + }`, + expected: `/* cssmodules-pure-ignore */ + .foo:hover > .bar + .baz { + color: blue; + }`, + }, + { + name: "should work with multiple selectors in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo), + :global(.bar), + :global(.baz) { + color: blue; + }`, + expected: `/* cssmodules-pure-ignore */ + .foo, + .bar, + .baz { + color: blue; + }`, + }, + { + name: "should work with pseudo-elements in ignored block", + options: { mode: "pure" }, + input: `/* cssmodules-pure-ignore */ + :global(.foo)::before, + :global(.foo)::after { + content: ''; + }`, + expected: `/* cssmodules-pure-ignore */ + .foo::before, + .foo::after { + content: ''; + }`, + }, { name: "css nesting", input: `