Skip to content

Regression with props destructuring and optional props between 10.0.0 and 10.1.0 #2741

Open
@mrleblanc101

Description

@mrleblanc101

Checklist

  • I have tried restarting my IDE and the issue persists.
    I have read the FAQ and my problem is not listed.

Tell us about your environment

  • ESLint version: 9.26.0
  • eslint-plugin-vue version: 10.1.0
  • Vue version: 3.5.13
  • Node version: v20.18.0
  • Operating System: macOS 15.4.1

Please show your full configuration:

import eslintConfigPrettier from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';

import withNuxt from './.nuxt/eslint.config.mjs';

export default withNuxt({
    plugins: {
        prettier: prettierPlugin,
    },
    rules: {
        ...prettierPlugin.configs.recommended.rules,
        ...eslintConfigPrettier.rules,
        ...eslintPluginPrettierRecommended.rules,
        'vue/no-bare-strings-in-template': [
            'error',
            {
                attributes: {
                    '/.+/': [
                        'title',
                        'description',
                        'label',
                        'help',
                        'tooltip',
                        'placeholder',
                        'errors',
                        'content',
                        'confirmButtonLabel',
                        'confirmButtonVariant',
                    ],
                    'UTab': ['name'],
                },
                directives: ['v-text'],
            },
        ],
        'vue/v-slot-style': ['error', 'shorthand'],
        'vue/define-macros-order': 'error',
    },
});

What did you do?

<template>
    <Component
        :is="to ? NuxtLink : 'button'"
        :type="!to ? type : null"
        :to="to"
        :class="
            twMerge(
                'relative inline-flex h-14 items-center justify-center gap-2.5 rounded border-2 border-transparent px-8 text-center text-sm font-bold leading-6 text-white transition duration-300 ease-in-out disabled:pointer-events-none disabled:opacity-25 [&[disabled]]:pointer-events-none [&[disabled]]:opacity-25',
                variant === 'info' && 'bg-blue hover:bg-blue-80 focus-visible:bg-blue-80',
                variant === 'primary' && 'bg-teal hover:bg-teal-80 focus-visible:bg-teal-80',
                variant === 'success' && 'bg-green hover:bg-green-80 focus-visible:bg-green-80',
                variant === 'warning' && 'bg-yellow hover:bg-yellow-80 focus-visible:bg-yellow-80',
                variant === 'danger' && 'bg-red hover:bg-red-80 focus-visible:bg-red-80',
                variant === 'secondary' &&
                    'border-gray-30 text-typo hover:bg-black/10 focus-visible:bg-black/10 dark:hover:bg-white/10 dark:focus-visible:bg-white/10',
                (prefixIcon || suffixIcon) && !balanced && size !== 'small' && 'px-6',
                (prefixIcon || suffixIcon) && balanced && 'px-16',
                size === 'large' && 'h-20 gap-5 px-6 text-lg',
                size === 'large' && prefixIcon && 'pr-10',
                size === 'large' && suffixIcon && 'pl-10',
                size === 'small' && 'h-10 gap-2 p-2',
                rounded && 'rounded-full',
                rounded && !$slots.default && 'h-auto rounded-full p-2.5',
                hoverEffect && 'hover:scale-110 focus-visible:scale-110',
                $props.class,
            )
        "
    >
        <Icon
            v-if="prefixIcon"
            :name="prefixIcon"
            :class="
                twMerge(
                    'prefixIcon size-6 shrink-0 text-current',
                    balanced && 'absolute left-4',
                    size === 'large' && 'size-9',
                    iconClasses,
                )
            "
        />
        <slot />
        <Icon
            v-if="suffixIcon"
            :name="suffixIcon"
            :class="
                twMerge(
                    'suffixIcon size-6 shrink-0 text-current',
                    balanced && 'absolute right-4',
                    size === 'large' && 'size-9',
                    iconClasses,
                )
            "
        />
    </Component>
</template>

<script lang="ts" setup>
import { twMerge } from 'tailwind-merge';
import { NuxtLink } from '#components';

type ButtonProps = {
    prefixIcon?: string;
    suffixIcon?: string;
    iconClasses?: string;
    size?: 'default' | 'small' | 'large';
    variant?: 'info' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
    rounded?: boolean;
    balanced?: boolean;
    hoverEffect?: boolean;
    class?: string;
};

type TypeButton = {
    type?: 'button' | 'submit' | 'reset';
    to?: never;
};

type TypeLink = {
    type?: never;
    to?: string;
};

const {
    type = 'button',
    variant = 'primary',
    hoverEffect = true,
} = defineProps<ButtonProps & (TypeButton | TypeLink)>();
</script>

What did you expect to happen?
No warning when running npm run lint or inside the VS Code

What actually happened?
Warning when running npm run lint and inside the VS Code.

Just updated from 10.0.0 where I had no eslint error to 10.1.0, and now I have vue/require-default-prop popping everywhere.
If I add the default value, eslint will then complain the the variable are unused since they are only used in the template and not the script part.

Before:
Image

After:
Image

Relates to #2475

Repository to reproduce this issue

https://codesandbox.io/p/devbox/relaxed-dirac-q6tgzp?file=%2Fcomponents%2FUButton.vue
Run npm run lint to see the warnings.
Downgrade to 10.0.0, run again.
There are no warnings.

Activity

ota-meshi

ota-meshi commented on May 14, 2025

@ota-meshi
Member

Could you please share a GitHub repository to reproduce the issue?
The reproduction you shared didn't work for me.

added
needs reproNeed a repository that can reproduce the problem, or a link to DEMO.
on May 14, 2025
mrleblanc101

mrleblanc101 commented on May 14, 2025

@mrleblanc101
Author

@ota-meshi The repro does work, did you run pnpm lint in the terminal ?

Image

Ericlm

Ericlm commented on May 15, 2025

@Ericlm
Contributor

I'm having the same issue (see https://github.com/Ericlm/eslint-vue-props-default). Basically, it occurs when having a destructured props which is undefinable, like :

defineProps({msg?: string}) // 👍
const {msg} = defineProps({msg?: string}) // 🚨
waynzh

waynzh commented on May 18, 2025

@waynzh
Member

This is related to previously reported issue #2725. The rule now checks when defaults are not defined in the destructured object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs reproNeed a repository that can reproduce the problem, or a link to DEMO.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @ota-meshi@mrleblanc101@Ericlm@waynzh

        Issue actions

          Regression with props destructuring and optional props between 10.0.0 and 10.1.0 · Issue #2741 · vuejs/eslint-plugin-vue