Repository: Textualize/textual

Issue:
Improve error message when value `none` is in a list of text styles
When you provide an illegal value for the CSS rule `link-style`, you get a helpful message displaying all legal values, saying that `text-style` expects a space-separated list of those.

If you set `text-style: bold italic reverse` it works fine.

If you set `text-style: none bold italic reverse` you are being silly, but there is no real reason for this to fail (I think). Yet, the error we get is 

```py
StyleSyntaxError: unable to parse 'none' as color; 'none' is not a valid color
```

To repro:

```py
from textual.app import App

class MyApp(App):
    DEFAULT_CSS = "* { text-style: bold italic none }"
```

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 402b8064c..a3e84657f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Fail-fast and print pretty tracebacks for Widget compose errors https://github.com/Textualize/textual/pull/1505
 - Added Widget._refresh_scroll to avoid expensive layout when scrolling https://github.com/Textualize/textual/pull/1524
 - `events.Paste` now bubbles https://github.com/Textualize/textual/issues/1434
+- Improved error message when style flag `none` is mixed with other flags (e.g., when setting `text-style`) https://github.com/Textualize/textual/issues/1420
 - Clock color in the `Header` widget now matches the header color https://github.com/Textualize/textual/issues/1459
 
 ### Fixed
diff --git a/docs/css_types/text_style.md b/docs/css_types/text_style.md
index f41646e3c..40afea528 100644
--- a/docs/css_types/text_style.md
+++ b/docs/css_types/text_style.md
@@ -8,13 +8,13 @@ The `<text-style>` CSS type represents styles that can be applied to text.
 
 ## Syntax
 
-A [`<text-style>`](/css_types/text_style) can be any _space-separated_ combination of the following values:
+A [`<text-style>`](/css_types/text_style) can be the value `none` for plain text with no styling,
+or any _space-separated_ combination of the following values:
 
 | Value       | Description                                                     |
 |-------------|-----------------------------------------------------------------|
 | `bold`      | **Bold text.**                                                  |
 | `italic`    | _Italic text._                                                  |
-| `none`      | Plain text with no styling.                                     |
 | `reverse`   | Reverse video text (foreground and background colors reversed). |
 | `strike`    | 
Strikethrough text.
                                      |
 | `underline` | <u>Underline text.</u>                                          |
@@ -42,5 +42,5 @@ A [`<text-style>`](/css_types/text_style) can be any _space-separated_ combinati
 widget.styles.text_style = "strike"
 
 # You can also combine multiple values
-widget.styles.text_style = "bold underline italic"
+widget.styles.text_style = "strike bold italic reverse
 ```
diff --git a/src/textual/css/_help_text.py b/src/textual/css/_help_text.py
index 7ac541645..8155dcd3c 100644
--- a/src/textual/css/_help_text.py
+++ b/src/textual/css/_help_text.py
@@ -729,6 +729,7 @@ def style_flags_property_help_text(
                 f"Style flag values such as [i]{property_name}[/] expect space-separated values"
             ),
             Bullet(f"Permitted values are {friendly_list(VALID_STYLE_FLAGS)}"),
+            Bullet("The value 'none' cannot be mixed with others"),
             *ContextSpecificBullets(
                 inline=[
                     Bullet(
diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py
index 266b1a5a1..36a40f19b 100644
--- a/src/textual/css/_style_properties.py
+++ b/src/textual/css/_style_properties.py
@@ -12,6 +12,7 @@ from __future__ import annotations
 from operator import attrgetter
 from typing import TYPE_CHECKING, Generic, Iterable, NamedTuple, TypeVar, cast
 
+import rich.errors
 import rich.repr
 from rich.style import Style
 
@@ -909,7 +910,17 @@ class StyleFlagsProperty:
                             self.name, word, context="inline"
                         ),
                     )
-            style = Style.parse(style_flags)
+            try:
+                style = Style.parse(style_flags)
+            except rich.errors.StyleSyntaxError as error:
+                if "none" in words and len(words) > 1:
+                    raise StyleValueError(
+                        "cannot mix 'none' with other style flags",
+                        help_text=style_flags_property_help_text(
+                            self.name, " ".join(words), context="inline"
+                        ),
+                    ) from None
+                raise error from None
             if obj.set_rule(self.name, style):
                 obj.refresh()
 


Test patch:
diff --git a/tests/test_style_properties.py b/tests/test_style_properties.py
index f8900c1b7..107aa2a22 100644
--- a/tests/test_style_properties.py
+++ b/tests/test_style_properties.py
@@ -1,4 +1,8 @@
+import pytest
+from rich.style import Style
+
 from textual.color import Color
+from textual.css.errors import StyleValueError
 from textual.css.styles import Styles
 
 
@@ -7,3 +11,22 @@ def test_box_normalization():
     styles = Styles()
     styles.border_left = ("none", "red")
     assert styles.border_left == ("", Color.parse("red"))
+
+
+@pytest.mark.parametrize("style_attr", ["text_style", "link_style"])
+def test_text_style_none_with_others(style_attr):
+    """Style "none" mixed with others should give custom Textual exception."""
+    styles = Styles()
+
+    with pytest.raises(StyleValueError):
+        setattr(styles, style_attr, "bold none underline italic")
+
+
+@pytest.mark.parametrize("style_attr", ["text_style", "link_style"])
+def test_text_style_set_to_none(style_attr):
+    """Setting text style to "none" should clear the styles."""
+    styles = Styles()
+    setattr(styles, style_attr, "bold underline italic")
+    assert getattr(styles, style_attr) != Style.null()
+    setattr(styles, style_attr, "none")
+    assert getattr(styles, style_attr) == Style.null()