Repository: francoismassart/eslint-plugin-tailwindcss (js)

Issue:
[BUG] Rule crash while typing in-editor due to `class` shorthand not containing a value.
**Describe the bug**
A `class` or `className` shorthand prop crashes 7 different rules in the project, causing an error to pop up in-editor:
```
ESLint: Cannot read properties of null (reading 'type') Occurred while linting
```

**To Reproduce**
Steps to reproduce the behavior:
1. Enable the `enforces-shorthand` rule.
2. Type: `<div class></div>`.

**Expected behavior**
No error

**Environment (please complete the following information):**
 - OS: macOS
 - Softwares + version used:
     -  VSCode 1.70.2
     -  npm 8.5.5, node v16.14.2

**Additional context**
Easily reproducible with tests, PR incoming

Pull request description:
Prevent rule crash from class/className shorthand while typing.
# Pull Request Name

## Description

Prevent `<div class></div>` from crashing.

Fixes #157

## Type of change

Please delete options that are not relevant.

- [X] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
Please also list any relevant details for your test configuration

tests/lib/rules/classnames-order.js
tests/lib/rules/enforces-negative-arbitrary-values.js
tests/lib/rules/enforces-shorthand.js
tests/lib/rules/migration-from-tailwind-2.js
tests/lib/rules/no-arbitrary-value.js
tests/lib/rules/no-contradicting-classname.js
tests/lib/rules/no-custom-classname.js

**Test Configuration**:
* macOS Monterey
* npm 8.5.5
* node v16.14.2

## Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my own code
- [X] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings
- [X] I have added tests that prove my fix is effective or that my feature works
- [X] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules
- [X] I have checked my code and corrected any misspellings


Patch:
diff --git a/lib/rules/classnames-order.js b/lib/rules/classnames-order.js
index 8463110..01a2575 100644
--- a/lib/rules/classnames-order.js
+++ b/lib/rules/classnames-order.js
@@ -204,7 +204,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         sortNodeArgumentValue(node);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         sortNodeArgumentValue(node, node.value.expression);
       }
     };
diff --git a/lib/rules/enforces-negative-arbitrary-values.js b/lib/rules/enforces-negative-arbitrary-values.js
index 9a2b249..778c5f2 100644
--- a/lib/rules/enforces-negative-arbitrary-values.js
+++ b/lib/rules/enforces-negative-arbitrary-values.js
@@ -147,7 +147,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         parseForNegativeArbitraryClassNames(node);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         parseForNegativeArbitraryClassNames(node, node.value.expression);
       }
     };
diff --git a/lib/rules/enforces-shorthand.js b/lib/rules/enforces-shorthand.js
index baceba1..698af31 100644
--- a/lib/rules/enforces-shorthand.js
+++ b/lib/rules/enforces-shorthand.js
@@ -375,7 +375,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         parseForShorthandCandidates(node);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         parseForShorthandCandidates(node, node.value.expression);
       }
     };
diff --git a/lib/rules/migration-from-tailwind-2.js b/lib/rules/migration-from-tailwind-2.js
index d40ebcf..06482c2 100644
--- a/lib/rules/migration-from-tailwind-2.js
+++ b/lib/rules/migration-from-tailwind-2.js
@@ -260,7 +260,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         parseForObsoleteClassNames(node);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         parseForObsoleteClassNames(node, node.value.expression);
       }
     };
diff --git a/lib/rules/no-arbitrary-value.js b/lib/rules/no-arbitrary-value.js
index faa4036..551543e 100644
--- a/lib/rules/no-arbitrary-value.js
+++ b/lib/rules/no-arbitrary-value.js
@@ -146,7 +146,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         parseForArbitraryValues(node);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         parseForArbitraryValues(node, node.value.expression);
       }
     };
diff --git a/lib/rules/no-contradicting-classname.js b/lib/rules/no-contradicting-classname.js
index fd33eb5..6e1df2d 100644
--- a/lib/rules/no-contradicting-classname.js
+++ b/lib/rules/no-contradicting-classname.js
@@ -133,7 +133,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         astUtil.parseNodeRecursive(node, null, parseForContradictingClassNames, true);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         astUtil.parseNodeRecursive(node, node.value.expression, parseForContradictingClassNames, true);
       }
     };
diff --git a/lib/rules/no-custom-classname.js b/lib/rules/no-custom-classname.js
index cc44a85..af60b3e 100644
--- a/lib/rules/no-custom-classname.js
+++ b/lib/rules/no-custom-classname.js
@@ -142,7 +142,7 @@ module.exports = {
       }
       if (astUtil.isLiteralAttributeValue(node)) {
         astUtil.parseNodeRecursive(node, null, parseForCustomClassNames);
-      } else if (node.value.type === 'JSXExpressionContainer') {
+      } else if (node.value && node.value.type === 'JSXExpressionContainer') {
         astUtil.parseNodeRecursive(node, node.value.expression, parseForCustomClassNames);
       }
     };
