From 29ca559ed93a59e4d41537fe823f7fa2d203ab95 Mon Sep 17 00:00:00 2001
From: Guillaume Ayoub <guillaume@courtbouillon.org>
Date: Tue, 28 Nov 2023 22:41:08 +0100
Subject: [PATCH] Allow var functions in shorthand properties

Related to #1219.
---
 tests/test_variables.py                  | 153 +++++++++++++++++++-
 weasyprint/css/__init__.py               |  21 ++-
 weasyprint/css/validation/__init__.py    |   2 +-
 weasyprint/css/validation/descriptors.py |   2 +-
 weasyprint/css/validation/expanders.py   | 175 ++++++++++++++---------
 weasyprint/css/validation/properties.py  |   2 +-
 6 files changed, 280 insertions(+), 75 deletions(-)

diff --git a/tests/test_variables.py b/tests/test_variables.py
index a10471bd0..a335cac29 100644
--- a/tests/test_variables.py
+++ b/tests/test_variables.py
@@ -3,7 +3,7 @@
 import pytest
 from weasyprint.css.properties import KNOWN_PROPERTIES
 
-from .testing_utils import assert_no_logs, render_pages
+from .testing_utils import assert_no_logs, capture_logs, render_pages
 
 SIDES = ('top', 'right', 'bottom', 'left')
 
@@ -106,7 +106,7 @@ def test_variable_chain_root_missing():
 
 
 @assert_no_logs
