diff --git a/CHANGES.rst b/CHANGES.rst index 774d596749..ea8b0e6f77 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,8 @@ Unreleased - Fix handling of ``flag_value`` when ``is_flag=False`` to allow such options to be used without an explicit value. :issue:`3084` +- Fix error message for unrecognized multicharacter short options (e.g. ``-dbg``) + to report the full option name instead of just the first character. :issue:`2779` Version 8.3.1 -------------- diff --git a/src/click/parser.py b/src/click/parser.py index 1ea1f7166e..09ad3aac0a 100644 --- a/src/click/parser.py +++ b/src/click/parser.py @@ -490,7 +490,17 @@ def _process_opts(self, arg: str, state: _ParsingState) -> None: # short option code and will instead raise the no option # error. if arg[:2] not in self._opt_prefixes: - self._match_short_opt(arg, state) + try: + self._match_short_opt(arg, state) + except NoSuchOption as e: + # If the short option parser fails on the very first + # character, the original argument is likely a + # multicharacter short option (e.g. ``-dbg``) that + # wasn't found, so report the full original option + # name instead of just the first character. + if e.option_name == f"{arg[0]}{arg[1]}": + raise NoSuchOption(long_opt, ctx=self.ctx) from None + raise return if not self.ignore_unknown_options: diff --git a/tests/test_parser.py b/tests/test_parser.py index f2a3ad5931..ec5e4e55b4 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -30,3 +30,27 @@ def test_parser_collects_prefixes(): click.Option("+p", is_flag=True).add_to_parser(parser, ctx) click.Option("!e", is_flag=True).add_to_parser(parser, ctx) assert parser._opt_prefixes == {"-", "--", "+", "!"} + + +def test_multichar_short_option_error_message(): + """A multicharacter short option that isn't found should report the + full option name in the error, not just the first character. See #2779.""" + + @click.command() + @click.option("-dbg", is_flag=True) + def cli(dbg): + pass + + runner = click.testing.CliRunner() + + # Passing the registered option should work. + result = runner.invoke(cli, ["-dbg"]) + assert result.exit_code == 0 + + # Passing an unrecognized variant should report the full option name, + # not just the first character of the option. + result = runner.invoke(cli, ["-dbgwrong"]) + assert result.exit_code != 0 + assert "No such option: -dbgwrong" in result.output + # The error should NOT be truncated to just the first character "-d". + assert result.output.rstrip().endswith("No such option: -dbgwrong")