Repository: jsx-eslint/eslint-plugin-react
Pull request #3474 (2022-10-27 19:24:47)

[Fix] `no-invalid-html-attribute`: convert autofix to suggestion
Fixes : #3458


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4aad7555..a4186757 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 * [`no-unknown-property`]: do not check `fbs` elements ([#3494][] @brianogilvie)
 * [`jsx-newline`]: No newline between comments and jsx elements ([#3493][] @justmejulian)
 * [`jsx-no-leaked-render`]: Don't report errors on empty strings if React >= v18 ([#3488][] @himanshu007-creator)
+* [`no-invalid-html-attribute`]: convert autofix to suggestion ([#3474][] @himanshu007-creator @ljharb)
 
 ### Changed
 * [Docs] [`jsx-no-leaked-render`]: Remove mentions of empty strings for React 18 ([#3468][] @karlhorky)
@@ -24,6 +25,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
 [#3494]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3494
 [#3493]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3493
 [#3488]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3488
+[#3474]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3474
 [#3471]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3471
 [#3468]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3468
 [#3461]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3461
diff --git a/README.md b/README.md
index ccdcb2bc..9bb7d2c9 100644
--- a/README.md
+++ b/README.md
@@ -352,7 +352,7 @@ module.exports = [
 | [no-did-update-set-state](docs/rules/no-did-update-set-state.md)                                                                                                                                     | Disallow usage of setState in componentDidUpdate                                                                                             |    |    |    |    |
 | [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md)                                                                                                                                   | Disallow direct mutation of this.state                                                                                                       | 💼 |    |    |    |
 | [no-find-dom-node](docs/rules/no-find-dom-node.md)                                                                                                                                                   | Disallow usage of findDOMNode                                                                                                                | 💼 |    |    |    |
-| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md)                                                                                                                                 | Disallow usage of invalid attributes                                                                                                         |    | 🔧 |    |    |
+| [no-invalid-html-attribute](docs/rules/no-invalid-html-attribute.md)                                                                                                                                 | Disallow usage of invalid attributes                                                                                                         |    |    | 💡 |    |
 | [no-is-mounted](docs/rules/no-is-mounted.md)                                                                                                                                                         | Disallow usage of isMounted                                                                                                                  | 💼 |    |    |    |
 | [no-multi-comp](docs/rules/no-multi-comp.md)                                                                                                                                                         | Disallow multiple component definition per file                                                                                              |    |    |    |    |
 | [no-namespace](docs/rules/no-namespace.md)                                                                                                                                                           | Enforce that namespaces are not used in React elements                                                                                       |    |    |    |    |
diff --git a/docs/rules/no-invalid-html-attribute.md b/docs/rules/no-invalid-html-attribute.md
index 12614ff9..d749ead0 100644
--- a/docs/rules/no-invalid-html-attribute.md
+++ b/docs/rules/no-invalid-html-attribute.md
@@ -1,6 +1,6 @@
 # Disallow usage of invalid attributes (`react/no-invalid-html-attribute`)
 
-🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
 
 <!-- end auto-generated rule header -->
 
diff --git a/lib/rules/hook-use-state.js b/lib/rules/hook-use-state.js
index ba078549..a9deed4f 100644
--- a/lib/rules/hook-use-state.js
+++ b/lib/rules/hook-use-state.js
@@ -114,16 +114,12 @@ module.exports = {
               getMessageData('suggestPair', messages.suggestPair),
               {
                 fix(fixer) {
-                  if (expectedSetterVariableNames.length === 0) {
-                    return;
+                  if (expectedSetterVariableNames.length > 0) {
+                    return fixer.replaceTextRange(
+                      node.parent.id.range,
+                      `[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
+                    );
                   }
-
-                  const fix = fixer.replaceTextRange(
-                    node.parent.id.range,
-                    `[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
-                  );
-
-                  return fix;
                 },
               }
             ),
diff --git a/lib/rules/no-invalid-html-attribute.js b/lib/rules/no-invalid-html-attribute.js
index 9ba7bfa1..997afc2c 100644
--- a/lib/rules/no-invalid-html-attribute.js
+++ b/lib/rules/no-invalid-html-attribute.js
@@ -8,6 +8,7 @@
 const matchAll = require('string.prototype.matchall');
 const docsUrl = require('../util/docsUrl');
 const report = require('../util/report');
+const getMessageData = require('../util/message');
 
 // ------------------------------------------------------------------------------
 // Rule Definition
@@ -232,6 +233,11 @@ const messages = {
   onlyMeaningfulFor: 'The ”{{attributeName}}“ attribute only has meaning on the tags: {{tagNames}}',
   onlyStrings: '“{{attributeName}}” attribute only supports strings.',
   spaceDelimited: '”{{attributeName}}“ attribute values should be space delimited.',
+  suggestRemoveDefault: '"remove {{attributeName}}"',
+  suggestRemoveEmpty: '"remove empty attribute {{attributeName}}"',
+  suggestRemoveInvalid: '“remove invalid attribute {{reportingValue}}”',
+  suggestRemoveWhitespaces: 'remove whitespaces in “{{reportingValue}}”',
+  suggestRemoveNonString: 'remove non-string value in “{{reportingValue}}”',
 };
 
 function splitIntoRangedParts(node, regex) {
@@ -254,9 +260,12 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
     report(context, messages.onlyStrings, 'onlyStrings', {
       node,
       data: { attributeName },
-      fix(fixer) {
-        return fixer.remove(parentNode);
-      },
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveNonString', messages.suggestRemoveNonString),
+          { fix(fixer) { return fixer.remove(parentNode); } }
+        ),
+      ],
     });
     return;
   }
@@ -265,9 +274,12 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
     report(context, messages.noEmpty, 'noEmpty', {
       node,
       data: { attributeName },
-      fix(fixer) {
-        return fixer.remove(parentNode);
-      },
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty),
+          { fix(fixer) { return fixer.remove(node.parent); } }
+        ),
+      ],
     });
     return;
   }
@@ -276,16 +288,23 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
   for (const singlePart of singleAttributeParts) {
     const allowedTags = VALID_VALUES.get(attributeName).get(singlePart.value);
     const reportingValue = singlePart.reportingValue;
+
+    const suggest = [
+      Object.assign(
+        getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
+        { fix(fixer) { return fixer.removeRange(singlePart.range); } }
+      ),
+    ];
+
     if (!allowedTags) {
+      const data = {
+        attributeName,
+        reportingValue,
+      };
       report(context, messages.neverValid, 'neverValid', {
         node,
-        data: {
-          attributeName,
-          reportingValue,
-        },
-        fix(fixer) {
-          return fixer.removeRange(singlePart.range);
-        },
+        data,
+        suggest,
       });
     } else if (!allowedTags.has(parentNodeName)) {
       report(context, messages.notValidFor, 'notValidFor', {
@@ -295,9 +314,7 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
           reportingValue,
           elementName: parentNodeName,
         },
-        fix(fixer) {
-          return fixer.removeRange(singlePart.range);
-        },
+        suggest,
       });
     }
   }
@@ -324,6 +341,7 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
                 secondValue,
                 missingValue: Array.from(siblings).join(', '),
               },
+              suggest: false,
             });
           }
         }
@@ -337,17 +355,23 @@ function checkLiteralValueNode(context, attributeName, node, parentNode, parentN
       report(context, messages.spaceDelimited, 'spaceDelimited', {
         node,
         data: { attributeName },
-        fix(fixer) {
-          return fixer.removeRange(whitespacePart.range);
-        },
+        suggest: [
+          Object.assign(
+            getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces),
+            { fix(fixer) { return fixer.removeRange(whitespacePart.range); } }
+          ),
+        ],
       });
     } else if (whitespacePart.value !== '\u0020') {
       report(context, messages.spaceDelimited, 'spaceDelimited', {
         node,
         data: { attributeName },
-        fix(fixer) {
-          return fixer.replaceTextRange(whitespacePart.range, '\u0020');
-        },
+        suggest: [
+          Object.assign(
+            getMessageData('suggestRemoveWhitespaces', messages.suggestRemoveWhitespaces),
+            { fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); } }
+          ),
+        ],
       });
     }
   }
@@ -358,10 +382,6 @@ const DEFAULT_ATTRIBUTES = ['rel'];
 function checkAttribute(context, node) {
   const attribute = node.name.name;
 
-  function fix(fixer) {
-    return fixer.remove(node);
-  }
-
   const parentNodeName = node.parent.name.name;
   if (!COMPONENT_ATTRIBUTE_MAP.has(attribute) || !COMPONENT_ATTRIBUTE_MAP.get(attribute).has(parentNodeName)) {
     const tagNames = Array.from(
@@ -374,16 +394,28 @@ function checkAttribute(context, node) {
         attributeName: attribute,
         tagNames,
       },
-      fix,
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
+          { fix(fixer) { return fixer.remove(node); } }
+        ),
+      ],
     });
     return;
   }
 
