Repository: vuejs/eslint-plugin-vue (js)

Issue:
vue/require-toggle-inside-transition - question: should it still be an error with a conditional appear attribute?
<!--
  Before proposing rule changes, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**What rule do you want to change?**

- vue/require-toggle-inside-transition

**Does this change cause the rule to produce more or fewer warnings?**

- fewer

**How will the change be implemented? (New option, new default behavior, etc.)?**

- new default behaviour - don't give an error when there is a conditional appear attribute on a node

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
```vue
<transition :appear="appIsLoaded">
  <div>Something</div>
</transition>
```

**What does the rule currently do for this code?**

- shows an error on the div - "The element inside `<transition>` is expected to have a `v-if` or `v-show` directive"

**What will the rule do after it's changed?**

- not show an error

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

We are using a conditional `appear` attribute so that the animation is not shown on the first load of the app, only when navigating between components.

I'm not 100% sure this is a valid use of appear and transition, so I would like to confirm this too.

Thanks!

Pull request:
feat(require-toggle-inside-transition): check directive
fix #2467

Patch:
diff --git a/docs/.vitepress/build-system/build.mts b/docs/.vitepress/build-system/build.mts
index 79521b52..9dce53fa 100644
--- a/docs/.vitepress/build-system/build.mts
+++ b/docs/.vitepress/build-system/build.mts
@@ -2,7 +2,7 @@
  * Pre-build cjs packages that cannot be bundled well.
  */
 import esbuild from 'esbuild'
-import path from 'path'
+import path from 'pathe'
 import fs from 'fs'
 import { fileURLToPath } from 'url'
 
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index c15f0233..77c052a5 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -1,6 +1,6 @@
 import type { DefaultTheme } from 'vitepress'
 import { defineConfig } from 'vitepress'
-import path from 'path'
+import path from 'pathe'
 import { fileURLToPath } from 'url'
 import { viteCommonjs, vitePluginRequireResolve } from './vite-plugin.mjs'
 
diff --git a/docs/.vitepress/vite-plugin.mts b/docs/.vitepress/vite-plugin.mts
index ba1ca7db..cd6811cb 100644
--- a/docs/.vitepress/vite-plugin.mts
+++ b/docs/.vitepress/vite-plugin.mts
@@ -1,5 +1,5 @@
 import type { UserConfig } from 'vitepress'
-import path from 'path'
+import path from 'pathe'
 import { fileURLToPath } from 'url'
 import esbuild from 'esbuild'
 type Plugin = Extract<
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
index df713852..56195056 100644
--- a/docs/user-guide/index.md
+++ b/docs/user-guide/index.md
@@ -146,6 +146,48 @@ If you want to use custom parsers such as [@babel/eslint-parser](https://www.npm
   }
 ```
 
+Full example:
+
+::: code-group
+
+```json [.eslintrc]
+{
+  "root": true,
+  "plugins": ["@typescript-eslint"],
+  "extends": [
+    "eslint:recommended",
+    "plugin:@typescript-eslint/recommended",
+    "plugin:vue/vue3-recommended"
+  ],
+  "parser": "vue-eslint-parser",
+  "parserOptions": {
+    "parser": "@typescript-eslint/parser"
+  }
+}
+```
+
+```js [eslint.config.js]
+import js from '@eslint/js'
+import eslintPluginVue from 'eslint-plugin-vue'
+import ts from 'typescript-eslint'
+
+export default ts.config(
+  js.configs.recommended,
+  ...ts.configs.recommended,
+  ...eslintPluginVue.configs['flat/recommended'],
+  {
+    files: ['*.vue', '**/*.vue'],
+    languageOptions: {
+      parserOptions: {
+        parser: '@typescript-eslint/parser'
+      }
+    }
+  }
+)
+```
+
+:::
+
 The `parserOptions.parser` option can also specify an object to specify multiple parsers. See [vue-eslint-parser README](https://github.com/vuejs/vue-eslint-parser#readme) for more details.
 
 ### How does ESLint detect components?
@@ -441,8 +483,7 @@ In [Nuxt 3](https://nuxt.com/) or with [`unplugin-auto-import`](https://github.c
 
 ::: code-group
 
-```json [Legacy Config]
-// .eslintrc
+```json [.eslintrc]
 {
   "globals": {
     "ref": "readonly",
@@ -454,8 +495,7 @@ In [Nuxt 3](https://nuxt.com/) or with [`unplugin-auto-import`](https://github.c
 }
 ```
 
