diff --git a/src/click/parser.py b/src/click/parser.py index 1ea1f7166e..b32d00ea44 100644 --- a/src/click/parser.py +++ b/src/click/parser.py @@ -386,6 +386,20 @@ def _match_long_opt( option.process(value, state) + def _get_single_prefix_long_opt_prefix(self, arg: str) -> str | None: + norm_arg = _normalize_opt(arg, self.ctx) + matches = [ + opt + for opt in self._long_opt + if len(opt) > 2 + and len(norm_arg) > len(opt) + and len(_split_opt(opt)[0]) == 1 + and norm_arg.startswith(opt) + ] + if not matches: + return None + return max(matches, key=len) + def _match_short_opt(self, arg: str, state: _ParsingState) -> None: stop = False i = 1 @@ -401,6 +415,10 @@ def _match_short_opt(self, arg: str, state: _ParsingState) -> None: if self.ignore_unknown_options: unknown_options.append(ch) continue + if i == 2: + long_opt_prefix = self._get_single_prefix_long_opt_prefix(arg) + if long_opt_prefix is not None: + raise NoSuchOption(long_opt_prefix, ctx=self.ctx) raise NoSuchOption(opt, ctx=self.ctx) if option.takes_value: # Any characters left in arg? Pretend they're the diff --git a/tests/test_options.py b/tests/test_options.py index f198d10183..380207949f 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -144,6 +144,17 @@ def cli(): assert f"No such option: {unknown_flag}" in result.output +def test_unknown_single_dash_multichar_option_shows_full_option(runner): + @click.command() + @click.option("-dbg", is_flag=True) + def cli(dbg): + click.echo(f"dbg={dbg}") + + result = runner.invoke(cli, ["-dbgwrong"]) + assert result.exception + assert "No such option: -dbg" in result.output + + @pytest.mark.parametrize( ("value", "expect"), [