-def test_variable_partial_1():
+def test_variable_shorthand_margin():
     page, = render_pages('''
       <style>
         html { --var: 10px }
@@ -123,6 +123,155 @@ def test_variable_partial_1():
     assert div.margin_left == 10
 
 
+@assert_no_logs
+def test_variable_shorthand_margin_multiple():
+    page, = render_pages('''
+      <style>
+        html { --var1: 10px; --var2: 20px }
+        div { margin: var(--var2) 0 0 var(--var1) }
+      </style>
+      <div></div>
+    ''')
+    html, = page.children
+    body, = html.children
+    div, = body.children
+    assert div.margin_top == 20
+    assert div.margin_right == 0
+    assert div.margin_bottom == 0
+    assert div.margin_left == 10
+
+
+@assert_no_logs
+def test_variable_shorthand_margin_invalid():
+    with capture_logs() as logs:
+        page, = render_pages('''
+          <style>
+            html { --var: blue }
+            div { margin: 0 0 0 var(--var) }
+          </style>
+          <div></div>
+        ''')
+        log, = logs
+        assert 'invalid value' in log
+    html, = page.children
+    body, = html.children
+    div, = body.children
+    assert div.margin_top == 0
+    assert div.margin_right == 0
+    assert div.margin_bottom == 0
+    assert div.margin_left == 0
+
+
+@assert_no_logs
+def test_variable_shorthand_border():
+    page, = render_pages('''
+      <style>
+        html { --var: 1px solid blue }
+        div { border: var(--var) }
+      </style>
+      <div></div>
+    ''')
+    html, = page.children
+    body, = html.children
+    div, = body.children
+    assert div.border_top_width == 1
+    assert div.border_right_width == 1
+    assert div.border_bottom_width == 1
+    assert div.border_left_width == 1
+
+
+@assert_no_logs
+def test_variable_shorthand_border_side():
+    page, = render_pages('''
+      <style>
+        html { --var: 1px solid blue }
+        div { border-top: var(--var) }
+      </style>
+      <div></div>
+    ''')
+    html, = page.children
+    body, = html.children
+    div, = body.children
+    assert div.border_top_width == 1
+    assert div.border_right_width == 0
+    assert div.border_bottom_width == 0
+    assert div.border_left_width == 0
+
+
+@assert_no_logs
+def test_variable_shorthand_border_mixed():
+    page, = render_pages('''
+      <style>
+        html { --var: 1px solid }
+        div { border: blue var(--var) }
+      </style>
+      <div></div>
+    ''')
+    html, = page.children
+    body, = html.children
+    div, = body.children
+    assert div.border_top_width == 1
+    assert div.border_right_width == 1
+    assert div.border_bottom_width == 1
+    assert div.border_left_width == 1
+
+
+def test_variable_shorthand_border_mixed_invalid():
+    with capture_logs() as logs:
+        page, = render_pages('''
+          <style>
+            html { --var: 1px solid blue }
+            div { border: blue var(--var) }
+          </style>
+          <div></div>
+        ''')
+        # TODO: we should only get one warning here
+        assert 'multiple color values' in logs[0]
+    html, = page.children
+    body, = html.children
+    div, = body.children
+    assert div.border_top_width == 0
+    assert div.border_right_width == 0
+    assert div.border_bottom_width == 0
+    assert div.border_left_width == 0
+
+
+@assert_no_logs
+@pytest.mark.parametrize('var, background', (
+    ('blue', 'var(--v)'),
+    ('padding-box url(pattern.png)', 'var(--v)'),
+    ('padding-box url(pattern.png)', 'white var(--v) center'),
+    ('100%', 'url(pattern.png) var(--v) var(--v) / var(--v) var(--v)'),
+    ('left / 100%', 'url(pattern.png) top var(--v) 100%'),
+))
+def test_variable_shorthand_background(var, background):
+    page, = render_pages('''
+      <style>
+        html { --v: %s }
+        div { background: %s }
+      </style>
+      <div></div>
+    ''' % (var, background))
+
+
+@pytest.mark.parametrize('var, background', (
+    ('invalid', 'var(--v)'),
+    ('blue', 'var(--v) var(--v)'),
+    ('100%', 'url(pattern.png) var(--v) var(--v) var(--v)'),
+))
+def test_variable_shorthand_background_invalid(var, background):
+    with capture_logs() as logs:
+        page, = render_pages('''
+          <style>
+            html { --v: %s }
+            div { background: %s }
+          </style>
+          <div></div>
+        ''' % (var, background))
+        log, = logs
+        assert 'invalid value' in log
+
+
 @assert_no_logs
 def test_variable_initial():
     page, = render_pages('''
diff --git a/weasyprint/css/__init__.py b/weasyprint/css/__init__.py
index 94b852af5..8ada03e6c 100644
--- a/weasyprint/css/__init__.py
+++ b/weasyprint/css/__init__.py
@@ -26,11 +26,11 @@
 from .computed_values import (
     COMPUTER_FUNCTIONS, ZERO_PIXELS, compute_var, resolve_var)
 from .properties import INHERITED, INITIAL_NOT_COMPUTED, INITIAL_VALUES
-from .utils import check_var_function, get_url, remove_whitespace
+from .utils import (
+    InvalidValues, check_var_function, get_url, remove_whitespace)
 from .validation import preprocess_declarations
 from .validation.descriptors import preprocess_descriptors
 from .validation.expanders import PendingExpander
-from .validation.properties import validate_non_shorthand
 
 # Reject anything not in here:
 PSEUDO_ELEMENTS = (
@@ -732,6 +732,8 @@ def __missing__(self, key):
             return self[key]
 
         if isinstance(value, PendingExpander):
+            # Expander with pending values, validate them.
+            computed = False
             solved_tokens = []
             for token in value.tokens:
                 variable = check_var_function(token)
@@ -743,8 +745,19 @@ def __missing__(self, key):
                 else:
                     solved_tokens.append(token)
             original_key = key.replace('_', '-')
-            solved = value.solve(solved_tokens, original_key)
-            value = validate_non_shorthand(None, original_key, solved)[0][1]
+            try:
+                value = value.solve(solved_tokens, original_key)
+            except InvalidValues:
+                if key in INHERITED:
+                    # Values in parent_style are already computed.
+                    self[key] = value = parent_style[key]
+                    return value
+                else:
+                    value = INITIAL_VALUES[key]
+                    if key not in INITIAL_NOT_COMPUTED:
+                        # The value is the same as when computed.
+                        self[key] = value
+                        return value
 
         if not computed and key in COMPUTER_FUNCTIONS:
             # Value not computed yet: compute.
diff --git a/weasyprint/css/validation/__init__.py b/weasyprint/css/validation/__init__.py
index cd860a96e..6da52769e 100644
--- a/weasyprint/css/validation/__init__.py
+++ b/weasyprint/css/validation/__init__.py
@@ -115,7 +115,7 @@ def validation_error(level, reason):
         tokens = remove_whitespace(declaration.value)
         try:
             # Use list() to consume generators now and catch any error.
-            result = list(expander(base_url, name, tokens))
+            result = list(expander(tokens, name, base_url))
         except InvalidValues as exc:
             validation_error(
                 'warning',
diff --git a/weasyprint/css/validation/descriptors.py b/weasyprint/css/validation/descriptors.py
index 096231d54..138f6c3b3 100644
--- a/weasyprint/css/validation/descriptors.py
+++ b/weasyprint/css/validation/descriptors.py
@@ -198,7 +198,7 @@ def font_variant(tokens):
     for name, sub_tokens in expand_font_variant(tokens):
         try:
             values.append(properties.validate_non_shorthand(
-                None, f'font-variant{name}', sub_tokens, required=True))
+                sub_tokens, f'font-variant{name}', None, required=True))
         except InvalidValues:
             return None
     return values
diff --git a/weasyprint/css/validation/expanders.py b/weasyprint/css/validation/expanders.py
index 4a14d955a..d5022fc73 100644
--- a/weasyprint/css/validation/expanders.py
+++ b/weasyprint/css/validation/expanders.py
@@ -5,6 +5,7 @@
 from tinycss2.ast import DimensionToken, IdentToken, NumberToken
 from tinycss2.color3 import parse_color
 
+from ...logger import LOGGER
 from ..properties import INITIAL_VALUES
 from ..utils import (
     InvalidValues, check_var_function, get_keyword, get_single_keyword,
@@ -23,19 +24,49 @@
 
 
 class PendingExpander:
+    """Expander with validation done when defining calculated values."""
+    # See https://drafts.csswg.org/css-variables-2/#variables-in-shorthands.
     def __init__(self, tokens, expander):
         self.tokens = tokens
         self.expander = expander
+        self._reported_error = False
 
     def solve(self, tokens, wanted_key):
-        for key, value in self.expander(tokens=tokens):
-            if key.startswith('-'):
-                key = f'{self.expander.keywords["name"]}{key}'
-            if key == wanted_key:
-                return value
+        """Get validated value for wanted key."""
+        try:
+            for key, value in self.expander(tokens):
+                if key.startswith('-'):
+                    key = f'{self.expander.keywords["name"]}{key}'
+                if key == wanted_key:
+                    return value
+        except InvalidValues as exc:
+            if self._reported_error:
+                raise exc
+            source_line = tokens[0].source_line
+            source_column = tokens[0].source_column
+            prop = self.expander.keywords["name"]
+            value = ' '.join(token.serialize() for token in tokens)
+            if exc.args and exc.args[0]:
+                message = exc.args[0]
+            else:
+                message = 'invalid value'
+            LOGGER.warning(
+                'Ignored `%s: %s` at %d:%d, %s.',
+                prop, value, source_line, source_column, message)
+            self._reported_error = True
+            raise exc
         raise KeyError
 
 
+def _find_var(tokens, expander, expanded_names):
+    """Return pending expanders when var is found in tokens."""
+    for token in tokens:
+        if check_var_function(token):
+            # Found CSS variable, keep pending-substitution values.
+            pending = PendingExpander(tokens, expander)
+            return {name: pending for name in expanded_names}
+
+
 def expander(property_name):
     """Decorator adding a function to the ``EXPANDERS``."""
     def expander_decorator(function):
@@ -60,29 +91,27 @@ def generic_expander(*expanded_names, **kwargs):
     def generic_expander_decorator(wrapped):
         """Decorate the ``wrapped`` expander."""
         @functools.wraps(wrapped)
-        def generic_expander_wrapper(base_url, name, tokens):
+        def generic_expander_wrapper(tokens, name, base_url):
             """Wrap the expander."""
-            expander = functools.partial(wrapped, name=name)
-            if wants_base_url:
-                expander = functools.partial(expander, base_url=base_url)
+            expander = functools.partial(
+                generic_expander_wrapper, name=name, base_url=base_url)
 
             skip_validation = False
             keyword = get_single_keyword(tokens)
             if keyword in ('inherit', 'initial'):
-                results = dict.fromkeys(expanded_names, keyword)
+                results = {name: keyword for name in expanded_names}
                 skip_validation = True
             else:
-                for token in tokens:
-                    if check_var_function(token):
-                        # Found CSS variable, keep pending-substitution values.
-                        pending = PendingExpander(tokens, expander)
-                        results = dict.fromkeys(expanded_names, pending)
-                        skip_validation = True
-                        break
+                results = _find_var(tokens, expander, expanded_names)
+                if results:
+                    skip_validation = True
 
             if not skip_validation:
                 results = {}
-                result = expander(tokens=tokens)
+                if wants_base_url:
+                    result = wrapped(tokens, name, base_url)
+                else:
+                    result = wrapped(tokens, name)
                 for new_name, new_token in result:
                     assert new_name in expanded_names, new_name
                     if new_name in results:
@@ -94,7 +123,7 @@ def generic_expander_wrapper(base_url, name, tokens):
             for new_name in expanded_names:
                 if new_name.startswith('-'):
                     # new_name is a suffix
-                    actual_new_name = name + new_name
+                    actual_new_name = f'{name}{new_name}'
                 else:
                     actual_new_name = new_name
 
@@ -103,7 +132,7 @@ def generic_expander_wrapper(base_url, name, tokens):
                     if not skip_validation:
                         # validate_non_shorthand returns ((name, value),)
                         (actual_new_name, value), = validate_non_shorthand(
-                            base_url, actual_new_name, value, required=True)
+                            value, actual_new_name, base_url, required=True)
                 else:
                     value = 'initial'
 
@@ -118,9 +147,25 @@ def generic_expander_wrapper(base_url, name, tokens):
 @expander('margin')
 @expander('padding')
 @expander('bleed')
-def expand_four_sides(base_url, name, tokens):
+def expand_four_sides(tokens, name, base_url):
     """Expand properties setting a token for the four sides of a box."""
-    # Make sure we have 4 tokens
+    # Define expanded names.
+    expanded_names = []
+    for suffix in ('-top', '-right', '-bottom', '-left'):
+        if (i := name.rfind('-')) == -1:
+            expanded_names.append(f'{name}{suffix}')
+        else:
+            # eg. border-color becomes border-*-color, not border-color-*
+            expanded_names.append(f'{name[:i]}{suffix}{name[i:]}')
+
+    # Return pending expanders if var is found.
+    expander = functools.partial(
+        expand_four_sides, name=name, base_url=base_url)
+    if result := _find_var(tokens, expander, expanded_names):
+        yield from result.items()
+        return
+
+    # Make sure we have 4 tokens.
     if len(tokens) == 1:
         tokens *= 4
     elif len(tokens) == 2:
@@ -130,18 +175,11 @@ def expand_four_sides(base_url, name, tokens):
     elif len(tokens) != 4:
         raise InvalidValues(
             f'Expected 1 to 4 token components got {len(tokens)}')
-    for suffix, token in zip(('-top', '-right', '-bottom', '-left'), tokens):
-        i = name.rfind('-')
-        if i == -1:
-            new_name = name + suffix
-        else:
-            # eg. border-color becomes border-*-color, not border-color-*
-            new_name = name[:i] + suffix + name[i:]
-
+    for expanded_name, token in zip(expanded_names, tokens):
         # validate_non_shorthand returns ((name, value),), we want
-        # to yield (name, value)
+        # to yield (name, value).
         result, = validate_non_shorthand(
-            base_url, new_name, [token], required=True)
+            [token], expanded_name, base_url, required=True)
         yield result
 
 
