diff --git a/script/build_language_schema.py b/script/build_language_schema.py index 9e9ae21c55..3ce94b674e 100755 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -152,17 +152,22 @@ _CV_STRING_VALIDATORS = ( cv.time_of_day, cv.directory, cv.file_, - cv.dimensions, cv.none, ) -_CV_INTEGER_VALIDATORS = (cv.hex_int, cv.percentage_int, cv.mqtt_qos) -_CV_FLOAT_VALIDATORS = ( - cv.percentage, - cv.possibly_negative_percentage, - cv.temperature, - cv.temperature_delta, - cv.color_temperature, -) +_CV_INTEGER_VALIDATORS = (cv.hex_int, cv.mqtt_qos) +# Validators that accept a number optionally carrying a unit suffix (e.g. +# "50%", "25°C", "10kB"). Dumped as their base numeric type with the canonical +# unit attached, mirroring float_with_unit, so editors accept both the +# bare-number and the unit-string form instead of rejecting one of them. +_CV_UNIT_VALIDATORS = { + cv.percentage: ("float", "%"), + cv.possibly_negative_percentage: ("float", "%"), + cv.temperature: ("float", "°C"), + cv.temperature_delta: ("float", "°C"), + cv.color_temperature: ("float", "mireds"), + cv.percentage_int: ("integer", "%"), + cv.validate_bytes: ("integer", "B"), +} _CV_TIME_VALIDATORS = ( cv.update_interval, cv.time_period_str_unit, @@ -1051,8 +1056,8 @@ def convert(schema, config_var, path): schema in _CV_STRING_VALIDATORS ): config_var[S_TYPE] = "string" - elif schema in _CV_FLOAT_VALIDATORS: - config_var[S_TYPE] = "float" + elif callable(schema) and schema in _CV_UNIT_VALIDATORS: + config_var[S_TYPE], config_var["unit"] = _CV_UNIT_VALIDATORS[schema] elif schema in _CV_TIME_VALIDATORS: config_var[S_TYPE] = "time" elif schema in _CV_LAMBDA_VALIDATORS: diff --git a/tests/script/test_build_language_schema.py b/tests/script/test_build_language_schema.py index 952ebb61f1..b1f44d0498 100644 --- a/tests/script/test_build_language_schema.py +++ b/tests/script/test_build_language_schema.py @@ -376,9 +376,6 @@ def _convert(validator: object) -> dict: (cv.directory, "string"), (cv.mqtt_qos, "integer"), (cv.hex_int, "integer"), - (cv.percentage, "float"), - (cv.temperature, "float"), - (cv.color_temperature, "float"), (cv.update_interval, "time"), (cv.time_period_str_colon, "time"), (cv.lambda_, "lambda"), @@ -389,6 +386,35 @@ def test_convert_types_scalar_cv_validators(validator: object, expected: str) -> assert _convert(validator).get("type") == expected +@pytest.mark.parametrize( + ("validator", "expected_type", "expected_unit"), + [ + (cv.percentage, "float", "%"), + (cv.possibly_negative_percentage, "float", "%"), + (cv.temperature, "float", "°C"), + (cv.temperature_delta, "float", "°C"), + (cv.color_temperature, "float", "mireds"), + (cv.percentage_int, "integer", "%"), + (cv.validate_bytes, "integer", "B"), + ], +) +def test_convert_types_unit_validators( + validator: object, expected_type: str, expected_unit: str +) -> None: + # Number-with-unit validators accept both a bare number and a unit-suffixed + # string, so they carry their base numeric type plus the canonical unit. + entry = _convert(validator) + assert entry.get("type") == expected_type + assert entry.get("unit") == expected_unit + + +def test_convert_dimensions_left_untyped() -> None: + # cv.dimensions accepts either a "WIDTHxHEIGHT" string or a [w, h] list; + # a single scalar type would reject one form, so it is left untyped. + entry = _convert(cv.dimensions) + assert "type" not in entry + + def test_convert_entity_category_is_enum() -> None: entry = _convert(cv.entity_category) assert entry["type"] == "enum" @@ -497,7 +523,9 @@ def test_cv_types_end_to_end(full_schema_dir: Path) -> None: climate = json.loads((full_schema_dir / "climate.json").read_text())["climate"] visual = climate["schemas"]["_CLIMATE_SCHEMA"]["schema"]["config_vars"]["visual"] - assert visual["schema"]["config_vars"]["min_temperature"]["type"] == "float" + min_temp = visual["schema"]["config_vars"]["min_temperature"] + assert min_temp["type"] == "float" + assert min_temp["unit"] == "°C" # message_type references both hex_uint8_t and uint8_t; shrink() must spread # it to integer instead of tripping the single-extends assertion.