Error handling

This commit is contained in:
Darren Burns
2022-02-04 11:43:35 +00:00
parent d50e2e1b0d
commit bb8b4e89d3
7 changed files with 215 additions and 180 deletions

View File

@@ -11,7 +11,7 @@ $animation: offset $animation-speed in_out_cubic;
App > View { App > View {
docks: side=left/1; docks: side=left/1;
text: on $animation; text: on $background;
} }
Widget:hover { Widget:hover {

View File

@@ -17,6 +17,7 @@ from .transition import Transition
from .types import Edge, Display, Visibility from .types import Edge, Display, Visibility
from .._duration import _duration_as_seconds from .._duration import _duration_as_seconds
from .._easing import EASING from .._easing import EASING
from .._loop import loop_last
from ..geometry import Spacing, SpacingDimensions from ..geometry import Spacing, SpacingDimensions
from ..layouts.factory import get_layout, LayoutName, MissingLayout, LAYOUT_MAP from ..layouts.factory import get_layout, LayoutName, MissingLayout, LAYOUT_MAP
@@ -302,20 +303,26 @@ class StylesBuilder:
def process_text(self, name: str, tokens: list[Token], important: bool) -> None: def process_text(self, name: str, tokens: list[Token], important: bool) -> None:
style_definition = " ".join(token.value for token in tokens) style_definition = " ".join(token.value for token in tokens)
if tokens and tokens[0].referenced_at:
variable_prefix = f"${tokens[0].referenced_at.name}=" # If every token in the value is a referenced by the same variable,
# we can display the variable name before the style definition.
# TODO: Factor this out to apply it to other properties too.
unique_references = set(t.referenced_by for t in tokens if t.referenced_by)
if tokens and tokens[0].referenced_by and len(unique_references) == 1:
variable_prefix = f"${tokens[0].referenced_by.name}="
else: else:
variable_prefix = "" variable_prefix = ""
try: try:
style = Style.parse(style_definition) style = Style.parse(style_definition)
self.styles.text = style
except Exception as error: except Exception as error:
message = f"property 'text' has invalid value {variable_prefix}{style_definition!r};\n{error}" message = f"property 'text' has invalid value {variable_prefix}{style_definition!r}; {error}"
self.error(name, tokens[0], message) self.error(name, tokens[0], message)
if important: if important:
self.styles.important.update( self.styles.important.update(
{"text_style", "text_background", "text_color"} {"text_style", "text_background", "text_color"}
) )
self.styles.text = style
def process_text_color( def process_text_color(
self, name: str, tokens: list[Token], important: bool self, name: str, tokens: list[Token], important: bool

View File

@@ -19,7 +19,7 @@ from .model import (
) )
from .styles import Styles from .styles import Styles
from .tokenize import tokenize, tokenize_declarations, Token from .tokenize import tokenize, tokenize_declarations, Token
from .tokenizer import EOFError, ReferencedAt from .tokenizer import EOFError, ReferencedBy
SELECTOR_MAP: dict[str, tuple[SelectorType, tuple[int, int, int]]] = { SELECTOR_MAP: dict[str, tuple[SelectorType, tuple[int, int, int]]] = {
"selector": (SelectorType.TYPE, (0, 0, 1)), "selector": (SelectorType.TYPE, (0, 0, 1)),
@@ -229,7 +229,8 @@ def substitute_references(tokens: Iterator[Token]) -> Iterable[Token]:
Iterable[Token]: Yields Tokens such that any variable references (tokens where Iterable[Token]: Yields Tokens such that any variable references (tokens where
token.name == "variable_ref") have been replaced with the tokens representing token.name == "variable_ref") have been replaced with the tokens representing
the value. In other words, an Iterable of Tokens similar to the original input, the value. In other words, an Iterable of Tokens similar to the original input,
but with variables resolved. but with variables resolved. Substituted tokens will have their referenced_by
attribute populated with information about where the tokens are being substituted to.
""" """
variables: dict[str, list[Token]] = defaultdict(list) variables: dict[str, list[Token]] = defaultdict(list)
while tokens: while tokens:
@@ -264,7 +265,7 @@ def substitute_references(tokens: Iterator[Token]) -> Iterable[Token]:
ref_length = cell_len(token.value) ref_length = cell_len(token.value)
for token in reference_tokens: for token in reference_tokens:
yield token.ref( yield token.ref(
ReferencedAt( ReferencedBy(
name=ref_name, name=ref_name,
location=ref_location, location=ref_location,
length=ref_length, length=ref_length,
@@ -285,7 +286,7 @@ def substitute_references(tokens: Iterator[Token]) -> Iterable[Token]:
ref_length = cell_len(token.value) ref_length = cell_len(token.value)
for token in variable_tokens: for token in variable_tokens:
yield token.ref( yield token.ref(
ReferencedAt( ReferencedBy(
name=variable_name, name=variable_name,
location=ref_location, location=ref_location,
length=ref_length, length=ref_length,

View File

@@ -1,27 +1,25 @@
from __future__ import annotations from __future__ import annotations
import os
from collections import defaultdict from collections import defaultdict
from operator import itemgetter from operator import itemgetter
import os
from typing import Iterable from typing import Iterable
import rich.repr import rich.repr
from rich.cells import cell_len from rich.console import Group, RenderableType
from rich.highlighter import ReprHighlighter from rich.highlighter import ReprHighlighter
from rich.padding import Padding from rich.padding import Padding
from rich.panel import Panel from rich.panel import Panel
from rich.syntax import Syntax from rich.syntax import Syntax
from rich.text import Text from rich.text import Text
from rich.console import Group, RenderableType
from textual._loop import loop_last
from .errors import StylesheetError from .errors import StylesheetError
from .match import _check_selectors from .match import _check_selectors
from .model import RuleSet from .model import RuleSet
from .parse import parse from .parse import parse
from .types import Specificity3, Specificity4 from .types import Specificity3, Specificity4
from ..dom import DOMNode from ..dom import DOMNode
from .. import log
class StylesheetParseError(Exception): class StylesheetParseError(Exception):
@@ -37,7 +35,7 @@ class StylesheetErrors:
self.stylesheet = stylesheet self.stylesheet = stylesheet
@classmethod @classmethod
def _get_snippet(cls, code: str, line_no: int, col_no: int, length: int) -> Panel: def _get_snippet(cls, code: str, line_no: int) -> Panel:
syntax = Syntax( syntax = Syntax(
code, code,
lexer="scss", lexer="scss",
@@ -55,25 +53,30 @@ class StylesheetErrors:
append = errors.append append = errors.append
for rule in self.stylesheet.rules: for rule in self.stylesheet.rules:
for token, message in rule.errors: for token, message in rule.errors:
if token.referenced_at: append("")
line_idx, col_idx = token.referenced_at.location append(Text(" Error in stylesheet:", style="bold red"))
if token.referenced_by:
line_idx, col_idx = token.referenced_by.location
line_no, col_no = line_idx + 1, col_idx + 1 line_no, col_no = line_idx + 1, col_idx + 1
append(highlighter(f"{token.path or '<unknown>'}:{line_no}"))
append( append(
self._get_snippet( highlighter(f" {token.path or '<unknown>'}:{line_no}:{col_no}")
token.code, line_no, col_no, token.referenced_at.length + 1
)
) )
append(self._get_snippet(token.code, line_no))
else: else:
line_idx, col_idx = token.location line_idx, col_idx = token.location
line_no, col_no = line_idx + 1, col_idx + 1 line_no, col_no = line_idx + 1, col_idx + 1
append(highlighter(f"{token.path or '<unknown>'}:{line_no}"))
append( append(
self._get_snippet( highlighter(f" {token.path or '<unknown>'}:{line_no}:{col_no}")
token.code, line_no, col_no, cell_len(token.value) + 1
)
) )
append(Padding(highlighter(Text(message, "red")), pad=(0, 1))) append(self._get_snippet(token.code, line_no))
final_message = ""
for is_last, message_part in loop_last(message.split(";")):
end = "" if is_last else "\n"
final_message += f"{message_part.strip()};{end}"
append(Padding(highlighter(Text(final_message, "red")), pad=(0, 1)))
append("") append("")
return Group(*errors) return Group(*errors)
@@ -184,7 +187,6 @@ class Stylesheet:
if __name__ == "__main__": if __name__ == "__main__":
from rich.traceback import install from rich.traceback import install
install(show_locals=True) install(show_locals=True)

View File

@@ -39,7 +39,7 @@ class Expect:
yield from zip(self.names, self.regexes) yield from zip(self.names, self.regexes)
class ReferencedAt(NamedTuple): class ReferencedBy(NamedTuple):
name: str name: str
location: tuple[int, int] location: tuple[int, int]
length: int length: int
@@ -52,16 +52,16 @@ class Token(NamedTuple):
path: str path: str
code: str code: str
location: tuple[int, int] location: tuple[int, int]
referenced_at: ReferencedAt | None referenced_by: ReferencedBy | None
def ref(self, at: ReferencedAt | None) -> "Token": def ref(self, by: ReferencedBy | None) -> "Token":
return Token( return Token(
name=self.name, name=self.name,
value=self.value, value=self.value,
path=self.path, path=self.path,
code=self.code, code=self.code,
location=self.location, location=self.location,
referenced_at=at, referenced_by=by,
) )
def __str__(self) -> str: def __str__(self) -> str:
@@ -72,7 +72,7 @@ class Token(NamedTuple):
yield "value", self.value yield "value", self.value
yield "path", self.path yield "path", self.path
yield "location", self.location yield "location", self.location
yield "referenced_at", self.referenced_at yield "referenced_by", self.referenced_by
class Tokenizer: class Tokenizer:
@@ -107,7 +107,7 @@ class Tokenizer:
break break
token = Token( token = Token(
name, value, self.path, self.code, (line_no, col_no), referenced_at=None name, value, self.path, self.code, (line_no, col_no), referenced_by=None
) )
col_no += len(value) col_no += len(value)
if col_no >= len(line): if col_no >= len(line):

View File

@@ -6,7 +6,7 @@ from textual.css.parse import substitute_references
from textual.css.scalar import Scalar, Unit from textual.css.scalar import Scalar, Unit
from textual.css.stylesheet import Stylesheet, StylesheetParseError from textual.css.stylesheet import Stylesheet, StylesheetParseError
from textual.css.tokenize import tokenize from textual.css.tokenize import tokenize
from textual.css.tokenizer import Token, ReferencedAt from textual.css.tokenizer import Token, ReferencedBy
from textual.css.transition import Transition from textual.css.transition import Transition
from textual.layouts.dock import DockLayout from textual.layouts.dock import DockLayout
@@ -16,38 +16,39 @@ class TestVariableReferenceSubstitution:
css = "$x: 1; #some-widget{border: $x;}" css = "$x: 1; #some-widget{border: $x;}"
variables = substitute_references(tokenize(css, "")) variables = substitute_references(tokenize(css, ""))
assert list(variables) == [ assert list(variables) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_at=None), Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 5), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 6), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 6), referenced_by=None),
Token(name='selector_start_id', value='#some-widget', path='', code=css, location=(0, 7), Token(name='selector_start_id', value='#some-widget', path='', code=css, location=(0, 7),
referenced_at=None), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 19), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 19), referenced_by=None),
Token(name='declaration_name', value='border:', path='', code=css, location=(0, 20), referenced_at=None), Token(name='declaration_name', value='border:', path='', code=css, location=(0, 20), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 27), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 27), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 4), Token(name='number', value='1', path='', code=css, location=(0, 4),
referenced_at=ReferencedAt(name='x', location=(0, 28), length=2)), referenced_by=ReferencedBy(name='x', location=(0, 28), length=2)),
Token(name='declaration_end', value=';', path='', code=css, location=(0, 30), referenced_at=None), Token(name='declaration_end', value=';', path='', code=css, location=(0, 30), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 31), referenced_at=None) Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 31), referenced_by=None)
] ]
def test_simple_reference_no_whitespace(self): def test_simple_reference_no_whitespace(self):
css = "$x:1; #some-widget{border: $x;}" css = "$x:1; #some-widget{border: $x;}"
variables = substitute_references(tokenize(css, "")) variables = substitute_references(tokenize(css, ""))
assert list(variables) == [ assert list(variables) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_at=None), Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='selector_start_id', value='#some-widget', path='', code=css, location=(0, 6), Token(name='selector_start_id', value='#some-widget', path='', code=css, location=(0, 6),
referenced_at=None), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 18), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 18), referenced_by=None),
Token(name='declaration_name', value='border:', path='', code=css, location=(0, 19), referenced_at=None), Token(name='declaration_name', value='border:', path='', code=css, location=(0, 19), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 26), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 26), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_at=ReferencedAt(name='x', location=(0, 27), length=2)), Token(name='number', value='1', path='', code=css, location=(0, 3),
Token(name='declaration_end', value=';', path='', code=css, location=(0, 29), referenced_at=None), referenced_by=ReferencedBy(name='x', location=(0, 27), length=2)),
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 30), referenced_at=None) Token(name='declaration_end', value=';', path='', code=css, location=(0, 29), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 30), referenced_by=None)
] ]
def test_undefined_variable(self): def test_undefined_variable(self):
@@ -58,69 +59,93 @@ class TestVariableReferenceSubstitution:
def test_transitive_reference(self): def test_transitive_reference(self):
css = "$x: 1\n$y: $x\n.thing { border: $y }" css = "$x: 1\n$y: $x\n.thing { border: $y }"
assert list(substitute_references(tokenize(css, ""))) == [ assert list(substitute_references(tokenize(css, ""))) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_at=None), Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 5), referenced_at=None), Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_at=None), Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_at=ReferencedAt(name='x', location=(1, 4), length=2)), Token(name='number', value='1', path='', code=css, location=(0, 4),
Token(name='variable_value_end', value='\n', path='', code=css, location=(1, 6), referenced_at=None), referenced_by=ReferencedBy(name='x', location=(1, 4), length=2)),
Token(name='selector_start_class', value='.thing', path='', code=css, location=(2, 0), referenced_at=None), Token(name='variable_value_end', value='\n', path='', code=css, location=(1, 6), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 6), referenced_at=None), Token(name='selector_start_class', value='.thing', path='', code=css, location=(2, 0), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(2, 7), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(2, 6), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 8), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(2, 7), referenced_by=None),
Token(name='declaration_name', value='border:', path='', code=css, location=(2, 9), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(2, 8), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 16), referenced_at=None), Token(name='declaration_name', value='border:', path='', code=css, location=(2, 9), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), Token(name='whitespace', value=' ', path='', code=css, location=(2, 16), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 19), referenced_at=None), Token(name='number', value='1', path='', code=css, location=(0, 4),
Token(name='declaration_set_end', value='}', path='', code=css, location=(2, 20), referenced_at=None) referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 19), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(2, 20), referenced_by=None)
] ]
def test_multi_value_variable(self): def test_multi_value_variable(self):
css = "$x: 2 4\n$y: 6 $x 2\n.thing { border: $y }" css = "$x: 2 4\n$y: 6 $x 2\n.thing { border: $y }"
assert list(substitute_references(tokenize(css, ""))) == [ assert list(substitute_references(tokenize(css, ""))) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='number', value='2', path='', code=css, location=(0, 4), referenced_at=None), Token(name='number', value='2', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='number', value='4', path='', code=css, location=(0, 6), referenced_at=None), Token(name='number', value='4', path='', code=css, location=(0, 6), referenced_by=None),
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 7), referenced_at=None), Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 7), referenced_by=None),
Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_at=None), Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_by=None),
Token(name='number', value='6', path='', code=css, location=(1, 4), referenced_at=None), Token(name='number', value='6', path='', code=css, location=(1, 4), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 5), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(1, 5), referenced_by=None),
Token(name='number', value='2', path='', code=css, location=(0, 4), Token(name='number', value='2', path='', code=css, location=(0, 4),
referenced_at=ReferencedAt(name='x', location=(1, 6), length=2)), referenced_by=ReferencedBy(name='x', location=(1, 6), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), Token(name='whitespace', value=' ', path='', code=css, location=(0, 5),
referenced_at=ReferencedAt(name='x', location=(1, 6), length=2)), referenced_by=ReferencedBy(name='x', location=(1, 6), length=2)),
Token(name='number', value='4', path='', code=css, location=(0, 6), Token(name='number', value='4', path='', code=css, location=(0, 6),
referenced_at=ReferencedAt(name='x', location=(1, 6), length=2)), referenced_by=ReferencedBy(name='x', location=(1, 6), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 8), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(1, 8), referenced_by=None),
Token(name='number', value='2', path='', code=css, location=(1, 9), referenced_at=None), Token(name='number', value='2', path='', code=css, location=(1, 9), referenced_by=None),
Token(name='variable_value_end', value='\n', path='', code=css, location=(1, 10), referenced_at=None), Token(name='variable_value_end', value='\n', path='', code=css, location=(1, 10), referenced_by=None),
Token(name='selector_start_class', value='.thing', path='', code=css, location=(2, 0), referenced_at=None), Token(name='selector_start_class', value='.thing', path='', code=css, location=(2, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 6), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(2, 6), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(2, 7), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(2, 7), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 8), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(2, 8), referenced_by=None),
Token(name='declaration_name', value='border:', path='', code=css, location=(2, 9), referenced_at=None), Token(name='declaration_name', value='border:', path='', code=css, location=(2, 9), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 16), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(2, 16), referenced_by=None),
Token(name='number', value='6', path='', code=css, location=(1, 4), Token(name='number', value='6', path='', code=css, location=(1, 4),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 5), Token(name='whitespace', value=' ', path='', code=css, location=(1, 5),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='number', value='2', path='', code=css, location=(0, 4), Token(name='number', value='2', path='', code=css, location=(0, 4),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), Token(name='whitespace', value=' ', path='', code=css, location=(0, 5),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='number', value='4', path='', code=css, location=(0, 6), Token(name='number', value='4', path='', code=css, location=(0, 6),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 8), Token(name='whitespace', value=' ', path='', code=css, location=(1, 8),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='number', value='2', path='', code=css, location=(1, 9), Token(name='number', value='2', path='', code=css, location=(1, 9),
referenced_at=ReferencedAt(name='y', location=(2, 17), length=2)), referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
Token(name='whitespace', value=' ', path='', code=css, location=(2, 19), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(2, 19), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(2, 20), referenced_at=None) Token(name='declaration_set_end', value='}', path='', code=css, location=(2, 20), referenced_by=None)
]
def test_variable_used_inside_property_value(self):
css = "$x: red\n.thing { border: on $x; }"
assert list(substitute_references(tokenize(css, ""))) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='token', value='red', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 7), referenced_by=None),
Token(name='selector_start_class', value='.thing', path='', code=css, location=(1, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 6), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(1, 7), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 8), referenced_by=None),
Token(name='declaration_name', value='border:', path='', code=css, location=(1, 9), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 16), referenced_by=None),
Token(name='token', value='on', path='', code=css, location=(1, 17), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 19), referenced_by=None),
Token(name='token', value='red', path='', code=css, location=(0, 4),
referenced_by=ReferencedBy(name='x', location=(1, 20), length=2)),
Token(name='declaration_end', value=';', path='', code=css, location=(1, 22), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 23), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(1, 24), referenced_by=None)
] ]

