Repository: francoismassart/eslint-plugin-tailwindcss
Pull request #285 (2023-10-20 07:51:24)

fix: Parsing spreads in function call returns
# Function return spreading

## Description

Similarly to https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/251 spreading function return values inside of `clsx` call or similar fails with `TypeError: Cannot read properties of undefined (reading 'type')`

This is a simple fix to simply ignore those nodes.

Fixes # (issue)

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

I added a test that matches my minimal reproduction. It fails without the introduced change.

**Test Configuration**:
* OS + version: e.g. macOS Mojave
* NPM version: ...
* Node version: ...

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
- [ ] I have checked my code and corrected any misspellings


egorpavlikhin: @francoismassart any chance this could be merged?

Patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index fcda1c3..747e6b6 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -25,9 +25,10 @@ Please also list any relevant details for your test configuration
 - [ ] Test B
 
 **Test Configuration**:
-* OS + version: e.g. macOS Mojave
-* NPM version: ...
-* Node version: ...
+
+- OS + version: e.g. macOS Mojave
+- NPM version: ...
+- Node version: ...
 
 ## Checklist:
 
@@ -37,6 +38,5 @@ Please also list any relevant details for your test configuration
 - [ ] I have made corresponding changes to the documentation
 - [ ] My changes generate no new warnings
 - [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] New and existing unit tests pass locally with my changes
 - [ ] Any dependent changes have been merged and published in downstream modules
 - [ ] I have checked my code and corrected any misspellings
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..80b1e69
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,41 @@
+name: build
+on: [push]
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        # waiting on: https://github.com/actions/setup-node/issues/531
+      - run: corepack enable
+      - uses: actions/setup-node@v4
+        with:
+          node-version: 21
+          cache: npm
+      - run: npm ci
+
+      - name: test build package on node@21 (current)
+        run: |
+          node --version
+          npm --version
+          npm run test
+
+      # Not using a matrix here since it's simpler
+      # to just duplicate it and not spawn new instances
+
+      - uses: actions/setup-node@v4
+        with:
+          node-version: 20
+      - name: test build package on node@20 (LTS)
+        run: |
+          node --version
+          npm --version
+          npm run test
+
+      - uses: actions/setup-node@v4
+        with:
+          node-version: 18
+      - name: test build package on node@18 (LTS)
+        run: |
+          node --version
+          npm --version
+          npm run test
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index be87e1c..9cbf0de 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,12 +1,17 @@
 # Contributing to `esLint-plugin-tailwindcss`
 
-# Contributing
+## Contributing
 
 When contributing to this repository, please first discuss the change you wish to make via issue,
 email, or any other method with the owners of this repository before making a change.
 
 Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
 
