diff --git a/esphome/components/alpha3/alpha3.cpp b/esphome/components/alpha3/alpha3.cpp index 048c365616..92b00d87cb 100644 --- a/esphome/components/alpha3/alpha3.cpp +++ b/esphome/components/alpha3/alpha3.cpp @@ -162,8 +162,9 @@ void Alpha3::send_request_(uint8_t *request, size_t len) { auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len, request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status); + } } void Alpha3::update() { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index bc088ca473..9c609aa047 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -2391,8 +2391,9 @@ void APIConnection::process_batch_() { } else if (payload_size == 0) { // payload_size == 0 with remove set means encoding hit OOM and the // connection is being dropped; warn only for a genuinely oversized message - if (!this->flags_.remove) + if (!this->flags_.remove) { ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); + } this->clear_batch_(); } return; diff --git a/esphome/components/bk72xx_ble/bdk_scan.cpp b/esphome/components/bk72xx_ble/bdk_scan.cpp index f17f21c06b..3192fc79d7 100644 --- a/esphome/components/bk72xx_ble/bdk_scan.cpp +++ b/esphome/components/bk72xx_ble/bdk_scan.cpp @@ -62,8 +62,9 @@ BdkActivityState bdk_scan_state(uint8_t activity_idx) { uint8_t bdk_scan_acquire_activity() { uint8_t idx = app_ble_get_idle_actv_idx_handle(SCAN_ACTV); - if (idx == INVALID_ACTIVITY_IDX) + if (idx == INVALID_ACTIVITY_IDX) { ESP_LOGE(TAG, "Scan start failed: no idle activity handle"); + } return idx; } diff --git a/esphome/components/bk72xx_ble/bk72xx_ble.cpp b/esphome/components/bk72xx_ble/bk72xx_ble.cpp index 52401114e6..7a4efff455 100644 --- a/esphome/components/bk72xx_ble/bk72xx_ble.cpp +++ b/esphome/components/bk72xx_ble/bk72xx_ble.cpp @@ -181,8 +181,9 @@ void BK72xxBLE::enable() { break; } } - if (!bdaddr_live) + if (!bdaddr_live) { ESP_LOGW(TAG, "Controller address still unset after init; BLE stack may not have started"); + } #endif this->state_ = BLEComponentState::ACTIVE; @@ -210,8 +211,9 @@ void BK72xxBLE::loop() { // Re-check a settled scan; scan_start() refills the bring-up budget. // WARN: the only report of a drop that recovers inside its budget. if (this->scan_start(this->requested_.interval, this->requested_.window, this->requested_.active) != - ScanOpResult::SETTLED) + ScanOpResult::SETTLED) { ESP_LOGW(TAG, "Controller dropped the scan; restarting"); + } } // Drain the lock-free ring filled by the BLE task; all per-report work runs @@ -230,8 +232,9 @@ void BK72xxBLE::loop() { // Log dropped reports — only reachable when reports were processed; drops can // only occur while the queue is full, and only this loop drains it. uint16_t dropped = this->report_queue_.get_and_reset_dropped_count(); - if (dropped > 0) + if (dropped > 0) { ESP_LOGW(TAG, "Dropped %u scan reports due to queue overflow", dropped); + } } void BK72xxBLE::get_mac_lsb_first(uint8_t out[MAC_ADDRESS_SIZE]) const { @@ -449,8 +452,9 @@ ScanOpResult BK72xxBLE::advance_stop_(BdkActivityState state, bool ready) { if (!ready) { // Acting mid-operation could delete an activity whose start lands // afterwards, leaking the slot with the radio on; wait. - if (this->last_result_ == ScanOpResult::SETTLED) + if (this->last_result_ == ScanOpResult::SETTLED) { ESP_LOGD(TAG, "Scan stop deferred (controller busy)"); + } return ScanOpResult::PENDING; } // Settled, so CREATED unambiguously means "never started". @@ -474,8 +478,9 @@ ScanOpResult BK72xxBLE::advance_start_(BdkActivityState state, bool ready) { return ScanOpResult::PENDING; } if (!ready) { - if (this->last_result_ == ScanOpResult::SETTLED) + if (this->last_result_ == ScanOpResult::SETTLED) { ESP_LOGD(TAG, "Scan start deferred (controller busy)"); + } return ScanOpResult::PENDING; } if (state == BdkActivityState::CREATED) { diff --git a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp index 1b4e6245ae..0939e1259f 100644 --- a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp +++ b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp @@ -69,8 +69,9 @@ void BK72xxBLETracker::on_ota_global_state(ota::OTAState state, float progress, this->stop_scan(); // The transfer starves the loop; a deferred stop would leave the radio // scanning for the whole update, so drain it here, bounded. - if (!this->parent_->flush_pending_stop(OTA_STOP_FLUSH_MS)) + if (!this->parent_->flush_pending_stop(OTA_STOP_FLUSH_MS)) { ESP_LOGE(TAG, "Scan still stopping at OTA start; the radio may contend with the update"); + } } else if (state == ota::OTA_ERROR || state == ota::OTA_ABORT) { // On success the device reboots, so restore only on a failed/aborted update; // loop() restarts the scan on its next iteration (continuous idle branch). diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 1cb83b9d8b..5d53c59708 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -80,8 +80,9 @@ void BLEBinaryOutput::write_state(bool state) { esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE); - if (err != ESP_GATT_OK) + if (err != ESP_GATT_OK) { ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err); + } } } // namespace esphome::ble_client diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index ef98174e06..164424de09 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -327,10 +327,12 @@ void BME680Component::read_data_() { ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure, humidity, gas_resistance); - if (!gas_valid) + if (!gas_valid) { ESP_LOGW(TAG, "Gas measurement unsuccessful, reading invalid!"); - if (!heat_stable) + } + if (!heat_stable) { ESP_LOGW(TAG, "Heater unstable, reading invalid! (Normal for a few readings after a power cycle)"); + } if (this->temperature_sensor_ != nullptr) this->temperature_sensor_->publish_state(temperature); diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 6ca9e394f7..34684a87e1 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -749,33 +749,39 @@ void Climate::dump_traits_(const char *tag) { } if (!traits.get_supported_modes().empty()) { ESP_LOGCONFIG(tag, " Supported modes:"); - for (ClimateMode m : traits.get_supported_modes()) + for (ClimateMode m : traits.get_supported_modes()) { ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m))); + } } if (!traits.get_supported_fan_modes().empty()) { ESP_LOGCONFIG(tag, " Supported fan modes:"); - for (ClimateFanMode m : traits.get_supported_fan_modes()) + for (ClimateFanMode m : traits.get_supported_fan_modes()) { ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m))); + } } if (!traits.get_supported_custom_fan_modes().empty()) { ESP_LOGCONFIG(tag, " Supported custom fan modes:"); - for (const char *s : traits.get_supported_custom_fan_modes()) + for (const char *s : traits.get_supported_custom_fan_modes()) { ESP_LOGCONFIG(tag, " - %s", s); + } } if (!traits.get_supported_presets().empty()) { ESP_LOGCONFIG(tag, " Supported presets:"); - for (ClimatePreset p : traits.get_supported_presets()) + for (ClimatePreset p : traits.get_supported_presets()) { ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p))); + } } if (!traits.get_supported_custom_presets().empty()) { ESP_LOGCONFIG(tag, " Supported custom presets:"); - for (const char *s : traits.get_supported_custom_presets()) + for (const char *s : traits.get_supported_custom_presets()) { ESP_LOGCONFIG(tag, " - %s", s); + } } if (!traits.get_supported_swing_modes().empty()) { ESP_LOGCONFIG(tag, " Supported swing modes:"); - for (ClimateSwingMode m : traits.get_supported_swing_modes()) + for (ClimateSwingMode m : traits.get_supported_swing_modes()) { ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m))); + } } } diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index a9117be4e1..2196f3a982 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -154,8 +154,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r } } if (error_code != 0) { - if (report_errors) + if (report_errors) { ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL); + } return false; } diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index 88579e9632..bee231d132 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -210,8 +210,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) { if (!image) { // A shutdown is not a lost frame: wait_for_image_() returns empty as soon // as running_ clears, and the loop condition below ends the stream anyway. - if (this->running_) + if (this->running_) { ESP_LOGW(TAG, "STREAM: failed to acquire frame"); + } res = ESP_FAIL; } if (res == ESP_OK) { diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 7dc0b5c6fe..65521e63d5 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -334,8 +334,9 @@ void Fan::dump_traits_(const char *tag, const char *prefix) { } if (traits.supports_preset_modes()) { ESP_LOGCONFIG(tag, "%s Supported presets:", prefix); - for (const char *s : traits.supported_preset_modes()) + for (const char *s : traits.supported_preset_modes()) { ESP_LOGCONFIG(tag, "%s - %s", prefix, s); + } } } diff --git a/esphome/components/hbridge/switch/hbridge_switch.cpp b/esphome/components/hbridge/switch/hbridge_switch.cpp index 1012a264f2..c8e472d7aa 100644 --- a/esphome/components/hbridge/switch/hbridge_switch.cpp +++ b/esphome/components/hbridge/switch/hbridge_switch.cpp @@ -29,8 +29,9 @@ void HBridgeSwitch::dump_config() { LOG_PIN(" On Pin: ", this->on_pin_); LOG_PIN(" Off Pin: ", this->off_pin_); ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_); - if (this->wait_time_) + if (this->wait_time_) { ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_); + } } void HBridgeSwitch::write_state(bool state) { diff --git a/esphome/components/he60r/he60r.cpp b/esphome/components/he60r/he60r.cpp index ea662e3ba9..f49224f17c 100644 --- a/esphome/components/he60r/he60r.cpp +++ b/esphome/components/he60r/he60r.cpp @@ -44,8 +44,9 @@ void HE60rCover::dump_config() { " Close Duration: %.1fs", this->open_duration_ / 1e3f, this->close_duration_ / 1e3f); auto restore = this->restore_state_(); - if (restore.has_value()) + if (restore.has_value()) { ESP_LOGCONFIG(TAG, " Saved position %d%%", (int) (restore->position * 100.f)); + } } void HE60rCover::endstop_reached_(CoverOperation operation) { @@ -77,8 +78,9 @@ void HE60rCover::process_rx_(uint8_t data) { ESP_LOGV(TAG, "Process RX data %X", data); if (!this->query_seen_) { this->query_seen_ = data == QUERY_BYTE; - if (!this->query_seen_) + if (!this->query_seen_) { ESP_LOGD(TAG, "RX Byte %02X", data); + } return; } switch (data) { diff --git a/esphome/components/hoermann_hcp/hoermann_hcp.cpp b/esphome/components/hoermann_hcp/hoermann_hcp.cpp index 17df927eb7..4aa2c79bb1 100644 --- a/esphome/components/hoermann_hcp/hoermann_hcp.cpp +++ b/esphome/components/hoermann_hcp/hoermann_hcp.cpp @@ -257,8 +257,9 @@ void HoermannHcp::on_state_reg_(uint16_t value) { } } // The low byte can change on its own, so only report a state we cannot decode once. - if (state != (previous >> 8)) + if (state != (previous >> 8)) { ESP_LOGW(TAG, "Unknown door state 0x%02X", state); + } } // Low byte of register 6: bit 0x10 is the lamp, bit 0x04 the relay. The reference implementation records diff --git a/esphome/components/key_collector/key_collector.cpp b/esphome/components/key_collector/key_collector.cpp index 69b7a6a7c6..42f02d39d4 100644 --- a/esphome/components/key_collector/key_collector.cpp +++ b/esphome/components/key_collector/key_collector.cpp @@ -16,26 +16,33 @@ void KeyCollector::loop() { void KeyCollector::dump_config() { #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_CONFIG ESP_LOGCONFIG(TAG, "Key Collector:"); - if (this->min_length_ > 0) + if (this->min_length_ > 0) { ESP_LOGCONFIG(TAG, " min length: %d", this->min_length_); - if (this->max_length_ > 0) + } + if (this->max_length_ > 0) { ESP_LOGCONFIG(TAG, " max length: %d", this->max_length_); - if (!this->back_keys_.empty()) + } + if (!this->back_keys_.empty()) { ESP_LOGCONFIG(TAG, " erase keys '%s'", this->back_keys_.c_str()); - if (!this->clear_keys_.empty()) + } + if (!this->clear_keys_.empty()) { ESP_LOGCONFIG(TAG, " clear keys '%s'", this->clear_keys_.c_str()); - if (!this->start_keys_.empty()) + } + if (!this->start_keys_.empty()) { ESP_LOGCONFIG(TAG, " start keys '%s'", this->start_keys_.c_str()); + } if (!this->end_keys_.empty()) { ESP_LOGCONFIG(TAG, " end keys '%s'\n" " end key is required: %s", this->end_keys_.c_str(), ONOFF(this->end_key_required_)); } - if (!this->allowed_keys_.empty()) + if (!this->allowed_keys_.empty()) { ESP_LOGCONFIG(TAG, " allowed keys '%s'", this->allowed_keys_.c_str()); - if (this->timeout_ > 0) + } + if (this->timeout_ > 0) { ESP_LOGCONFIG(TAG, " entry timeout: %0.1f", this->timeout_ / 1000.0); + } #endif } diff --git a/esphome/components/ln882h_ble/ln882h_ble.cpp b/esphome/components/ln882h_ble/ln882h_ble.cpp index 021e138f08..0b15bf434c 100644 --- a/esphome/components/ln882h_ble/ln882h_ble.cpp +++ b/esphome/components/ln882h_ble/ln882h_ble.cpp @@ -333,8 +333,9 @@ void LN882HBLE::loop() { // the queue empty — from the very first report on. Checking here keeps that // failure visible instead of producing a scanner that is silently dead. uint16_t dropped = this->report_queue_.get_and_reset_dropped_count(); - if (dropped > 0) + if (dropped > 0) { ESP_LOGW(TAG, "Dropped %u scan reports (queue full or out of memory for a report slot)", dropped); + } // Drain the lock-free ring filled by the rw task; all per-report work runs // here on the main task, then the report returns to the pool. BLEScanReport *report = this->report_queue_.pop(); diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index a10fdb0582..2c988473a9 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -1059,8 +1059,9 @@ static void *lv_alloc_draw_buf(size_t size, bool internal) { void *buffer; size = LV_ROUND_UP(size, LV_DRAW_BUF_ALIGN); buffer = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size, internal ? MALLOC_CAP_8BIT : cap_bits); // NOLINT - if (buffer == nullptr) + if (buffer == nullptr) { ESP_LOGW(esphome::lvgl::TAG, "Failed to allocate %zu bytes for %sdraw buffer", size, internal ? "internal " : ""); + } return buffer; } diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index 0ff934ae94..0850b50c85 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -237,8 +237,9 @@ void MipiDsi::write_to_display_(int x_start, int y_start, int w, int h, const ui xSemaphoreTake(this->io_lock_, portMAX_DELAY); } } - if (err != ESP_OK) + if (err != ESP_OK) { ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); + } } bool MipiDsi::check_buffer_() { diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index aeb04c155c..f43bbab21c 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -243,8 +243,9 @@ void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const ui ptr += stride; // next line } } - if (err != ESP_OK) + if (err != ESP_OK) { ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); + } } bool MipiRgb::check_buffer_() { diff --git a/esphome/components/mipi_spi/mipi_spi.cpp b/esphome/components/mipi_spi/mipi_spi.cpp index 80ae96720b..b2658de6e8 100644 --- a/esphome/components/mipi_spi/mipi_spi.cpp +++ b/esphome/components/mipi_spi/mipi_spi.cpp @@ -31,12 +31,15 @@ void internal_dump_config(const char *model, int width, int height, int offset_w LOG_PIN(" CS Pin: ", cs); LOG_PIN(" Reset Pin: ", reset); LOG_PIN(" DC Pin: ", dc); - if (offset_width != 0) + if (offset_width != 0) { ESP_LOGCONFIG(TAG, " Offset width: %d", offset_width); - if (offset_height != 0) + } + if (offset_height != 0) { ESP_LOGCONFIG(TAG, " Offset height: %d", offset_height); - if (brightness.has_value()) + } + if (brightness.has_value()) { ESP_LOGCONFIG(TAG, " Brightness: %u", brightness.value()); + } } } // namespace esphome::mipi_spi diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 25687ba106..f428236a82 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -1199,15 +1199,17 @@ void ModbusServerHub::send_raw_(const uint8_t *payload, uint16_t len) { this->set_timeout("deferred_send", (this->tx_delay_remaining() + US_PER_MS - 1) / US_PER_MS, [this]() { ModbusFrame frame(this->deferred_payload_[0], this->deferred_payload_.data() + 1, this->deferred_payload_len_ - 1); - if (!this->send_frame_(frame)) + if (!this->send_frame_(frame)) { ESP_LOGE(TAG, "Deferred server reply dropped: transmission still blocked"); + } }); return; } ModbusFrame frame(payload[0], payload + 1, len - 1); - if (!this->send_frame_(frame)) + if (!this->send_frame_(frame)) { ESP_LOGE(TAG, "Server reply dropped: a frame arrived during the send delay"); + } } void Modbus::clear_rx_buffer_(const LogString *reason, bool warn, size_t bytes_to_clear) { diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 18a759725f..a80cea6bd6 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -39,10 +39,12 @@ inline char *append_char(char *p, char c) { // Function implementation of LOG_MQTT_COMPONENT macro to reduce code size void log_mqtt_component(const char *tag, MQTTComponent *obj, bool state_topic, bool command_topic) { char buf[MQTT_DEFAULT_TOPIC_MAX_LEN]; - if (state_topic) + if (state_topic) { ESP_LOGCONFIG(tag, " State Topic: '%s'", obj->get_state_topic_to_(buf).c_str()); - if (command_topic) + } + if (command_topic) { ESP_LOGCONFIG(tag, " Command Topic: '%s'", obj->get_command_topic_to_(buf).c_str()); + } } void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; } diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp index c7ea59050c..b62e4f47d4 100644 --- a/esphome/components/one_wire/one_wire_bus.cpp +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -18,8 +18,9 @@ const std::vector &OneWireBus::get_devices() { return this->devices_; bool OneWireBus::reset_() { int res = this->reset_int(); - if (res == -1) + if (res == -1) { ESP_LOGE(TAG, "1-wire bus is held low"); + } return res == 1; } diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index a21f0e2f63..998e1be5fc 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -551,12 +551,14 @@ void PacketTransport::dump_config() { " Ping-pong: %s", this->platform_name_, YESNO(this->is_encrypted_()), YESNO(this->ping_pong_enable_)); #ifdef USE_SENSOR - for (const auto &sensor : this->sensors_) + for (const auto &sensor : this->sensors_) { ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id); + } #endif #ifdef USE_BINARY_SENSOR - for (const auto &sensor : this->binary_sensors_) + for (const auto &sensor : this->binary_sensors_) { ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id); + } #endif for (const auto &host : this->providers_) { ESP_LOGCONFIG(TAG, " Remote host: %s", host.first.c_str()); @@ -564,15 +566,17 @@ void PacketTransport::dump_config() { #ifdef USE_SENSOR auto rs = this->remote_sensors_.find(host.first.c_str()); if (rs != this->remote_sensors_.end()) { - for (const auto &key : rs->second | std::views::keys) + for (const auto &key : rs->second | std::views::keys) { ESP_LOGCONFIG(TAG, " Sensor: %s", key.c_str()); + } } #endif #ifdef USE_BINARY_SENSOR auto rbs = this->remote_binary_sensors_.find(host.first.c_str()); if (rbs != this->remote_binary_sensors_.end()) { - for (const auto &key : rbs->second | std::views::keys) + for (const auto &key : rbs->second | std::views::keys) { ESP_LOGCONFIG(TAG, " Binary Sensor: %s", key.c_str()); + } } #endif } diff --git a/esphome/components/qwiic_pir/qwiic_pir.cpp b/esphome/components/qwiic_pir/qwiic_pir.cpp index baf8dc122d..eb338db772 100644 --- a/esphome/components/qwiic_pir/qwiic_pir.cpp +++ b/esphome/components/qwiic_pir/qwiic_pir.cpp @@ -124,8 +124,9 @@ void QwiicPIRComponent::dump_config() { void QwiicPIRComponent::clear_events_() { // Clear event status register - if (!this->write_byte(QWIIC_PIR_EVENT_STATUS, 0x00)) + if (!this->write_byte(QWIIC_PIR_EVENT_STATUS, 0x00)) { ESP_LOGW(TAG, "Failed to clear events"); + } } } // namespace esphome::qwiic_pir diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index aacb217965..c0afc0607e 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -75,8 +75,9 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin break; } } - if (err != ESP_OK) + if (err != ESP_OK) { ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); + } } int RpiDpiRgb::get_width() { diff --git a/esphome/components/shelly_dimmer/stm32flash.cpp b/esphome/components/shelly_dimmer/stm32flash.cpp index c758b0a312..beb015851e 100644 --- a/esphome/components/shelly_dimmer/stm32flash.cpp +++ b/esphome/components/shelly_dimmer/stm32flash.cpp @@ -629,8 +629,9 @@ stm32_unique_ptr stm32_init(uart::UARTDevice *stream, const uint8_t flags, const stm->pid = (buf[1] << 8) | buf[2]; if (returned > 2) { ESP_LOGD(TAG, "This bootloader returns %d extra bytes in PID:", returned); - for (auto i = 2; i <= returned; i++) + for (auto i = 2; i <= returned; i++) { ESP_LOGD(TAG, " %02x", buf[i]); + } } if (stm32_get_ack(stm) != STM32_ERR_OK) { return make_stm32_with_deletor(nullptr); diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index f8233c48d1..2dfb3c75a8 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -406,8 +406,9 @@ class SPIClient { this->release_device_, this->write_only_); #ifdef USE_SPI_PSRAM_DMA this->delegate_->set_psram_dma(this->psram_dma_); - if (this->psram_dma_) + if (this->psram_dma_) { esph_log_config("spi_device", "PSRAM DMA: enabled"); + } #endif } diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp index 45d38c1719..95b5e4f14b 100644 --- a/esphome/components/spi/spi_esp_idf.cpp +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -42,8 +42,9 @@ class SPIDelegateHw : public SPIDelegate { if (this->release_device_) this->add_device_(); if (this->is_ready()) { - if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK) + if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to acquire SPI bus"); + } SPIDelegate::begin_transaction(); } else { ESP_LOGW(TAG, "SPI device not ready, cannot begin transaction"); @@ -63,8 +64,9 @@ class SPIDelegateHw : public SPIDelegate { ~SPIDelegateHw() override { esp_err_t const err = spi_bus_remove_device(this->handle_); - if (err != ESP_OK) + if (err != ESP_OK) { ESP_LOGE(TAG, "Remove device failed - err %X", err); + } } // do a transfer. either txbuf or rxbuf (but not both) may be null. @@ -284,8 +286,9 @@ class SPIBusHw : public SPIBus { } buscfg.max_transfer_sz = MAX_TRANSFER_SIZE; auto err = spi_bus_initialize(channel, &buscfg, SPI_DMA_CH_AUTO); - if (err != ESP_OK) + if (err != ESP_OK) { ESP_LOGE(TAG, "Bus init failed - err %X", err); + } } SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp index 3ffef86f3e..83f7bc9ce5 100644 --- a/esphome/components/st7701s/st7701s.cpp +++ b/esphome/components/st7701s/st7701s.cpp @@ -78,8 +78,9 @@ void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8 break; } } - if (err != ESP_OK) + if (err != ESP_OK) { esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); + } } void ST7701S::draw_pixel_at(int x, int y, Color color) { diff --git a/esphome/components/tuya/water_heater/tuya_water_heater.cpp b/esphome/components/tuya/water_heater/tuya_water_heater.cpp index 2fca3bf581..e1c78530e3 100644 --- a/esphome/components/tuya/water_heater/tuya_water_heater.cpp +++ b/esphome/components/tuya/water_heater/tuya_water_heater.cpp @@ -177,14 +177,18 @@ water_heater::WaterHeaterMode TuyaWaterHeater::default_on_mode_() const { void TuyaWaterHeater::dump_config() { LOG_WATER_HEATER("", "Tuya Water Heater", this); - if (this->switch_id_.has_value()) + if (this->switch_id_.has_value()) { ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); - if (this->mode_id_.has_value()) + } + if (this->mode_id_.has_value()) { ESP_LOGCONFIG(TAG, " Mode has datapoint ID %u", *this->mode_id_); - if (this->target_temperature_id_.has_value()) + } + if (this->target_temperature_id_.has_value()) { ESP_LOGCONFIG(TAG, " Target Temperature has datapoint ID %u", *this->target_temperature_id_); - if (this->current_temperature_id_.has_value()) + } + if (this->current_temperature_id_.has_value()) { ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_); + } } } // namespace esphome::tuya diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index c144212ecf..858516c746 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -129,8 +129,9 @@ void UDPComponent::dump_config() { " Listen Port: %u\n" " Broadcast Port: %u", this->listen_port_, this->broadcast_port_); - for (const char *address : this->addresses_) + for (const char *address : this->addresses_) { ESP_LOGCONFIG(TAG, " Address: %s", address); + } if (this->listen_address_.has_value()) { char addr_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str_to(addr_buf)); @@ -145,8 +146,9 @@ void UDPComponent::send_packet(const uint8_t *data, size_t size) { #if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) for (const auto &saddr : this->sockaddrs_) { auto result = this->broadcast_socket_->sendto(data, size, 0, &saddr, sizeof(saddr)); - if (result < 0) + if (result < 0) { ESP_LOGW(TAG, "sendto() error %d", errno); + } } #endif #ifdef USE_SOCKET_IMPL_LWIP_TCP @@ -155,8 +157,9 @@ void UDPComponent::send_packet(const uint8_t *data, size_t size) { if (this->udp_client_.beginPacketMulticast(saddr, this->broadcast_port_, iface, 128) != 0) { this->udp_client_.write(data, size); auto result = this->udp_client_.endPacket(); - if (result == 0) + if (result == 0) { ESP_LOGW(TAG, "udp.write() error"); + } } } #endif diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp index 0ba19f5cd7..c77f3468c7 100644 --- a/esphome/components/uponor_smatrix/uponor_smatrix.cpp +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -110,8 +110,9 @@ bool UponorSmatrixComponent::parse_byte_(uint8_t byte) { // Handle packet size_t data_len = (packet_len - 6) / 3; if (data_len == 0) { - if (packet[4] == UPONOR_ID_REQUEST) + if (packet[4] == UPONOR_ID_REQUEST) { ESP_LOGVV(TAG, "Ignoring request packet for device 0x%08" PRIX32 "", device_address); + } return true; } diff --git a/esphome/components/usb_uart/pl2303.cpp b/esphome/components/usb_uart/pl2303.cpp index a9f7348331..db177fd308 100644 --- a/esphome/components/usb_uart/pl2303.cpp +++ b/esphome/components/usb_uart/pl2303.cpp @@ -194,8 +194,9 @@ std::vector USBUartTypePL2303::parse_descriptors(usb_device_handle_t dev } } - if (cdc_devs.empty()) + if (cdc_devs.empty()) { ESP_LOGE(TAG, "PL2303: failed to find bulk IN+OUT endpoints"); + } return cdc_devs; } diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index a514a55d80..e46b96c86a 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -40,8 +40,9 @@ void WakeOnLanButton::press_action() { memcpy(buffer + i * sizeof(this->macaddr_) + sizeof(PREFIX), this->macaddr_, sizeof(this->macaddr_)); } if (this->broadcast_socket_->sendto(buffer, sizeof(buffer), 0, reinterpret_cast(&saddr), - addr_len) <= 0) + addr_len) <= 0) { ESP_LOGW(TAG, "sendto() error %d", errno); + } #else IPAddress broadcast = IPAddress(255, 255, 255, 255); for (auto ip : esphome::network::get_ip_addresses()) { diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index 043df86be9..b95d474fd3 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -348,14 +348,18 @@ size_t WeikaiChannel::rx_in_fifo_() { uint8_t const fsr = this->reg(WKREG_FSR); if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) { char bin_buf[9]; - if (fsr & FSR_RFOE) + if (fsr & FSR_RFOE) { ESP_LOGE(TAG, "Receive data overflow FSR=%s", format_bin_to(bin_buf, fsr)); - if (fsr & FSR_RFLB) + } + if (fsr & FSR_RFLB) { ESP_LOGE(TAG, "Receive line break FSR=%s", format_bin_to(bin_buf, fsr)); - if (fsr & FSR_RFFE) + } + if (fsr & FSR_RFFE) { ESP_LOGE(TAG, "Receive frame error FSR=%s", format_bin_to(bin_buf, fsr)); - if (fsr & FSR_RFPE) + } + if (fsr & FSR_RFPE) { ESP_LOGE(TAG, "Receive parity error FSR=%s", format_bin_to(bin_buf, fsr)); + } } if ((available == 0) && (fsr & FSR_RFDAT)) { // here we should be very careful because we can have something like this: @@ -495,8 +499,9 @@ void print_buffer(std::vector buffer) { hex_buffer[(3 * 32) + 1] = 0; for (size_t i = 0; i < buffer.size(); i++) { snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", buffer[i]); - if (i % 32 == 31) + if (i % 32 == 31) { ESP_LOGI(TAG, " %s", hex_buffer); + } } if (buffer.size() % 32) { // null terminate if incomplete line diff --git a/script/ci-custom.py b/script/ci-custom.py index 724a350884..f481fda860 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -319,6 +319,154 @@ def lint_no_long_delays(fname, match): ) +# An if/else/for/while whose only body is an unbraced ESP_LOG*() call. When the build's compile-time +# log level drops that macro, the body expands to nothing and the compiler warns (-Wempty-body). +# clang-tidy's brace check does not catch these (ShortStatementLines allows short unbraced bodies), so +# this fills that gap. Matched against comment/string-masked content, so commented-out or quoted code +# is ignored. Both spellings are covered: core/log.h defines the uppercase ESP_LOG*() macros and +# the lowercase esph_log_*() ones, and both expand to nothing below their log level. +# 'for' allows ';' inside its parentheses (the classic C-style header); 'if'/'while' do not, so their +# condition cannot run past the statement it guards. The 'for' header permits one level of nested +# parens so it stays bounded to its own statement: without that, it can run past the loop body and +# latch onto a later ')', mis-reporting the line and skipping the '#' preprocessor check below. +ESP_LOG_NEEDS_BRACES_RE = re.compile( + r"(?:\bif\s*\([^{};]*\)|\bwhile\s*\([^{};]*\)|\bfor\s*\((?:[^{}()]|\([^{}()]*\))*\)|\belse\b)" + r"[ \t]*\n?[ \t]*(?:ESP_LOG[A-Z]*|esph_log_[a-z]+)\s*\(", + re.MULTILINE, +) + + +def _mask_cpp_comments_strings(s): + """Return s with // and /* */ comments and string/char/raw-string literals blanked to spaces + (length and newlines preserved) so a regex only matches real code. Parentheses in real code are + kept, so callers can still balance them on the masked text.""" + out = list(s) + i = 0 + n = len(s) + while i < n: + c = s[i] + # Raw string literal: an optional encoding prefix, then R"delim( ... )delim". The body may + # contain quotes, //, /* and unbalanced parens, so it must be consumed as one unit. + if c == "R" and i + 1 < n and s[i + 1] == '"': + j = i + 2 + delim = "" + while j < n and s[j] not in "( \t\r\n\\" and len(delim) < 16: + delim += s[j] + j += 1 + if j < n and s[j] == "(": + closing = ")" + delim + '"' + end = s.find(closing, j + 1) + end = n if end == -1 else end + len(closing) + for k in range(i, end): + if s[k] != "\n": + out[k] = " " + i = end + continue + i += 1 + elif c == "/" and i + 1 < n and s[i + 1] == "/": + while i < n and s[i] != "\n": + out[i] = " " + i += 1 + elif c == "/" and i + 1 < n and s[i + 1] == "*": + out[i] = out[i + 1] = " " + i += 2 + while i < n and not (s[i] == "*" and i + 1 < n and s[i + 1] == "/"): + if s[i] != "\n": + out[i] = " " + i += 1 + if i < n: + out[i] = " " + if i + 1 < n: + out[i + 1] = " " + i += 2 + # A "'" after an alphanumeric or '_' is a C++ digit separator (1'000), not a literal opener. + elif c == '"' or ( + c == "'" and not (i and (s[i - 1].isalnum() or s[i - 1] == "_")) + ): + quote = c + out[i] = " " + i += 1 + while i < n: + if s[i] == "\\": + out[i] = " " + if i + 1 < n: + out[i + 1] = " " + i += 2 + continue + if s[i] == quote: + out[i] = " " + i += 1 + break + if s[i] != "\n": + out[i] = " " + i += 1 + else: + i += 1 + return "".join(out) + + +def _log_statement_end(masked, open_paren): + """Index of the ';' ending the ESP_LOG call whose '(' is at open_paren, or None. Balanced on the + masked text so quotes/comments inside the arguments do not confuse the paren count.""" + depth = 0 + i = open_paren + n = len(masked) + while i < n: + ch = masked[i] + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + if depth == 0: + j = i + 1 + while j < n and masked[j] != ";": + if not masked[j].isspace(): + return None + j += 1 + return j if j < n else None + i += 1 + return None + + +@lint_content_check(include=cpp_include) +def lint_esp_log_needs_braces(fname, content): + # Cheap bailout: no log call means nothing to flag, and skips masking the file entirely. + if "ESP_LOG" not in content and "esph_log_" not in content: + return [] + masked = _mask_cpp_comments_strings(content) + errors = [] + for match in ESP_LOG_NEEDS_BRACES_RE.finditer(masked): + pos = match.start() + line_start = content.rfind("\n", 0, pos) + 1 + # Skip preprocessor conditionals (#if/#else/#elif): not C++ control statements. + if content[line_start:pos].lstrip().startswith("#"): + continue + # A '// NOLINT' may sit at the end of the log line (where the message says to put it) or on the + # control-statement line, so scan the whole statement rather than only up to the ESP_LOG token. + stmt_end = _log_statement_end(masked, match.end() - 1) + nolint_end = ( + content.find("\n", stmt_end) if stmt_end is not None else match.end() + ) + if nolint_end == -1: + nolint_end = len(content) + if "NOLINT" in content[pos:nolint_end]: + continue + snippet = content[pos : match.end()].replace("\n", " ").strip() + errors.append( + ( + content.count("\n", 0, pos) + 1, + pos - line_start + 1, + ( + f"{highlight(snippet)} - an if/else/for/while body that is a single log " + "call must be wrapped in braces. When the log level compiles the macro out, the " + "body becomes empty and the compiler warns (-Wempty-body). Add { } around the " + "log call (or a '// NOLINT' comment if this is genuinely intended)." + ), + ) + ) + return errors + + @lint_content_check( include=[ "esphome/const.py", diff --git a/tests/script/test_ci_custom.py b/tests/script/test_ci_custom.py new file mode 100644 index 0000000000..d340a816c6 --- /dev/null +++ b/tests/script/test_ci_custom.py @@ -0,0 +1,147 @@ +"""Unit tests for the ESP_LOG-needs-braces lint rule in script/ci-custom.py. + +The rule flags an if/else/for/while whose only body is an unbraced ESP_LOG*() call (which becomes an +empty statement -- and a -Wempty-body warning -- once the log level compiles the macro out). These +tests pin the comment/string/raw-string masker, the accepted control-statement shapes, and the +NOLINT escape hatch at both placements a contributor would try. +""" + +import importlib.util +from pathlib import Path +import sys + +SCRIPT_DIR = (Path(__file__).parent / ".." / ".." / "script").resolve() +sys.path.insert(0, str(SCRIPT_DIR)) +_spec = importlib.util.spec_from_file_location("ci_custom", SCRIPT_DIR / "ci-custom.py") +ci_custom = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(ci_custom) + +mask = ci_custom._mask_cpp_comments_strings + + +def _lint(content: str) -> list: + return ci_custom.lint_esp_log_needs_braces("test.cpp", content) + + +# --- masker --- + + +def test_mask_preserves_length_newlines_and_real_parens() -> None: + src = 'foo("bar") + baz();\nqux();\n' + masked = mask(src) + assert len(masked) == len(src) + assert masked.count("\n") == src.count("\n") + assert masked.count("(") == src.count("(") # real parens survive for balancing + + +def test_mask_blanks_line_and_block_comments() -> None: + assert "ESP_LOGD" not in mask("a; // if (x) ESP_LOGD(t);\n") + assert "ESP_LOGD" not in mask("a; /* if (x) ESP_LOGD(t); */ b;\n") + + +def test_mask_blanks_string_literals() -> None: + assert "if" not in mask('x = "if (y) ESP_LOGD";\n') + + +def test_mask_handles_raw_string_without_desync() -> None: + # A raw string full of quotes/parens must be consumed as one unit; code after it stays intact. + src = 's.print(R"()");\nreturn;\n' + masked = mask(src) + assert "href" not in masked + assert "return;" in masked # not swallowed by a desynced string scan + + +# --- rule: flags real violations --- + + +def test_flags_unbraced_if_next_line() -> None: + assert _lint("if (x)\n ESP_LOGD(t);\n") + + +def test_flags_unbraced_same_line() -> None: + assert _lint("if (x) ESP_LOGW(t);\n") + + +def test_flags_c_style_for() -> None: + assert _lint("for (int i = 0; i < n; i++)\n ESP_LOGD(t, i);\n") + + +def test_flags_range_for_and_else() -> None: + assert _lint("for (auto &x : v)\n ESP_LOGCONFIG(t);\n") + assert _lint("else\n ESP_LOGE(t);\n") + + +def test_flags_for_header_with_nested_call() -> None: + assert _lint("for (auto it = v.begin(); it != v.end(); ++it)\n ESP_LOGD(t);\n") + + +def test_for_header_does_not_reach_into_a_later_statement() -> None: + # The 'for' header is bounded to its own statement, so it cannot swallow the loop body and latch + # onto a later ')'. Without that, the '#if' line below is reported as an unbraced body even though + # the '#' preprocessor check should skip it. + assert not _lint( + "for (int i = 0; i < n; i++)\n arr[i] = 0;\n#if defined(USE_X)\n ESP_LOGD(t);\n#endif\n" + ) + + +def test_violation_after_a_for_loop_is_reported_at_its_own_line() -> None: + errors = _lint( + "for (int i = 0; i < n; i++)\n sum += a[i];\nif (verbose)\n ESP_LOGD(t, sum);\n" + ) + lines = [line for line, _col, _msg in errors] + assert lines == [3] # the 'if', not the 'for' on line 1 + + +def test_flags_lowercase_esph_log_family() -> None: + # core/log.h defines esph_log_*() alongside ESP_LOG*(); both expand to nothing below their level. + assert _lint('if (x)\n esph_log_config(t, "m");\n') + assert _lint('if (err != ESP_OK)\n esph_log_e(t, "m");\n') + + +def test_digit_separator_does_not_disable_the_rest_of_the_file() -> None: + # A "'" digit separator must not be read as a char-literal opener, which blanked everything after. + assert _lint("uint32_t x = 1'000;\nif (y)\n ESP_LOGD(t);\n") + + +def test_mask_still_blanks_real_char_literals() -> None: + assert "ESP_LOGD" not in mask("char c = '\"'; // if (x) ESP_LOGD(t);\n") + assert not _lint("char sep = ';';\nif (x) {\n ESP_LOGD(t);\n}\n") + + +def test_flags_multiline_log_body() -> None: + assert _lint('if (x)\n ESP_LOGD(t, "%d %d",\n a, b);\n') + + +def test_raw_string_before_violation_still_caught() -> None: + # Regression for the masker desyncing on a raw string and disabling the check for the rest. + assert _lint('s.print(R"()");\nif (y)\n ESP_LOGD(t);\n') + + +# --- rule: ignores non-violations --- + + +def test_ignores_braced_body() -> None: + assert not _lint("if (x) {\n ESP_LOGD(t);\n}\n") + + +def test_ignores_commented_out_code() -> None: + assert not _lint("// if (x) ESP_LOGD(t);\n") + + +def test_ignores_preprocessor_else() -> None: + assert not _lint("#else\n ESP_LOGCONFIG(t);\n#endif\n") + + +def test_ignores_non_log_body() -> None: + assert not _lint("if (x)\n return false;\n") + + +# --- NOLINT escape hatch, both placements --- + + +def test_nolint_at_end_of_log_line_suppresses() -> None: + assert not _lint("if (x)\n ESP_LOGD(t); // NOLINT\n") + + +def test_nolint_on_control_line_suppresses() -> None: + assert not _lint("if (x) // NOLINT\n ESP_LOGD(t);\n")