diff --git a/lib/util/ast.js b/lib/util/ast.js
index 49c4a32..225f2ba 100644
--- a/lib/util/ast.js
+++ b/lib/util/ast.js
@@ -189,7 +189,7 @@ function parseNodeRecursive(node, arg, cb, skipConditional = false, isolate = fa
     switch (arg.type) {
       case 'TemplateLiteral':
         arg.expressions.forEach((exp) => {
-          parseNodeRecursive(node, exp.right, cb, skipConditional, forceIsolation);
+          parseNodeRecursive(node, exp, cb, skipConditional, forceIsolation);
         });
         arg.quasis.forEach((quasis) => {
           parseNodeRecursive(node, quasis, cb, skipConditional, isolate);


Test patch:
diff --git a/tests/lib/rules/classnames-order.js b/tests/lib/rules/classnames-order.js
index aa944db..a8ba0ee 100644
--- a/tests/lib/rules/classnames-order.js
+++ b/tests/lib/rules/classnames-order.js
@@ -241,6 +241,9 @@ ruleTester.run("classnames-order", rule, {
       </div>
       `,
     },
+    {
+      code: `<div class>No errors while typing</div>`,
+    },
   ],
   invalid: [
     {
diff --git a/tests/lib/rules/enforces-negative-arbitrary-values.js b/tests/lib/rules/enforces-negative-arbitrary-values.js
index 141d526..00dae9b 100644
--- a/tests/lib/rules/enforces-negative-arbitrary-values.js
+++ b/tests/lib/rules/enforces-negative-arbitrary-values.js
@@ -49,6 +49,9 @@ ruleTester.run("enforces-negative-arbitrary-values", rule, {
     {
       code: `<div className={\`top-[-50px]\`}>top-[-50px]</div>`,
     },
+    {
+      code: `<div className>No errors while typing</div>`,
+    },
   ],
   invalid: [
     {
diff --git a/tests/lib/rules/enforces-shorthand.js b/tests/lib/rules/enforces-shorthand.js
index 672f02c..31c30dc 100644
--- a/tests/lib/rules/enforces-shorthand.js
+++ b/tests/lib/rules/enforces-shorthand.js
@@ -86,6 +86,9 @@ ruleTester.run("shorthands", rule, {
     {
       code: `<div className="py-2.5 md:py-3 pl-2.5 md:pl-4 font-medium uppercase">issue #91</div>`,
     },
+    {
+      code: `<div class>No errors while typing</div>`,
+    },
   ],
 
   invalid: [
diff --git a/tests/lib/rules/migration-from-tailwind-2.js b/tests/lib/rules/migration-from-tailwind-2.js
index 257c029..89a0c32 100644
--- a/tests/lib/rules/migration-from-tailwind-2.js
+++ b/tests/lib/rules/migration-from-tailwind-2.js
@@ -36,6 +36,9 @@ ruleTester.run("migration-from-tailwind-2", rule, {
     {
       code: `<div class="transition-[var(--transform)]">transition-[var(--transform)]</div>`,
     },
+    {
+      code: `<div class>No errors while typing</div>`,
+    },
   ],
   invalid: [
     {
diff --git a/tests/lib/rules/no-arbitrary-value.js b/tests/lib/rules/no-arbitrary-value.js
index a83508f..ee29e43 100644
--- a/tests/lib/rules/no-arbitrary-value.js
+++ b/tests/lib/rules/no-arbitrary-value.js
@@ -46,6 +46,9 @@ ruleTester.run("no-arbitrary-value", rule, {
     {
       code: `<div class="flex shrink-0 flex-col">No arbitrary value</div>`,
     },
+    {
+      code: `<div class>No errors while typing</div>`,
+    },
   ],
 
   invalid: [
diff --git a/tests/lib/rules/no-contradicting-classname.js b/tests/lib/rules/no-contradicting-classname.js
index 1d47921..723169a 100644
--- a/tests/lib/rules/no-contradicting-classname.js
+++ b/tests/lib/rules/no-contradicting-classname.js
@@ -235,6 +235,9 @@ ruleTester.run("no-contradicting-classname", rule, {
         },
       ],
     },
+    {
+      code: "<div class>No errors while typing</div>",
+    },
   ],
 
   invalid: [
@@ -403,6 +406,11 @@ ruleTester.run("no-contradicting-classname", rule, {
       options: [{ tags: ["myTag"] }],
       errors: generateErrors("px-2 px-0"),
     },
+    {
+      code: `myTag\`\${enabled ? "px-2 px-0" : ""}\``,
+      options: [{ tags: ["myTag"] }],
+      errors: generateErrors("px-2 px-0"),
+    },
     {
       code: `
       <div class="shrink shrink-0 shrink-[inherit] shrink-[initial] shrink-[unset] shrink-[var(--some)] shrink-[0.5] shrink-[5]">
diff --git a/tests/lib/rules/no-custom-classname.js b/tests/lib/rules/no-custom-classname.js
index 1095367..37a22b3 100644
--- a/tests/lib/rules/no-custom-classname.js
+++ b/tests/lib/rules/no-custom-classname.js
@@ -791,6 +791,9 @@ ruleTester.run("no-custom-classname", rule, {
         border-spacing
       </div>`,
     },
+    {
+      code: `<div class>No errors while typing</div>`,
+    },
   ],
 
   invalid: [
@@ -985,6 +988,11 @@ ruleTester.run("no-custom-classname", rule, {
       options: [{ tags: ["myTag"] }],
       errors: generateErrors("custom-2 custom-3 custom-1"),
     },
+    {
+      code: `myTag\`custom-1 \${isDisabled ? "custom-2" : "m-4"}\``,
+      options: [{ tags: ["myTag"] }],
+      errors: generateErrors("custom-2 custom-1"),
+    },
     {
       code: `
       <div class="bg-red-600 p-10">