+## Development
+
+You can use [Corepack](https://nodejs.org/api/corepack.html) to ensure you're using the same package
+manager. Run `corepack enabled` before running `npm install`.
+
 ## Pull Request Process
 
 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
diff --git a/README.md b/README.md
index 9a9639a..a9aaeec 100644
--- a/README.md
+++ b/README.md
@@ -30,16 +30,19 @@ You can can the same information on your favorite command line software as well.
 
 ## 🤝 Support `eslint-plugin-tailwindcss`
 
-| 🥰 How you can support us?                                                                                                                                                                                                            | 💪 They did it!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **Premium Sponsors** <br /> Support us by becoming a sponsor. <br /> [Become a recurring sponsor](https://github.com/sponsors/francoismassart?frequency=recurring)                                                                    | <a href="https://daily.dev/" target="_blank"><img width="150" src="https://raw.githubusercontent.com/francoismassart/eslint-plugin-tailwindcss/master/sponsors/daily.dev.jpg"></a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
-| **Current Sponsors** <br /> Any amount is appreciated.                                                                                                                                                                                | <a href="https://github.com/jonz94" target="_blank"><img src="https://avatars.githubusercontent.com/u/16042676?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@jonz94"></a> <a href="https://github.com/theMosaad" target="_blank"><img src="https://avatars.githubusercontent.com/u/48773133?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@theMosaad"></a> <a href="https://github.com/acewf" target="_blank"><img src="https://avatars.githubusercontent.com/u/4835572?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@acewf"></a> <a href="https://github.com/charkour" target="_blank"><img src="https://avatars.githubusercontent.com/u/33156025?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@charkour"></a> <a href="https://github.com/dailydotdev" target="_blank"><img class="avatar" src="https://avatars.githubusercontent.com/u/41463883?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@dailydotdev"></a> |
-| **Past sponsors** <br /> Even if this is just a one-time thing. <br /> [Become a backer](https://github.com/sponsors/francoismassart?frequency=one-time)                                                                              | <a href="https://github.com/aniravi24" target="_blank"><img src="https://avatars.githubusercontent.com/u/5902976?s=100&amp;v=4" width="50" height="50" style="border-radius:100%;" alt="@aniravi24"></a> <a href="https://github.com/mongolyy" target="_blank"><img src="https://avatars.githubusercontent.com/u/10972787?s=100&amp;v=4" width="50" height="50" style="border-radius:100%;" alt="@mongolyy"></a> <a href="https://github.com/t3dotgg" target="_blank"><img src="https://avatars.githubusercontent.com/u/6751787?s=100&amp;v=4" width="50" height="50" style="border-radius:100%;" alt="@t3dotgg"></a>                                                                                                                                                                                                                                                                                                                                                                                                                               |
-| **Contributors** <br /> This project can evolve thanks to all the people who contribute. <br /> You are welcome to [contribute](CONTRIBUTING.md) to this project by reporting issues, feature requests or even opening Pull Requests. | <a href="https://github.com/francoismassart/eslint-plugin-tailwindcss/graphs/contributors"><img src="https://contrib.rocks/image?repo=francoismassart/eslint-plugin-tailwindcss&width=300&columns=4" /></a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
-| **Supporters** <br /> Talk about the plugin on your social networks                                                                                                                                                                   | <a href="https://twitter.com/search?q=eslint-plugin-tailwindcss&src=recent_search_click" target="_blank">eslint-plugin-tailwindcss on Twitter</a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
+| 🥰 How you can support us?                                                                                                                                                                                                            | 💪 They did it!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Premium Sponsors** <br /> Support us by becoming a sponsor. <br /> [Become a recurring sponsor](https://github.com/sponsors/francoismassart?frequency=recurring)                                                                    | <a href="https://daily.dev/" target="_blank"><img width="150" src="https://raw.githubusercontent.com/francoismassart/eslint-plugin-tailwindcss/master/sponsors/daily.dev.jpg"></a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
+| **Current Sponsors** <br /> Any amount is appreciated.                                                                                                                                                                                | <a href="https://github.com/jonz94" target="_blank"><img src="https://avatars.githubusercontent.com/u/16042676?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@jonz94"></a> <a href="https://github.com/theMosaad" target="_blank"><img src="https://avatars.githubusercontent.com/u/48773133?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@theMosaad"></a> <a href="https://github.com/acewf" target="_blank"><img src="https://avatars.githubusercontent.com/u/4835572?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@acewf"></a> <a href="https://github.com/charkour" target="_blank"><img src="https://avatars.githubusercontent.com/u/33156025?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@charkour"></a> <a href="https://github.com/dailydotdev" target="_blank"><img class="avatar" src="https://avatars.githubusercontent.com/u/41463883?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@dailydotdev"></a> <a href="https://github.com/codecov" target="_blank"><img class="avatar" src="https://avatars.githubusercontent.com/u/8226205?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@codecov"></a> <a href="https://github.com/sourcegraph" target="_blank"><img class="avatar" src="https://avatars.githubusercontent.com/u/3979584?s=150&amp;v=4" width="75" height="75" style="border-radius:100%;" alt="@sourcegraph"></a> |
+| **Past sponsors** <br /> Even if this is just a one-time thing. <br /> [Become a backer](https://github.com/sponsors/francoismassart?frequency=one-time)                                                                              | <a href="https://github.com/aniravi24" target="_blank"><img src="https://avatars.githubusercontent.com/u/5902976?s=100&amp;v=4" width="50" height="50" style="border-radius:100%;" alt="@aniravi24"></a> <a href="https://github.com/mongolyy" target="_blank"><img src="https://avatars.githubusercontent.com/u/10972787?s=100&amp;v=4" width="50" height="50" style="border-radius:100%;" alt="@mongolyy"></a> <a href="https://github.com/t3dotgg" target="_blank"><img src="https://avatars.githubusercontent.com/u/6751787?s=100&amp;v=4" width="50" height="50" style="border-radius:100%;" alt="@t3dotgg"></a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
+| **Contributors** <br /> This project can evolve thanks to all the people who contribute. <br /> You are welcome to [contribute](CONTRIBUTING.md) to this project by reporting issues, feature requests or even opening Pull Requests. | <a href="https://github.com/francoismassart/eslint-plugin-tailwindcss/graphs/contributors"><img src="https://contrib.rocks/image?repo=francoismassart/eslint-plugin-tailwindcss&width=300&columns=4" /></a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| **Supporters** <br /> Talk about the plugin on your social networks                                                                                                                                                                   | <a href="https://twitter.com/search?q=eslint-plugin-tailwindcss&src=recent_search_click" target="_blank">eslint-plugin-tailwindcss on Twitter</a>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
 
 ## Latest changelog
 
+- feat: [support for Tailwind CSS 3.4.0](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/297)
+- ci: [add github actions workflow](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/303) (by [nihalgonsalves](https://github.com/nihalgonsalves) 🙏)
+- fix: [bg-center mark as conflicting with bg-[image:xxx]](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/260)
 - feat: [support enforcing truncate shorthand](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/255) (by [bezbac](https://github.com/bezbac) 🙏)
 - fix: [parsing spreads in object expressions](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/251) (by [bezbac](https://github.com/bezbac) 🙏)
 - fix: [do not handle non-ASCII whitespace as separator](https://github.com/francoismassart/eslint-plugin-tailwindcss/pull/246) (by [uhyo](https://github.com/uhyo) 🙏)
@@ -198,7 +201,7 @@ FYI, here are the `default` values:
       removeDuplicates: true,
       skipClassAttribute: false,
       whitelist: [],
-      tags: [],
+      tags: [], // can be set to e.g. ['tw'] for use in tw`bg-blue`
       classRegex: "^class(Name)?$", // can be modified to support custom attributes. E.g. "^tw$" for `twin.macro`
     },
   },
@@ -218,5 +221,8 @@ The plugin will look for each setting in this order and stops searching as soon
 - `no-redundant-variant`: e.g. avoid `mx-5 sm:mx-5`, no need to redefine `mx` in `sm:` variant as it uses the same value (`5`)
 
 - `only-valid-arbitrary-values`:
+
   - e.g. avoid `top-[42]`, only `0` value can be unitless.
   - e.g. avoid `text-[rgba(10%,20%,30,50%)]`, can't mix `%` and `0-255`.
+
+- `no-unnecessary-arbitrary-value`: e.g. replacing `h-[0]` by its configuration based classname `h-0`.
diff --git a/lib/config/groups.js b/lib/config/groups.js
index a77a6be..234c89d 100644
--- a/lib/config/groups.js
+++ b/lib/config/groups.js
@@ -507,6 +507,10 @@ module.exports.groups = [
         type: 'Max-Height',
         members: 'max\\-h\\-(?<value>${maxHeight})',
       },
+      {
+        type: 'Size',
+        members: 'size\\-(?<value>${size})',
+      },
     ],
   },
   {
@@ -594,6 +598,10 @@ module.exports.groups = [
         members: 'overflow\\-(ellipsis|clip)',
         deprecated: true,
       },
+      {
+        type: 'Text Wrap',
+        members: 'text\\-(wrap|nowrap|balance|pretty)',
+      },
       {
         type: 'Text Indent',
         members: '(indent\\-(?<value>${textIndent})|\\-indent\\-(?<negativeValue>${-textIndent}))',
@@ -621,7 +629,7 @@ module.exports.groups = [
     members: [
       {
         type: 'Background Image URL',
-        members: 'bg\\-\\[url\\((?<value>${backgroundImageUrl})\\)\\]',
+        members: 'bg\\-\\[(image\\:|url\\()(?<value>${backgroundImageUrl})\\)\\]',
       },
       {
         type: 'Background Attachment',
@@ -1341,6 +1349,10 @@ module.exports.groups = [
         type: 'Screen Readers',
         members: '(not\\-)?sr\\-only',
       },
+      {
+        type: 'Forced Color Adjust',
+        members: 'forced\\-color\\-adjust\\-(auto|none)',
+      },
     ],
   },
   {
diff --git a/lib/rules/enforces-shorthand.js b/lib/rules/enforces-shorthand.js
index 9e40ba3..1fcc366 100644
--- a/lib/rules/enforces-shorthand.js
+++ b/lib/rules/enforces-shorthand.js
@@ -75,18 +75,28 @@ module.exports = {
 
     // These are shorthand candidates that do not share the same parent type
     const complexEquivalences = [
-      [["overflow-hidden", "text-ellipsis", "whitespace-nowrap"], "truncate"]
-    ]
+      {
+        needles: ['overflow-hidden', 'text-ellipsis', 'whitespace-nowrap'],
+        shorthand: 'truncate',
+        mode: 'exact',
+      },
+      {
+        needles: ['w-', 'h-'],
+        shorthand: 'size-',
+        mode: 'value',
+      },
+    ];
 
     // Init assets
     const targetProperties = {
       Layout: ['Overflow', 'Overscroll Behavior', 'Top / Right / Bottom / Left'],
       'Flexbox & Grid': ['Gap'],
       Spacing: ['Padding', 'Margin'],
+      Sizing: ['Width', 'Height'],
       Borders: ['Border Radius', 'Border Width', 'Border Color'],
       Tables: ['Border Spacing'],
       Transforms: ['Scale'],
-      Typography: ['Text Overflow', 'Whitespace']
+      Typography: ['Text Overflow', 'Whitespace'],
     };
 
     // We don't want to affect other rules by object reference
@@ -219,53 +229,79 @@ module.exports = {
       const validated = [];
 
       // Handle sets of classnames with different parent types
-      let remaining = parsed
-      for (const [inputSet, outputClassname] of complexEquivalences) {
+      let remaining = parsed;
+      for (const { needles: inputSet, shorthand: outputClassname, mode } of complexEquivalences) {
         if (remaining.length < inputSet.length) {
-          continue
-        }
-
-        const parsedElementsInInputSet = remaining.filter(remainingClass => inputSet.some(inputClass => remainingClass.name.includes(inputClass)))
-
-        // Make sure all required classes for the shorthand are present
-        if (parsedElementsInInputSet.length !== inputSet.length) {
-          continue
+          continue;
         }
 
-        // Make sure the classes share all the same variants
-        if (new Set(parsedElementsInInputSet.map(p => p.variants)).size !== 1) {
-          continue
-        }
-
-        // Make sure the classes share all the same importance
-        if (new Set(parsedElementsInInputSet.map(p => p.important)).size !== 1) {
-          continue
-        }
+        // Matching classes
+        const parsedElementsInInputSet = remaining.filter((remainingClass) => {
+          if (mode === 'exact') {
+            // Test if the name contains the target class, eg. 'text-ellipsis' inside 'md:text-ellipsis'...
+            return inputSet.some((inputClass) => remainingClass.name.includes(inputClass));
+          }
+          // Test if the body of the class matches, eg. 'h-' inside 'h-10'
+          if (mode === 'value') {
+            return inputSet.some((inputClassPattern) => inputClassPattern === remainingClass.body);
+          }
+        });
 
-        const index = parsedElementsInInputSet[0].index
-        const variants = parsedElementsInInputSet[0].variants
-        const important = parsedElementsInInputSet[0].important ? "!" : ""
+        const variantGroups = new Map();
+        parsedElementsInInputSet.forEach((o) => {
+          const val = mode === 'value' ? o.value : '';
+          const v = `${o.variants}${o.important ? '!' : ''}${val}`;
+          if (!variantGroups.has(v)) {
+            variantGroups.set(
+              v,
+              parsedElementsInInputSet.filter(
+                (c) => c.variants === o.variants && c.important === o.important && (val === '' || c.value === val)
+              )
+            );
+          }
+        });
+        const validKeys = new Set();
+        variantGroups.forEach((classes, key) => {
+          let skip = false;
+          // Make sure all required classes for the shorthand are present
+          if (classes.length < inputSet.length) {
+            skip = true;
+          }
+          // Make sure the classes share all the single/shared/same value
+          if (mode === 'value' && new Set(classes.map((p) => p.value)).size !== 1) {
+            skip = true;
+          }
+          if (!skip) {
+            validKeys.add(key);
+          }
+        });
+        validKeys.forEach((k) => {
+          const candidates = variantGroups.get(k);
+          const index = candidates[0].index;
+          const variants = candidates[0].variants;
+          const important = candidates[0].important ? '!' : '';
+          const classValue = mode === 'value' ? candidates[0].value : '';
 
-        const patchedClassname = `${variants}${important}${mergedConfig.prefix}${outputClassname}`
-        troubles.push([parsedElementsInInputSet.map((c) => `${c.name}`), patchedClassname]);
+          const patchedClassname = `${variants}${important}${mergedConfig.prefix}${outputClassname}${classValue}`;
+          troubles.push([candidates.map((c) => `${c.name}`), patchedClassname]);
 
-        const validatedClassname = groupUtil.parseClassname(patchedClassname, targetGroups, mergedConfig, index)
-        validated.push(validatedClassname);
+          const validatedClassname = groupUtil.parseClassname(patchedClassname, targetGroups, mergedConfig, index);
+          validated.push(validatedClassname);
 
-        remaining = remaining.filter(p => !parsedElementsInInputSet.includes(p))
+          remaining = remaining.filter((p) => !candidates.includes(p));
+        });
       }
 
       // Handle sets of classnames with the same parent type
-
       // Each group parentType
       const checkedGroups = [];
-      remaining.forEach((classname) => {
+      remaining.forEach((classname, idx, arr) => {
         // Valid candidate
         if (classname.parentType === '') {
           validated.push(classname);
         } else if (!checkedGroups.includes(classname.parentType)) {
           checkedGroups.push(classname.parentType);
-          const sameType = parsed.filter((cls) => cls.parentType === classname.parentType);
+          const sameType = remaining.filter((cls) => cls.parentType === classname.parentType);
           // Comparing same parentType classnames
           const checkedVariantsValue = [];
           sameType.forEach((cls) => {
@@ -404,27 +440,22 @@ module.exports = {
         }
       }
 
-      troubles
-        .filter((trouble) => {
-          // Only valid issue if there are classes to replace
-          return trouble[0].length;
-        })
-        .forEach((issue) => {
-          if (originalClassNamesValue !== validatedClassNamesValue) {
-            validatedClassNamesValue = prefix + validatedClassNamesValue + suffix;
-            context.report({
-              node: node,
-              messageId: 'shorthandCandidateDetected',
-              data: {
-                classnames: issue[0].join(', '),
-                shorthand: issue[1],
-              },
-              fix: function (fixer) {
-                return fixer.replaceTextRange([start, end], validatedClassNamesValue);
-              },
-            });
-          }
-        });
+      troubles.forEach((issue) => {
+        if (originalClassNamesValue !== validatedClassNamesValue) {
+          validatedClassNamesValue = prefix + validatedClassNamesValue + suffix;
+          context.report({
+            node: node,
+            messageId: 'shorthandCandidateDetected',
+            data: {
+              classnames: issue[0].join(', '),
+              shorthand: issue[1],
+            },
+            fix: function (fixer) {
+              return fixer.replaceTextRange([start, end], validatedClassNamesValue);
+            },
+          });
+        }
+      });
     };
 
     //----------------------------------------------------------------------
diff --git a/lib/util/ast.js b/lib/util/ast.js
index e420ef1..4648f8d 100644
--- a/lib/util/ast.js
+++ b/lib/util/ast.js
@@ -184,6 +184,9 @@ function extractRangeFromNode(node) {
   if (node.type === 'TextAttribute' && node.name === 'class') {
     return [node.valueSpan.fullStart.offset, node.valueSpan.end.offset];
   }
+  if (node.value === undefined) {
+    return [0,0];
+  }
   switch (node.value.type) {
     case 'JSXExpressionContainer':
       return node.value.expression.range;
@@ -196,6 +199,10 @@ function extractValueFromNode(node) {
   if (node.type === 'TextAttribute' && node.name === 'class') {
     return node.value;
   }
+  if (node.value === undefined) {
+    return node.value;
+  }
+
   switch (node.value.type) {
     case 'JSXExpressionContainer':
       return node.value.expression.value;
diff --git a/lib/util/groupMethods.js b/lib/util/groupMethods.js
index 4ee1558..7b59571 100644
--- a/lib/util/groupMethods.js
+++ b/lib/util/groupMethods.js
@@ -160,6 +160,7 @@ function generateOptions(propName, keys, config, isNegative = false) {
     case 'height':
     case 'lineHeight':
     case 'maxHeight':
+    case 'size':
     case 'maxWidth':
     case 'minHeight':
     case 'minWidth':
diff --git a/package-lock.json b/package-lock.json
index 5566e91..71367da 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "eslint-plugin-tailwindcss",
-  "version": "3.13.0",
+  "version": "3.14.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "eslint-plugin-tailwindcss",
-      "version": "3.13.0",
+      "version": "3.14.0",
       "license": "MIT",
       "dependencies": {
         "fast-glob": "^3.2.5",
@@ -23,7 +23,7 @@
         "daisyui": "^2.6.4",
         "eslint": "^7.1.0",
         "mocha": "^10.2.0",
-        "tailwindcss": "^3.3.2",
+        "tailwindcss": "^3.4.0",
         "typescript": "4.3.5",
         "vue-eslint-parser": "^9.1.0"
       },
@@ -31,7 +31,7 @@
         "node": ">=12.13.0"
       },
       "peerDependencies": {
-        "tailwindcss": "^3.3.2"
+        "tailwindcss": "^3.4.0"
       }
     },
     "node_modules/@alloc/quick-lru": {
@@ -1211,9 +1211,9 @@
       "dev": true
     },
     "node_modules/fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
       "dependencies": {
         "@nodelib/fs.stat": "^2.0.2",
         "@nodelib/fs.walk": "^1.2.3",
@@ -1627,9 +1627,9 @@
       "dev": true
     },
     "node_modules/jiti": {
-      "version": "1.18.2",
-      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
-      "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+      "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
       "dev": true,
       "bin": {
         "jiti": "bin/jiti.js"
@@ -2777,9 +2777,9 @@
       "dev": true
     },
     "node_modules/tailwindcss": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
-      "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
+      "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==",
       "dev": true,
       "dependencies": {
         "@alloc/quick-lru": "^5.2.0",
@@ -2787,10 +2787,10 @@
         "chokidar": "^3.5.3",
         "didyoumean": "^1.2.2",
         "dlv": "^1.1.3",
-        "fast-glob": "^3.2.12",
+        "fast-glob": "^3.3.0",
         "glob-parent": "^6.0.2",
         "is-glob": "^4.0.3",
-        "jiti": "^1.18.2",
+        "jiti": "^1.19.1",
         "lilconfig": "^2.1.0",
         "micromatch": "^4.0.5",
         "normalize-path": "^3.0.0",
@@ -2802,7 +2802,6 @@
         "postcss-load-config": "^4.0.1",
         "postcss-nested": "^6.0.1",
         "postcss-selector-parser": "^6.0.11",
-        "postcss-value-parser": "^4.2.0",
         "resolve": "^1.22.2",
         "sucrase": "^3.32.0"
       },
@@ -4085,9 +4084,9 @@
       "dev": true
     },
     "fast-glob": {
-      "version": "3.2.12",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
-      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
       "requires": {
         "@nodelib/fs.stat": "^2.0.2",
         "@nodelib/fs.walk": "^1.2.3",
@@ -4389,9 +4388,9 @@
       "dev": true
     },
     "jiti": {
-      "version": "1.18.2",
-      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
-      "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+      "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
       "dev": true
     },
     "js-tokens": {
@@ -5181,9 +5180,9 @@
       }
     },
     "tailwindcss": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
-      "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
+      "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==",
       "dev": true,
       "requires": {
         "@alloc/quick-lru": "^5.2.0",
@@ -5191,10 +5190,10 @@
         "chokidar": "^3.5.3",
         "didyoumean": "^1.2.2",
         "dlv": "^1.1.3",
-        "fast-glob": "^3.2.12",
+        "fast-glob": "^3.3.0",
         "glob-parent": "^6.0.2",
         "is-glob": "^4.0.3",
-        "jiti": "^1.18.2",
+        "jiti": "^1.19.1",
         "lilconfig": "^2.1.0",
         "micromatch": "^4.0.5",
         "normalize-path": "^3.0.0",
@@ -5206,7 +5205,6 @@
         "postcss-load-config": "^4.0.1",
         "postcss-nested": "^6.0.1",
         "postcss-selector-parser": "^6.0.11",
-        "postcss-value-parser": "^4.2.0",
         "resolve": "^1.22.2",
         "sucrase": "^3.32.0"
       },
diff --git a/package.json b/package.json
index 1add55c..887f3e9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-tailwindcss",
-  "version": "3.13.0",
+  "version": "3.14.0",
   "description": "Rules enforcing best practices while using Tailwind CSS",
   "keywords": [
     "eslint",
@@ -18,13 +18,14 @@
   "bugs": "https://github.com/francoismassart/eslint-plugin-tailwindcss/issues",
   "main": "lib/index.js",
   "scripts": {
+    "wip": "mocha tests/lib/rules/no-arbitrary-value.js",
     "test": "mocha tests --recursive"
   },
   "files": [
     "lib"
   ],
   "peerDependencies": {
-    "tailwindcss": "^3.3.2"
+    "tailwindcss": "^3.4.0"
   },
   "dependencies": {
     "fast-glob": "^3.2.5",
@@ -41,10 +42,11 @@
     "daisyui": "^2.6.4",
     "eslint": "^7.1.0",
     "mocha": "^10.2.0",
-    "tailwindcss": "^3.3.2",
+    "tailwindcss": "^3.4.0",
     "typescript": "4.3.5",
     "vue-eslint-parser": "^9.1.0"
   },
+  "packageManager": "npm@10.2.5+sha256.8002e3e7305d2abd4016e1368af48d49b066c269079eeb10a56e7d6598acfdaa",
   "engines": {
     "node": ">=12.13.0"
   },


Test patch:
diff --git a/tests/lib/rules/classnames-order.js b/tests/lib/rules/classnames-order.js
index 894367c..c4bba5d 100644
--- a/tests/lib/rules/classnames-order.js
+++ b/tests/lib/rules/classnames-order.js
@@ -84,7 +84,7 @@ ruleTester.run("classnames-order", rule, {
       ],
     },
     {
-      code: "<div className={ctl(`w-full p-10 ${live && 'bg-blue-100 dark:bg-purple-400 sm:rounded-lg'}`)}>ctl + exp</div>",
+      code: "<div className={ctl(`w-full p-10 ${live && 'bg-blue-100 sm:rounded-lg dark:bg-purple-400'}`)}>ctl + exp</div>",
     },
     {
       code: "<div className={ctl(`h-48 w-48 rounded-full bg-blue-500 ${className}`)}>ctl + var</div>",
@@ -243,7 +243,7 @@ ruleTester.run("classnames-order", rule, {
     {
       // Kitchensink
       code: `
-      <div class="custom custom2 aspect-auto group peer container prose prose-slate aspect-h-9 aspect-w-16 sr-only pointer-events-auto visible sticky inset-0 inset-x-2 inset-y-1 bottom-5 left-6 right-4 top-3 isolate z-auto order-1 col-span-1 col-start-2 col-end-3 row-span-1 row-start-2 row-end-3 float-left clear-both m-0 mx-1.5 my-px mb-3 ml-4 mr-2 mt-1 box-border block h-0 max-h-fit min-h-full w-0 min-w-full max-w-fit flex-none flex-shrink shrink flex-grow grow basis-0 table-fixed border-collapse origin-bottom-right translate-x-px translate-y-2 rotate-90 skew-x-2 skew-y-0 scale-0 scale-x-75 scale-y-50 transform transform-gpu transform-none animate-spin cursor-text touch-auto select-none resize-none snap-x snap-mandatory snap-center snap-always scroll-m-0 scroll-mx-1.5 scroll-my-px scroll-mb-3 scroll-ml-4 scroll-mr-2 scroll-mt-1 scroll-p-0 scroll-px-1.5 scroll-py-px scroll-pb-3 scroll-pl-4 scroll-pr-2 scroll-pt-1 list-outside list-decimal appearance-none columns-1 break-before-avoid-page break-inside-avoid-column break-after-auto auto-cols-min grid-flow-col auto-rows-min grid-cols-1 grid-rows-1 flex-col flex-wrap place-content-between place-items-stretch content-between items-baseline justify-start justify-items-center gap-0 gap-x-1 gap-y-2 space-x-1 space-y-0 space-y-reverse space-x-reverse divide-x-2 divide-y-4 divide-y-reverse divide-x-reverse divide-dashed divide-black place-self-end self-baseline justify-self-stretch overflow-hidden overflow-x-auto overflow-y-scroll overscroll-auto overscroll-y-none overscroll-x-contain scroll-smooth overflow-ellipsis text-ellipsis whitespace-nowrap break-words rounded rounded-b-sm rounded-l-none rounded-r-2xl rounded-t-xl rounded-bl-none rounded-br-sm rounded-tl-xl rounded-tr-2xl border-0 border-x-2 border-y-2 border-b border-l border-r-4 border-t-4 border-solid border-black border-x-slate-50 border-y-transparent border-b-inherit border-l-current border-r-transparent border-t-white bg-current bg-opacity-50 bg-none from-current via-black to-orange-500 decoration-clone box-decoration-clone bg-auto bg-fixed bg-clip-padding bg-bottom bg-no-repeat bg-origin-border fill-white stroke-white stroke-2 object-contain object-bottom p-0 px-1.5 py-px pb-3 pl-4 pr-2 pt-1 text-left indent-px align-top font-sans text-lg font-semibold uppercase italic ordinal leading-4 tracking-normal text-current underline decoration-black decoration-double decoration-from-font underline-offset-auto subpixel-antialiased caret-black accent-black opacity-50 bg-blend-lighten mix-blend-darken shadow-lg shadow-white outline-dotted outline-0 outline-offset-1 outline-current ring-1 ring-inset ring-white ring-opacity-50 ring-offset-2 ring-offset-current blur brightness-50 contrast-100 drop-shadow-xl grayscale hue-rotate-90 invert saturate-100 sepia filter backdrop-blur-lg backdrop-brightness-110 backdrop-contrast-125 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-30 backdrop-saturate-100 backdrop-sepia transition-all delay-150 duration-1000 ease-linear will-change-scroll content-none line-clamp-2 [--scroll-offset:56px]">Kitchensink</div>
+      <div class="custom custom2 aspect-auto group peer container prose prose-slate aspect-h-9 aspect-w-16 sr-only pointer-events-auto visible sticky inset-0 inset-x-2 inset-y-1 bottom-5 left-6 right-4 top-3 isolate z-auto order-1 col-span-1 col-start-2 col-end-3 row-span-1 row-start-2 row-end-3 float-left clear-both m-0 mx-1.5 my-px mb-3 ml-4 mr-2 mt-1 box-border line-clamp-2 block h-0 max-h-fit min-h-full w-0 min-w-full max-w-fit flex-none flex-shrink shrink flex-grow grow basis-0 table-fixed border-collapse origin-bottom-right translate-x-px translate-y-2 rotate-90 skew-x-2 skew-y-0 scale-0 scale-x-75 scale-y-50 transform transform-gpu transform-none animate-spin cursor-text touch-auto select-none resize-none snap-x snap-mandatory snap-center snap-always scroll-m-0 scroll-mx-1.5 scroll-my-px scroll-mb-3 scroll-ml-4 scroll-mr-2 scroll-mt-1 scroll-p-0 scroll-px-1.5 scroll-py-px scroll-pb-3 scroll-pl-4 scroll-pr-2 scroll-pt-1 list-outside list-decimal appearance-none columns-1 break-before-avoid-page break-inside-avoid-column break-after-auto auto-cols-min grid-flow-col auto-rows-min grid-cols-1 grid-rows-1 flex-col flex-wrap place-content-between place-items-stretch content-between items-baseline justify-start justify-items-center gap-0 gap-x-1 gap-y-2 space-x-1 space-y-0 space-y-reverse space-x-reverse divide-x-2 divide-y-4 divide-y-reverse divide-x-reverse divide-dashed divide-black place-self-end self-baseline justify-self-stretch overflow-hidden overflow-x-auto overflow-y-scroll overscroll-auto overscroll-y-none overscroll-x-contain scroll-smooth overflow-ellipsis text-ellipsis whitespace-nowrap break-words rounded rounded-b-sm rounded-l-none rounded-r-2xl rounded-t-xl rounded-bl-none rounded-br-sm rounded-tl-xl rounded-tr-2xl border-0 border-x-2 border-y-2 border-b border-l border-r-4 border-t-4 border-solid border-black border-x-slate-50 border-y-transparent border-b-inherit border-l-current border-r-transparent border-t-white bg-current bg-opacity-50 bg-none from-current via-black to-orange-500 decoration-clone box-decoration-clone bg-auto bg-fixed bg-clip-padding bg-bottom bg-no-repeat bg-origin-border fill-white stroke-white stroke-2 object-contain object-bottom p-0 px-1.5 py-px pb-3 pl-4 pr-2 pt-1 text-left indent-px align-top font-sans text-lg font-semibold uppercase italic ordinal leading-4 tracking-normal text-current underline decoration-black decoration-double decoration-from-font underline-offset-auto subpixel-antialiased caret-black accent-black opacity-50 bg-blend-lighten mix-blend-darken shadow-lg shadow-white outline-dotted outline-0 outline-offset-1 outline-current ring-1 ring-inset ring-white ring-opacity-50 ring-offset-2 ring-offset-current blur brightness-50 contrast-100 drop-shadow-xl grayscale hue-rotate-90 invert saturate-100 sepia filter backdrop-blur-lg backdrop-brightness-110 backdrop-contrast-125 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-30 backdrop-saturate-100 backdrop-sepia transition-all delay-150 duration-1000 ease-linear will-change-scroll content-none [--scroll-offset:56px]">Kitchensink</div>
       `,
       options: sharedOptions,
     },
@@ -281,6 +281,14 @@ ruleTester.run("classnames-order", rule, {
     {
       code: `<div class="py-1\u3000px-2 block">Do not treat full width space as class separator</div>`,
     },
+    {
+      code: `
+        const func = () => ({ a: 12 });
+        <div className={clsx(['text-sm', {
+          ...func()
+        }])}>Spread of a function return inside clsx</div>
+      `,
+    },
   ],
   invalid: [
     {
diff --git a/tests/lib/rules/enforces-shorthand.js b/tests/lib/rules/enforces-shorthand.js
index f21411f..d14368b 100644
--- a/tests/lib/rules/enforces-shorthand.js
+++ b/tests/lib/rules/enforces-shorthand.js
@@ -393,8 +393,7 @@ ruleTester.run("shorthands", rule, {
       output: `
       <div className={ctl(\`
         p-8
-        w-48
-        h-48
+        size-48
         text-white
         bg-black/50
         hover:bg-black/70
@@ -406,21 +405,21 @@ ruleTester.run("shorthands", rule, {
       \`)}>
         Multilines
       </div>`,
-      errors: [generateError(["py-8", "px-8"], "p-8")],
+      errors: [generateError(["w-48", "h-48"], "size-48"), generateError(["py-8", "px-8"], "p-8")],
     },
     {
       code: `classnames(['py-8 px-8 w-48 h-48 text-white'])`,
-      output: `classnames(['p-8 w-48 h-48 text-white'])`,
-      errors: [generateError(["py-8", "px-8"], "p-8")],
+      output: `classnames(['p-8 size-48 text-white'])`,
+      errors: [generateError(["w-48", "h-48"], "size-48"), generateError(["py-8", "px-8"], "p-8")],
     },
     {
-      code: `classnames({'py-8 px-8 w-48 h-48 text-white': true})`,
-      output: `classnames({'p-8 w-48 h-48 text-white': true})`,
+      code: `classnames({'py-8 px-8 text-white': true})`,
+      output: `classnames({'p-8 text-white': true})`,
       errors: [generateError(["py-8", "px-8"], "p-8")],
     },
     {
-      code: `classnames({'!py-8 !px-8 w-48 h-48 text-white': true})`,
-      output: `classnames({'!p-8 w-48 h-48 text-white': true})`,
+      code: `classnames({'!py-8 !px-8 text-white': true})`,
+      output: `classnames({'!p-8 text-white': true})`,
       errors: [generateError(["!py-8", "!px-8"], "!p-8")],
     },
     {
@@ -652,7 +651,9 @@ ruleTester.run("shorthands", rule, {
         Possible shorthand when using truncate with hover
       </div>
       `,
-      errors: [generateError(["hover:overflow-hidden", "hover:text-ellipsis", "hover:whitespace-nowrap"], "hover:truncate")],
+      errors: [
+        generateError(["hover:overflow-hidden", "hover:text-ellipsis", "hover:whitespace-nowrap"], "hover:truncate"),
+      ],
     },
     {
       code: `
@@ -665,7 +666,12 @@ ruleTester.run("shorthands", rule, {
         Possible shorthand when using truncate with hover, breakpoint, important and prefix
       </div>
       `,
-      errors: [generateError(["hover:sm:!tw-overflow-hidden", "hover:sm:!tw-text-ellipsis", "hover:sm:!tw-whitespace-nowrap"], "hover:sm:!tw-truncate")],
+      errors: [
+        generateError(
+          ["hover:sm:!tw-overflow-hidden", "hover:sm:!tw-text-ellipsis", "hover:sm:!tw-whitespace-nowrap"],
+          "hover:sm:!tw-truncate"
+        ),
+      ],
       options: [
         {
           config: { prefix: "tw-" },
@@ -685,5 +691,15 @@ ruleTester.run("shorthands", rule, {
       `,
       errors: [generateError(["overflow-hidden", "text-ellipsis", "whitespace-nowrap"], "truncate")],
     },
+    {
+      code: `<div class="h-10 w-10">New size-* utilities</div>`,
+      output: `<div class="size-10">New size-* utilities</div>`,
+      errors: [generateError(["h-10", "w-10"], "size-10")],
+    },
+    {
+      code: `<div class="h-10 md:h-5 md:w-5 lg:w-10">New size-* utilities</div>`,
+      output: `<div class="h-10 md:size-5 lg:w-10">New size-* utilities</div>`,
+      errors: [generateError(["md:h-5", "md:w-5"], "md:size-5")],
+    },
   ],
 });
diff --git a/tests/lib/rules/no-arbitrary-value.js b/tests/lib/rules/no-arbitrary-value.js
index bd7f7ce..f564813 100644
--- a/tests/lib/rules/no-arbitrary-value.js
+++ b/tests/lib/rules/no-arbitrary-value.js
@@ -156,5 +156,9 @@ ruleTester.run("no-arbitrary-value", rule, {
       filename: "test.vue",
       parser: require.resolve("vue-eslint-parser"),
     },
+    {
+      code: `<div className={'min-h-[75dvh]'}>Dynamic viewport units</div>`,
+      errors: generateErrors(["min-h-[75dvh]"]),
+    },
   ],
 });
diff --git a/tests/lib/rules/no-contradicting-classname.js b/tests/lib/rules/no-contradicting-classname.js
index 87f9ae6..89adda1 100644
--- a/tests/lib/rules/no-contradicting-classname.js
+++ b/tests/lib/rules/no-contradicting-classname.js
@@ -295,6 +295,18 @@ ruleTester.run("no-contradicting-classname", rule, {
       code: `
       <div class="bg-[url('/image.jpg')] bg-center">Issue #186</div>`,
     },
+    {
+      code: `<section className="bg-[image:var(--bg-small)] bg-center" />`,
+    },
+    {
+      code: `
+        <div>
+          <div className={'h-svh min-h-svh max-h-svh'}>Dynamic viewport units</div>
+          <div className={'h-lvh min-h-lvh max-h-lvh'}>Dynamic viewport units</div>
+          <div className={'h-dvh min-h-dvh max-h-dvh'}>Dynamic viewport units</div>
+        </div>
+      `,
+    },
   ],
 
   invalid: [
@@ -705,6 +717,22 @@ ruleTester.run("no-contradicting-classname", rule, {
       </div>`,
       errors: generateErrors(["sm:bg-[url('foo.jpg')] sm:bg-[url('bar.jpg')]"]),
     },
+    {
+      code: `<div className={'h-svh h-lvh h-dvh min-h-svh min-h-lvh min-h-dvh max-h-svh max-h-lvh max-h-dvh'}>Dynamic viewport units</div>`,
+      errors: generateErrors(["h-svh h-lvh h-dvh", "min-h-svh min-h-lvh min-h-dvh", "max-h-svh max-h-lvh max-h-dvh"]),
+    },
+    {
+      code: `<div className={'size-5 size-10'}>Dynamic viewport units</div>`,
+      errors: generateErrors(["size-5 size-10"]),
+    },
+    {
+      code: `<h1 class="text-wrap text-nowrap">Balanced headlines with text-wrap utilities</h1>`,
+      errors: generateErrors(["text-wrap text-nowrap"]),
+    },
+    {
+      code: `<div class="grid grid-rows-4 grid-flow-col gap-4 grid-rows-subgrid">Subgrid support</div>`,
+      errors: generateErrors(["grid-rows-4 grid-rows-subgrid"]),
+    },
     // {
     //   code: `
     //   <div class="scale-75 transform-none">
diff --git a/tests/lib/rules/no-custom-classname.js b/tests/lib/rules/no-custom-classname.js
index d4bcda5..0840cb0 100644
--- a/tests/lib/rules/no-custom-classname.js
+++ b/tests/lib/rules/no-custom-classname.js
@@ -993,6 +993,76 @@ ruleTester.run("no-custom-classname", rule, {
         }}>Spread inside classname object</div>
       `,
     },
+    {
+      code: `
+        <div>
+          <div className={'h-svh min-h-svh max-h-svh'}>Dynamic viewport units</div>
+          <div className={'h-lvh min-h-lvh max-h-lvh'}>Dynamic viewport units</div>
+          <div className={'h-dvh min-h-dvh max-h-dvh'}>Dynamic viewport units</div>
+        </div>
+      `,
+    },
+    {
+      code: `
+      <label class="has-[:checked]:ring-indigo-500 has-[:checked]:text-indigo-900 has-[:checked]:bg-indigo-50">New :has() variant</label>
+      `,
+    },
+    {
+      code: `
+      <ul class="*:rounded-full *:border *:border-sky-100 *:bg-sky-50 *:px-2 *:py-0.5 dark:text-sky-300 dark:*:border-sky-500/15 dark:*:bg-sky-500/10">
+        <li>Sales</li>
+        <li>Marketing</li>
+        <li>SEO</li>
+      </ul>
+      `,
+    },
+    {
+      code: `<button class="size-10">New size-* utilities</button>`,
+    },
+    {
+      code: `<h1 class="text-wrap sm:text-nowrap md:text-balance lg:text-pretty">Balanced headlines with text-wrap utilities</h1>`,
+    },
+    {
+      code: `
+      <div class="grid grid-cols-4 gap-4">
+        <div>01</div>
+        <div>05</div>
+        <div class="grid grid-cols-subgrid gap-4 col-span-3">
+          <div class="col-start-2">06</div>
+        </div>
+      </div>`,
+    },
+    {
+      code: `
+      <div class="grid grid-rows-4 grid-flow-col gap-4">
+        <div>01</div>
+        <div>05</div>
+        <div class="grid grid-rows-subgrid gap-4 row-span-3">
+            <div class="row-start-2">06</div>
+        </div>
+        <div>07</div>
+        <div>10</div>
+      </div>`,
+    },
+    {
+      code: `<div class="min-w-12">Extended min-width, max-width, and min-height scales</div>`,
+    },
+    {
+      code: `<div class="opacity-35">Extended opacity scale</div>`,
+    },
+    {
+      code: `<div class="grid grid-rows-9">Extended grid-rows-* scale</div>`,
+    },
+    {
+      code: `
+      <form>
+        <input type="checkbox" class="appearance-none forced-colors:appearance-auto" />
+      </form>`,
+    },
+    {
+      code: `
+      <h1 class="forced-color-adjust-none">New forced-color-adjust utilities</h1>`,
+    },
   ],
 
   invalid: [
@@ -1414,5 +1484,9 @@ ruleTester.run("no-custom-classname", rule, {
       code: `<div className="\u3000px-1 py-2\u3000">Full-width space before and after classes</div>`,
       errors: generateErrors("\u3000px-1 py-2\u3000"),
     },
+    {
+      code: `<div class="grid grid-flow-col gap-4 grid-rows-supagrid">Subgrid support</div>`,
+      errors: generateErrors("grid-rows-supagrid"),
+    },
   ],
 });