Repository: francoismassart/eslint-plugin-tailwindcss
Pull request #178 (2022-11-16 07:51:38)

feat: add `skipClassAttribute` option
See https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/154


Patch:
diff --git a/README.md b/README.md
index f4cb1a1..785188b 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@ If you enjoy my work you can:
 
 ## Latest changelog
 
+- ADD: New option `skipClassAttribute` you can turn on to only lint the `callees`
 - FIX: support for Tailwind CSS version `3.2.3`
 - FIX: [prefix parsing when using attribute variants](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/164)
 - FIX: [add support for contradicting arbitrary properties](https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/163)
@@ -178,6 +179,7 @@ All these settings have nice default values that are explained in each rules' do
       ],
       cssFilesRefreshRate: 5_000,
       removeDuplicates: true,
+      skipClassAttribute: false,
       whitelist: [],
     },
   },
diff --git a/docs/rules/classnames-order.md b/docs/rules/classnames-order.md
index b121198..0ffe0ed 100644
--- a/docs/rules/classnames-order.md
+++ b/docs/rules/classnames-order.md
@@ -26,6 +26,7 @@ Examples of **correct** code for this rule:
   "callees": Array<string>,
   "config": <string>|<object>,
   "removeDuplicates": <boolean>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
 }]
 ...
