Repository: vuejs/eslint-plugin-vue
Pull request #2795 (2025-07-14 04:29:53)

Add new `vue/no-negated-condition` rule
Resolve #2769
Related PR https://github.com/vuejs/eslint-plugin-vue/pull/2794

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 4e0b13a50b0c892c429d283ae36a54b18e09139f

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name              | Type  |
| ----------------- | ----- |
| eslint-plugin-vue | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/vuejs/eslint-plugin-vue/new/feature/no-negated-condition?filename=.changeset/gentle-kangaroos-rest.md&value=---%0A%22eslint-plugin-vue%22%3A%20patch%0A---%0A%0AAdd%20new%20%60vue%2Fno-negated-condition%60%20rule%0A)



Patch:
diff --git a/.changeset/eight-camels-refuse.md b/.changeset/eight-camels-refuse.md
new file mode 100644
index 00000000..527b123e
--- /dev/null
+++ b/.changeset/eight-camels-refuse.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-vue": patch
+---
+
+Resolved TypeScript compatibility issues introduced by eslint-typegen
diff --git a/.changeset/true-pumas-open.md b/.changeset/true-pumas-open.md
new file mode 100644
index 00000000..7a49b0f2
--- /dev/null
+++ b/.changeset/true-pumas-open.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-vue': minor
+---
+
+Added new [`vue/no-negated-condition`](https://eslint.vuejs.org/rules/no-negated-condition.html) rule
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 6423ef36..34462fba 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -327,6 +327,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
 | [vue/no-implicit-coercion] | Disallow shorthand type conversions in `<template>` | :wrench::bulb: | :hammer: |
 | [vue/no-irregular-whitespace] | disallow irregular whitespace in `.vue` files |  | :warning: |
 | [vue/no-loss-of-precision] | Disallow literal numbers that lose precision in `<template>` |  | :warning: |
+| [vue/no-negated-condition] | Disallow negated conditions in `<template>` |  | :hammer: |
 | [vue/no-restricted-syntax] | Disallow specified syntax in `<template>` |  | :hammer: |
 | [vue/no-sparse-arrays] | Disallow sparse arrays in `<template>` |  | :warning: |
 | [vue/no-useless-concat] | Disallow unnecessary concatenation of literals or template literals in `<template>` |  | :hammer: |
@@ -482,6 +483,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
 [vue/no-multiple-slot-args]: ./no-multiple-slot-args.md
 [vue/no-multiple-template-root]: ./no-multiple-template-root.md
 [vue/no-mutating-props]: ./no-mutating-props.md
+[vue/no-negated-condition]: ./no-negated-condition.md
 [vue/no-parsing-error]: ./no-parsing-error.md
 [vue/no-potential-component-option-typo]: ./no-potential-component-option-typo.md
 [vue/no-ref-as-operand]: ./no-ref-as-operand.md
diff --git a/docs/rules/no-negated-condition.md b/docs/rules/no-negated-condition.md
new file mode 100644
index 00000000..e389172a
--- /dev/null
+++ b/docs/rules/no-negated-condition.md
@@ -0,0 +1,33 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-negated-condition
+description: Disallow negated conditions in `<template>`
+---
+
+# vue/no-negated-condition
+
+> Disallow negated conditions in `<template>`
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
+
+## :book: Rule Details
+
+This rule is the same rule as core [no-negated-condition] rule but it applies to the expressions in `<template>`.
+
+## :couple: Related Rules
+
+- [unicorn/no-negated-condition](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-negated-condition.md)
+
+## :books: Further Reading
+
+- [no-negated-condition]
+
+[no-negated-condition]: https://eslint.org/docs/rules/no-negated-condition
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-negated-condition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-negated-condition.js)
+
+<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/latest/rules/no-negated-condition)</sup>
diff --git a/lib/index.js b/lib/index.js
index e511536f..bfc35b11 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -141,6 +141,7 @@ const plugin = {
     'no-multiple-slot-args': require('./rules/no-multiple-slot-args'),
     'no-multiple-template-root': require('./rules/no-multiple-template-root'),
     'no-mutating-props': require('./rules/no-mutating-props'),
+    'no-negated-condition': require('./rules/no-negated-condition'),
     'no-parsing-error': require('./rules/no-parsing-error'),
     'no-potential-component-option-typo': require('./rules/no-potential-component-option-typo'),
     'no-ref-as-operand': require('./rules/no-ref-as-operand'),
diff --git a/lib/rules/no-negated-condition.js b/lib/rules/no-negated-condition.js
new file mode 100644
index 00000000..22a9a6fc
--- /dev/null
+++ b/lib/rules/no-negated-condition.js
@@ -0,0 +1,12 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const { wrapCoreRule } = require('../utils')
+
+// eslint-disable-next-line internal/no-invalid-meta
+module.exports = wrapCoreRule('no-negated-condition', {
+  applyDocument: true
+})
diff --git a/tools/generate-typegen.mjs b/tools/generate-typegen.mjs
index 5bcb325a..1f3b4a12 100644
--- a/tools/generate-typegen.mjs
+++ b/tools/generate-typegen.mjs
@@ -2,8 +2,13 @@ import fs from 'node:fs/promises'
 import { pluginsToRulesDTS } from 'eslint-typegen/core'
 import plugin from '../lib/index.js'
 
-const dts = await pluginsToRulesDTS({
-  vue: plugin
-})
+const dts = await pluginsToRulesDTS(
+  {
+    vue: plugin
+  },
+  {
+    includeAugmentation: false
+  }
+)
 
 await fs.writeFile('lib/eslint-typegen.d.ts', dts)


Test patch:
diff --git a/tests/lib/rules/no-negated-condition.js b/tests/lib/rules/no-negated-condition.js
new file mode 100644
index 00000000..4a4d5e4c
--- /dev/null
+++ b/tests/lib/rules/no-negated-condition.js
@@ -0,0 +1,166 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/no-negated-condition')
+
+const tester = new RuleTester({
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('no-negated-condition', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :class="foo ? 'baz' : 'bar'" />
+      </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{ color: isActive ? 'red' : 'blue' }" />
+      </template>
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :class="!foo ? 'bar' : 'baz'" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 3,
+          column: 22,
+          endLine: 3,
+          endColumn: 42
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{
+          'display': !isVisible ? 'none' : 'block',
+          'color': 'red'
+        }" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 4,
+          column: 22,
+          endLine: 4,
+          endColumn: 51
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :class="{
+          'hidden': !foo ? true : false,
+          'visible': bar
+        }" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 4,
+          column: 21,
+          endLine: 4,
+          endColumn: 40
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :disabled="!enabled ? true : false" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 3,
+          column: 25,
+          endLine: 3,
+          endColumn: 48
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :class="a !== b ? 'different' : 'same'" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 3,
+          column: 22,
+          endLine: 3,
+          endColumn: 52
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :class="a != b ? 'not-equal' : 'equal'" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 3,
+          column: 22,
+          endLine: 3,
+          endColumn: 52
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div :style="{
+          opacity: !(x && y) ? 0 : 1
+        }" />
+      </template>
+      `,
+      errors: [
+        {
+          message: 'Unexpected negated condition.',
+          line: 4,
+          column: 20,
+          endLine: 4,
+          endColumn: 37
+        }
+      ]
+    }
+  ]
+})