Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
tailwindcss/nesting used with postcss-nesting breaks some postcss plugins
<!-- Please provide all of the information requested below. We're a small team and without all of this information it's not possible for us to help and your bug report will be closed. -->

**What version of Tailwind CSS are you using?**

v3.0.17

**What build tool (or framework if it abstracts the build tool) are you using?**

vite@2.7.13

**What version of Node.js are you using?**

For example: v17.1.0

**What operating system are you using?**

MacOS

**Describe your issue**

`tailwindcss/nesting` used with `postcss-nesting` breaks some postcss plugins. In this example vite config `cssHasPseudo` is not working. Also another plugin that is not working is `postcss-mixins`

```js
import tailwindcss from 'tailwindcss'
import tailwindcssNesting from 'tailwindcss/nesting'
import autoprefixer from 'autoprefixer'
import postcssImport from 'postcss-import'
import postcssNesting from 'postcss-nesting'
import cssHasPseudo from 'css-has-pseudo'
import postcssCustomMedia from 'postcss-custom-media'

export default {
    css: {
        postcss: {
            plugins: [postcssImport, tailwindcssNesting(postcssNesting), postcssCustomMedia, cssHasPseudo, tailwindcss, autoprefixer]
        }
    }
}
```


Hints:
Could you provide a reproduction? It would help to see the versions of the plugins you have installed along with the whole of the setup.
Here is a repro, hope it helps (text should turn red).  All plugins are latest versions

with tailwind (doesn't work) - https://stackblitz.com/edit/vitejs-vite-fv2tnq?file=style.pcss&terminal=dev
without tailwind (works) - https://stackblitz.com/edit/vitejs-vite-vbidlr?file=style.pcss&terminal=dev

Patch:
diff --git a/src/postcss-plugins/nesting/plugin.js b/src/postcss-plugins/nesting/plugin.js
--- a/src/postcss-plugins/nesting/plugin.js
+++ b/src/postcss-plugins/nesting/plugin.js
@@ -39,6 +39,42 @@ export function nesting(opts = postcssNested) {
       decl.remove()
     })
 
+    /**
+     * Use a private PostCSS API to remove the "clean" flag from the entire AST.
+     * This is done because running process() on the AST will set the "clean"
+     * flag on all nodes, which we don't want.
+     *
+     * This causes downstream plugins using the visitor API to be skipped.
+     *
+     * This is guarded because the PostCSS API is not public
+     * and may change in future versions of PostCSS.
+     *
+     * See https://github.com/postcss/postcss/issues/1712 for more details
+     *
+     * @param {import('postcss').Node} node
+     */
+    function markDirty(node) {
+      if (!('markDirty' in node)) {
+        return
+      }
+
+      // Traverse the tree down to the leaf nodes
+      if (node.nodes) {
+        node.nodes.forEach((n) => markDirty(n))
+      }
+
+      // If it's a leaf node mark it as dirty
+      // We do this here because marking a node as dirty
+      // will walk up the tree and mark all parents as dirty
+      // resulting in a lot of unnecessary work if we did this
+      // for every single node
+      if (!node.nodes) {
+        node.markDirty()
+      }
+    }
+
+    markDirty(root)
+
     return root
   }
 }


Test patch:
diff --git a/tests/postcss-plugins/nesting/index.test.js b/tests/postcss-plugins/nesting/index.test.js
--- a/tests/postcss-plugins/nesting/index.test.js
+++ b/tests/postcss-plugins/nesting/index.test.js
@@ -1,6 +1,7 @@
 import postcss from 'postcss'
 import postcssNested from 'postcss-nested'
 import plugin from '../../../src/postcss-plugins/nesting'
+import { visitorSpyPlugin } from './plugins.js'
 
 it('should be possible to load a custom nesting plugin', async () => {
   let input = css`
@@ -166,6 +167,46 @@ test('@screen rules can work with `@apply`', async () => {
   `)
 })
 
+test('nesting does not break downstream plugin visitors', async () => {
+  let input = css`
+    .foo {
+      color: black;
+    }
+    @suppoerts (color: blue) {
+      .foo {
+        color: blue;
+      }
+    }
+    /* Comment */
+  `
+
+  let spyPlugin = visitorSpyPlugin()
+
+  let plugins = [plugin(postcssNested), spyPlugin.plugin]
+
+  let result = await run(input, plugins)
+
+  expect(result).toMatchCss(css`
+    .foo {
+      color: black;
+    }
+    @suppoerts (color: blue) {
+      .foo {
+        color: blue;
+      }
+    }
+    /* Comment */
+  `)
+
+  expect(spyPlugin.spies.Once).toHaveBeenCalled()
+  expect(spyPlugin.spies.OnceExit).toHaveBeenCalled()
+  expect(spyPlugin.spies.Root).toHaveBeenCalled()
+  expect(spyPlugin.spies.Rule).toHaveBeenCalled()
+  expect(spyPlugin.spies.AtRule).toHaveBeenCalled()
+  expect(spyPlugin.spies.Comment).toHaveBeenCalled()
+  expect(spyPlugin.spies.Declaration).toHaveBeenCalled()
+})
+
 // ---
 
 function indentRecursive(node, indent = 0) {
@@ -187,11 +228,21 @@ function formatNodes(root) {
 }
 
 async function run(input, options) {
-  return (
-    await postcss([options === undefined ? plugin : plugin(options), formatNodes]).process(input, {
-      from: undefined,
-    })
-  ).toString()
+  let plugins = []
+
+  if (Array.isArray(options)) {
+    plugins = options
+  } else {
+    plugins.push(options === undefined ? plugin : plugin(options))
+  }
+
+  plugins.push(formatNodes)
+
+  let result = await postcss(plugins).process(input, {
+    from: undefined,
+  })
+
+  return result.toString()
 }
 
 function css(templates) {
diff --git a/tests/postcss-plugins/nesting/plugins.js b/tests/postcss-plugins/nesting/plugins.js
new file mode 100644
--- /dev/null
+++ b/tests/postcss-plugins/nesting/plugins.js
@@ -0,0 +1,42 @@
+export function visitorSpyPlugin() {
+  let Once = jest.fn()
+  let OnceExit = jest.fn()
+  let Root = jest.fn()
+  let AtRule = jest.fn()
+  let Rule = jest.fn()
+  let Comment = jest.fn()
+  let Declaration = jest.fn()
+
+  let plugin = Object.assign(
+    function () {
+      return {
+        postcssPlugin: 'visitor-test',
+
+        // These work fine
+        Once,
+        OnceExit,
+
+        // These break
+        Root,
+        Rule,
+        AtRule,
+        Declaration,
+        Comment,
+      }
+    },
+    { postcss: true }
+  )
+
+  return {
+    plugin,
+    spies: {
+      Once,
+      OnceExit,
+      Root,
+      AtRule,
+      Rule,
+      Comment,
+      Declaration,
+    },
+  }
+}