+  function fix(fixer) { return fixer.remove(node); }
+
   if (!node.value) {
     report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', {
       node,
       data: { attributeName: attribute },
-      fix,
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveEmpty', messages.suggestRemoveEmpty),
+          { fix }
+        ),
+      ],
     });
     return;
   }
@@ -404,16 +436,23 @@ function checkAttribute(context, node) {
     report(context, messages.onlyStrings, 'onlyStrings', {
       node,
       data: { attributeName: attribute },
-      fix,
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
+          { fix }
+        ),
+      ],
     });
-    return;
-  }
-
-  if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') {
+  } else if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') {
     report(context, messages.onlyStrings, 'onlyStrings', {
       node,
       data: { attributeName: attribute },
-      fix,
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveDefault', messages.suggestRemoveDefault),
+          { fix }
+        ),
+      ],
     });
   }
 }
@@ -441,11 +480,14 @@ function checkPropValidValue(context, node, value, attribute) {
         attributeName: attribute,
         reportingValue: value.value,
       },
+      suggest: [
+        Object.assign(
+          getMessageData('suggestRemoveInvalid', messages.suggestRemoveInvalid),
+          { fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); } }
+        ),
+      ],
     });
-    return;
-  }
-
-  if (!validTagSet.has(node.arguments[0].value)) {
+  } else if (!validTagSet.has(node.arguments[0].value)) {
     report(context, messages.notValidFor, 'notValidFor', {
       node: value,
       data: {
@@ -453,6 +495,7 @@ function checkPropValidValue(context, node, value, attribute) {
         reportingValue: value.raw,
         elementName: node.arguments[0].value,
       },
+      suggest: false,
     });
   }
 }