-```js [Flat Config]
-// eslint.config.js
+```js [eslint.config.js]
 export default [
   {
     languageOptions: {
diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js
index 38624d00..22d90920 100644
--- a/lib/rules/order-in-components.js
+++ b/lib/rules/order-in-components.js
@@ -259,6 +259,7 @@ module.exports = {
     }
 
     /**
+     * @param {RuleFixer} fixer
      * @param {Property} propertyNode
      * @param {Property} unorderedPropertyNode
      */
diff --git a/lib/rules/require-toggle-inside-transition.js b/lib/rules/require-toggle-inside-transition.js
index fc46f75f..423d4beb 100644
--- a/lib/rules/require-toggle-inside-transition.js
+++ b/lib/rules/require-toggle-inside-transition.js
@@ -6,6 +6,20 @@
 
 const utils = require('../utils')
 
+/**
+ * @param {VDirective} vBindAppear
+ */
+function isValidBindAppear(vBindAppear) {
+  if (
+    vBindAppear.value?.expression &&
+    vBindAppear.value.expression.type === 'Literal'
+  ) {
+    return vBindAppear.value.expression?.value !== false
+  }
+
+  return true
+}
+
 module.exports = {
   meta: {
     type: 'problem',
@@ -35,7 +49,11 @@ module.exports = {
 
       /** @type VElement */ // @ts-expect-error
       const parent = element.parent
-      if (utils.hasAttribute(parent, 'appear')) {
+      const vBindAppear = utils.getDirective(parent, 'bind', 'appear')
+      if (
+        utils.hasAttribute(parent, 'appear') ||
+        (vBindAppear && isValidBindAppear(vBindAppear))
+      ) {
         return
       }
 
diff --git a/package.json b/package.json
index 8cb321f2..b35fd5da 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
     "nth-check": "^2.1.1",
     "postcss-selector-parser": "^6.0.15",
     "semver": "^7.6.0",
-    "vue-eslint-parser": "^9.4.2",
+    "vue-eslint-parser": "^9.4.3",
     "xml-name-validator": "^4.0.0"
   },
   "devDependencies": {
@@ -92,6 +92,7 @@
     "markdownlint-cli": "^0.41.0",
     "mocha": "^10.3.0",
     "nyc": "^17.0.0",
+    "pathe": "^1.1.2",
     "prettier": "^3.2.5",
     "typescript": "^5.3.3",
     "vitepress": "^1.0.0-rc.42"


Test patch:
diff --git a/tests/lib/rules/require-toggle-inside-transition.js b/tests/lib/rules/require-toggle-inside-transition.js
index 815f5de3..d40ad474 100644
--- a/tests/lib/rules/require-toggle-inside-transition.js
+++ b/tests/lib/rules/require-toggle-inside-transition.js
@@ -68,6 +68,15 @@ tester.run('require-toggle-inside-transition', rule, {
     {
       filename: 'test.vue',
       code: '<template><transition appear><div /></transition></template>'
+    },
+    {
+      // https://github.com/vuejs/eslint-plugin-vue/issues/2467
+      filename: 'test.vue',
+      code: '<template><transition :appear="foo"><div /></transition></template>'
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition :appear="true"><div /></transition></template>'
     }
   ],
   invalid: [
@@ -113,6 +122,16 @@ tester.run('require-toggle-inside-transition', rule, {
       filename: 'test.vue',
       code: '<template><Transition>  <div /></Transition></template>',
       errors: [{ messageId: 'expected' }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition :appear="false"><div /></transition></template>',
+      errors: [{ messageId: 'expected' }]
+    },
+    {
+      filename: 'test.vue',
+      code: '<template><transition @appear="isLoaded"><div /></transition></template>',
+      errors: [{ messageId: 'expected' }]
     }
   ]
 })