@@ -150,7 +188,7 @@ def expand_four_sides(base_url, name, tokens):
     'border-top-left-radius', 'border-top-right-radius',
     'border-bottom-right-radius', 'border-bottom-left-radius',
     wants_base_url=True)
-def border_radius(name, tokens, base_url):
+def border_radius(tokens, name, base_url):
     """Validator for the ``border-radius`` property."""
     current = horizontal = []
     vertical = []
@@ -183,13 +221,13 @@ def border_radius(name, tokens, base_url):
     corners = ('top-left', 'top-right', 'bottom-right', 'bottom-left')
     for corner, tokens in zip(corners, zip(horizontal, vertical)):
         new_name = f'border-{corner}-radius'
-        validate_non_shorthand(base_url, new_name, tokens, required=True)
+        validate_non_shorthand(tokens, new_name, base_url, required=True)
         yield new_name, tokens
 
 
 @expander('list-style')
 @generic_expander('-type', '-position', '-image', wants_base_url=True)
-def expand_list_style(name, tokens, base_url):
+def expand_list_style(tokens, name, base_url):
     """Expand the ``list-style`` shorthand property.
 
     See https://www.w3.org/TR/CSS21/generate.html#propdef-list-style
@@ -231,15 +269,14 @@ def expand_list_style(name, tokens, base_url):
 
 
 @expander('border')
-def expand_border(base_url, name, tokens):
+def expand_border(tokens, name, base_url):
     """Expand the ``border`` shorthand property.
 
     See https://www.w3.org/TR/CSS21/box.html#propdef-border
 
     """
     for suffix in ('-top', '-right', '-bottom', '-left'):
-        for new_prop in expand_border_side(base_url, name + suffix, tokens):
-            yield new_prop
+        yield from expand_border_side(tokens, f'{name}{suffix}', base_url)
 
 
 @expander('border-top')
@@ -249,7 +286,7 @@ def expand_border(base_url, name, tokens):
 @expander('column-rule')
 @expander('outline')
 @generic_expander('-width', '-color', '-style')
-def expand_border_side(name, tokens):
+def expand_border_side(tokens, name):
     """Expand the ``border-*`` shorthand properties.
 
     See https://www.w3.org/TR/CSS21/box.html#propdef-border-top