@@ -493,6 +536,7 @@ function checkCreateProps(context, node, attribute) {
           attributeName: attribute,
           tagNames,
         },
+        suggest: false,
       });
 
       // eslint-disable-next-line no-continue
@@ -505,6 +549,7 @@ function checkCreateProps(context, node, attribute) {
         data: {
           attributeName: attribute,
         },
+        suggest: false,
       });
 
       // eslint-disable-next-line no-continue
@@ -531,7 +576,6 @@ function checkCreateProps(context, node, attribute) {
 
 module.exports = {
   meta: {
-    fixable: 'code',
     docs: {
       description: 'Disallow usage of invalid attributes',
       category: 'Possible Errors',
@@ -545,6 +589,8 @@ module.exports = {
         enum: ['rel'],
       },
     }],
+    type: 'suggestion',
+    hasSuggestions: true, // eslint-disable-line eslint-plugin/require-meta-has-suggestions
   },
 
   create(context) {


Test patch:
diff --git a/tests/helpers/parsers.js b/tests/helpers/parsers.js
index 1403f114..1d05610c 100644
--- a/tests/helpers/parsers.js
+++ b/tests/helpers/parsers.js
@@ -174,6 +174,8 @@ const parsers = {
         tsNew ? addComment(Object.assign({}, test, { parser: parsers['@TYPESCRIPT_ESLINT'] }), '@typescript-eslint/parser') : []
       );
     });
