From e4ea016d1e6a7cb7a93f12c6cd2c1e419858c899 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 12:26:19 -0600 Subject: [PATCH] [ci] Block new std::to_string() usage, suggest snprintf alternatives (#13369) --- .../components/api/homeassistant_service.h | 4 +- esphome/components/esp32_ble/ble_uuid.h | 2 +- .../voice_assistant/voice_assistant.h | 2 +- script/ci-custom.py | 47 +++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 8ee23c75fe..2322d96eef 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -25,7 +25,9 @@ template class TemplatableStringValue : public TemplatableValue static std::string value_to_string(T &&val) { return to_string(std::forward(val)); } + template static std::string value_to_string(T &&val) { + return to_string(std::forward(val)); // NOLINT + } // Overloads for string types - needed because std::to_string doesn't support them static std::string value_to_string(char *val) { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 6c8ef7bfd9..503fde6945 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -48,7 +48,7 @@ class ESPBTUUID { // Remove before 2026.8.0 ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0") - std::string to_string() const; + std::string to_string() const; // NOLINT const char *to_str(std::span output) const; protected: diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 2a5f3a55a7..0ef7ecc81a 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -83,7 +83,7 @@ struct Timer { } // Remove before 2026.8.0 ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0") - std::string to_string() const { + std::string to_string() const { // NOLINT char buffer[TO_STR_BUFFER_SIZE]; return this->to_str(buffer); } diff --git a/script/ci-custom.py b/script/ci-custom.py index b5bec74fa7..8c405b04ae 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -756,6 +756,53 @@ def lint_no_sprintf(fname, match): ) +@lint_re_check( + # Match std::to_string() or unqualified to_string() calls + # The esphome namespace has "using std::to_string;" so unqualified calls resolve to std::to_string + # Use negative lookbehind for unqualified calls to avoid matching: + # - Function definitions: "const char *to_string(" or "std::string to_string(" + # - Method definitions: "Class::to_string(" + # - Method calls: ".to_string(" or "->to_string(" + # - Other identifiers: "_to_string(" + # Also explicitly match std::to_string since : is in the lookbehind + r"(?:(?:])to_string|std\s*::\s*to_string)\s*\(" + CPP_RE_EOL, + include=cpp_include, + exclude=[ + # Vendored library + "esphome/components/http_request/httplib.h", + # Deprecated helpers that return std::string + "esphome/core/helpers.cpp", + # The using declaration itself + "esphome/core/helpers.h", + # Test fixtures - not production embedded code + "tests/integration/fixtures/*", + ], +) +def lint_no_std_to_string(fname, match): + return ( + f"{highlight('std::to_string()')} (including unqualified {highlight('to_string()')}) " + f"allocates heap memory. On long-running embedded devices, repeated heap allocations " + f"fragment memory over time.\n" + f"Please use {highlight('snprintf()')} with a stack buffer instead.\n" + f"\n" + f"Buffer sizes and format specifiers (sizes include sign and null terminator):\n" + f" uint8_t: 4 chars - %u (or PRIu8)\n" + f" int8_t: 5 chars - %d (or PRId8)\n" + f" uint16_t: 6 chars - %u (or PRIu16)\n" + f" int16_t: 7 chars - %d (or PRId16)\n" + f" uint32_t: 11 chars - %" + "PRIu32\n" + " int32_t: 12 chars - %" + "PRId32\n" + " uint64_t: 21 chars - %" + "PRIu64\n" + " int64_t: 21 chars - %" + "PRId64\n" + f" float/double: 24 chars - %.8g (15 digits + sign + decimal + e+XXX)\n" + f" 317 chars - %f (for DBL_MAX: 309 int digits + decimal + 6 frac + sign)\n" + f"\n" + f"For sensor values, use value_accuracy_to_buf() from helpers.h.\n" + f'Example: char buf[11]; snprintf(buf, sizeof(buf), "%" PRIu32, value);\n' + f"(If strictly necessary, add `{highlight('// NOLINT')}` to the end of the line)" + ) + + @lint_re_check( # Match scanf family functions: scanf, sscanf, fscanf, vscanf, vsscanf, vfscanf # Also match std:: prefixed versions