From ce5be77ed6a34f5777983181587fce0269c236bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 3 Jun 2026 01:09:01 -0500 Subject: [PATCH] [vscode] Mark the component key for "Component not found" errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `vscode --ace` validator resolves an error's range to the deepest matching node, which for a "Component not found" error is the component's value mapping — so the range starts at the first child, and editors underline the children instead of the offending top-level key. Request the key's range for these errors (as already done for "extra keys not allowed"). Backward compatible: only the range for this error type changes. --- esphome/vscode.py | 10 +++++++--- tests/unit_tests/test_vscode.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/esphome/vscode.py b/esphome/vscode.py index f404f02f00..f17565b7b1 100644 --- a/esphome/vscode.py +++ b/esphome/vscode.py @@ -13,9 +13,13 @@ from esphome.yaml_util import parse_yaml def _get_invalid_range(res: Config, invalid: cv.Invalid) -> DocumentRange | None: - return res.get_deepest_document_range_for_path( - invalid.path, invalid.error_message == "extra keys not allowed" - ) + # Errors about the key itself (an unknown option, an unknown component) + # mark the key, not its value mapping — otherwise the range lands on the + # component's children instead of the offending key. + mark_key = invalid.error_message == "extra keys not allowed" or ( + invalid.error_message or "" + ).startswith("Component not found:") + return res.get_deepest_document_range_for_path(invalid.path, mark_key) def _dump_range(range: DocumentRange | None) -> dict | None: diff --git a/tests/unit_tests/test_vscode.py b/tests/unit_tests/test_vscode.py index 63bdf3e255..20f6742cac 100644 --- a/tests/unit_tests/test_vscode.py +++ b/tests/unit_tests/test_vscode.py @@ -97,6 +97,39 @@ esp8266: assert range["end_col"] == 7 +def test_component_not_found_marks_key(): + source_path = str(Path("dir_path", "x.yaml")) + output_lines = _run_repl_test( + [ + _validate(source_path), + # read_file x.yaml + _file_response("""esphome: + name: test1 +esp8266: + board: esp01_1m + +apccci: + id: foo +"""), + ] + ) + + error = json.loads(output_lines[-1]) + not_found = next( + e + for e in error["validation_errors"] + if e["message"].startswith("Component not found:") + ) + assert not_found["message"] == "Component not found: apccci." + # Range covers the ``apccci`` key, not its child mapping. + range = not_found["range"] + assert range["document"] == source_path + assert range["start_line"] == 5 + assert range["start_col"] == 0 + assert range["end_line"] == 5 + assert range["end_col"] == 6 + + def test_shows_correct_loaded_file_error(): source_path = str(Path("dir_path", "x.yaml")) output_lines = _run_repl_test(