@@ -268,29 +305,35 @@ def expand_border_side(name, tokens):
 
 
 @expander('background')
-def expand_background(base_url, name, tokens):
+def expand_background(tokens, name, base_url):
     """Expand the ``background`` shorthand property.
 
     See https://drafts.csswg.org/css-backgrounds-3/#the-background
 
     """
-    properties = [
-        'background_color', 'background_image', 'background_repeat',
-        'background_attachment', 'background_position', 'background_size',
-        'background_clip', 'background_origin']
+    expanded_names = (
+        'background-color', 'background-image', 'background-repeat',
+        'background-attachment', 'background-position', 'background-size',
+        'background-clip', 'background-origin')
     keyword = get_single_keyword(tokens)
     if keyword in ('initial', 'inherit'):
-        for name in properties:
+        for name in expanded_names:
             yield name, keyword
         return
 
+    expander = functools.partial(
+        expand_background, name=name, base_url=base_url)
+    if result := _find_var(tokens, expander, expanded_names):
+        yield from result.items()
+        return
+
     def parse_layer(tokens, final_layer=False):
         results = {}
 
         def add(name, value):
             if value is None:
                 return False
-            name = f'background_{name}'
+            name = f'background-{name}'
             if name in results:
                 raise InvalidValues
             results[name] = value
