From c1204bbf5a92acec43db61da84701c3bdec4640e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Thu, 9 Nov 2023 17:48:39 +0000 Subject: [PATCH] Be consistent with line/col numbers. I added some comments so that we know what line/column numbers are 0-based and which ones are 1-based. Related issue: #3625. --- CHANGELOG.md | 1 + src/textual/css/tokenizer.py | 29 +++++++++++++++++------------ tests/css/test_parse.py | 24 +++++++++++++++++++++--- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f82cf3a40f..4bad66e340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added missing `blur` pseudo class https://github.com/Textualize/textual/issues/3439 - Fixed visual glitched characters on Windows due to Python limitation https://github.com/Textualize/textual/issues/2548 - Fixed live-reloading of screen CSS https://github.com/Textualize/textual/issues/3454 +- Off-by-one in CSS error reporting https://github.com/Textualize/textual/issues/3625 ### Changed diff --git a/src/textual/css/tokenizer.py b/src/textual/css/tokenizer.py index d7cfc449d3..875537eb7e 100644 --- a/src/textual/css/tokenizer.py +++ b/src/textual/css/tokenizer.py @@ -34,9 +34,9 @@ def __init__( Args: read_from: The location where the CSS was read from. code: The code being parsed. - start: Line number of the error. + start: Line and column number of the error (1-indexed). message: A message associated with the error. - end: End location of token, or None if not known. + end: End location of token (1-indexed), or None if not known. """ self.read_from = read_from @@ -60,9 +60,13 @@ def _get_snippet(self) -> Panel: line_numbers=True, indent_guides=True, line_range=(max(0, line_no - 2), line_no + 2), - highlight_lines={line_no + 1}, + highlight_lines={line_no}, + ) + syntax.stylize_range( + "reverse bold", + (self.start[0], self.start[1] - 1), + (self.end[0], self.end[1] - 1), ) - syntax.stylize_range("reverse bold", self.start, self.end) return Panel(syntax, border_style="red") def __rich__(self) -> RenderableType: @@ -136,19 +140,20 @@ class Token(NamedTuple): read_from: CSSLocation code: str location: tuple[int, int] + """Token starting location, 0-indexed.""" referenced_by: ReferencedBy | None = None @property def start(self) -> tuple[int, int]: - """Start line and column (1 indexed).""" + """Start line and column (1-indexed).""" line, offset = self.location - return (line + 1, offset) + return (line + 1, offset + 1) @property def end(self) -> tuple[int, int]: - """End line and column (1 indexed).""" + """End line and column (1-indexed).""" line, offset = self.location - return (line + 1, offset + len(self.value)) + return (line + 1, offset + len(self.value) + 1) def with_reference(self, by: ReferencedBy | None) -> "Token": """Return a copy of the Token, with reference information attached. @@ -199,7 +204,7 @@ def get_token(self, expect: Expect) -> Token: "", self.read_from, self.code, - (line_no + 1, col_no + 1), + (line_no, col_no), None, ) else: @@ -217,7 +222,7 @@ def get_token(self, expect: Expect) -> Token: raise TokenError( self.read_from, self.code, - (line_no, col_no), + (line_no + 1, col_no + 1), message, ) iter_groups = iter(match.groups()) @@ -251,14 +256,14 @@ def get_token(self, expect: Expect) -> Token: raise TokenError( self.read_from, self.code, - (line_no, col_no), + (line_no + 1, col_no + 1), f"unknown pseudo-class {pseudo_class!r}; did you mean {suggestion!r}?; {all_valid}", ) else: raise TokenError( self.read_from, self.code, - (line_no, col_no), + (line_no + 1, col_no + 1), f"unknown pseudo-class {pseudo_class!r}; {all_valid}", ) diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index 124f820d53..febe4f06bb 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -1238,7 +1238,7 @@ def test_combined_type_starts_with_number(self): stylesheet.parse() -def test_parse_bad_psuedo_selector(): +def test_parse_bad_pseudo_selector(): """Check unknown selector raises a token error.""" bad_selector = """\ @@ -1248,9 +1248,27 @@ def test_parse_bad_psuedo_selector(): """ stylesheet = Stylesheet() - stylesheet.add_source(bad_selector, "foo") + stylesheet.add_source(bad_selector, None) with pytest.raises(TokenError) as error: stylesheet.parse() - assert error.value.start == (0, 6) + assert error.value.start == (1, 7) + + +def test_parse_bad_pseudo_selector_with_suggestion(): + """Check unknown pseudo selector raises token error with correct position.""" + + bad_selector = """ +Widget:blu { + border: red; +} +""" + + stylesheet = Stylesheet() + stylesheet.add_source(bad_selector, None) + + with pytest.raises(TokenError) as error: + stylesheet.parse() + + assert error.value.start == (2, 7)