[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:
Olivier Philippon
2022-04-29 10:28:30 +01:00
parent 6fba64face
commit 2c03f8cfe1
2 changed files with 69 additions and 13 deletions

View File

@@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from difflib import get_close_matches
from functools import lru_cache
from typing import cast, Iterable, NoReturn from typing import cast, Iterable, NoReturn
import rich.repr import rich.repr
@@ -84,24 +86,35 @@ class StylesBuilder:
process_method = getattr(self, f"process_{rule_name}", None) process_method = getattr(self, f"process_{rule_name}", None)
if process_method is 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( self.error(
declaration.name, declaration.name,
declaration.token, declaration.token,
f"unknown declaration {declaration.name!r}", error_message,
) )
else: return
tokens = declaration.tokens
important = tokens[-1].name == "important" tokens = declaration.tokens
if important:
tokens = tokens[:-1] important = tokens[-1].name == "important"
self.styles.important.add(rule_name) if important:
try: tokens = tokens[:-1]
process_method(declaration.name, tokens) self.styles.important.add(rule_name)
except DeclarationError: try:
raise process_method(declaration.name, tokens)
except Exception as error: except DeclarationError:
self.error(declaration.name, declaration.token, str(error)) raise
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( def _process_enum_multiple(
self, name: str, tokens: list[Token], valid_values: set[str], count: int self, name: str, tokens: list[Token], valid_values: set[str], count: int
@@ -717,3 +730,9 @@ class StylesBuilder:
process_content_align = process_align process_content_align = process_align
process_content_align_horizontal = process_align_horizontal process_content_align_horizontal = process_align_horizontal
process_content_align_vertical = process_align_vertical 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]

View File

@@ -53,3 +53,40 @@ def test_color_property_parsing(css_value, expectation, expected_color):
if expected_color: if expected_color:
css_rule = stylesheet.rules[0] css_rule = stylesheet.rules[0]
assert css_rule.styles.background == expected_color 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