mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add config validator location (#1490)
* show validation source location for id * show validation source location for lambda * refactor lambda #line position * account content offset on made lambdas * lint * remove redundant check
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							de3377132d
						
					
				
				
					commit
					28e39f7f76
				
			| @@ -268,6 +268,8 @@ class Config(OrderedDict): | |||||||
|                 data = data[item_index] |                 data = data[item_index] | ||||||
|             except (KeyError, IndexError, TypeError): |             except (KeyError, IndexError, TypeError): | ||||||
|                 return doc_range |                 return doc_range | ||||||
|  |             if isinstance(data, core.ID): | ||||||
|  |                 data = data.id | ||||||
|             if isinstance(data, ESPHomeDataBase) and data.esp_range is not None: |             if isinstance(data, ESPHomeDataBase) and data.esp_range is not None: | ||||||
|                 doc_range = data.esp_range |                 doc_range = data.esp_range | ||||||
|  |  | ||||||
| @@ -700,6 +702,8 @@ def line_info(obj, highlight=True): | |||||||
|     """Display line config source.""" |     """Display line config source.""" | ||||||
|     if not highlight: |     if not highlight: | ||||||
|         return None |         return None | ||||||
|  |     if isinstance(obj, core.ID): | ||||||
|  |         obj = obj.id | ||||||
|     if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None: |     if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None: | ||||||
|         mark = obj.esp_range.start_mark |         mark = obj.esp_range.start_mark | ||||||
|         source = "[source {}:{}]".format(mark.document, mark.line + 1) |         source = "[source {}:{}]".format(mark.document, mark.line + 1) | ||||||
|   | |||||||
| @@ -17,10 +17,10 @@ from esphome.const import ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TO | |||||||
|     CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \ |     CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \ | ||||||
|     CONF_TYPE, CONF_PACKAGES |     CONF_TYPE, CONF_PACKAGES | ||||||
| from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ | from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \ | ||||||
|     TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes, DocumentLocation |     TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes | ||||||
| from esphome.helpers import list_starts_with, add_class_to_obj | from esphome.helpers import list_starts_with, add_class_to_obj | ||||||
| from esphome.voluptuous_schema import _Schema | from esphome.voluptuous_schema import _Schema | ||||||
| from esphome.yaml_util import ESPHomeDataBase | from esphome.yaml_util import make_data_base | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -983,11 +983,7 @@ LAMBDA_ENTITY_ID_PROG = re.compile(r'id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\) | |||||||
| def lambda_(value): | def lambda_(value): | ||||||
|     """Coerce this configuration option to a lambda.""" |     """Coerce this configuration option to a lambda.""" | ||||||
|     if not isinstance(value, Lambda): |     if not isinstance(value, Lambda): | ||||||
|         start_mark = None |         value = make_data_base(Lambda(string_strict(value)), value) | ||||||
|         if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: |  | ||||||
|             start_mark = DocumentLocation.copy(value.esp_range.start_mark) |  | ||||||
|             start_mark.line += value.content_offset |  | ||||||
|         value = Lambda(string_strict(value), start_mark) |  | ||||||
|     entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) |     entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value) | ||||||
|     if len(entity_id_parts) != 1: |     if len(entity_id_parts) != 1: | ||||||
|         entity_ids = ' '.join("'{}'".format(entity_id_parts[i]) |         entity_ids = ' '.join("'{}'".format(entity_id_parts[i]) | ||||||
|   | |||||||
| @@ -227,7 +227,7 @@ LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)') | |||||||
|  |  | ||||||
|  |  | ||||||
| class Lambda: | class Lambda: | ||||||
|     def __init__(self, value, start_mark=None): |     def __init__(self, value): | ||||||
|         # pylint: disable=protected-access |         # pylint: disable=protected-access | ||||||
|         if isinstance(value, Lambda): |         if isinstance(value, Lambda): | ||||||
|             self._value = value._value |             self._value = value._value | ||||||
| @@ -235,7 +235,6 @@ class Lambda: | |||||||
|             self._value = value |             self._value = value | ||||||
|         self._parts = None |         self._parts = None | ||||||
|         self._requires_ids = None |         self._requires_ids = None | ||||||
|         self._source_location = start_mark |  | ||||||
|  |  | ||||||
|     # https://stackoverflow.com/a/241506/229052 |     # https://stackoverflow.com/a/241506/229052 | ||||||
|     def comment_remover(self, text): |     def comment_remover(self, text): | ||||||
| @@ -278,10 +277,6 @@ class Lambda: | |||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return f'Lambda<{self.value}>' |         return f'Lambda<{self.value}>' | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def source_location(self): |  | ||||||
|         return self._source_location |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ID: | class ID: | ||||||
|     def __init__(self, id, is_declaration=False, type=None, is_manual=None): |     def __init__(self, id, is_declaration=False, type=None, is_manual=None): | ||||||
| @@ -339,14 +334,6 @@ class DocumentLocation: | |||||||
|             mark.column |             mark.column | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def copy(cls, location): |  | ||||||
|         return cls( |  | ||||||
|             location.document, |  | ||||||
|             location.line, |  | ||||||
|             location.column |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return f'{self.document} {self.line}:{self.column}' |         return f'{self.document} {self.line}:{self.column}' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import abc | |||||||
| import inspect | import inspect | ||||||
| import math | import math | ||||||
| import re | import re | ||||||
|  | from esphome.yaml_util import ESPHomeDataBase | ||||||
|  |  | ||||||
| # pylint: disable=unused-import, wrong-import-order | # pylint: disable=unused-import, wrong-import-order | ||||||
| from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence | from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence | ||||||
| @@ -560,7 +561,13 @@ def process_lambda( | |||||||
|         else: |         else: | ||||||
|             parts[i * 3 + 1] = var |             parts[i * 3 + 1] = var | ||||||
|         parts[i * 3 + 2] = '' |         parts[i * 3 + 2] = '' | ||||||
|     yield LambdaExpression(parts, parameters, capture, return_type, value.source_location) |  | ||||||
|  |     if isinstance(value, ESPHomeDataBase) and value.esp_range is not None: | ||||||
|  |         location = value.esp_range.start_mark | ||||||
|  |         location.line += value.content_offset | ||||||
|  |     else: | ||||||
|  |         location = None | ||||||
|  |     yield LambdaExpression(parts, parameters, capture, return_type, location) | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_template(value): | def is_template(value): | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import yaml.constructor | |||||||
| from esphome import core | from esphome import core | ||||||
| from esphome.config_helpers import read_config_file | from esphome.config_helpers import read_config_file | ||||||
| from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, \ | from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, \ | ||||||
|     DocumentRange, DocumentLocation |     DocumentRange | ||||||
| from esphome.helpers import add_class_to_obj | from esphome.helpers import add_class_to_obj | ||||||
| from esphome.util import OrderedDict, filter_yaml_files | from esphome.util import OrderedDict, filter_yaml_files | ||||||
|  |  | ||||||
| @@ -42,14 +42,22 @@ class ESPHomeDataBase: | |||||||
|             if node.style is not None and node.style in '|>': |             if node.style is not None and node.style in '|>': | ||||||
|                 self._content_offset = 1 |                 self._content_offset = 1 | ||||||
|  |  | ||||||
|  |     def from_database(self, database): | ||||||
|  |         # pylint: disable=attribute-defined-outside-init | ||||||
|  |         self._esp_range = database.esp_range | ||||||
|  |         self._content_offset = database.content_offset | ||||||
|  |  | ||||||
|  |  | ||||||
| class ESPForceValue: | class ESPForceValue: | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| def make_data_base(value): | def make_data_base(value, from_database: ESPHomeDataBase = None): | ||||||
|     try: |     try: | ||||||
|         return add_class_to_obj(value, ESPHomeDataBase) |         value = add_class_to_obj(value, ESPHomeDataBase) | ||||||
|  |         if from_database is not None: | ||||||
|  |             value.from_database(from_database) | ||||||
|  |         return value | ||||||
|     except TypeError: |     except TypeError: | ||||||
|         # Adding class failed, ignore error |         # Adding class failed, ignore error | ||||||
|         return value |         return value | ||||||
| @@ -265,10 +273,7 @@ class ESPHomeLoader(yaml.SafeLoader):  # pylint: disable=too-many-ancestors | |||||||
|  |  | ||||||
|     @_add_data_ref |     @_add_data_ref | ||||||
|     def construct_lambda(self, node): |     def construct_lambda(self, node): | ||||||
|         start_mark = DocumentLocation.from_mark(node.start_mark) |         return Lambda(str(node.value)) | ||||||
|         if node.style is not None and node.style in '|>': |  | ||||||
|             start_mark.line += 1 |  | ||||||
|         return Lambda(str(node.value), start_mark) |  | ||||||
|  |  | ||||||
|     @_add_data_ref |     @_add_data_ref | ||||||
|     def construct_force(self, node): |     def construct_force(self, node): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user