From 030e79fa5a50b71c10fb99b1d686d5098ba1e888 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 22 Aug 2026 22:04:47 -0500 Subject: [PATCH] Keep the empty-argument flag check next to the lexer that leaves the bare token --- esphome/platformio/library.py | 15 +++++++++++++++ tests/unit_tests/test_platformio_library.py | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/esphome/platformio/library.py b/esphome/platformio/library.py index 24bd9db490..86139d61d7 100644 --- a/esphome/platformio/library.py +++ b/esphome/platformio/library.py @@ -645,6 +645,21 @@ def lex_build_flags(entries: str | list[str], owner: str) -> list[str]: BARE_ARG_FLAGS = frozenset({"-I", "-L", "-l", "-D"}) +def raise_on_empty_arg_flags(tokens: list[str], owner: str) -> None: + """Reject bare ``-I``/``-D``/``-L``/``-l`` tokens left by an empty glued + argument (``-D ""``). + + Lives next to ``join_flag_args`` because the bare token is its + postcondition: a trailing bare flag is warned and dropped there, so a + surviving one always means an empty argument. gcc would eat the next + flag as the argument (or add the CWD for ``-L``); always a typo. + """ + if empty := sorted({tok for tok in tokens if tok in BARE_ARG_FLAGS}): + raise EsphomeError( + f"{owner} contain empty-argument flag(s): {', '.join(empty)}" + ) + + def join_flag_args(tokens: Iterable[str], owner: str) -> list[str]: """Join a bare ``-I``/``-L``/``-l``/``-D`` with its following token, the way PlatformIO's ParseFlags lexes them.""" diff --git a/tests/unit_tests/test_platformio_library.py b/tests/unit_tests/test_platformio_library.py index 25d074e26f..4cc5e6ba0a 100644 --- a/tests/unit_tests/test_platformio_library.py +++ b/tests/unit_tests/test_platformio_library.py @@ -656,6 +656,13 @@ def test_prefetch_wave_unknown_size_falls_back_to_sequential( assert "No Content-Length for https://x/b.tar.gz" in caplog.text +def test_raise_on_empty_arg_flags() -> None: + """A surviving bare flag means an empty glued argument; reject by name.""" + with pytest.raises(EsphomeError, match=r"build_flags contain empty-argument"): + lib.raise_on_empty_arg_flags(["-DFOO", "-D", "-l"], "build_flags") + lib.raise_on_empty_arg_flags(["-DFOO", "-Iinc"], "build_flags") + + def test_content_lengths_head_requests(monkeypatch: pytest.MonkeyPatch) -> None: """Sizes come from HEAD Content-Length; a failing HEAD reads as 0 so the combined bar is skipped rather than wrong."""