@@ -55,6 +56,11 @@ Finally, the plugin will [merge the provided configuration](https://tailwindcss.
 
 Duplicate classnames are automatically removed but you can always disable this behavior by setting `removeDuplicates` to `false`.
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/docs/rules/enforces-negative-arbitrary-values.md b/docs/rules/enforces-negative-arbitrary-values.md
index cbc6d00..98ba2f0 100644
--- a/docs/rules/enforces-negative-arbitrary-values.md
+++ b/docs/rules/enforces-negative-arbitrary-values.md
@@ -44,6 +44,7 @@ Examples of **correct** code for this rule:
 "tailwindcss/enforces-negative-arbitrary-values": [<enabled>, {
   "callees": Array<string>,
   "config": <string>|<object>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
 }]
 ...
@@ -69,6 +70,11 @@ It is also possible to directly inject a configuration as plain `object` like `{
 
 Finally, the plugin will [merge the provided configuration](https://tailwindcss.com/docs/configuration#referencing-in-java-script) with [Tailwind CSS's default configuration](https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js).
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/docs/rules/enforces-shorthand.md b/docs/rules/enforces-shorthand.md
index b095eb9..ddc6c9e 100644
--- a/docs/rules/enforces-shorthand.md
+++ b/docs/rules/enforces-shorthand.md
@@ -36,6 +36,7 @@ If indeed, you are using the `classnames-order` rule, then it'll be automaticall
 "tailwindcss/enforces-shorthand": [<enabled>, {
   "callees": Array<string>,
   "config": <string>|<object>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
 }]
 ...
@@ -61,6 +62,11 @@ It is also possible to directly inject a configuration as plain `object` like `{
 
 Finally, the plugin will [merge the provided configuration](https://tailwindcss.com/docs/configuration#referencing-in-java-script) with [Tailwind CSS's default configuration](https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js).
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/docs/rules/migration-from-tailwind-2.md b/docs/rules/migration-from-tailwind-2.md
index 79d1df6..ff45476 100644
--- a/docs/rules/migration-from-tailwind-2.md
+++ b/docs/rules/migration-from-tailwind-2.md
@@ -81,6 +81,7 @@ This rule will report the issue but **it will not fix it for you**...
 "tailwindcss/migration-from-tailwind-2": [<enabled>, {
   "callees": Array<string>,
   "config": <string>|<object>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
 }]
 ...
@@ -106,6 +107,11 @@ It is also possible to directly inject a configuration as plain `object` like `{
 
 Finally, the plugin will [merge the provided configuration](https://tailwindcss.com/docs/configuration#referencing-in-java-script) with [Tailwind CSS's default configuration](https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js).
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/docs/rules/no-arbitrary-value.md b/docs/rules/no-arbitrary-value.md
index 5e1a3c3..1fa4668 100644
--- a/docs/rules/no-arbitrary-value.md
+++ b/docs/rules/no-arbitrary-value.md
@@ -26,6 +26,7 @@ Examples of **correct** code for this rule:
 "tailwindcss/no-arbitrary-value": [<enabled>, {
   "callees": Array<string>,
   "config": <string>|<object>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
 }]
 ...
@@ -51,6 +52,11 @@ It is also possible to directly inject a configuration as plain `object` like `{
 
 Finally, the plugin will [merge the provided configuration](https://tailwindcss.com/docs/configuration#referencing-in-java-script) with [Tailwind CSS's default configuration](https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js).
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/docs/rules/no-contradicting-classname.md b/docs/rules/no-contradicting-classname.md
index 1e6993c..88d7e13 100644
--- a/docs/rules/no-contradicting-classname.md
+++ b/docs/rules/no-contradicting-classname.md
@@ -26,6 +26,7 @@ Examples of **correct** code for this rule:
 "tailwindcss/no-contradicting-classname": [<enabled>, {
   "callees": Array<string>,
   "config": <string>|<object>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
 }]
 ...
@@ -51,6 +52,11 @@ It is also possible to directly inject a configuration as plain `object` like `{
 
 Finally, the plugin will [merge the provided configuration](https://tailwindcss.com/docs/configuration#referencing-in-java-script) with [Tailwind CSS's default configuration](https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js).
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/docs/rules/no-custom-classname.md b/docs/rules/no-custom-classname.md
index ca1c838..d7fe602 100644
--- a/docs/rules/no-custom-classname.md
+++ b/docs/rules/no-custom-classname.md
@@ -27,6 +27,7 @@ Examples of **correct** code for this rule:
   "config": <string>|<object>,
   "cssFiles": Array<string>,
   "cssFilesRefreshRate": <number>,
+  "skipClassAttribute": <boolean>,
   "tags": Array<string>,
   "whitelist": Array<string>,
 }]
@@ -67,6 +68,11 @@ The plugin read and parses CSS files which can be a time consuming process depen
 
 By default, it runs the process if files were updated for at least 5 seconds (`5_000` ms) but you can increase this setting to enhance performances while reducing the update interval.
 
+### `skipClassAttribute` (default: `false`)
+
+Set `skipClassAttribute` to `true` if you only want to lint the classnames inside one of the `callees`.
+While, this will avoid linting the `class` and `className` attributes, it will still lint matching `callees` inside of these attributes.
+
 ### `tags` (default: `[]`)
 
 Optional, if you are using tagged templates, you should provide the tags in this array.
diff --git a/lib/rules/classnames-order.js b/lib/rules/classnames-order.js
index c011e3d..d1e324f 100644
--- a/lib/rules/classnames-order.js
+++ b/lib/rules/classnames-order.js
@@ -64,6 +64,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
     const removeDuplicates = getOption(context, 'removeDuplicates');
@@ -196,7 +197,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/rules/enforces-negative-arbitrary-values.js b/lib/rules/enforces-negative-arbitrary-values.js
index 778c5f2..5f297f0 100644
--- a/lib/rules/enforces-negative-arbitrary-values.js
+++ b/lib/rules/enforces-negative-arbitrary-values.js
@@ -57,6 +57,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
 
@@ -142,7 +143,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/rules/enforces-shorthand.js b/lib/rules/enforces-shorthand.js
index 9adf667..b389688 100644
--- a/lib/rules/enforces-shorthand.js
+++ b/lib/rules/enforces-shorthand.js
@@ -57,6 +57,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
 
@@ -371,7 +372,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/rules/migration-from-tailwind-2.js b/lib/rules/migration-from-tailwind-2.js
index 06482c2..46d3e59 100644
--- a/lib/rules/migration-from-tailwind-2.js
+++ b/lib/rules/migration-from-tailwind-2.js
@@ -62,6 +62,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
 
@@ -255,7 +256,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/rules/no-arbitrary-value.js b/lib/rules/no-arbitrary-value.js
index 551543e..e448e41 100644
--- a/lib/rules/no-arbitrary-value.js
+++ b/lib/rules/no-arbitrary-value.js
@@ -56,6 +56,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
 
@@ -141,7 +142,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/rules/no-contradicting-classname.js b/lib/rules/no-contradicting-classname.js
index 6763a46..f9a9418 100644
--- a/lib/rules/no-contradicting-classname.js
+++ b/lib/rules/no-contradicting-classname.js
@@ -57,6 +57,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
 
@@ -152,7 +153,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/rules/no-custom-classname.js b/lib/rules/no-custom-classname.js
index af60b3e..ff578f5 100644
--- a/lib/rules/no-custom-classname.js
+++ b/lib/rules/no-custom-classname.js
@@ -76,6 +76,7 @@ module.exports = {
 
   create: function (context) {
     const callees = getOption(context, 'callees');
+    const skipClassAttribute = getOption(context, 'skipClassAttribute');
     const tags = getOption(context, 'tags');
     const twConfig = getOption(context, 'config');
     const cssFiles = getOption(context, 'cssFiles');
@@ -137,7 +138,7 @@ module.exports = {
     //----------------------------------------------------------------------
 
     const attributeVisitor = function (node) {
-      if (!astUtil.isClassAttribute(node)) {
+      if (!astUtil.isClassAttribute(node) || skipClassAttribute) {
         return;
       }
       if (astUtil.isLiteralAttributeValue(node)) {
diff --git a/lib/util/settings.js b/lib/util/settings.js
index 93967f9..3c23baa 100644
--- a/lib/util/settings.js
+++ b/lib/util/settings.js
@@ -20,6 +20,8 @@ function getOption(context, name) {
       return ['**/*.css', '!**/node_modules', '!**/.*', '!**/dist', '!**/build'];
     case 'removeDuplicates':
       return true;
+    case 'skipClassAttribute':
+      return false;
     case 'tags':
       return [];
     case 'whitelist':


Test patch:
diff --git a/tests/lib/rules/classnames-order.js b/tests/lib/rules/classnames-order.js
index 79a092b..7680258 100644
--- a/tests/lib/rules/classnames-order.js
+++ b/tests/lib/rules/classnames-order.js
@@ -60,6 +60,16 @@ const sharedOptions = [
   },
 ];
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
+
 ruleTester.run("classnames-order", rule, {
   valid: [
     {
@@ -246,6 +256,12 @@ ruleTester.run("classnames-order", rule, {
     {
       code: `<div class>No errors while typing</div>`,
     },
+    {
+      code: `
+      <div className={\`sm:flex block \${ctl('relative w-full overflow-hidden')}\`}>skipClassAttribute</div>
+      `,
+      options: skipClassAttributeOptions,
+    },
   ],
   invalid: [
     {
@@ -815,5 +831,15 @@ ruleTester.run("classnames-order", rule, {
       output: `<Button className={\`relative h-full w-full overflow-hidden\`}>{name}</Button>`,
       errors: errors,
     },
+    {
+      code: `
+      <div className={\`sm:flex block \${ctl('w-full relative')}\`}>skipClassAttribute</div>
+      `,
+      output: `
+      <div className={\`sm:flex block \${ctl('relative w-full')}\`}>skipClassAttribute</div>
+      `,
+      options: skipClassAttributeOptions,
+      errors: errors,
+    },
   ],
 });
diff --git a/tests/lib/rules/enforces-negative-arbitrary-values.js b/tests/lib/rules/enforces-negative-arbitrary-values.js
index 00dae9b..7d9fc72 100644
--- a/tests/lib/rules/enforces-negative-arbitrary-values.js
+++ b/tests/lib/rules/enforces-negative-arbitrary-values.js
@@ -23,6 +23,16 @@ var parserOptions = {
   },
 };
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
+
 var generateErrors = (classes) => {
   const errors = [];
   classes.split(" ").forEach((cls) =>
@@ -52,6 +62,12 @@ ruleTester.run("enforces-negative-arbitrary-values", rule, {
     {
       code: `<div className>No errors while typing</div>`,
     },
+    {
+      code: `
+      <div className={\`-top-[-50px] \${ctl('inset-y-[-1px]')}\`}>skipClassAttribute</div>
+      `,
+      options: skipClassAttributeOptions,
+    },
   ],
   invalid: [
     {
@@ -60,5 +76,12 @@ ruleTester.run("enforces-negative-arbitrary-values", rule, {
         "-inset-[1px] -inset-y-[1px] -inset-x-[1px] -top-[1px] -right-[1px] -bottom-[1px] -left-[1px] -top-[1px] -z-[2] -order-[2] -m-[1px] -my-[1px] -mx-[1px] -mt-[1px] -mr-[1px] -mb-[1px] -ml-[1px] -mt-[1px] -space-y-[1px] -space-x-[1px] -tracking-[1px] -indent-[1px] -hue-rotate-[50%] -backdrop-hue-rotate-[50%] -scale-[50%] -scale-y-[50%] -scale-x-[50%] -rotate-[45deg] -translate-x-[1px] -translate-y-[1px] -skew-x-[45deg] -skew-y-[45deg] -scroll-m-[1px] -scroll-my-[1px] -scroll-mx-[1px] -scroll-mt-[1px] -scroll-mr-[1px] -scroll-mb-[1px] -scroll-ml-[1px] -scroll-mt-[1px]"
       ),
     },
+    {
+      code: `
+      <div className={\`-top-[-50px] \${ctl('-inset-y-[1px]')}\`}>skipClassAttribute</div>
+      `,
+      options: skipClassAttributeOptions,
+      errors: generateErrors("-inset-y-[1px]"),
+    },
   ],
 });
diff --git a/tests/lib/rules/enforces-shorthand.js b/tests/lib/rules/enforces-shorthand.js
index bba0e70..4fe139d 100644
--- a/tests/lib/rules/enforces-shorthand.js
+++ b/tests/lib/rules/enforces-shorthand.js
@@ -24,6 +24,16 @@ var parserOptions = {
   },
 };
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
+
 var generateError = (classnames, shorthand) => {
   return {
     messageId: "shorthandCandidateDetected",
@@ -89,6 +99,12 @@ ruleTester.run("shorthands", rule, {
     {
       code: `<div class>No errors while typing</div>`,
     },
+    {
+      code: `
+      <div className={"px-0 py-[0]"}>skipClassAttribute</div>
+      `,
+      options: skipClassAttributeOptions,
+    },
   ],
 
   invalid: [
@@ -452,6 +468,16 @@ ruleTester.run("shorthands", rule, {
       `,
       errors: [generateError(["border-spacing-x-px", "border-spacing-y-px"], "border-spacing-px")],
     },
+    {
+      code: `
+      <div className={\`mr-px ml-px \${ctl('mt-0 mb-0')}\`}>skipClassAttribute</div>
+      `,
+      output: `
+      <div className={\`mr-px ml-px \${ctl('my-0')}\`}>skipClassAttribute</div>
+      `,
+      options: skipClassAttributeOptions,
+      errors: [generateError(["mt-0", "mb-0"], "my-0")],
+    },
     {
       code: `
       <div class="p-2 pl-2 pr-2">
diff --git a/tests/lib/rules/migration-from-tailwind-2.js b/tests/lib/rules/migration-from-tailwind-2.js
index 89a0c32..67fd93f 100644
--- a/tests/lib/rules/migration-from-tailwind-2.js
+++ b/tests/lib/rules/migration-from-tailwind-2.js
@@ -23,6 +23,16 @@ var parserOptions = {
   },
 };
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
+
 var ruleTester = new RuleTester({ parserOptions });
 
 ruleTester.run("migration-from-tailwind-2", rule, {
@@ -249,5 +259,19 @@ ruleTester.run("migration-from-tailwind-2", rule, {
         },
       ],
     },
+    {
+      code: `<div className={\`flex flex-shrink \${ctl('placeholder-red-900')}\`}></div>`,
+      output: `<div className={\`flex flex-shrink \${ctl('placeholder:text-red-900')}\`}></div>`,
+      options: skipClassAttributeOptions,
+      errors: [
+        {
+          messageId: "classnameChanged",
+          data: {
+            deprecated: "placeholder-red-900",
+            updated: "placeholder:text-red-900",
+          },
+        },
+      ],
+    },
   ],
 });
diff --git a/tests/lib/rules/no-arbitrary-value.js b/tests/lib/rules/no-arbitrary-value.js
index ee29e43..1959c5e 100644
--- a/tests/lib/rules/no-arbitrary-value.js
+++ b/tests/lib/rules/no-arbitrary-value.js
@@ -23,6 +23,16 @@ var parserOptions = {
   },
 };
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
+
 var generateErrors = (classnames) => {
   const errors = [];
   if (typeof classnames === "string") {
@@ -49,6 +59,10 @@ ruleTester.run("no-arbitrary-value", rule, {
     {
       code: `<div class>No errors while typing</div>`,
     },
+    {
+      code: `<div class="w-[10px]">Skip class attribute</div>`,
+      options: skipClassAttributeOptions,
+    },
   ],
 
   invalid: [
@@ -102,5 +116,10 @@ ruleTester.run("no-arbitrary-value", rule, {
       );`,
       errors: generateErrors("text-[length:var(--font-size)] rounded-[2em] p-[4vw] leading-[1]"),
     },
+    {
+      code: `<div className={ctl('w-[10px]')}>Skip class attribute</div>`,
+      options: skipClassAttributeOptions,
+      errors: generateErrors("w-[10px]"),
+    },
   ],
 });
diff --git a/tests/lib/rules/no-contradicting-classname.js b/tests/lib/rules/no-contradicting-classname.js
index dcd02f5..f8f90a1 100644
--- a/tests/lib/rules/no-contradicting-classname.js
+++ b/tests/lib/rules/no-contradicting-classname.js
@@ -23,6 +23,16 @@ var parserOptions = {
   },
 };
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
+
 var config = [
   {
     config: {
@@ -608,6 +618,14 @@ ruleTester.run("no-contradicting-classname", rule, {
       </div>`,
       errors: generateErrors("-outline-offset-2 outline-offset-4"),
     },
+    {
+      code: `
+      <div className={\`stroke-white stroke-none \${ctl('-outline-offset-2 outline-offset-4')}\`}>
+        Conflicting outline offset
+      </div>`,
+      options: skipClassAttributeOptions,
+      errors: generateErrors("-outline-offset-2 outline-offset-4"),
+    },
     // {
     //   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 2430e74..80cb615 100644
--- a/tests/lib/rules/no-custom-classname.js
+++ b/tests/lib/rules/no-custom-classname.js
@@ -23,6 +23,15 @@ var parserOptions = {
   },
 };
 
+const skipClassAttributeOptions = [
+  {
+    skipClassAttribute: true,
+    config: {
+      theme: {},
+      plugins: [],
+    },
+  },
+];
 var generateErrors = (classnames) => {
   const errors = [];
   if (typeof classnames === "string") {
@@ -1059,6 +1068,13 @@ ruleTester.run("no-custom-classname", rule, {
       ],
       errors: generateErrors("prose-md"),
     },
+    {
+      code: `
+      <p className={\`yolo \${ctl('border-t-nada')}\`}>border-t-nada</p>
+      `,
+      options: skipClassAttributeOptions,
+      errors: generateErrors("border-t-nada"),
+    },
     {
       code: `
       <div className="custom-but-allowed bg-404 bg-black text-white md:text-white lg:text-unknown text-color-not-found">