@@ -346,15 +389,15 @@ def add(name, value):
             raise InvalidValues
 
         color = results.pop(
-            'background_color', INITIAL_VALUES['background_color'])
-        for name in properties:
-            if name not in results:
-                results[name] = INITIAL_VALUES[name][0]
+            'background-color', INITIAL_VALUES['background_color'])
+        for name in expanded_names:
+            if name not in results and name != 'background-color':
+                results[name] = INITIAL_VALUES[name.replace('-', '_')][0]
         return color, results
 
     layers = reversed(split_on_comma(tokens))
     color, last_layer = parse_layer(next(layers), final_layer=True)
-    results = dict((k, [v]) for k, v in last_layer.items())
+    results = {key: [value] for key, value in last_layer.items()}
     for tokens in layers:
         _, layer = parse_layer(tokens)
         for name, value in layer.items():
@@ -366,7 +409,7 @@ def add(name, value):
 
 @expander('text-decoration')
 @generic_expander('-line', '-color', '-style')
-def expand_text_decoration(name, tokens):
+def expand_text_decoration(tokens, name):
     """Expand the ``text-decoration`` shorthand property."""
     text_decoration_line = []
     text_decoration_color = []
@@ -404,7 +447,7 @@ def expand_text_decoration(name, tokens):
         yield '-style', text_decoration_style
 
 