View File

@@ -20,65 +20,65 @@ VALID_VARIABLE_NAMES = [
def test_variable_declaration_valid_names(name): def test_variable_declaration_valid_names(name):
css = f"${name}: black on red;" css = f"${name}: black on red;"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value=f'${name}:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value=f'${name}:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 14), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 14), referenced_by=None),
Token(name='token', value='black', path='', code=css, location=(0, 15), referenced_at=None), Token(name='token', value='black', path='', code=css, location=(0, 15), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 20), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 20), referenced_by=None),
Token(name='token', value='on', path='', code=css, location=(0, 21), referenced_at=None), Token(name='token', value='on', path='', code=css, location=(0, 21), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 23), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 23), referenced_by=None),
Token(name='token', value='red', path='', code=css, location=(0, 24), referenced_at=None), Token(name='token', value='red', path='', code=css, location=(0, 24), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 27), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 27), referenced_by=None),
] ]
def test_variable_declaration_multiple_values(): def test_variable_declaration_multiple_values():
css = "$x: 2vw\t4% 6s red;" css = "$x: 2vw\t4% 6s red;"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='scalar', value='2vw', path='', code=css, location=(0, 4), referenced_at=None), Token(name='scalar', value='2vw', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='whitespace', value='\t', path='', code=css, location=(0, 7), referenced_at=None), Token(name='whitespace', value='\t', path='', code=css, location=(0, 7), referenced_by=None),
Token(name='scalar', value='4%', path='', code=css, location=(0, 8), referenced_at=None), Token(name='scalar', value='4%', path='', code=css, location=(0, 8), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 10), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 10), referenced_by=None),
Token(name='duration', value='6s', path='', code=css, location=(0, 11), referenced_at=None), Token(name='duration', value='6s', path='', code=css, location=(0, 11), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 13), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 13), referenced_by=None),
Token(name='token', value='red', path='', code=css, location=(0, 15), referenced_at=None), Token(name='token', value='red', path='', code=css, location=(0, 15), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 18), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 18), referenced_by=None),
] ]
def test_variable_declaration_comment_ignored(): def test_variable_declaration_comment_ignored():
css = "$x: red; /* comment */" css = "$x: red; /* comment */"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='token', value='red', path='', code=css, location=(0, 4), referenced_at=None), Token(name='token', value='red', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 7), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 7), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 8), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 8), referenced_by=None),
] ]
def test_variable_declaration_comment_interspersed_ignored(): def test_variable_declaration_comment_interspersed_ignored():
css = "$x: re/* comment */d;" css = "$x: re/* comment */d;"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='token', value='re', path='', code=css, location=(0, 4), referenced_at=None), Token(name='token', value='re', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='token', value='d', path='', code=css, location=(0, 19), referenced_at=None), Token(name='token', value='d', path='', code=css, location=(0, 19), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 20), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 20), referenced_by=None),
] ]
def test_variable_declaration_no_semicolon(): def test_variable_declaration_no_semicolon():
css = "$x: 1\n$y: 2" css = "$x: 1\n$y: 2"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_at=None), Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 5), referenced_at=None), Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_at=None), Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_by=None),
Token(name='number', value='2', path='', code=css, location=(1, 4), referenced_at=None), Token(name='number', value='2', path='', code=css, location=(1, 4), referenced_by=None),
] ]
@@ -92,68 +92,68 @@ def test_variables_declarations_amongst_rulesets():
css = "$x:1; .thing{text:red;} $y:2;" css = "$x:1; .thing{text:red;} $y:2;"
tokens = list(tokenize(css, "")) tokens = list(tokenize(css, ""))
assert tokens == [ assert tokens == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_at=None), Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='selector_start_class', value='.thing', path='', code=css, location=(0, 6), referenced_at=None), Token(name='selector_start_class', value='.thing', path='', code=css, location=(0, 6), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 12), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 12), referenced_by=None),
Token(name='declaration_name', value='text:', path='', code=css, location=(0, 13), referenced_at=None), Token(name='declaration_name', value='text:', path='', code=css, location=(0, 13), referenced_by=None),
Token(name='token', value='red', path='', code=css, location=(0, 18), referenced_at=None), Token(name='token', value='red', path='', code=css, location=(0, 18), referenced_by=None),
Token(name='declaration_end', value=';', path='', code=css, location=(0, 21), referenced_at=None), Token(name='declaration_end', value=';', path='', code=css, location=(0, 21), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 22), referenced_at=None), Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 22), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 23), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 23), referenced_by=None),
Token(name='variable_name', value='$y:', path='', code=css, location=(0, 24), referenced_at=None), Token(name='variable_name', value='$y:', path='', code=css, location=(0, 24), referenced_by=None),
Token(name='number', value='2', path='', code=css, location=(0, 27), referenced_at=None), Token(name='number', value='2', path='', code=css, location=(0, 27), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 28), referenced_at=None), Token(name='variable_value_end', value=';', path='', code=css, location=(0, 28), referenced_by=None),
] ]
def test_variables_reference_in_rule_declaration_value(): def test_variables_reference_in_rule_declaration_value():
css = ".warn{text: $warning;}" css = ".warn{text: $warning;}"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='selector_start_class', value='.warn', path='', code=css, location=(0, 0), referenced_at=None), Token(name='selector_start_class', value='.warn', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='declaration_name', value='text:', path='', code=css, location=(0, 6), referenced_at=None), Token(name='declaration_name', value='text:', path='', code=css, location=(0, 6), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 11), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 11), referenced_by=None),
Token(name='variable_ref', value='$warning', path='', code=css, location=(0, 12), referenced_at=None), Token(name='variable_ref', value='$warning', path='', code=css, location=(0, 12), referenced_by=None),
Token(name='declaration_end', value=';', path='', code=css, location=(0, 20), referenced_at=None), Token(name='declaration_end', value=';', path='', code=css, location=(0, 20), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 21), referenced_at=None), Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 21), referenced_by=None),
] ]
def test_variables_reference_in_rule_declaration_value_multiple(): def test_variables_reference_in_rule_declaration_value_multiple():
css = ".card{padding: $pad-y $pad-x;}" css = ".card{padding: $pad-y $pad-x;}"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='selector_start_class', value='.card', path='', code=css, location=(0, 0), referenced_at=None), Token(name='selector_start_class', value='.card', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5), referenced_at=None), Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5), referenced_by=None),
Token(name='declaration_name', value='padding:', path='', code=css, location=(0, 6), referenced_at=None), Token(name='declaration_name', value='padding:', path='', code=css, location=(0, 6), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 14), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 14), referenced_by=None),
Token(name='variable_ref', value='$pad-y', path='', code=css, location=(0, 15), referenced_at=None), Token(name='variable_ref', value='$pad-y', path='', code=css, location=(0, 15), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 21), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 21), referenced_by=None),
Token(name='variable_ref', value='$pad-x', path='', code=css, location=(0, 22), referenced_at=None), Token(name='variable_ref', value='$pad-x', path='', code=css, location=(0, 22), referenced_by=None),
Token(name='declaration_end', value=';', path='', code=css, location=(0, 28), referenced_at=None), Token(name='declaration_end', value=';', path='', code=css, location=(0, 28), referenced_by=None),
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 29), referenced_at=None) Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 29), referenced_by=None)
] ]
def test_variables_reference_in_variable_declaration(): def test_variables_reference_in_variable_declaration():
css = "$x: $y;" css = "$x: $y;"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='variable_ref', value='$y', path='', code=css, location=(0, 4), referenced_at=None), Token(name='variable_ref', value='$y', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 6), referenced_at=None) Token(name='variable_value_end', value=';', path='', code=css, location=(0, 6), referenced_by=None)
] ]
def test_variable_references_in_variable_declaration_multiple(): def test_variable_references_in_variable_declaration_multiple():
css = "$x: $y $z\n" css = "$x: $y $z\n"
assert list(tokenize(css, "")) == [ assert list(tokenize(css, "")) == [
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_at=None), Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
Token(name='variable_ref', value='$y', path='', code=css, location=(0, 4), referenced_at=None), Token(name='variable_ref', value='$y', path='', code=css, location=(0, 4), referenced_by=None),
Token(name='whitespace', value=' ', path='', code=css, location=(0, 6), referenced_at=None), Token(name='whitespace', value=' ', path='', code=css, location=(0, 6), referenced_by=None),
Token(name='variable_ref', value='$z', path='', code=css, location=(0, 8), referenced_at=None), Token(name='variable_ref', value='$z', path='', code=css, location=(0, 8), referenced_by=None),
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 10), referenced_at=None) Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 10), referenced_by=None)
] ]