Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Updating from v3.0.23 to v3.1.1 & 3.1.2 getting variant.replace not a function error
Hi All. I am trying to update our project from tailwinds v3.0.23 to the latest. Going to v3.0.24 I have no issues but updating to v3.1.1 or 3.1.2 I am getting this error when I try to run my project. It also comes up as an intellisense error as shown below. Not sure if it something I am doing wrong that worked in the older versions. I am using laravel mix v6.0.11 which runs on top of webpack 4.4.31

Here is the output:
<img width="1204" alt="Screen Shot 2022-06-10 at 4 54 42 PM" src="https://user-images.githubusercontent.com/17321059/173164135-1ba43fe6-e938-4d49-99e6-99f6cbaf3e32.png">


Then here is what installed. I am running node v16.13.2:
<img width="1462" alt="Screen Shot 2022-06-10 at 5 03 23 PM" src="https://user-images.githubusercontent.com/17321059/173164218-d91ecf25-53f3-40ec-8d9b-cc7004ba3116.png">

Here is my tailwind config:

``` javascript

const plugin = require('tailwindcss/plugin')
const defaultTheme = require('tailwindcss/defaultTheme')
const { extend } = require('tailwindcss/defaultTheme')

const customColors = {
  charcoal: '#2F1F21',
  'charcoal-50': '#5B5757',
  burgandy: '#85300D',
  ocre: '#AB6B05',
  ocean: '#125759',
  cream: '#DEDBD9',
  'cream-30': '#F5F4F4',
  'cream-50': '#EEECEB',
  sand: '#BAAB99',
  'blaze-red': '#FF5717',
  'warm-yellow': '#FF5717',
  turquoise: '#2BBDA6',
}

module.exports = {
  content: [
    'shopify/snippets/*.liquid',
    'shopify/templates/*.liquid',
    'shopify/templates/customers/*.liquid',
    'shopify/layout/*.liquid',
    'src/sections/*.liquid',
    'src/scripts/*.{js,ts}',
  ],
  theme: {
    screens: {
      xs: '475px',
      ...defaultTheme.screens,
    },
    extend: {
      fontFamily: {
        sans: ['DM Sans', 'Helvetica', 'Artial', 'sans-serif'],
        serif: ['GT Super', 'Times', 'serif'],
      },
      colors: customColors,
      animation: {
        'spin-10s': 'spin 10s linear infinite',
      },
      letterSpacing: {
        tight: '-0.035em',
      },
    },
  },
  safelist: [
    {
      pattern: /font-*/,
    },
  ],
  plugins: [
    require('tailwind-scrollbar-hide'),
    plugin(function ({ addVariant }) {
      addVariant('current', ['&[aria-current="true"]', '&.active'])
    }),
    plugin(function ({ addVariant }) {
      addVariant('accessible', ['body.accessible &'])
    }),
    plugin(function ({ addVariant, e }) {
      //add more true or false aria attributes or custom boolean attributes
      const ariaAttributes = ['aria-expanded', 'aria-hidden', 'aria-current', 'aria-busy', 'data-check-validity']
      ariaAttributes.forEach((adj) => {
        const selector = adj
        addVariant(selector, [`&[${selector}="true"]`])

        const groupSelector = `group-${adj}`
        addVariant(groupSelector, ({ modifySelectors, separator }) =>
          modifySelectors(({ className }) => `.group[${adj}="true"] .${e(`${groupSelector}${separator}${className}`)}`)
        )

        const peerSelector = `peer-${adj}`
        addVariant(peerSelector, ({ modifySelectors, separator }) =>
          modifySelectors(({ className }) => `.peer[${adj}="true"] ~ .${e(`${peerSelector}${separator}${className}`)}`)
        )
      })
    }),
    //extra custom colors into root variables, can extra all by passing in theme,
    //then pass theme('colors') into the extra function
    plugin(function ({ addBase }) {
      function extractColorVars(colorObj, colorGroup = '') {
        return Object.keys(colorObj).reduce((vars, colorKey) => {
          const value = colorObj[colorKey]

          const newVars =
            typeof value === 'string'
              ? { [`--color${colorGroup}-${colorKey}`]: value }
              : extractColorVars(value, `-${colorKey}`)

          return { ...vars, ...newVars }
        }, {})
      }

      addBase({
        ':root': extractColorVars(customColors),
      })
    }),
  ],
}


```



Hints:
Getting the same error, in my case, it's related to **tailwindcss-named-groups** plugin.
A shot in the dark here, but it might be related to this change: https://github.com/tailwindlabs/tailwindcss/commit/bb4f5dab6b1f44887b0264cc558090b906715d02

You can clear the error by making your `addVariant` calls return `undefined` instead of returning the result of `modifySelectors`. So try adding some brackets here for example:

```
addVariant(peerSelector, ({ modifySelectors, separator }) => {
  modifySelectors(({ className }) => `.peer[${adj}="true"] ~ .${e(`${peerSelector}${separator}${className}`)}`)
})
```

I don't know if that will actually make what you're intending to happen start working again, but hopefully this helps.

Patch:
diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js
--- a/src/lib/setupContextUtils.js
+++ b/src/lib/setupContextUtils.js
@@ -465,11 +465,14 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
             }
 
             if (Array.isArray(result)) {
-              return result.map((variant) => parseVariant(variant))
+              return result
+                .filter((variant) => typeof variant === 'string')
+                .map((variant) => parseVariant(variant))
             }
 
             // result may be undefined with legacy variants that use APIs like `modifySelectors`
-            return result && parseVariant(result)(api)
+            // result may also be a postcss node if someone was returning the result from `modifySelectors`
+            return result && typeof result === 'string' && parseVariant(result)(api)
           }
         }
 


Test patch:
diff --git a/tests/variants.test.js b/tests/variants.test.js
--- a/tests/variants.test.js
+++ b/tests/variants.test.js
@@ -461,6 +461,57 @@ test('before and after variants are a bit special, and forced to the end (2)', (
   })
 })
 
+test('returning non-strings and non-selectors in addVariant', () => {
+  /** @type {import('../types/config').Config} */
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="peer-aria-expanded:text-center"></div>
+          <div class="peer-aria-expanded-2:text-center"></div>
+        `,
+      },
+    ],
+    plugins: [
+      function ({ addVariant, e }) {
+        addVariant('peer-aria-expanded', ({ modifySelectors, separator }) =>
+          // Returning anything other string | string[] | undefined here is not supported
+          // But we're trying to be lenient here and just throw it out
+          modifySelectors(
+            ({ className }) =>
+              `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
+          )
+        )
+
+        addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => {
+          let nodes = modifySelectors(
+            ({ className }) =>
+              `.peer[aria-expanded="false"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
+          )
+
+          return [
+            // Returning anything other than strings here is not supported
+            // But we're trying to be lenient here and just throw it out
+            nodes,
+            '.peer[aria-expanded="false"] ~ &',
+          ]
+        })
+      },
+    ],
+  }
+
+  return run('@tailwind components;@tailwind utilities', config).then((result) => {
+    return expect(result.css).toMatchFormattedCss(css`
+      .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center {
+        text-align: center;
+      }
+      .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center {
+        text-align: center;
+      }
+    `)
+  })
+})
+
 it('should not generate variants of user css if it is not inside a layer', () => {
   let config = {
     content: [{ raw: html`<div class="hover:foo"></div>` }],