+
+    // console.log(require('util').inspect(t, { depth: null }));
     return t;
   },
 };
diff --git a/tests/lib/rules/no-invalid-html-attribute.js b/tests/lib/rules/no-invalid-html-attribute.js
index 2ba5b168..cde601ba 100644
--- a/tests/lib/rules/no-invalid-html-attribute.js
+++ b/tests/lib/rules/no-invalid-html-attribute.js
@@ -11,6 +11,7 @@
 
 const RuleTester = require('eslint').RuleTester;
 const rule = require('../../../lib/rules/no-invalid-html-attribute');
+const parsers = require('../../helpers/parsers');
 
 const parserOptions = {
   ecmaVersion: 2018,
@@ -27,7 +28,7 @@ const parserOptions = {
 const ruleTester = new RuleTester({ parserOptions });
 
 ruleTester.run('no-invalid-html-attribute', rule, {
-  valid: [
+  valid: parsers.all([
     { code: '<a rel="alternate"></a>' },
     { code: 'React.createElement("a", { rel: "alternate" })' },
     { code: 'React.createElement("a", { rel: ["alternate"] })' },
@@ -232,11 +233,10 @@ ruleTester.run('no-invalid-html-attribute', rule, {
     {
       code: '<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#fff" />',
     },
-  ],
-  invalid: [
+  ]),
+  invalid: parsers.all([
     {
       code: '<a rel="alternatex"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -244,12 +244,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: 'React.createElement("a", { rel: "alternatex" })',
-      output: null,
       errors: [
         {
           messageId: 'neverValid',
@@ -257,12 +262,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: "" })',
+            },
+          ],
         },
       ],
     },
     {
       code: 'React.createElement("a", { rel: ["alternatex"] })',
-      output: null,
       errors: [
         {
           messageId: 'neverValid',
@@ -270,12 +280,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: [""] })',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="alternatex alternate"></a>',
-      output: '<a rel=" alternate"></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -283,6 +298,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=" alternate"></a>',
+            },
+          ],
         },
       ],
     },
@@ -295,6 +316,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex alternate',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: "" })',
+            },
+          ],
         },
       ],
     },
@@ -307,12 +334,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex alternate',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: [""] })',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="alternate alternatex"></a>',
-      output: '<a rel="alternate "></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -320,6 +352,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel="alternate "></a>',
+            },
+          ],
         },
       ],
     },
@@ -332,6 +370,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternate alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: "" })',
+            },
+          ],
         },
       ],
     },
@@ -344,12 +388,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'alternate alternatex',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: [""] })',
+            },
+          ],
         },
       ],
     },
     {
       code: '<html rel></html>',
-      output: '<html ></html>',
       errors: [
         {
           messageId: 'onlyMeaningfulFor',
@@ -357,6 +406,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             tagNames: '"<link>", "<a>", "<area>", "<form>"',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveDefault',
+              output: '<html ></html>',
+            },
+          ],
         },
       ],
     },
@@ -369,16 +424,27 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             tagNames: '"<link>", "<a>", "<area>", "<form>"',
           },
+          // suggestions: [
+          //   {
+          //     messageId: 'suggestRemoveDefault',
+          //     output: 'React.createElement("html", { })',
+          //   },
+          // ],
         },
       ],
     },
     {
       code: '<a rel></a>',
-      output: '<a ></a>',
       errors: [
         {
           messageId: 'emptyIsMeaningless',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveEmpty',
+              output: '<a ></a>',
+            },
+          ],
         },
       ],
     },
@@ -391,6 +457,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             reportingValue: 1,
           },
+          // suggestions: [
+          //   {
+          //     messageId: 'suggestRemoveDefault',
+          //     output: 'React.createElement("a", { })',
+          //   },
+          // ],
         },
       ],
     },
