Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Fails to compile "expected opening square bracket" with two attribute selectors
<!-- 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?**

Latest Tailwind v3.2.4, reproducible in live playground below.

**Reproduction URL**
https://play.tailwindcss.com/w66a9h6SsL

**Describe your issue**

The Tailwind compiler fails with `Expected an opening square bracket.` when parsing a second attribute selector after one with a value.

The following code attempts to style the element when one of its parents has two attributes. Checking for the presence of two attributes works flawlessly. Checking for the presence of one and the value of the second works great as well. Swapping the order of the attributes breaks the compiler.
- ✅ `[data-beta][data-testid]` 
- ✅ `[data-beta][data-testid='test']` 
- ❌ `[data-testid='test'][data-beta]` 

```html
<ul data-testid="test" data-beta>

  <li class="[[data-beta][data-testid]_&]:bg-violet-500"> This works </li>
  <li class="[[data-beta][data-testid='test']_&]:bg-violet-500"> This works </li>
  <li class="[[data-testid='test'][data-beta]_&]:bg-violet-500"> This breaks </li>
</ul>
```


Hints:

Patch:
diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js
--- a/src/lib/generateRules.js
+++ b/src/lib/generateRules.js
@@ -733,6 +733,8 @@ function* resolveMatches(candidate, context, original = candidate) {
     }
 
     for (let match of matches) {
+      let isValid = true
+
       match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
 
       // Apply final format selector
@@ -742,7 +744,7 @@ function* resolveMatches(candidate, context, original = candidate) {
         container.walkRules((rule) => {
           if (inKeyframes(rule)) return
 
-          rule.selector = finalizeSelector(finalFormat, {
+          let selectorOptions = {
             selector: rule.selector,
             candidate: original,
             base: candidate
@@ -751,11 +753,31 @@ function* resolveMatches(candidate, context, original = candidate) {
             isArbitraryVariant: match[0].isArbitraryVariant,
 
             context,
-          })
+          }
+
+          try {
+            rule.selector = finalizeSelector(finalFormat, selectorOptions)
+          } catch {
+            // The selector we produced is invalid
+            // This could be because:
+            // - A bug exists
+            // - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`)
+            // - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`)
+            // Either way the build will fail because of this
+            // We would rather that the build pass "silently" given that this could
+            // happen because of picking up invalid things when scanning content
+            // So we'll throw out the candidate instead
+            isValid = false
+            return false
+          }
         })
         match[1] = container.nodes[0]
       }
 
+      if (!isValid) {
+        continue
+      }
+
       yield match
     }
   }


Test patch:
diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js
--- a/tests/arbitrary-variants.test.js
+++ b/tests/arbitrary-variants.test.js
@@ -1099,3 +1099,47 @@ it('Arbitrary variants are ordered alphabetically', () => {
     `)
   })
 })
+
+it('Arbitrary variants support multiple attribute selectors', () => {
+  let config = {
+    content: [
+      {
+        raw: html` <div class="[[data-foo='bar'][data-baz]_&]:underline"></div> `,
+      },
+    ],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      [data-foo='bar'][data-baz] .\[\[data-foo\=\'bar\'\]\[data-baz\]_\&\]\:underline {
+        text-decoration-line: underline;
+      }
+    `)
+  })
+})
+
+it('Invalid arbitrary variants selectors should produce nothing instead of failing', () => {
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="[&;foo]:underline"></div>
+        `,
+      },
+    ],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css``)
+  })
+})