-def expand_page_break_before_after(name, tokens):
+def expand_page_break_before_after(tokens, name):
     """Expand legacy ``page-break-before`` and ``page-break-after`` properties.
 
     See https://www.w3.org/TR/css-break-3/#page-break-properties
@@ -424,29 +467,29 @@ def expand_page_break_before_after(name, tokens):
 
 @expander('page-break-after')
 @generic_expander('break-after')
-def expand_page_break_after(name, tokens):
+def expand_page_break_after(tokens, name):
     """Expand legacy ``page-break-after`` property.
 
     See https://www.w3.org/TR/css-break-3/#page-break-properties
 
     """
-    return expand_page_break_before_after(name, tokens)
+    return expand_page_break_before_after(tokens, name)
 
 
 @expander('page-break-before')
 @generic_expander('break-before')
-def expand_page_break_before(name, tokens):
+def expand_page_break_before(tokens, name):
     """Expand legacy ``page-break-before`` property.
 
     See https://www.w3.org/TR/css-break-3/#page-break-properties
 
     """
-    return expand_page_break_before_after(name, tokens)
+    return expand_page_break_before_after(tokens, name)
 
 
 @expander('page-break-inside')
 @generic_expander('break-inside')
-def expand_page_break_inside(name, tokens):
+def expand_page_break_inside(tokens, name):
     """Expand the legacy ``page-break-inside`` property.
 
     See https://www.w3.org/TR/css-break-3/#page-break-properties
@@ -461,7 +504,7 @@ def expand_page_break_inside(name, tokens):
 
 @expander('columns')
 @generic_expander('column-width', 'column-count')
-def expand_columns(name, tokens):
+def expand_columns(tokens, name):
     """Expand the ``columns`` shorthand property."""
     name = None
     if len(tokens) == 2 and get_keyword(tokens[0]) == 'auto':
@@ -484,7 +527,7 @@ def expand_columns(name, tokens):
 @expander('font-variant')
 @generic_expander('-alternates', '-caps', '-east-asian', '-ligatures',
                   '-numeric', '-position')
-def font_variant(name, tokens):
+def font_variant(tokens, name):
     """Expand the ``font-variant`` shorthand property.
 
     https://www.w3.org/TR/css-fonts-3/#font-variant-prop
@@ -496,7 +539,7 @@ def font_variant(name, tokens):
 @expander('font')
 @generic_expander('-style', '-variant-caps', '-weight', '-stretch', '-size',
                   'line-height', '-family')  # line-height is not a suffix
-def expand_font(name, tokens):
+def expand_font(tokens, name):
     """Expand the ``font`` shorthand property.
 
     https://www.w3.org/TR/css-fonts-3/#font-prop
@@ -568,7 +611,7 @@ def expand_font(name, tokens):
 
 @expander('word-wrap')
 @generic_expander('overflow-wrap')
-def expand_word_wrap(name, tokens):
+def expand_word_wrap(tokens, name):
     """Expand the ``word-wrap`` legacy property.
 
     See https://www.w3.org/TR/css-text-3/#overflow-wrap
@@ -582,7 +625,7 @@ def expand_word_wrap(name, tokens):
 
 @expander('flex')
 @generic_expander('-grow', '-shrink', '-basis')
-def expand_flex(name, tokens):
+def expand_flex(tokens, name):
     """Expand the ``flex`` property."""
     keyword = get_single_keyword(tokens)
     if keyword == 'none':
@@ -640,7 +683,7 @@ def expand_flex(name, tokens):
 
 @expander('flex-flow')
 @generic_expander('flex-direction', 'flex-wrap')
-def expand_flex_flow(name, tokens):
+def expand_flex_flow(tokens, name):
     """Expand the ``flex-flow`` property."""
     if len(tokens) == 2:
         for sorted_tokens in tokens, tokens[::-1]:
@@ -668,7 +711,7 @@ def expand_flex_flow(name, tokens):
 
 @expander('line-clamp')
 @generic_expander('max-lines', 'continue', 'block-ellipsis')
-def expand_line_clamp(name, tokens):
+def expand_line_clamp(tokens, name):
     """Expand the ``line-clamp`` property."""
     if len(tokens) == 1:
         keyword = get_single_keyword(tokens)
@@ -708,7 +751,7 @@ def expand_line_clamp(name, tokens):
 
 @expander('text-align')
 @generic_expander('-all', '-last')
-def expand_text_align(name, tokens):
+def expand_text_align(tokens, name):
     """Expand the ``text-align`` property."""
     if len(tokens) == 1:
         keyword = get_single_keyword(tokens)
diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py
index 6cdfcb003..8392b92da 100644
--- a/weasyprint/css/validation/properties.py
+++ b/weasyprint/css/validation/properties.py
@@ -75,7 +75,7 @@ def decorator(function):
     return decorator
 
 
-def validate_non_shorthand(base_url, name, tokens, required=False):
+def validate_non_shorthand(tokens, name, base_url, required=False):
     """Validator for non-shorthand properties."""
     if name.startswith('--'):
         # TODO: validate content