@@ -405,7 +477,6 @@ ruleTester.run('no-invalid-html-attribute', rule, {
     },
     {
       code: '<span rel></span>',
-      output: '<span ></span>',
       errors: [
         {
           messageId: 'onlyMeaningfulFor',
@@ -413,62 +484,92 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             tagNames: '"<link>", "<a>", "<area>", "<form>"',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveDefault',
+              output: '<span ></span>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={null}></a>',
-      output: '<a ></a>',
       errors: [
         {
           messageId: 'onlyStrings',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveNonString',
+              output: '<a ></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={5}></a>',
-      output: '<a ></a>',
       errors: [
         {
           messageId: 'onlyStrings',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveNonString',
+              output: '<a ></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={true}></a>',
-      output: '<a ></a>',
       errors: [
         {
           messageId: 'onlyStrings',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveNonString',
+              output: '<a ></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={{}}></a>',
-      output: '<a ></a>',
       errors: [
         {
           messageId: 'onlyStrings',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveDefault',
+              output: '<a ></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={undefined}></a>',
-      output: '<a ></a>',
       errors: [
         {
           messageId: 'onlyStrings',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveDefault',
+              output: '<a ></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="noreferrer noopener foobar"></a>',
-      output: '<a rel="noreferrer noopener "></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -476,42 +577,62 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             reportingValue: 'foobar',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel="noreferrer noopener "></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="noreferrer noopener   "></a>',
-      output: '<a rel="noreferrer noopener"></a>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<a rel="noreferrer noopener"></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="noreferrer        noopener"></a>',
-      output: '<a rel="noreferrer noopener"></a>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<a rel="noreferrer noopener"></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="noreferrer\xa0\xa0noopener"></a>',
-      output: '<a rel="noreferrer noopener"></a>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<a rel="noreferrer noopener"></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={"noreferrer noopener foobar"}></a>',
-      output: '<a rel={"noreferrer noopener "}></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -519,6 +640,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'foobar',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel={"noreferrer noopener "}></a>',
+            },
+          ],
         },
       ],
     },
@@ -531,12 +658,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'foobar',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: 'React.createElement("a", { rel: ["noreferrer", "noopener", "" ] })',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={"foobar noreferrer noopener"}></a>',
-      output: '<a rel={" noreferrer noopener"}></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -544,12 +676,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'foobar',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel={" noreferrer noopener"}></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={"foobar batgo       noopener"}></a>',
-      output: '<a rel={"        noopener"}></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -557,6 +694,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'foobar',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel={" batgo       noopener"}></a>',
+            },
+          ],
         },
         {
           messageId: 'neverValid',
@@ -564,6 +707,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'batgo',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel={"foobar        noopener"}></a>',
+            },
+          ],
         },
         {
           messageId: 'spaceDelimited',
@@ -573,27 +722,36 @@ ruleTester.run('no-invalid-html-attribute', rule, {
     },
     {
       code: '<a rel={"        noopener"}></a>',
-      output: '<a rel={"noopener"}></a>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<a rel={"noopener"}></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={"noopener        "}></a>',
-      output: '<a rel={"noopener"}></a>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<a rel={"noopener"}></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={" batgo noopener"}></a>',
-      output: '<a rel={"batgo noopener"}></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -601,6 +759,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'batgo',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel={"  noopener"}></a>',
+            },
+          ],
         },
         {
           messageId: 'spaceDelimited',
@@ -610,7 +774,6 @@ ruleTester.run('no-invalid-html-attribute', rule, {
     },
     {
       code: '<a rel={"batgo noopener"}></a>',
-      output: '<a rel={" noopener"}></a>',
       errors: [
         {
           messageId: 'neverValid',
@@ -618,22 +781,32 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'batgo',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel={" noopener"}></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel={" noopener"}></a>',
-      output: '<a rel={"noopener"}></a>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<a rel={"noopener"}></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="canonical"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -642,12 +815,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="dns-prefetch"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -656,12 +834,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="icon"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -670,6 +853,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
@@ -687,7 +876,6 @@ ruleTester.run('no-invalid-html-attribute', rule, {
     },
     {
       code: '<link rel="shortcut foo"></link>',
-      output: '<link rel="shortcut "></link>',
       errors: [
         {
           messageId: 'neverValid',
@@ -695,6 +883,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'foo',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel="shortcut "></link>',
+            },
+          ],
         },
         {
           messageId: 'notPaired',
@@ -708,17 +902,21 @@ ruleTester.run('no-invalid-html-attribute', rule, {
     },
     {
       code: '<link rel="shortcut  icon"></link>',
-      output: '<link rel="shortcut icon"></link>',
       errors: [
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<link rel="shortcut icon"></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="shortcut  foo"></link>',
-      output: '<link rel="shortcut foo"></link>',
       errors: [
         {
           messageId: 'neverValid',
@@ -726,6 +924,12 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             reportingValue: 'foo',
             attributeName: 'rel',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel="shortcut  "></link>',
+            },
+          ],
         },
         {
           messageId: 'notAlone',
@@ -737,12 +941,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
         {
           messageId: 'spaceDelimited',
           data: { attributeName: 'rel' },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveWhitespaces',
+              output: '<link rel="shortcut foo"></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="manifest"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -751,12 +960,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="modulepreload"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -765,12 +979,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="pingback"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -779,12 +998,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="preconnect"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -793,12 +1017,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="prefetch"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -807,12 +1036,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="preload"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -821,12 +1055,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="prerender"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -835,12 +1074,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<a rel="stylesheet"></a>',
-      output: '<a rel=""></a>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -849,12 +1093,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'a',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<a rel=""></a>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="canonical"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -863,12 +1112,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="dns-prefetch"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -877,12 +1131,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="icon"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -891,12 +1150,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="manifest"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -905,12 +1169,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="modulepreload"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -919,12 +1188,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="pingback"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -933,12 +1207,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="preconnect"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -947,12 +1226,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="prefetch"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -961,12 +1245,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="preload"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -975,12 +1264,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="prerender"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -989,12 +1283,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<area rel="stylesheet"></area>',
-      output: '<area rel=""></area>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1003,12 +1302,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'area',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<area rel=""></area>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="bookmark"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1017,12 +1321,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="external"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1031,12 +1340,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="nofollow"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1045,12 +1359,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="noopener"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1059,12 +1378,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="noreferrer"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1073,12 +1397,16 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="opener"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1087,12 +1415,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<link rel="tag"></link>',
-      output: '<link rel=""></link>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1101,12 +1434,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'link',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<link rel=""></link>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="alternate"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1115,12 +1453,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="author"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1129,12 +1472,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="bookmark"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1143,12 +1491,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="canonical"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1157,12 +1510,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="dns-prefetch"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1171,12 +1529,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="icon"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1185,12 +1548,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="manifest"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1199,12 +1567,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="modulepreload"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1213,12 +1586,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="pingback"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1227,12 +1605,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="preconnect"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1241,12 +1624,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="prefetch"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1255,12 +1643,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="preload"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1269,12 +1662,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="prerender"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1283,12 +1681,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="stylesheet"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1297,12 +1700,17 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
     {
       code: '<form rel="tag"></form>',
-      output: '<form rel=""></form>',
       errors: [
         {
           messageId: 'notValidFor',
@@ -1311,8 +1719,33 @@ ruleTester.run('no-invalid-html-attribute', rule, {
             attributeName: 'rel',
             elementName: 'form',
           },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveInvalid',
+              output: '<form rel=""></form>',
+            },
+          ],
         },
       ],
     },
-  ],
+    {
+      code: '<form rel=""></form>',
+      errors: [
+        {
+          messageId: 'noEmpty',
+          data: {
+            reportingValue: 'tag',
+            attributeName: 'rel',
+            elementName: 'form',
+          },
+          suggestions: [
+            {
+              messageId: 'suggestRemoveEmpty',
+              output: '<form ></form>',
+            },
+          ],
+        },
+      ],
+    },
+  ]),
 });