mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
[css] Add a "did you mean" suggestion when an unknown CSS property is spotted
So using "bckgroundu: red" in a CSS file will report _"unknown declaration 'bckgroundu'; did you mean 'background'?"_
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from difflib import get_close_matches
|
||||
from functools import lru_cache
|
||||
from typing import cast, Iterable, NoReturn
|
||||
|
||||
import rich.repr
|
||||
@@ -84,12 +86,17 @@ class StylesBuilder:
|
||||
process_method = getattr(self, f"process_{rule_name}", None)
|
||||
|
||||
if process_method is None:
|
||||
error_message = f"unknown declaration {declaration.name!r}"
|
||||
did_you_mean_rule_name = self._did_you_mean_for_rule_name(declaration.name)
|
||||
if did_you_mean_rule_name:
|
||||
error_message += f"; did you mean {did_you_mean_rule_name!r}?"
|
||||
self.error(
|
||||
declaration.name,
|
||||
declaration.token,
|
||||
f"unknown declaration {declaration.name!r}",
|
||||
error_message,
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
tokens = declaration.tokens
|
||||
|
||||
important = tokens[-1].name == "important"
|
||||
@@ -103,6 +110,12 @@ class StylesBuilder:
|
||||
except Exception as error:
|
||||
self.error(declaration.name, declaration.token, str(error))
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _processable_rule_names(self) -> frozenset[str]:
|
||||
return frozenset(
|
||||
[attr[8:] for attr in dir(self) if attr.startswith("process_")]
|
||||
)
|
||||
|
||||
def _process_enum_multiple(
|
||||
self, name: str, tokens: list[Token], valid_values: set[str], count: int
|
||||
) -> tuple[str, ...]:
|
||||
@@ -717,3 +730,9 @@ class StylesBuilder:
|
||||
process_content_align = process_align
|
||||
process_content_align_horizontal = process_align_horizontal
|
||||
process_content_align_vertical = process_align_vertical
|
||||
|
||||
def _did_you_mean_for_rule_name(self, rule_name: str) -> str | None:
|
||||
possible_matches = get_close_matches(
|
||||
rule_name, self._processable_rule_names(), n=1
|
||||
)
|
||||
return None if not possible_matches else possible_matches[0]
|
||||
|
||||
@@ -53,3 +53,40 @@ def test_color_property_parsing(css_value, expectation, expected_color):
|
||||
if expected_color:
|
||||
css_rule = stylesheet.rules[0]
|
||||
assert css_rule.styles.background == expected_color
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"css_property_name,expected_property_name_suggestion",
|
||||
[
|
||||
["backgroundu", "background"],
|
||||
["bckgroundu", "background"],
|
||||
["colr", "color"],
|
||||
["colour", "color"],
|
||||
["wdth", "width"],
|
||||
["wth", "width"],
|
||||
["wh", None],
|
||||
["xkcd", None],
|
||||
],
|
||||
)
|
||||
def test_did_you_mean_for_css_property_names(
|
||||
css_property_name, expected_property_name_suggestion
|
||||
):
|
||||
stylesheet = Stylesheet()
|
||||
css = """
|
||||
* {
|
||||
border: blue;
|
||||
${PROPERTY}: red;
|
||||
}
|
||||
""".replace(
|
||||
"${PROPERTY}", css_property_name
|
||||
)
|
||||
|
||||
with pytest.raises(StylesheetParseError) as err:
|
||||
stylesheet.parse(css)
|
||||
|
||||
error_token, error_message = err.value.errors.stylesheet.rules[0].errors[0]
|
||||
if expected_property_name_suggestion is None:
|
||||
assert "did you mean" not in error_message
|
||||
else:
|
||||
expected_did_you_mean_error_message = f"unknown declaration '{css_property_name}'; did you mean '{expected_property_name_suggestion}'?"
|
||||
assert expected_did_you_mean_error_message == error_message
|
||||
|
||||
Reference in New Issue
Block a user