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:
Guillermo Ruffino 2021-02-06 12:09:15 -03:00 committed by GitHub
parent de3377132d
commit 28e39f7f76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 30 additions and 31 deletions

View file

@ -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)

View file

@ -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])

View file

@ -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}'

View file

@ -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):

View file

@ -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):