Skip to content

add cssmodules-pure-no-check #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading