Repository: vega/altair (language: python)

Issue:
Improve `expr` method signatures
# Problem

While I was working on https://github.com/vega/altair/pull/3544, I tried out re-writing some expression strings to use `alt.expr`.
This idea stems from what I saw as a poor UX - that has a relatively simple solution with multiple benefits.

## Related
- *"expressions are hard to debug"* in https://github.com/vega/altair/discussions/3476
- https://github.com/vega/altair/issues/3365

## Minimal Repro
Based on https://github.com/vega/altair/commit/a681ec53cb57524f1fd347200df94c88c1d151ef

<details><summary>Code block</summary>
<p>

```py
import altair as alt

label = alt.datum.label
window_lead_label = alt.datum.window_lead_label

source = {
    "values": [
        {"label": "Begin", "amount": 4000},
        {"label": "Jan", "amount": 1707},
        {"label": "Feb", "amount": -1425},
        {"label": "Mar", "amount": -1030},
        {"label": "Apr", "amount": 1812},
        {"label": "May", "amount": -1067},
        {"label": "Jun", "amount": -1481},
        {"label": "Jul", "amount": 1228},
        {"label": "Aug", "amount": 1176},
        {"label": "Sep", "amount": 1146},
        {"label": "Oct", "amount": 1205},
        {"label": "Nov", "amount": -1388},
        {"label": "Dec", "amount": 1492},
        {"label": "End", "amount": 0},
    ]
}

chart = (
    alt.Chart(source)
    .mark_bar(size=45)
    .encode(alt.X("label:O", sort=None))
    .transform_window(window_lead_label="lead(label)")
    .transform_calculate(calc_lead=alt.expr.if_(window_lead_label == None, label))
)
>>> alt.expr.if_(window_lead_label == None, label)
>>> chart
```
</p>
</details> 

## Visual Feedback

<details><summary>Screenshot</summary>
<p>

![image](https://github.com/user-attachments/assets/efaa7546-622d-4145-b789-67c4bf57000b)

</p>
</details> 

- Error is only present **after** validation
- The error itself is not useful if the spec contains *more than one* `expr`
- Also not clear *which* argument(s) are missing

# Solution
Replacing the `alt.expr` classmethod(s) `*args` parameter with named [positional-only parameter(s)](https://peps.python.org/pep-0570/).

## Benefits
- Raising a `TypeError` at the time of `expr` definition
	- Rather than silently passing until validated within a `ChartType`
	- Importantly, this is within `python` and **not** `javascript`
- Providing a meaningful traceback, identifying both the `expr` and the missing argument
- Opens the door for defining default argument values
	-  E.g. in [waterfall_chart](https://altair-viz.github.io/gallery/waterfall_chart.html), there are repeat uses of `""` for the `elseValue`
	- If this is a common case, we could use that as a default and it would be clear by the signature alone
- The current docstrings - which refer to parameters by name - would be easier to understand
	- Right now, you'd need to refer to [Vega Expressions](https://vega.github.io/vega/docs/expressions/) to determine the order they must appear in
- The change **would not** add any new requirements to currently valid specs
	- Therefore, suitable as a [**MINOR**](https://altair-viz.github.io/about/versioning.html#version-numbers) version feature

## Example (`alt.expr.if_`)
Although there are a **large number** of signatures to be updated, the process for each should be as simple as refering to the docstring or [Vega Expressions](https://vega.github.io/vega/docs/expressions/)

![image](https://github.com/user-attachments/assets/f1f75cea-d0db-4cce-9dbe-c16c0e8a731c)

## Drawbacks
The only downside I can see - hypothetically - is if there is a utility in having invalid expressions at `expr` definition time?
Maybe there is some use-case here I'm unaware of, but it seems unlikely to me it could outweigh the traceback improvement


Discussion / hints:
I like it! Sounds like a big UX improvement for the Python-side of the expression syntax. And I also don't see any drawback to this, defining invalid expressions could still be done by just skipping the `expr` functionalities and providing strings (just in case anyone would need this which I also don't expect).
I also think it would be great to have better error messages from expressions, thanks for exploring this! Directly to your comment about using `alt.expr` in the docs instead of expression strings: I think we would actually like to do this everywhere once the full functionality of expression strings is present in `alt.expr` (the only one I'm aware is missing currently is https://github.com/vega/altair/issues/3366). The changes you suggest here are a further improvement and makes the case even stronger for switching to `alt.expr` everywhere.
@mattijn @jonmmease do you guys have any thoughts on this idea?

(*providing everyone was onboard*) 
I think we could implement this quite quickly; especially if anyone else wanted to help spread the workload out 🤝 
I believe the Vega Expressions syntax is a powerful asset of the grammar that remains under-utilised. I'm very supportive with any attempt to improve it, such as your suggestion here to introduce positional-only arguments.

However, if a broader overhaul is needed to improve the ergonomics, making it more accessible and pythonic, I am also in favour. And to improve this properly I think this requires a major version bump, and it would be great to have one. So yeah, good idea, but if your brain can see further, also great!😊
Exciting! I like the idea of trying to get to a v6. That would allow us to work on these: https://github.com/vega/altair/issues?q=is%3Aopen+is%3Aissue+label%3Av6 Especially, #2918 could be of big help for users.

Fix patch:
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index 1f93ac2b7..38d87f4c5 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -1,8 +1,12 @@
+# The contents of this file are automatically written by
+# tools/generate_schema_wrapper.py. Do not modify directly.
+
 """Tools for creating transform & filter expressions with a python syntax."""
 
 from __future__ import annotations
 
 import sys
+from typing import TYPE_CHECKING, Any
 
 from altair.expr.core import ConstExpression, FunctionExpression
 from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
@@ -12,58 +16,65 @@
 else:
     from typing_extensions import override
 
+if TYPE_CHECKING:
+    from altair.expr.core import Expression, IntoExpression
+
+
+class _ExprMeta(type):
+    """
+    Metaclass for :class:`expr`.
 
-class _ConstExpressionType(type):
-    """Metaclass providing read-only class properties for :class:`expr`."""
+    Currently providing read-only class properties, representing JavaScript constants.
+    """
 
     @property
-    def NaN(cls) -> ConstExpression:
+    def NaN(cls) -> Expression:
         """Not a number (same as JavaScript literal NaN)."""
         return ConstExpression("NaN")
 
     @property
-    def LN10(cls) -> ConstExpression:
+    def LN10(cls) -> Expression:
         """The natural log of 10 (alias to Math.LN10)."""
         return ConstExpression("LN10")
 
     @property
-    def E(cls) -> ConstExpression:
+    def E(cls) -> Expression:
         """The transcendental number e (alias to Math.E)."""
         return ConstExpression("E")
 
     @property
-    def LOG10E(cls) -> ConstExpression:
+    def LOG10E(cls) -> Expression:
         """The base 10 logarithm e (alias to Math.LOG10E)."""
         return ConstExpression("LOG10E")
 
     @property
-    def LOG2E(cls) -> ConstExpression:
+    def LOG2E(cls) -> Expression:
         """The base 2 logarithm of e (alias to Math.LOG2E)."""
         return ConstExpression("LOG2E")
 
     @property
-    def SQRT1_2(cls) -> ConstExpression:
+    def SQRT1_2(cls) -> Expression:
         """The square root of 0.5 (alias to Math.SQRT1_2)."""
         return ConstExpression("SQRT1_2")
 
     @property
-    def LN2(cls) -> ConstExpression:
+    def LN2(cls) -> Expression:
         """The natural log of 2 (alias to Math.LN2)."""
         return ConstExpression("LN2")
 
     @property
-    def SQRT2(cls) -> ConstExpression:
+    def SQRT2(cls) -> Expression:
         """The square root of 2 (alias to Math.SQRT1_2)."""
         return ConstExpression("SQRT2")
 
     @property
-    def PI(cls) -> ConstExpression:
+    def PI(cls) -> Expression:
         """The transcendental number pi (alias to Math.PI)."""
         return ConstExpression("PI")
 
 
-class expr(_ExprRef, metaclass=_ConstExpressionType):
-    r"""
+class expr(_ExprRef, metaclass=_ExprMeta):
+    """
     Utility providing *constants* and *classmethods* to construct expressions.
 
     `Expressions`_ can be used to write basic formulas that enable custom interactions.
@@ -110,1321 +121,1803 @@ class expr(_ExprRef, metaclass=_ConstExpressionType):
 
     @override
     def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef:  # type: ignore[misc]
-        # NOTE: `mypy<=1.10.1` is not consistent with typing spec
-        # https://github.com/python/mypy/issues/1020
-        # https://docs.python.org/3/reference/datamodel.html#object.__new__
-        # https://typing.readthedocs.io/en/latest/spec/constructors.html#new-method
         return _ExprRef(expr=expr)
 
     @classmethod
-    def if_(cls, *args) -> FunctionExpression:
-        """
-        If *test* is truthy, returns *thenValue*. Otherwise, returns *elseValue*.
-
-        The *if* function is equivalent to the ternary operator `a ? b : c`.
-        """
-        return FunctionExpression("if", args)
+    def isArray(cls, value: IntoExpression, /) -> Expression:
+        """Returns true if ``value`` is an array, false otherwise."""
+        return FunctionExpression("isArray", (value,))
 
     @classmethod
-    def isArray(cls, *args) -> FunctionExpression:
-        """Returns true if *value* is an array, false otherwise."""
-        return FunctionExpression("isArray", args)
+    def isBoolean(cls, value: IntoExpression, /) -> Expression:
+        """Returns true if ``value`` is a boolean (``true`` or ``false``), false otherwise."""
+        return FunctionExpression("isBoolean", (value,))
 
     @classmethod
-    def isBoolean(cls, *args) -> FunctionExpression:
-        """Returns true if *value* is a boolean (`true` or `false`), false otherwise."""
-        return FunctionExpression("isBoolean", args)
-
-    @classmethod
-    def isDate(cls, *args) -> FunctionExpression:
+    def isDate(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns true if *value* is a Date object, false otherwise.
+        Returns true if ``value`` is a Date object, false otherwise.
 
-        This method will return false for timestamp numbers or date-formatted strings; it recognizes Date objects only.
+        This method will return false for timestamp numbers or date-formatted strings; it recognizes
+        Date objects only.
         """
-        return FunctionExpression("isDate", args)
+        return FunctionExpression("isDate", (value,))
 
     @classmethod
-    def isDefined(cls, *args) -> FunctionExpression:
+    def isDefined(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns true if *value* is a defined value, false if *value* equals `undefined`.
+        Returns true if ``value`` is a defined value, false if ``value`` equals ``undefined``.
 
-        This method will return true for `null` and `NaN` values.
+        This method will return true for ``null`` and ``NaN`` values.
         """
-        return FunctionExpression("isDefined", args)
+        return FunctionExpression("isDefined", (value,))
 
     @classmethod
-    def isNumber(cls, *args) -> FunctionExpression:
+    def isNumber(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns true if *value* is a number, false otherwise.
+        Returns true if ``value`` is a number, false otherwise.
 
-        `NaN` and `Infinity` are considered numbers.
+        ``NaN`` and ``Infinity`` are considered numbers.
         """
-        return FunctionExpression("isNumber", args)
+        return FunctionExpression("isNumber", (value,))
+
+    @classmethod
+    def isObject(cls, value: IntoExpression, /) -> Expression:
+        """Returns true if ``value`` is an object (including arrays and Dates), false otherwise."""
+        return FunctionExpression("isObject", (value,))
 
     @classmethod
-    def isObject(cls, *args) -> FunctionExpression:
-        """Returns true if *value* is an object (including arrays and Dates), false otherwise."""
-        return FunctionExpression("isObject", args)
+    def isRegExp(cls, value: IntoExpression, /) -> Expression:
+        """Returns true if ``value`` is a RegExp (regular expression) object, false otherwise."""
+        return FunctionExpression("isRegExp", (value,))
 
     @classmethod
-    def isRegExp(cls, *args) -> FunctionExpression:
-        """Returns true if *value* is a RegExp (regular expression) object, false otherwise."""
-        return FunctionExpression("isRegExp", args)
+    def isString(cls, value: IntoExpression, /) -> Expression:
+        """Returns true if ``value`` is a string, false otherwise."""
+        return FunctionExpression("isString", (value,))
 
     @classmethod
-    def isString(cls, *args) -> FunctionExpression:
-        """Returns true if *value* is a string, false otherwise."""
-        return FunctionExpression("isString", args)
+    def isValid(cls, value: IntoExpression, /) -> Expression:
+        """Returns true if ``value`` is not ``null``, ``undefined``, or ``NaN``, false otherwise."""
+        return FunctionExpression("isValid", (value,))
 
     @classmethod
-    def isValid(cls, *args) -> FunctionExpression:
-        """Returns true if *value* is not `null`, `undefined`, or `NaN`, false otherwise."""
-        return FunctionExpression("isValid", args)
+    def toBoolean(cls, value: IntoExpression, /) -> Expression:
+        """
+        Coerces the input ``value`` to a string.
+
+        Null values and empty strings are mapped to ``null``.
+        """
+        return FunctionExpression("toBoolean", (value,))
 
     @classmethod
-    def toBoolean(cls, *args) -> FunctionExpression:
+    def toDate(cls, value: IntoExpression, /) -> Expression:
         """
-        Coerces the input *value* to a string.
+        Coerces the input ``value`` to a Date instance.
 
-        Null values and empty strings are mapped to `null`.
+        Null values and empty strings are mapped to ``null``. If an optional *parser* function is
+        provided, it is used to perform date parsing, otherwise ``Date.parse`` is used. Be aware
+        that ``Date.parse`` has different implementations across browsers!
         """
-        return FunctionExpression("toBoolean", args)
+        return FunctionExpression("toDate", (value,))
 
     @classmethod
-    def toDate(cls, *args) -> FunctionExpression:
+    def toNumber(cls, value: IntoExpression, /) -> Expression:
         """
-        Coerces the input *value* to a Date instance.
+        Coerces the input ``value`` to a number.
 
-        Null values and empty strings are mapped to `null`.
-        If an optional *parser* function is provided, it is used to perform date parsing, otherwise `Date.parse` is used.
-        Be aware that `Date.parse` has different implementations across browsers!
+        Null values and empty strings are mapped to ``null``.
         """
-        return FunctionExpression("toDate", args)
+        return FunctionExpression("toNumber", (value,))
 
     @classmethod
-    def toNumber(cls, *args) -> FunctionExpression:
+    def toString(cls, value: IntoExpression, /) -> Expression:
         """
-        Coerces the input *value* to a number.
+        Coerces the input ``value`` to a string.
 
-        Null values and empty strings are mapped to `null`.
+        Null values and empty strings are mapped to ``null``.
         """
-        return FunctionExpression("toNumber", args)
+        return FunctionExpression("toString", (value,))
 
     @classmethod
-    def toString(cls, *args) -> FunctionExpression:
+    def if_(
+        cls,
+        test: IntoExpression,
+        thenValue: IntoExpression,
+        elseValue: IntoExpression,
+        /,
+    ) -> Expression:
         """
-        Coerces the input *value* to a string.
+        If ``test`` is truthy, returns ``thenValue``.
 
-        Null values and empty strings are mapped to `null`.
+        Otherwise, returns ``elseValue``. The *if* function is equivalent to the ternary operator
+        ``a ? b : c``.
         """
-        return FunctionExpression("toString", args)
+        return FunctionExpression("if", (test, thenValue, elseValue))
 
     @classmethod
-    def isNaN(cls, *args) -> FunctionExpression:
+    def isNaN(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns true if *value* is not a number.
+        Returns true if ``value`` is not a number.
+
+        Same as JavaScript's `Number.isNaN`_.
 
-        Same as JavaScript's `isNaN`.
+        .. _Number.isNaN:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNan
         """
-        return FunctionExpression("isNaN", args)
+        return FunctionExpression("isNaN", (value,))
 
     @classmethod
-    def isFinite(cls, *args) -> FunctionExpression:
+    def isFinite(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns true if *value* is a finite number.
+        Returns true if ``value`` is a finite number.
+
+        Same as JavaScript's `Number.isFinite`_.
 
-        Same as JavaScript's `isFinite`.
+        .. _Number.isFinite:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
         """
-        return FunctionExpression("isFinite", args)
+        return FunctionExpression("isFinite", (value,))
 
     @classmethod
-    def abs(cls, *args) -> FunctionExpression:
+    def abs(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns the absolute value of *value*.
+        Returns the absolute value of ``value``.
 
-        Same as JavaScript's `Math.abs`.
+        Same as JavaScript's `Math.abs`_.
+
+        .. _Math.abs:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
         """
-        return FunctionExpression("abs", args)
+        return FunctionExpression("abs", (value,))
 
     @classmethod
-    def acos(cls, *args) -> FunctionExpression:
+    def acos(cls, value: IntoExpression, /) -> Expression:
         """
         Trigonometric arccosine.
 
-        Same as JavaScript's `Math.acos`.
+        Same as JavaScript's `Math.acos`_.
+
+        .. _Math.acos:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
         """
-        return FunctionExpression("acos", args)
+        return FunctionExpression("acos", (value,))
 
     @classmethod
-    def asin(cls, *args) -> FunctionExpression:
+    def asin(cls, value: IntoExpression, /) -> Expression:
         """
         Trigonometric arcsine.
 
-        Same as JavaScript's `Math.asin`.
+        Same as JavaScript's `Math.asin`_.
+
+        .. _Math.asin:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
         """
-        return FunctionExpression("asin", args)
+        return FunctionExpression("asin", (value,))
 
     @classmethod
-    def atan(cls, *args) -> FunctionExpression:
+    def atan(cls, value: IntoExpression, /) -> Expression:
         """
         Trigonometric arctangent.
 
-        Same as JavaScript's `Math.atan`.
+        Same as JavaScript's `Math.atan`_.
+
+        .. _Math.atan:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
         """
-        return FunctionExpression("atan", args)
+        return FunctionExpression("atan", (value,))
 
     @classmethod
-    def atan2(cls, *args) -> FunctionExpression:
+    def atan2(cls, dy: IntoExpression, dx: IntoExpression, /) -> Expression:
         """
         Returns the arctangent of *dy / dx*.
 
-        Same as JavaScript's `Math.atan2`.
+        Same as JavaScript's `Math.atan2`_.
+
+        .. _Math.atan2:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
         """
-        return FunctionExpression("atan2", args)
+        return FunctionExpression("atan2", (dy, dx))
 
     @classmethod
-    def ceil(cls, *args) -> FunctionExpression:
+    def ceil(cls, value: IntoExpression, /) -> Expression:
         """
-        Rounds *value* to the nearest integer of equal or greater value.
+        Rounds ``value`` to the nearest integer of equal or greater value.
+
+        Same as JavaScript's `Math.ceil`_.
 
-        Same as JavaScript's `Math.ceil`.
+        .. _Math.ceil:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
         """
-        return FunctionExpression("ceil", args)
+        return FunctionExpression("ceil", (value,))
 
     @classmethod
-    def clamp(cls, *args) -> FunctionExpression:
-        """Restricts *value* to be between the specified *min* and *max*."""
-        return FunctionExpression("clamp", args)
+    def clamp(
+        cls, value: IntoExpression, min: IntoExpression, max: IntoExpression, /
+    ) -> Expression:
+        """Restricts ``value`` to be between the specified ``min`` and ``max``."""
+        return FunctionExpression("clamp", (value, min, max))
 
     @classmethod
-    def cos(cls, *args) -> FunctionExpression:
+    def cos(cls, value: IntoExpression, /) -> Expression:
         """
         Trigonometric cosine.
 
-        Same as JavaScript's `Math.cos`.
+        Same as JavaScript's `Math.cos`_.
+
+        .. _Math.cos:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
         """
-        return FunctionExpression("cos", args)
+        return FunctionExpression("cos", (value,))
 
     @classmethod
-    def exp(cls, *args) -> FunctionExpression:
+    def exp(cls, exponent: IntoExpression, /) -> Expression:
         """
-        Returns the value of *e* raised to the provided *exponent*.
+        Returns the value of *e* raised to the provided ``exponent``.
+
+        Same as JavaScript's `Math.exp`_.
 
-        Same as JavaScript's `Math.exp`.
+        .. _Math.exp:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
         """
-        return FunctionExpression("exp", args)
+        return FunctionExpression("exp", (exponent,))
 
     @classmethod
-    def floor(cls, *args) -> FunctionExpression:
+    def floor(cls, value: IntoExpression, /) -> Expression:
         """
-        Rounds *value* to the nearest integer of equal or lower value.
+        Rounds ``value`` to the nearest integer of equal or lower value.
+
+        Same as JavaScript's `Math.floor`_.
 
-        Same as JavaScript's `Math.floor`.
+        .. _Math.floor:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
         """
-        return FunctionExpression("floor", args)
+        return FunctionExpression("floor", (value,))
 
     @classmethod
-    def hypot(cls, *args) -> FunctionExpression:
+    def hypot(cls, value: IntoExpression, /) -> Expression:
         """
         Returns the square root of the sum of squares of its arguments.
 
-        Same as JavaScript's `Math.hypot`.
+        Same as JavaScript's `Math.hypot`_.
+
+        .. _Math.hypot:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
         """
-        return FunctionExpression("hypot", args)
+        return FunctionExpression("hypot", (value,))
 
     @classmethod
-    def log(cls, *args) -> FunctionExpression:
+    def log(cls, value: IntoExpression, /) -> Expression:
         """
-        Returns the natural logarithm of *value*.
+        Returns the natural logarithm of ``value``.
+
+        Same as JavaScript's `Math.log`_.
 
-        Same as JavaScript's `Math.log`.
+        .. _Math.log:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
         """
-        return FunctionExpression("log", args)
+        return FunctionExpression("log", (value,))
 
     @classmethod
-    def max(cls, *args) -> FunctionExpression:
+    def max(
+        cls, value1: IntoExpression, value2: IntoExpression, *args: Any
+    ) -> Expression:
         """
         Returns the maximum argument value.
 
-        Same as JavaScript's `Math.max`.
+        Same as JavaScript's `Math.max`_.
+
+        .. _Math.max:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
         """
-        return FunctionExpression("max", args)
+        return FunctionExpression("max", (value1, value2, *args))
 
     @classmethod
-    def min(cls, *args) -> FunctionExpression:
+    def min(
+        cls, value1: IntoExpression, value2: IntoExpression, *args: Any
+    ) -> Expression:
         """
         Returns the minimum argument value.
 
-        Same as JavaScript's `Math.min`.
+        Same as JavaScript's `Math.min`_.
+
+        .. _Math.min:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
         """
-        return FunctionExpression("min", args)
+        return FunctionExpression("min", (value1, value2, *args))
 
     @classmethod
-    def pow(cls, *args) -> FunctionExpression:
+    def pow(cls, value: IntoExpression, exponent: IntoExpression, /) -> Expression:
         """
-        Returns *value* raised to the given *exponent*.
+        Returns ``value`` raised to the given ``exponent``.
+
+        Same as JavaScript's `Math.pow`_.
 
-        Same as JavaScript's `Math.pow`.
+        .. _Math.pow:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
         """
-        return FunctionExpression("pow", args)
+        return FunctionExpression("pow", (value, exponent))
 
     @classmethod
-    def random(cls, *args) -> FunctionExpression:
+    def random(cls) -> Expression:
         """
-        Returns a pseudo-random number in the range `[0, 1]`.
+        Returns a pseudo-random number in the range [0,1).
+
+        Same as JavaScript's `Math.random`_.
 
-        Same as JavaScript's `Math.random`.
+        .. _Math.random:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
         """
-        return FunctionExpression("random", args)
+        return FunctionExpression("random", ())
 
     @classmethod
-    def round(cls, *args) -> FunctionExpression:
+    def round(cls, value: IntoExpression, /) -> Expression:
         """
-        Rounds *value* to the nearest integer.
+        Rounds ``value`` to the nearest integer.
 
-        Same as JavaScript's `Math.round`.
+        Same as JavaScript's `Math.round`_.
+
+        .. _Math.round:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
         """
-        return FunctionExpression("round", args)
+        return FunctionExpression("round", (value,))
 
     @classmethod
-    def sin(cls, *args) -> FunctionExpression:
+    def sin(cls, value: IntoExpression, /) -> Expression:
         """
         Trigonometric sine.
 
-        Same as JavaScript's `Math.sin`.
+        Same as JavaScript's `Math.sin`_.
+
+        .. _Math.sin:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
         """
-        return FunctionExpression("sin", args)
+        return FunctionExpression("sin", (value,))
 
     @classmethod
-    def sqrt(cls, *args) -> FunctionExpression:
+    def sqrt(cls, value: IntoExpression, /) -> Expression:
         """
         Square root function.
 
-        Same as JavaScript's `Math.sqrt`.
+        Same as JavaScript's `Math.sqrt`_.
+
+        .. _Math.sqrt:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
         """
-        return FunctionExpression("sqrt", args)
+        return FunctionExpression("sqrt", (value,))
 
     @classmethod
-    def tan(cls, *args) -> FunctionExpression:
+    def tan(cls, value: IntoExpression, /) -> Expression:
         """
         Trigonometric tangent.
 
-        Same as JavaScript's `Math.tan`.
+        Same as JavaScript's `Math.tan`_.
+
+        .. _Math.tan:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
         """
-        return FunctionExpression("tan", args)
+        return FunctionExpression("tan", (value,))
 
     @classmethod
-    def sampleNormal(cls, *args) -> FunctionExpression:
+    def sampleNormal(
+        cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a sample from a univariate `normal (Gaussian) probability distribution <https://en.wikipedia.org/wiki/Normal_distribution>`__ with specified *mean* and standard deviation *stdev*.
+        Returns a sample from a univariate `normal (Gaussian) probability distribution`_ with specified ``mean`` and standard deviation ``stdev``.
+
+        If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
 
-        If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+        .. _normal (Gaussian) probability distribution:
+            https://en.wikipedia.org/wiki/Normal_distribution
         """
-        return FunctionExpression("sampleNormal", args)
+        return FunctionExpression("sampleNormal", (mean, stdev))
 
     @classmethod
-    def cumulativeNormal(cls, *args) -> FunctionExpression:
+    def cumulativeNormal(
+        cls,
+        value: IntoExpression,
+        mean: IntoExpression = None,
+        stdev: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the value of the `cumulative distribution function <https://en.wikipedia.org/wiki/Cumulative_distribution_function>`__ at the given input domain *value* for a normal distribution with specified *mean* and standard deviation *stdev*.
+        Returns the value of the `cumulative distribution function`_ at the given input domain ``value`` for a normal distribution with specified ``mean`` and standard deviation ``stdev``.
+
+        If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
 
-        If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+        .. _cumulative distribution function:
+            https://en.wikipedia.org/wiki/Cumulative_distribution_function
         """
-        return FunctionExpression("cumulativeNormal", args)
+        return FunctionExpression("cumulativeNormal", (value, mean, stdev))
 
     @classmethod
-    def densityNormal(cls, *args) -> FunctionExpression:
+    def densityNormal(
+        cls,
+        value: IntoExpression,
+        mean: IntoExpression = None,
+        stdev: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the value of the `probability density function <https://en.wikipedia.org/wiki/Probability_density_function>`__ at the given input domain *value*, for a normal distribution with specified *mean* and standard deviation *stdev*.
+        Returns the value of the `probability density function`_ at the given input domain ``value``, for a normal distribution with specified ``mean`` and standard deviation ``stdev``.
 
-        If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+        If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
+
+        .. _probability density function:
+            https://en.wikipedia.org/wiki/Probability_density_function
         """
-        return FunctionExpression("densityNormal", args)
+        return FunctionExpression("densityNormal", (value, mean, stdev))
 
     @classmethod
-    def quantileNormal(cls, *args) -> FunctionExpression:
+    def quantileNormal(
+        cls,
+        probability: IntoExpression,
+        mean: IntoExpression = None,
+        stdev: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the quantile value (the inverse of the `cumulative distribution function <https://en.wikipedia.org/wiki/Cumulative_distribution_function)>`__ for the given input *probability*, for a normal distribution with specified *mean* and standard deviation *stdev*.
+        Returns the quantile value (the inverse of the `cumulative distribution function`_) for the given input ``probability``, for a normal distribution with specified ``mean`` and standard deviation ``stdev``.
+
+        If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
 
-        If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+        .. _cumulative distribution function:
+            https://en.wikipedia.org/wiki/Cumulative_distribution_function
         """
-        return FunctionExpression("quantileNormal", args)
+        return FunctionExpression("quantileNormal", (probability, mean, stdev))
 
     @classmethod
-    def sampleLogNormal(cls, *args) -> FunctionExpression:
+    def sampleLogNormal(
+        cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a sample from a univariate `log-normal probability distribution <https://en.wikipedia.org/wiki/Log-normal_distribution>`__ with specified log *mean* and log standard deviation *stdev*.
+        Returns a sample from a univariate `log-normal probability distribution`_ with specified log ``mean`` and log standard deviation ``stdev``.
+
+        If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+        ``1``.
 
-        If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
+        .. _log-normal probability distribution:
+            https://en.wikipedia.org/wiki/Log-normal_distribution
         """
-        return FunctionExpression("sampleLogNormal", args)
+        return FunctionExpression("sampleLogNormal", (mean, stdev))
 
     @classmethod
-    def cumulativeLogNormal(cls, *args) -> FunctionExpression:
+    def cumulativeLogNormal(
+        cls,
+        value: IntoExpression,
+        mean: IntoExpression = None,
+        stdev: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the value of the `cumulative distribution function <https://en.wikipedia.org/wiki/Cumulative_distribution_function>`__ at the given input domain *value* for a log-normal distribution with specified log *mean* and log standard deviation *stdev*.
+        Returns the value of the `cumulative distribution function`_ at the given input domain ``value`` for a log-normal distribution with specified log ``mean`` and log standard deviation ``stdev``.
 
-        If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
+        If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+        ``1``.
+
+        .. _cumulative distribution function:
+            https://en.wikipedia.org/wiki/Cumulative_distribution_function
         """
-        return FunctionExpression("cumulativeLogNormal", args)
+        return FunctionExpression("cumulativeLogNormal", (value, mean, stdev))
 
     @classmethod
-    def densityLogNormal(cls, *args) -> FunctionExpression:
+    def densityLogNormal(
+        cls,
+        value: IntoExpression,
+        mean: IntoExpression = None,
+        stdev: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the value of the `probability density function <https://en.wikipedia.org/wiki/Probability_density_function>`__ at the given input domain *value*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*.
+        Returns the value of the `probability density function`_ at the given input domain ``value``, for a log-normal distribution with specified log ``mean`` and log standard deviation ``stdev``.
+
+        If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+        ``1``.
 
-        If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
+        .. _probability density function:
+            https://en.wikipedia.org/wiki/Probability_density_function
         """
-        return FunctionExpression("densityLogNormal", args)
+        return FunctionExpression("densityLogNormal", (value, mean, stdev))
 
     @classmethod
-    def quantileLogNormal(cls, *args) -> FunctionExpression:
+    def quantileLogNormal(
+        cls,
+        probability: IntoExpression,
+        mean: IntoExpression = None,
+        stdev: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the quantile value (the inverse of the `cumulative distribution function <https://en.wikipedia.org/wiki/Cumulative_distribution_function)>`__ for the given input *probability*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*.
+        Returns the quantile value (the inverse of the `cumulative distribution function`_) for the given input ``probability``, for a log-normal distribution with specified log ``mean`` and log standard deviation ``stdev``.
+
+        If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+        ``1``.
 
-        If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
+        .. _cumulative distribution function:
+            https://en.wikipedia.org/wiki/Cumulative_distribution_function
         """
-        return FunctionExpression("quantileLogNormal", args)
+        return FunctionExpression("quantileLogNormal", (probability, mean, stdev))
 
     @classmethod
-    def sampleUniform(cls, *args) -> FunctionExpression:
+    def sampleUniform(
+        cls, min: IntoExpression = None, max: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a sample from a univariate `continuous uniform probability distribution <https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)>`__ over the interval `[min, max]`.
+        Returns a sample from a univariate `continuous uniform probability distribution`_) over the interval [``min``, ``max``).
 
-        If unspecified, *min* defaults to `0` and *max* defaults to `1`.
-        If only one argument is provided, it is interpreted as the *max* value.
+        If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+        argument is provided, it is interpreted as the ``max`` value.
+
+        .. _continuous uniform probability distribution:
+            https://en.wikipedia.org/wiki/Uniform_distribution_(continuous
         """
-        return FunctionExpression("sampleUniform", args)
+        return FunctionExpression("sampleUniform", (min, max))
 
     @classmethod
-    def cumulativeUniform(cls, *args) -> FunctionExpression:
+    def cumulativeUniform(
+        cls,
+        value: IntoExpression,
+        min: IntoExpression = None,
+        max: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the value of the `cumulative distribution function <https://en.wikipedia.org/wiki/Cumulative_distribution_function>`__ at the given input domain *value* for a uniform distribution over the interval `[min, max]`.
+        Returns the value of the `cumulative distribution function`_ at the given input domain ``value`` for a uniform distribution over the interval [``min``, ``max``).
+
+        If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+        argument is provided, it is interpreted as the ``max`` value.
 
-        If unspecified, *min* defaults to `0` and *max* defaults to `1`.
-        If only one argument is provided, it is interpreted as the *max* value.
+        .. _cumulative distribution function:
+            https://en.wikipedia.org/wiki/Cumulative_distribution_function
         """
-        return FunctionExpression("cumulativeUniform", args)
+        return FunctionExpression("cumulativeUniform", (value, min, max))
 
     @classmethod
-    def densityUniform(cls, *args) -> FunctionExpression:
+    def densityUniform(
+        cls,
+        value: IntoExpression,
+        min: IntoExpression = None,
+        max: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the value of the `probability density function <https://en.wikipedia.org/wiki/Probability_density_function>`__ at the given input domain *value*,  for a uniform distribution over the interval `[min, max]`.
+        Returns the value of the `probability density function`_ at the given input domain ``value``,  for a uniform distribution over the interval [``min``, ``max``).
 
-        If unspecified, *min* defaults to `0` and *max* defaults to `1`.
-        If only one argument is provided, it is interpreted as the *max* value.
+        If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+        argument is provided, it is interpreted as the ``max`` value.
+
+        .. _probability density function:
+            https://en.wikipedia.org/wiki/Probability_density_function
         """
-        return FunctionExpression("densityUniform", args)
+        return FunctionExpression("densityUniform", (value, min, max))
 
     @classmethod
-    def quantileUniform(cls, *args) -> FunctionExpression:
+    def quantileUniform(
+        cls,
+        probability: IntoExpression,
+        min: IntoExpression = None,
+        max: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the quantile value (the inverse of the `cumulative distribution function <https://en.wikipedia.org/wiki/Cumulative_distribution_function)>`__ for the given input *probability*, for a uniform distribution over the interval `[min, max]`.
+        Returns the quantile value (the inverse of the `cumulative distribution function`_) for the given input ``probability``,  for a uniform distribution over the interval [``min``, ``max``).
+
+        If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+        argument is provided, it is interpreted as the ``max`` value.
 
-        If unspecified, *min* defaults to `0` and *max* defaults to `1`.
-        If only one argument is provided, it is interpreted as the *max* value.
+        .. _cumulative distribution function:
+            https://en.wikipedia.org/wiki/Cumulative_distribution_function
         """
-        return FunctionExpression("quantileUniform", args)
+        return FunctionExpression("quantileUniform", (probability, min, max))
 
     @classmethod
-    def now(cls, *args) -> FunctionExpression:
+    def now(cls) -> Expression:
         """Returns the timestamp for the current time."""
-        return FunctionExpression("now", args)
+        return FunctionExpression("now", ())
 
     @classmethod
-    def datetime(cls, *args) -> FunctionExpression:
+    def datetime(
+        cls,
+        year: IntoExpression,
+        month: IntoExpression,
+        day: IntoExpression = None,
+        hour: IntoExpression = None,
+        min: IntoExpression = None,
+        sec: IntoExpression = None,
+        millisec: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns a new `Date` instance.
+        Returns a new ``Date`` instance.
 
-        The *month* is 0-based, such that `1` represents February.
+        The ``month`` is 0-based, such that ``1`` represents February.
         """
-        return FunctionExpression("datetime", args)
+        return FunctionExpression(
+            "datetime", (year, month, day, hour, min, sec, millisec)
+        )
 
     @classmethod
-    def date(cls, *args) -> FunctionExpression:
-        """Returns the day of the month for the given *datetime* value, in local time."""
-        return FunctionExpression("date", args)
+    def date(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the day of the month for the given ``datetime`` value, in local time."""
+        return FunctionExpression("date", (datetime,))
 
     @classmethod
-    def day(cls, *args) -> FunctionExpression:
-        """Returns the day of the week for the given *datetime* value, in local time."""
-        return FunctionExpression("day", args)
+    def day(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the day of the week for the given ``datetime`` value, in local time."""
+        return FunctionExpression("day", (datetime,))
 
     @classmethod
-    def dayofyear(cls, *args) -> FunctionExpression:
-        """Returns the one-based day of the year for the given *datetime* value, in local time."""
-        return FunctionExpression("dayofyear", args)
+    def dayofyear(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the one-based day of the year for the given ``datetime`` value, in local time."""
+        return FunctionExpression("dayofyear", (datetime,))
 
     @classmethod
-    def year(cls, *args) -> FunctionExpression:
-        """Returns the year for the given *datetime* value, in local time."""
-        return FunctionExpression("year", args)
+    def year(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the year for the given ``datetime`` value, in local time."""
+        return FunctionExpression("year", (datetime,))
 
     @classmethod
-    def quarter(cls, *args) -> FunctionExpression:
-        """Returns the quarter of the year (0-3) for the given *datetime* value, in local time."""
-        return FunctionExpression("quarter", args)
+    def quarter(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the quarter of the year (0-3) for the given ``datetime`` value, in local time."""
+        return FunctionExpression("quarter", (datetime,))
 
     @classmethod
-    def month(cls, *args) -> FunctionExpression:
-        """Returns the (zero-based) month for the given *datetime* value, in local time."""
-        return FunctionExpression("month", args)
+    def month(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the (zero-based) month for the given ``datetime`` value, in local time."""
+        return FunctionExpression("month", (datetime,))
 
     @classmethod
-    def week(cls, *args) -> FunctionExpression:
+    def week(cls, date: IntoExpression, /) -> Expression:
         """
         Returns the week number of the year for the given *datetime*, in local time.
 
-        This function assumes Sunday-based weeks.
-        Days before the first Sunday of the year are considered to be in week 0,
-        the first Sunday of the year is the start of week 1,
-        the second Sunday week 2, etc.
+        This function assumes Sunday-based weeks. Days before the first Sunday of the year are
+        considered to be in week 0, the first Sunday of the year is the start of week 1, the second
+        Sunday week 2, *etc.*.
         """
-        return FunctionExpression("week", args)
+        return FunctionExpression("week", (date,))
 
     @classmethod
-    def hours(cls, *args) -> FunctionExpression:
-        """Returns the hours component for the given *datetime* value, in local time."""
-        return FunctionExpression("hours", args)
+    def hours(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the hours component for the given ``datetime`` value, in local time."""
+        return FunctionExpression("hours", (datetime,))
 
     @classmethod
-    def minutes(cls, *args) -> FunctionExpression:
-        """Returns the minutes component for the given *datetime* value, in local time."""
-        return FunctionExpression("minutes", args)
+    def minutes(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the minutes component for the given ``datetime`` value, in local time."""
+        return FunctionExpression("minutes", (datetime,))
 
     @classmethod
-    def seconds(cls, *args) -> FunctionExpression:
-        """Returns the seconds component for the given *datetime* value, in local time."""
-        return FunctionExpression("seconds", args)
+    def seconds(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the seconds component for the given ``datetime`` value, in local time."""
+        return FunctionExpression("seconds", (datetime,))
 
     @classmethod
-    def milliseconds(cls, *args) -> FunctionExpression:
-        """Returns the milliseconds component for the given *datetime* value, in local time."""
-        return FunctionExpression("milliseconds", args)
+    def milliseconds(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the milliseconds component for the given ``datetime`` value, in local time."""
+        return FunctionExpression("milliseconds", (datetime,))
 
     @classmethod
-    def time(cls, *args) -> FunctionExpression:
-        """Returns the epoch-based timestamp for the given *datetime* value."""
-        return FunctionExpression("time", args)
+    def time(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the epoch-based timestamp for the given ``datetime`` value."""
+        return FunctionExpression("time", (datetime,))
 
     @classmethod
-    def timezoneoffset(cls, *args) -> FunctionExpression:
-        """Returns the timezone offset from the local timezone to UTC for the given *datetime* value."""
-        return FunctionExpression("timezoneoffset", args)
+    def timezoneoffset(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the timezone offset from the local timezone to UTC for the given ``datetime`` value."""
+        return FunctionExpression("timezoneoffset", (datetime,))
 
     @classmethod
-    def timeOffset(cls, *args) -> FunctionExpression:
+    def timeOffset(
+        cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a new `Date` instance that offsets the given *date* by the specified time `unit <https://vega.github.io/vega/docs/api/time/#time-units>`__ in the local timezone.
+        Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in the local timezone.
+
+        The optional ``step`` argument indicates the number of time unit steps to offset by (default
+        1).
 
-        The optional *step* argument indicates the number of time unit steps to offset by (default 1).
+        .. _*unit*:
+            https://vega.github.io/vega/docs/api/time/#time-units
         """
-        return FunctionExpression("timeOffset", args)
+        return FunctionExpression("timeOffset", (unit, date, step))
 
     @classmethod
-    def timeSequence(cls, *args) -> FunctionExpression:
+    def timeSequence(
+        cls,
+        unit: IntoExpression,
+        start: IntoExpression,
+        stop: IntoExpression,
+        step: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit <https://vega.github.io/vega/docs/api/time/#time-units>`__ in the local timezone.
+        Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in the local timezone.
 
-        The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).
+        The optional ``step`` argument indicates the number of time unit steps to take between each
+        sequence entry (default 1).
+
+        .. _*unit*:
+            https://vega.github.io/vega/docs/api/time/#time-units
         """
-        return FunctionExpression("timeSequence", args)
+        return FunctionExpression("timeSequence", (unit, start, stop, step))
 
     @classmethod
-    def utc(cls, *args) -> FunctionExpression:
-        """Returns a timestamp for the given UTC date. The *month* is 0-based, such that `1` represents February."""
-        return FunctionExpression("utc", args)
+    def utc(
+        cls,
+        year: IntoExpression,
+        month: IntoExpression,
+        day: IntoExpression = None,
+        hour: IntoExpression = None,
+        min: IntoExpression = None,
+        sec: IntoExpression = None,
+        millisec: IntoExpression = None,
+        /,
+    ) -> Expression:
+        """
+        Returns a timestamp for the given UTC date.
+
+        The ``month`` is 0-based, such that ``1`` represents February.
+        """
+        return FunctionExpression("utc", (year, month, day, hour, min, sec, millisec))
 
     @classmethod
-    def utcdate(cls, *args) -> FunctionExpression:
-        """Returns the day of the month for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcdate", args)
+    def utcdate(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the day of the month for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcdate", (datetime,))
 
     @classmethod
-    def utcday(cls, *args) -> FunctionExpression:
-        """Returns the day of the week for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcday", args)
+    def utcday(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the day of the week for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcday", (datetime,))
 
     @classmethod
-    def utcdayofyear(cls, *args) -> FunctionExpression:
-        """Returns the one-based day of the year for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcdayofyear", args)
+    def utcdayofyear(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the one-based day of the year for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcdayofyear", (datetime,))
 
     @classmethod
-    def utcyear(cls, *args) -> FunctionExpression:
-        """Returns the year for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcyear", args)
+    def utcyear(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the year for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcyear", (datetime,))
 
     @classmethod
-    def utcquarter(cls, *args) -> FunctionExpression:
-        """Returns the quarter of the year (0-3) for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcquarter", args)
+    def utcquarter(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the quarter of the year (0-3) for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcquarter", (datetime,))
 
     @classmethod
-    def utcmonth(cls, *args) -> FunctionExpression:
-        """Returns the (zero-based) month for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcmonth", args)
+    def utcmonth(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the (zero-based) month for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcmonth", (datetime,))
 
     @classmethod
-    def utcweek(cls, *args) -> FunctionExpression:
+    def utcweek(cls, date: IntoExpression, /) -> Expression:
         """
         Returns the week number of the year for the given *datetime*, in UTC time.
 
-        This function assumes Sunday-based weeks.
-        Days before the first Sunday of the year are considered to be in week 0,
-        the first Sunday of the year is the start of week 1,
-        the second Sunday week 2, etc.
+        This function assumes Sunday-based weeks. Days before the first Sunday of the year are
+        considered to be in week 0, the first Sunday of the year is the start of week 1, the second
+        Sunday week 2, *etc.*.
         """
-        return FunctionExpression("utcweek", args)
+        return FunctionExpression("utcweek", (date,))
 
     @classmethod
-    def utchours(cls, *args) -> FunctionExpression:
-        """Returns the hours component for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utchours", args)
+    def utchours(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the hours component for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utchours", (datetime,))
 
     @classmethod
-    def utcminutes(cls, *args) -> FunctionExpression:
-        """Returns the minutes component for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcminutes", args)
+    def utcminutes(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the minutes component for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcminutes", (datetime,))
 
     @classmethod
-    def utcseconds(cls, *args) -> FunctionExpression:
-        """Returns the seconds component for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcseconds", args)
+    def utcseconds(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the seconds component for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcseconds", (datetime,))
 
     @classmethod
-    def utcmilliseconds(cls, *args) -> FunctionExpression:
-        """Returns the milliseconds component for the given *datetime* value, in UTC time."""
-        return FunctionExpression("utcmilliseconds", args)
+    def utcmilliseconds(cls, datetime: IntoExpression, /) -> Expression:
+        """Returns the milliseconds component for the given ``datetime`` value, in UTC time."""
+        return FunctionExpression("utcmilliseconds", (datetime,))
 
     @classmethod
-    def utcOffset(cls, *args) -> FunctionExpression:
+    def utcOffset(
+        cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a new `Date` instance that offsets the given *date* by the specified time `unit <https://vega.github.io/vega/docs/api/time/#time-units>`__ in UTC time.
+        Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in UTC time.
+
+        The optional ``step`` argument indicates the number of time unit steps to offset by (default
+        1).
 
-        The optional *step* argument indicates the number of time unit steps to offset by (default 1).
+        .. _*unit*:
+            https://vega.github.io/vega/docs/api/time/#time-units
         """
-        return FunctionExpression("utcOffset", args)
+        return FunctionExpression("utcOffset", (unit, date, step))
 
     @classmethod
-    def utcSequence(cls, *args) -> FunctionExpression:
+    def utcSequence(
+        cls,
+        unit: IntoExpression,
+        start: IntoExpression,
+        stop: IntoExpression,
+        step: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit <https://vega.github.io/vega/docs/api/time/#time-units>`__ in UTC time.
+        Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in UTC time.
+
+        The optional ``step`` argument indicates the number of time unit steps to take between each
+        sequence entry (default 1).
 
-        The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).
+        .. _*unit*:
+            https://vega.github.io/vega/docs/api/time/#time-units
         """
-        return FunctionExpression("utcSequence", args)
+        return FunctionExpression("utcSequence", (unit, start, stop, step))
 
     @classmethod
-    def extent(cls, *args) -> FunctionExpression:
-        """Returns a new `[min, max]` array with the minimum and maximum values of the input array, ignoring `null`, `undefined`, and `NaN` values."""
-        return FunctionExpression("extent", args)
+    def extent(cls, array: IntoExpression, /) -> Expression:
+        """Returns a new *[min, max]* array with the minimum and maximum values of the input array, ignoring ``null``, ``undefined``, and ``NaN`` values."""
+        return FunctionExpression("extent", (array,))
 
     @classmethod
-    def clampRange(cls, *args) -> FunctionExpression:
+    def clampRange(
+        cls, range: IntoExpression, min: IntoExpression, max: IntoExpression, /
+    ) -> Expression:
         """
-        Clamps a two-element *range* array in a span-preserving manner.
+        Clamps a two-element ``range`` array in a span-preserving manner.
 
-        If the span of the input *range* is less than `(max - min)` and an endpoint exceeds either the *min* or *max* value,
-        the range is translated such that the span is preserved and one endpoint touches the boundary of the `[min, max]` range.
-        If the span exceeds `(max - min)`, the range `[min, max]` is returned.
+        If the span of the input ``range`` is less than *(max - min)* and an endpoint exceeds either
+        the ``min`` or ``max`` value, the range is translated such that the span is preserved and
+        one endpoint touches the boundary of the *[min, max]* range. If the span exceeds *(max -
+        min)*, the range *[min, max]* is returned.
         """
-        return FunctionExpression("clampRange", args)
+        return FunctionExpression("clampRange", (range, min, max))
 
     @classmethod
-    def indexof(cls, *args) -> FunctionExpression:
-        """Returns the first index of *value* in the input *array*, or the first index of *substring* in the input *string*."""
-        return FunctionExpression("indexof", args)
+    def indexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
+        """Returns the first index of ``value`` in the input ``array``."""
+        return FunctionExpression("indexof", (array, value))
 
     @classmethod
-    def inrange(cls, *args) -> FunctionExpression:
-        """Tests whether *value* lies within (or is equal to either) the first and last values of the *range* array."""
-        return FunctionExpression("inrange", args)
+    def inrange(cls, value: IntoExpression, range: IntoExpression, /) -> Expression:
+        """Tests whether ``value`` lies within (or is equal to either) the first and last values of the ``range`` array."""
+        return FunctionExpression("inrange", (value, range))
 
     @classmethod
-    def join(cls, *args) -> FunctionExpression:
-        """Returns a new string by concatenating all of the elements of the input *array*, separated by commas or a specified *separator* string."""
-        return FunctionExpression("join", args)
+    def join(
+        cls, array: IntoExpression, separator: IntoExpression = None, /
+    ) -> Expression:
+        """Returns a new string by concatenating all of the elements of the input ``array``, separated by commas or a specified ``separator`` string."""
+        return FunctionExpression("join", (array, separator))
 
     @classmethod
-    def lastindexof(cls, *args) -> FunctionExpression:
-        """Returns the last index of *value* in the input *array*, or the last index of *substring* in the input *string*."""
-        return FunctionExpression("lastindexof", args)
+    def lastindexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
+        """Returns the last index of ``value`` in the input ``array``."""
+        return FunctionExpression("lastindexof", (array, value))
 
     @classmethod
-    def length(cls, *args) -> FunctionExpression:
-        """Returns the length of the input *array*, or the length of the input *string*."""
-        return FunctionExpression("length", args)
+    def length(cls, array: IntoExpression, /) -> Expression:
+        """Returns the length of the input ``array``."""
+        return FunctionExpression("length", (array,))
 
     @classmethod
-    def lerp(cls, *args) -> FunctionExpression:
+    def lerp(cls, array: IntoExpression, fraction: IntoExpression, /) -> Expression:
         """
-        Returns the linearly interpolated value between the first and last entries in the *array* for the provided interpolation *fraction* (typically between 0 and 1).
+        Returns the linearly interpolated value between the first and last entries in the ``array`` for the provided interpolation ``fraction`` (typically between 0 and 1).
 
-        For example, `lerp([0, 50], 0.5)` returns 25.
+        For example, ``alt.expr.lerp([0, 50], 0.5)`` returns 25.
         """
-        return FunctionExpression("lerp", args)
+        return FunctionExpression("lerp", (array, fraction))
 
     @classmethod
-    def peek(cls, *args) -> FunctionExpression:
+    def peek(cls, array: IntoExpression, /) -> Expression:
         """
-        Returns the last element in the input *array*.
+        Returns the last element in the input ``array``.
 
-        Similar to the built-in `Array.pop` method, except that it does not remove the last element.
-        This method is a convenient shorthand for `array[array.length - 1]`.
+        Similar to the built-in ``Array.pop`` method, except that it does not remove the last
+        element. This method is a convenient shorthand for ``array[array.length - 1]``.
         """
-        return FunctionExpression("peek", args)
+        return FunctionExpression("peek", (array,))
 
     @classmethod
-    def pluck(cls, *args) -> FunctionExpression:
+    def pluck(cls, array: IntoExpression, field: IntoExpression, /) -> Expression:
         """
-        Retrieves the value for the specified *field* from a given *array* of objects.
+        Retrieves the value for the specified ``field`` from a given ``array`` of objects.
 
-        The input *field* string may include nested properties (e.g., `foo.bar.bz`).
+        The input ``field`` string may include nested properties (e.g., ``foo.bar.bz``).
         """
-        return FunctionExpression("pluck", args)
+        return FunctionExpression("pluck", (array, field))
 
     @classmethod
-    def reverse(cls, *args) -> FunctionExpression:
+    def reverse(cls, array: IntoExpression, /) -> Expression:
         """
-        Returns a new array with elements in a reverse order of the input *array*.
+        Returns a new array with elements in a reverse order of the input ``array``.
 
         The first array element becomes the last, and the last array element becomes the first.
         """
-        return FunctionExpression("reverse", args)
+        return FunctionExpression("reverse", (array,))
 
     @classmethod
-    def sequence(cls, *args) -> FunctionExpression:
-        r"""
+    def sequence(cls, *args: Any) -> Expression:
+        """
         Returns an array containing an arithmetic sequence of numbers.
 
-        If *step* is omitted, it defaults to 1.
-        If *start* is omitted, it defaults to 0.
-
-        The *stop* value is exclusive; it is not included in the result.
-        If *step* is positive, the last element is the largest `start + i * step` less than *stop*;
-        if *step* is negative, the last element is the smallest `start + i * step` greater than *stop*.
-
-        If the returned array would contain an infinite number of values, an empty range is returned.
-        The arguments are not required to be integers.
+        If ``step`` is omitted, it defaults to 1. If ``start`` is omitted, it defaults to 0. The
+        ``stop`` value is exclusive; it is not included in the result. If ``step`` is positive, the
+        last element is the largest *start + i * step* less than ``stop``; if ``step`` is negative,
+        the last element is the smallest *start + i * step* greater than ``stop``. If the returned
+        array would contain an infinite number of values, an empty range is returned. The arguments
+        are not required to be integers.
         """
         return FunctionExpression("sequence", args)
 
     @classmethod
-    def slice(cls, *args) -> FunctionExpression:
+    def slice(
+        cls, array: IntoExpression, start: IntoExpression, end: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a section of *array* between the *start* and *end* indices.
+        Returns a section of ``array`` between the ``start`` and ``end`` indices.
 
-        If the *end* argument is negative, it is treated as an offset from the end of the array `length(array) + end`.
+        If the ``end`` argument is negative, it is treated as an offset from the end of the array
+        (*alt.expr.length(array) + end*).
         """
-        return FunctionExpression("slice", args)
+        return FunctionExpression("slice", (array, start, end))
 
     @classmethod
-    def span(cls, *args) -> FunctionExpression:
-        """
-        Returns the span of *array*: the difference between the last and first elements, or `array[array.length-1] - array[0]`.
-
-        Or if input is a string: a section of *string* between the *start* and *end* indices.
-        If the *end* argument is negative, it is treated as an offset from the end of the string `length(string) + end`.
-        """
-        return FunctionExpression("span", args)
+    def span(cls, array: IntoExpression, /) -> Expression:
+        """Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
+        return FunctionExpression("span", (array,))
 
     @classmethod
-    def lower(cls, *args) -> FunctionExpression:
-        """Transforms *string* to lower-case letters."""
-        return FunctionExpression("lower", args)
+    def lower(cls, string: IntoExpression, /) -> Expression:
+        """Transforms ``string`` to lower-case letters."""
+        return FunctionExpression("lower", (string,))
 
     @classmethod
-    def pad(cls, *args) -> FunctionExpression:
+    def pad(
+        cls,
+        string: IntoExpression,
+        length: IntoExpression,
+        character: IntoExpression = None,
+        align: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Pads a *string* value with repeated instances of a *character* up to a specified *length*.
+        Pads a ``string`` value with repeated instances of a ``character`` up to a specified ``length``.
 
-        If *character* is not specified, a space (' ') is used.
-        By default, padding is added to the end of a string.
-        An optional *align* parameter specifies if padding should be added to the `'left'` (beginning), `'center'`, or `'right'` (end) of the input string.
+        If ``character`` is not specified, a space (' ') is used. By default, padding is added to
+        the end of a string. An optional ``align`` parameter specifies if padding should be added to
+        the ``'left'`` (beginning), ``'center'``, or ``'right'`` (end) of the input string.
         """
-        return FunctionExpression("pad", args)
+        return FunctionExpression("pad", (string, length, character, align))
 
     @classmethod
-    def parseFloat(cls, *args) -> FunctionExpression:
+    def parseFloat(cls, string: IntoExpression, /) -> Expression:
         """
-        Parses the input *string* to a floating-point value.
+        Parses the input ``string`` to a floating-point value.
 
-        Same as JavaScript's `parseFloat`.
+        Same as JavaScript's ``parseFloat``.
         """
-        return FunctionExpression("parseFloat", args)
+        return FunctionExpression("parseFloat", (string,))
 
     @classmethod
-    def parseInt(cls, *args) -> FunctionExpression:
+    def parseInt(cls, string: IntoExpression, /) -> Expression:
         """
-        Parses the input *string* to an integer value.
+        Parses the input ``string`` to an integer value.
 
-        Same as JavaScript's `parseInt`.
+        Same as JavaScript's ``parseInt``.
         """
-        return FunctionExpression("parseInt", args)
+        return FunctionExpression("parseInt", (string,))
 
     @classmethod
-    def replace(cls, *args) -> FunctionExpression:
+    def replace(
+        cls,
+        string: IntoExpression,
+        pattern: IntoExpression,
+        replacement: IntoExpression,
+        /,
+    ) -> Expression:
         """
-        Returns a new string with some or all matches of *pattern* replaced by a *replacement* string.
+        Returns a new string with some or all matches of ``pattern`` replaced by a ``replacement`` string.
 
-        The *pattern* can be a string or a regular expression.
-        If *pattern* is a string, only the first instance will be replaced.
-        Same as `JavaScript's String.replace <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace>`__.
-        """
-        return FunctionExpression("replace", args)
+        The ``pattern`` can be a string or a regular expression. If ``pattern`` is a string, only
+        the first instance will be replaced. Same as `JavaScript's String.replace`_.
 
-    @classmethod
-    def split(cls, *args) -> FunctionExpression:
+        .. _JavaScript's String.replace:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
         """
-        Returns an array of tokens created by splitting the input *string* according to a provided *separator* pattern.
-
-        The result can optionally be constrained to return at most *limit* tokens.
-        """
-        return FunctionExpression("split", args)
+        return FunctionExpression("replace", (string, pattern, replacement))
 
     @classmethod
-    def substring(cls, *args) -> FunctionExpression:
-        """Returns a section of *string* between the *start* and *end* indices."""
-        return FunctionExpression("substring", args)
+    def substring(
+        cls,
+        string: IntoExpression,
+        start: IntoExpression,
+        end: IntoExpression = None,
+        /,
+    ) -> Expression:
+        """Returns a section of ``string`` between the ``start`` and ``end`` indices."""
+        return FunctionExpression("substring", (string, start, end))
 
     @classmethod
-    def trim(cls, *args) -> FunctionExpression:
+    def trim(cls, string: IntoExpression, /) -> Expression:
         """Returns a trimmed string with preceding and trailing whitespace removed."""
-        return FunctionExpression("trim", args)
+        return FunctionExpression("trim", (string,))
 
     @classmethod
-    def truncate(cls, *args) -> FunctionExpression:
-        r"""
-        Truncates an input *string* to a target *length*.
+    def truncate(
+        cls,
+        string: IntoExpression,
+        length: IntoExpression,
+        align: IntoExpression = None,
+        ellipsis: IntoExpression = None,
+        /,
+    ) -> Expression:
+        """
+        Truncates an input ``string`` to a target ``length``.
 
-        The optional *align* argument indicates what part of the string should be truncated: `'left'` (the beginning), `'center'`, or `'right'` (the end).
-        By default, the `'right'` end of the string is truncated.
-        The optional *ellipsis* argument indicates the string to use to indicate truncated content;
-        by default the ellipsis character `...` (`\\u2026`) is used.
+        The optional ``align`` argument indicates what part of the string should be truncated:
+        ``'left'`` (the beginning), ``'center'``, or ``'right'`` (the end). By default, the
+        ``'right'`` end of the string is truncated. The optional ``ellipsis`` argument indicates the
+        string to use to indicate truncated content; by default the ellipsis character ``…``
+        (``\u2026``) is used.
         """
-        return FunctionExpression("truncate", args)
+        return FunctionExpression("truncate", (string, length, align, ellipsis))
 
     @classmethod
-    def upper(cls, *args) -> FunctionExpression:
-        """Transforms *string* to upper-case letters."""
-        return FunctionExpression("upper", args)
+    def upper(cls, string: IntoExpression, /) -> Expression:
+        """Transforms ``string`` to upper-case letters."""
+        return FunctionExpression("upper", (string,))
 
     @classmethod
-    def merge(cls, *args) -> FunctionExpression:
+    def merge(
+        cls, object1: IntoExpression, object2: IntoExpression = None, *args: Any
+    ) -> Expression:
         """
-        Merges the input objects *object1*, *object2*, etc into a new output object.
-
-        Inputs are visited in sequential order, such that key values from later arguments can overwrite those from earlier arguments.
+        Merges the input objects ``object1``, ``object2``, etc into a new output object.
 
-        Example: `merge({a:1, b:2}, {a:3}) -> {a:3, b:2}`.
+        Inputs are visited in sequential order, such that key values from later arguments can
+        overwrite those from earlier arguments. Example: ``alt.expr.merge({a:1, b:2}, {a:3}) ->
+        {a:3, b:2}``.
         """
-        return FunctionExpression("merge", args)
+        return FunctionExpression("merge", (object1, object2, *args))
 
     @classmethod
-    def dayFormat(cls, *args) -> FunctionExpression:
+    def dayFormat(cls, day: IntoExpression, /) -> Expression:
         """
         Formats a (0-6) *weekday* number as a full week day name, according to the current locale.
 
-        For example: `dayFormat(0) -> "Sunday"`.
+        For example: ``alt.expr.dayFormat(0) -> "Sunday"``.
         """
-        return FunctionExpression("dayFormat", args)
+        return FunctionExpression("dayFormat", (day,))
 
     @classmethod
-    def dayAbbrevFormat(cls, *args) -> FunctionExpression:
+    def dayAbbrevFormat(cls, day: IntoExpression, /) -> Expression:
         """
         Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale.
 
-        For example: `dayAbbrevFormat(0) -> "Sun"`.
+        For example: ``alt.expr.dayAbbrevFormat(0) -> "Sun"``.
         """
-        return FunctionExpression("dayAbbrevFormat", args)
+        return FunctionExpression("dayAbbrevFormat", (day,))
 
     @classmethod
-    def format(cls, *args) -> FunctionExpression:
+    def format(cls, value: IntoExpression, specifier: IntoExpression, /) -> Expression:
         """
-        Formats a numeric *value* as a string.
+        Formats a numeric ``value`` as a string.
 
-        The *specifier* must be a valid `d3-format specifier <https://d3js.org/d3-format/>`__ (e.g., `format(value, ',.2f')`.
+        The ``specifier`` must be a valid `d3-format specifier`_ (e.g., ``alt.expr.format(value,
+        ',.2f')``. Null values are formatted as ``"null"``.
+
+        .. _d3-format specifier:
+            https://github.com/d3/d3-format/
         """
-        return FunctionExpression("format", args)
+        return FunctionExpression("format", (value, specifier))
 
     @classmethod
-    def monthFormat(cls, *args) -> FunctionExpression:
+    def monthFormat(cls, month: IntoExpression, /) -> Expression:
         """
-        Formats a (zero-based) *month* number as a full month name, according to the current locale.
+        Formats a (zero-based) ``month`` number as a full month name, according to the current locale.
 
-        For example: `monthFormat(0) -> "January"`.
+        For example: ``alt.expr.monthFormat(0) -> "January"``.
         """
-        return FunctionExpression("monthFormat", args)
+        return FunctionExpression("monthFormat", (month,))
 
     @classmethod
-    def monthAbbrevFormat(cls, *args) -> FunctionExpression:
+    def monthAbbrevFormat(cls, month: IntoExpression, /) -> Expression:
         """
-        Formats a (zero-based) *month* number as an abbreviated month name, according to the current locale.
+        Formats a (zero-based) ``month`` number as an abbreviated month name, according to the current locale.
 
-        For example: `monthAbbrevFormat(0) -> "Jan"`.
+        For example: ``alt.expr.monthAbbrevFormat(0) -> "Jan"``.
         """
-        return FunctionExpression("monthAbbrevFormat", args)
+        return FunctionExpression("monthAbbrevFormat", (month,))
 
     @classmethod
-    def timeUnitSpecifier(cls, *args) -> FunctionExpression:
+    def timeUnitSpecifier(
+        cls, units: IntoExpression, specifiers: IntoExpression = None, /
+    ) -> Expression:
         """
-        Returns a time format specifier string for the given time `unit <https://vega.github.io/vega/docs/api/time/#time-units>`__.
-
-        The optional *specifiers* object provides a set of specifier sub-strings for customizing the format;
-        for more, see the `timeUnitSpecifier API documentation <https://vega.github.io/vega/docs/api/time/#timeUnitSpecifier>`__.
+        Returns a time format specifier string for the given time `*units*`_.
 
-        The resulting specifier string can then be used as input to the `timeFormat <https://vega.github.io/vega/docs/expressions/#timeFormat>`__ or
-        `utcFormat <https://vega.github.io/vega/docs/expressions/#utcFormat>`__ functions, or as the *format* parameter of an axis or legend.
+        The optional ``specifiers`` object provides a set of specifier sub-strings for customizing
+        the format; for more, see the `timeUnitSpecifier API documentation`_. The resulting
+        specifier string can then be used as input to the `timeFormat`_ or `utcFormat`_ functions,
+        or as the *format* parameter of an axis or legend. For example: ``alt.expr.timeFormat(date,
+        alt.expr.timeUnitSpecifier('year'))`` or ``alt.expr.timeFormat(date,
+        alt.expr.timeUnitSpecifier(['hours', 'minutes']))``.
 
-        For example: `timeFormat(date, timeUnitSpecifier('year'))` or `timeFormat(date, timeUnitSpecifier(['hours', 'minutes']))`.
+        .. _*units*:
+            https://vega.github.io/vega/docs/api/time/#time-units
+        .. _timeUnitSpecifier API documentation:
+            https://vega.github.io/vega/docs/api/time/#timeUnitSpecifier
+        .. _timeFormat:
+            https://vega.github.io/vega/docs/expressions/#timeFormat
+        .. _utcFormat:
+            https://vega.github.io/vega/docs/expressions/#utcFormat
         """
-        return FunctionExpression("timeUnitSpecifier", args)
+        return FunctionExpression("timeUnitSpecifier", (units, specifiers))
 
     @classmethod
-    def timeFormat(cls, *args) -> FunctionExpression:
+    def timeFormat(
+        cls, value: IntoExpression, specifier: IntoExpression, /
+    ) -> Expression:
         """
-        Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to the local time.
+        Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to the local time.
+
+        The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
+        For example: ``alt.expr.timeFormat(timestamp, '%A')``. Null values are formatted as
+        ``"null"``.
 
-        The *specifier* must be a valid `d3-time-format specifier <https://d3js.org/d3-time-format/>`__.
-        For example: `timeFormat(timestamp, '%A')`.
+        .. _d3-time-format specifier:
+            https://github.com/d3/d3-time-format/
+        .. _TimeMultiFormat object:
+            https://vega.github.io/vega/docs/types/#TimeMultiFormat
         """
-        return FunctionExpression("timeFormat", args)
+        return FunctionExpression("timeFormat", (value, specifier))
 
     @classmethod
-    def timeParse(cls, *args) -> FunctionExpression:
+    def timeParse(
+        cls, string: IntoExpression, specifier: IntoExpression, /
+    ) -> Expression:
         """
-        Parses a *string* value to a Date object, according to the local time.
+        Parses a ``string`` value to a Date object, according to the local time.
 
-        The *specifier* must be a valid `d3-time-format specifier <https://d3js.org/d3-time-format/>`__.
-        For example: `timeParse('June 30, 2015', '%B %d, %Y')`.
+        The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
+        ``alt.expr.timeParse('June 30, 2015', '%B %d, %Y')``.
+
+        .. _d3-time-format specifier:
+            https://github.com/d3/d3-time-format/
         """
-        return FunctionExpression("timeParse", args)
+        return FunctionExpression("timeParse", (string, specifier))
 
     @classmethod
-    def utcFormat(cls, *args) -> FunctionExpression:
+    def utcFormat(
+        cls, value: IntoExpression, specifier: IntoExpression, /
+    ) -> Expression:
         """
-        Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to `UTC <https://en.wikipedia.org/wiki/Coordinated_Universal_Time>`__ time.
+        Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to `UTC`_ time.
+
+        The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
+        For example: ``alt.expr.utcFormat(timestamp, '%A')``. Null values are formatted as
+        ``"null"``.
 
-        The *specifier* must be a valid `d3-time-format specifier <https://d3js.org/d3-time-format/>`__.
-        For example: `utcFormat(timestamp, '%A')`.
+        .. _UTC:
+            https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+        .. _d3-time-format specifier:
+            https://github.com/d3/d3-time-format/
+        .. _TimeMultiFormat object:
+            https://vega.github.io/vega/docs/types/#TimeMultiFormat
         """
-        return FunctionExpression("utcFormat", args)
+        return FunctionExpression("utcFormat", (value, specifier))
 
     @classmethod
-    def utcParse(cls, *args) -> FunctionExpression:
+    def utcParse(
+        cls, value: IntoExpression, specifier: IntoExpression, /
+    ) -> Expression:
         """
-        Parses a *string* value to a Date object, according to `UTC <https://en.wikipedia.org/wiki/Coordinated_Universal_Time>`__ time.
+        Parses a *string* value to a Date object, according to `UTC`_ time.
+
+        The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
+        ``alt.expr.utcParse('June 30, 2015', '%B %d, %Y')``.
 
-        The *specifier* must be a valid `d3-time-format specifier <https://d3js.org/d3-time-format/>`__.
-        For example: `utcParse('June 30, 2015', '%B %d, %Y')`.
+        .. _UTC:
+            https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+        .. _d3-time-format specifier:
+            https://github.com/d3/d3-time-format/
         """
-        return FunctionExpression("utcParse", args)
+        return FunctionExpression("utcParse", (value, specifier))
 
     @classmethod
-    def regexp(cls, *args) -> FunctionExpression:
+    def regexp(
+        cls, pattern: IntoExpression, flags: IntoExpression = None, /
+    ) -> Expression:
         """
-        Creates a regular expression instance from an input *pattern* string and optional *flags*.
+        Creates a regular expression instance from an input ``pattern`` string and optional ``flags``.
 
-        Same as `JavaScript's `RegExp` <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp>`__.
+        Same as `JavaScript's RegExp`_.
+
+        .. _JavaScript's RegExp:
+            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
         """
-        return FunctionExpression("regexp", args)
+        return FunctionExpression("regexp", (pattern, flags))
 
     @classmethod
-    def test(cls, *args) -> FunctionExpression:
+    def test(
+        cls, regexp: IntoExpression, string: IntoExpression = None, /
+    ) -> Expression:
         r"""
-        Evaluates a regular expression *regexp* against the input *string*, returning `true` if the string matches the pattern, `false` otherwise.
+        Evaluates a regular expression ``regexp`` against the input ``string``, returning ``true`` if the string matches the pattern, ``false`` otherwise.
 
-        For example: `test(\d{3}, "32-21-9483") -> true`.
+        For example: ``alt.expr.test(/\\d{3}/, "32-21-9483") -> true``.
         """
-        return FunctionExpression("test", args)
+        return FunctionExpression("test", (regexp, string))
 
     @classmethod
-    def rgb(cls, *args) -> FunctionExpression:
+    def rgb(cls, *args: Any) -> Expression:
         """
-        Constructs a new `RGB <https://en.wikipedia.org/wiki/RGB_color_model>`__ color.
+        Constructs a new `RGB`_ color.
+
+        If ``r``, ``g`` and ``b`` are specified, these represent the channel values of the returned
+        color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+        string is specified, it is parsed and then converted to the RGB color space. Uses
+        `d3-color's rgb function`_.
 
-        If *r*, *g* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
-        If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the RGB color space. Uses `d3-color's rgb function <https://d3js.org/d3-color#color_rgb>`__.
+        .. _RGB:
+            https://en.wikipedia.org/wiki/RGB_color_model
+        .. _d3-color's rgb function:
+            https://github.com/d3/d3-color#rgb
         """
         return FunctionExpression("rgb", args)
 
     @classmethod
-    def hsl(cls, *args) -> FunctionExpression:
+    def hsl(cls, *args: Any) -> Expression:
         """
-        Constructs a new `HSL <https://en.wikipedia.org/wiki/HSL_and_HSV>`__ color.
+        Constructs a new `HSL`_ color.
+
+        If ``h``, ``s`` and ``l`` are specified, these represent the channel values of the returned
+        color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+        string is specified, it is parsed and then converted to the HSL color space. Uses
+        `d3-color's hsl function`_.
 
-        If *h*, *s* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
-        If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HSL color space.
-        Uses `d3-color's hsl function <https://d3js.org/d3-color#hsl>`__.
+        .. _HSL:
+            https://en.wikipedia.org/wiki/HSL_and_HSV
+        .. _d3-color's hsl function:
+            https://github.com/d3/d3-color#hsl
         """
         return FunctionExpression("hsl", args)
 
     @classmethod
-    def lab(cls, *args) -> FunctionExpression:
+    def lab(cls, *args: Any) -> Expression:
         """
-        Constructs a new `CIE LAB <https://en.wikipedia.org/wiki/Lab_color_space#CIELAB>`__ color.
+        Constructs a new `CIE LAB`_ color.
 
-        If *l*, *a* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
-        If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the LAB color space.
-        Uses `d3-color's lab function <https://d3js.org/d3-color#lab>`__.
+        If ``l``, ``a`` and ``b`` are specified, these represent the channel values of the returned
+        color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+        string is specified, it is parsed and then converted to the LAB color space. Uses
+        `d3-color's lab function`_.
+
+        .. _CIE LAB:
+            https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
+        .. _d3-color's lab function:
+            https://github.com/d3/d3-color#lab
         """
         return FunctionExpression("lab", args)
 
     @classmethod
-    def hcl(cls, *args) -> FunctionExpression:
+    def hcl(cls, *args: Any) -> Expression:
         """
-        Constructs a new `HCL <https://en.wikipedia.org/wiki/Lab_color_space#CIELAB>`__ (hue, chroma, luminance) color.
+        Constructs a new `HCL`_ (hue, chroma, luminance) color.
+
+        If ``h``, ``c`` and ``l`` are specified, these represent the channel values of the returned
+        color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+        string is specified, it is parsed and then converted to the HCL color space. Uses
+        `d3-color's hcl function`_.
 
-        If *h*, *c* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
-        If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HCL color space.
-        Uses `d3-color's hcl function <https://d3js.org/d3-color#hcl>`__.
+        .. _HCL:
+            https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
+        .. _d3-color's hcl function:
+            https://github.com/d3/d3-color#hcl
         """
         return FunctionExpression("hcl", args)
 
     @classmethod
-    def luminance(cls, *args) -> FunctionExpression:
+    def luminance(cls, specifier: IntoExpression, /) -> Expression:
         """
-        Returns the luminance for the given color *specifier* (compatible with `d3-color's rgb function <https://d3js.org/d3-color#rgb)>`__.
+        Returns the luminance for the given color ``specifier`` (compatible with `d3-color's rgb function`_).
+
+        The luminance is calculated according to the `W3C Web Content Accessibility Guidelines`_.
 
-        The luminance is calculated according to the `W3C Web Content Accessibility Guidelines <https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef>`__.
+        .. _d3-color's rgb function:
+            https://github.com/d3/d3-color#rgb
+        .. _W3C Web Content Accessibility Guidelines:
+            https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
         """
-        return FunctionExpression("luminance", args)
+        return FunctionExpression("luminance", (specifier,))
 
     @classmethod
-    def contrast(cls, *args) -> FunctionExpression:
+    def contrast(
+        cls, specifier1: IntoExpression, specifier2: IntoExpression, /
+    ) -> Expression:
         """
         Returns the contrast ratio between the input color specifiers as a float between 1 and 21.
 
-        The contrast is calculated according to the `W3C Web Content Accessibility Guidelines <https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef>`__.
+        The contrast is calculated according to the `W3C Web Content Accessibility Guidelines`_.
+
+        .. _W3C Web Content Accessibility Guidelines:
+            https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
         """
-        return FunctionExpression("contrast", args)
+        return FunctionExpression("contrast", (specifier1, specifier2))
 
     @classmethod
-    def item(cls, *args) -> FunctionExpression:
+    def item(cls) -> Expression:
         """Returns the current scenegraph item that is the target of the event."""
-        return FunctionExpression("item", args)
+        return FunctionExpression("item", ())
 
     @classmethod
-    def group(cls, *args) -> FunctionExpression:
+    def group(cls, name: IntoExpression = None, /) -> Expression:
         """
         Returns the scenegraph group mark item in which the current event has occurred.
 
-        If no arguments are provided, the immediate parent group is returned.
-        If a group name is provided, the matching ancestor group item is returned.
+        If no arguments are provided, the immediate parent group is returned. If a group name is
+        provided, the matching ancestor group item is returned.
         """
-        return FunctionExpression("group", args)
+        return FunctionExpression("group", (name,))
 
     @classmethod
-    def xy(cls, *args) -> FunctionExpression:
+    def xy(cls, item: IntoExpression = None, /) -> Expression:
         """
         Returns the x- and y-coordinates for the current event as a two-element array.
 
-        If no arguments are provided, the top-level coordinate space of the view is used.
-        If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.
+        If no arguments are provided, the top-level coordinate space of the view is used. If a
+        scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+        item is used.
         """
-        return FunctionExpression("xy", args)
+        return FunctionExpression("xy", (item,))
 
     @classmethod
-    def x(cls, *args) -> FunctionExpression:
+    def x(cls, item: IntoExpression = None, /) -> Expression:
         """
         Returns the x coordinate for the current event.
 
-        If no arguments are provided, the top-level coordinate space of the view is used.
-        If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.
+        If no arguments are provided, the top-level coordinate space of the view is used. If a
+        scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+        item is used.
         """
-        return FunctionExpression("x", args)
+        return FunctionExpression("x", (item,))
 
     @classmethod
-    def y(cls, *args) -> FunctionExpression:
+    def y(cls, item: IntoExpression = None, /) -> Expression:
         """
         Returns the y coordinate for the current event.
 
-        If no arguments are provided, the top-level coordinate space of the view is used.
-        If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.
+        If no arguments are provided, the top-level coordinate space of the view is used. If a
+        scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+        item is used.
         """
-        return FunctionExpression("y", args)
+        return FunctionExpression("y", (item,))
 
     @classmethod
-    def pinchDistance(cls, *args) -> FunctionExpression:
+    def pinchDistance(cls, event: IntoExpression, /) -> Expression:
         """Returns the pixel distance between the first two touch points of a multi-touch event."""
-        return FunctionExpression("pinchDistance", args)
+        return FunctionExpression("pinchDistance", (event,))
 
     @classmethod
-    def pinchAngle(cls, *args) -> FunctionExpression:
+    def pinchAngle(cls, event: IntoExpression, /) -> Expression:
         """Returns the angle of the line connecting the first two touch points of a multi-touch event."""
-        return FunctionExpression("pinchAngle", args)
+        return FunctionExpression("pinchAngle", (event,))
 
     @classmethod
-    def inScope(cls, *args) -> FunctionExpression:
-        """Returns true if the given scenegraph *item* is a descendant of the group mark in which the event handler was defined, false otherwise."""
-        return FunctionExpression("inScope", args)
+    def inScope(cls, item: IntoExpression, /) -> Expression:
+        """Returns true if the given scenegraph ``item`` is a descendant of the group mark in which the event handler was defined, false otherwise."""
+        return FunctionExpression("inScope", (item,))
 
     @classmethod
-    def data(cls, *args) -> FunctionExpression:
+    def data(cls, name: IntoExpression, /) -> Expression:
         """
-        Returns the array of data objects for the Vega data set with the given *name*.
+        Returns the array of data objects for the Vega data set with the given ``name``.
 
         If the data set is not found, returns an empty array.
         """
-        return FunctionExpression("data", args)
+        return FunctionExpression("data", (name,))
 
     @classmethod
-    def indata(cls, *args) -> FunctionExpression:
+    def indata(
+        cls, name: IntoExpression, field: IntoExpression, value: IntoExpression, /
+    ) -> Expression:
         """
-        Tests if the data set with a given *name* contains a datum with a *field* value that matches the input *value*.
+        Tests if the data set with a given ``name`` contains a datum with a ``field`` value that matches the input ``value``.
 
-        For example: `indata('table', 'category', value)`.
+        For example: ``alt.expr.indata('table', 'category', value)``.
         """
-        return FunctionExpression("indata", args)
+        return FunctionExpression("indata", (name, field, value))
 
     @classmethod
-    def scale(cls, *args) -> FunctionExpression:
+    def scale(
+        cls,
+        name: IntoExpression,
+        value: IntoExpression,
+        group: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Applies the named scale transform (or projection) to the specified *value*.
+        Applies the named scale transform (or projection) to the specified ``value``.
 
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the scale or projection.
         """
-        return FunctionExpression("scale", args)
+        return FunctionExpression("scale", (name, value, group))
 
     @classmethod
-    def invert(cls, *args) -> FunctionExpression:
+    def invert(
+        cls,
+        name: IntoExpression,
+        value: IntoExpression,
+        group: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Inverts the named scale transform (or projection) for the specified *value*.
+        Inverts the named scale transform (or projection) for the specified ``value``.
 
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the scale or projection.
         """
-        return FunctionExpression("invert", args)
+        return FunctionExpression("invert", (name, value, group))
 
     @classmethod
-    def copy(cls, *args) -> FunctionExpression:  # type: ignore[override]
+    def copy(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression:  # type: ignore[override]
         """
-        Returns a copy (a new cloned instance) of the named scale transform of projection, or `undefined` if no scale or projection is found.
+        Returns a copy (a new cloned instance) of the named scale transform of projection, or ``undefined`` if no scale or projection is found.
 
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the scale or projection.
         """
-        # error: Signature of "copy" incompatible with supertype "SchemaBase"  [override]
-        # note:      def copy(self, deep: bool | Iterable[Any] = ..., ignore: list[str] | None = ...) -> expr
-        # NOTE: Not relevant as `expr() -> ExprRef`
-        # this method is only accesible via `expr.copy()`
-        return FunctionExpression("copy", args)
+        return FunctionExpression("copy", (name, group))
 
     @classmethod
-    def domain(cls, *args) -> FunctionExpression:
+    def domain(
+        cls, name: IntoExpression, group: IntoExpression = None, /
+    ) -> Expression:
         """
         Returns the scale domain array for the named scale transform, or an empty array if the scale is not found.
 
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the scale.
         """
-        return FunctionExpression("domain", args)
+        return FunctionExpression("domain", (name, group))
 
     @classmethod
-    def range(cls, *args) -> FunctionExpression:
+    def range(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression:
         """
         Returns the scale range array for the named scale transform, or an empty array if the scale is not found.
 
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the scale.
         """
-        return FunctionExpression("range", args)
+        return FunctionExpression("range", (name, group))
 
     @classmethod
-    def bandwidth(cls, *args) -> FunctionExpression:
+    def bandwidth(
+        cls, name: IntoExpression, group: IntoExpression = None, /
+    ) -> Expression:
         """
         Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale.
 
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the scale.
         """
-        return FunctionExpression("bandwidth", args)
+        return FunctionExpression("bandwidth", (name, group))
 
     @classmethod
-    def bandspace(cls, *args) -> FunctionExpression:
+    def bandspace(
+        cls,
+        count: IntoExpression,
+        paddingInner: IntoExpression = None,
+        paddingOuter: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the number of steps needed within a band scale, based on the *count* of domain elements and the inner and outer padding values.
+        Returns the number of steps needed within a band scale, based on the ``count`` of domain elements and the inner and outer padding values.
 
-        While normally calculated within the scale itself, this function can be helpful for determining the size of a chart's layout.
+        While normally calculated within the scale itself, this function can be helpful for
+        determining the size of a chart's layout.
         """
-        return FunctionExpression("bandspace", args)
+        return FunctionExpression("bandspace", (count, paddingInner, paddingOuter))
 
     @classmethod
-    def gradient(cls, *args) -> FunctionExpression:
+    def gradient(
+        cls,
+        scale: IntoExpression,
+        p0: IntoExpression,
+        p1: IntoExpression,
+        count: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns a linear color gradient for the *scale* (whose range must be a `continuous color scheme <https://vega.github.io/vega/docs/schemes>`__ and starting and ending points *p0* and *p1*, each an `[x, y]` array.
+        Returns a linear color gradient for the ``scale`` (whose range must be a `continuous color scheme`_) and starting and ending points ``p0`` and ``p1``, each an *[x, y]* array.
 
-        The points *p0* and *p1* should be expressed in normalized coordinates in the domain `[0, 1]`, relative to the bounds of the item being colored.
+        The points ``p0`` and ``p1`` should be expressed in normalized coordinates in the domain [0,
+        1], relative to the bounds of the item being colored. If unspecified, ``p0`` defaults to
+        ``[0, 0]`` and ``p1`` defaults to ``[1, 0]``, for a horizontal gradient that spans the full
+        bounds of an item. The optional ``count`` argument indicates a desired target number of
+        sample points to take from the color scale.
 
-        If unspecified, *p0* defaults to `[0, 0]` and *p1* defaults to `[1, 0]`, for a horizontal gradient that spans the full bounds of an item.
-        The optional *count* argument indicates a desired target number of sample points to take from the color scale.
+        .. _continuous color scheme:
+            https://vega.github.io/vega/docs/schemes
         """
-        return FunctionExpression("gradient", args)
+        return FunctionExpression("gradient", (scale, p0, p1, count))
 
     @classmethod
-    def panLinear(cls, *args) -> FunctionExpression:
+    def panLinear(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
         """
-        Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+        Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
 
-        The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+        The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+        indicates panning the scale domain to the right by half the scale range.
         """
-        return FunctionExpression("panLinear", args)
+        return FunctionExpression("panLinear", (domain, delta))
 
     @classmethod
-    def panLog(cls, *args) -> FunctionExpression:
+    def panLog(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
         """
-        Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+        Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
 
-        The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+        The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+        indicates panning the scale domain to the right by half the scale range.
         """
-        return FunctionExpression("panLog", args)
+        return FunctionExpression("panLog", (domain, delta))
 
     @classmethod
-    def panPow(cls, *args) -> FunctionExpression:
+    def panPow(
+        cls, domain: IntoExpression, delta: IntoExpression, exponent: IntoExpression, /
+    ) -> Expression:
         """
-        Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+        Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
 
-        The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+        The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+        indicates panning the scale domain to the right by half the scale range.
         """
-        return FunctionExpression("panPow", args)
+        return FunctionExpression("panPow", (domain, delta, exponent))
 
     @classmethod
-    def panSymlog(cls, *args) -> FunctionExpression:
+    def panSymlog(
+        cls, domain: IntoExpression, delta: IntoExpression, constant: IntoExpression, /
+    ) -> Expression:
         """
-        Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+        Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
 
-        The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+        The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+        indicates panning the scale domain to the right by half the scale range.
         """
-        return FunctionExpression("panSymlog", args)
+        return FunctionExpression("panSymlog", (domain, delta, constant))
 
     @classmethod
-    def zoomLinear(cls, *args) -> FunctionExpression:
+    def zoomLinear(
+        cls,
+        domain: IntoExpression,
+        anchor: IntoExpression,
+        scaleFactor: IntoExpression,
+        /,
+    ) -> Expression:
         """
-        Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+        Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
 
-        The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+        The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+        range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
         """
-        return FunctionExpression("zoomLinear", args)
+        return FunctionExpression("zoomLinear", (domain, anchor, scaleFactor))
 
     @classmethod
-    def zoomLog(cls, *args) -> FunctionExpression:
+    def zoomLog(
+        cls,
+        domain: IntoExpression,
+        anchor: IntoExpression,
+        scaleFactor: IntoExpression,
+        /,
+    ) -> Expression:
         """
-        Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+        Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
 
-        The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+        The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+        range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
         """
-        return FunctionExpression("zoomLog", args)
+        return FunctionExpression("zoomLog", (domain, anchor, scaleFactor))
 
     @classmethod
-    def zoomPow(cls, *args) -> FunctionExpression:
+    def zoomPow(
+        cls,
+        domain: IntoExpression,
+        anchor: IntoExpression,
+        scaleFactor: IntoExpression,
+        exponent: IntoExpression,
+        /,
+    ) -> Expression:
         """
-        Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+        Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
 
-        The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+        The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+        range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
         """
-        return FunctionExpression("zoomPow", args)
+        return FunctionExpression("zoomPow", (domain, anchor, scaleFactor, exponent))
 
     @classmethod
-    def zoomSymlog(cls, *args) -> FunctionExpression:
+    def zoomSymlog(
+        cls,
+        domain: IntoExpression,
+        anchor: IntoExpression,
+        scaleFactor: IntoExpression,
+        constant: IntoExpression,
+        /,
+    ) -> Expression:
         """
-        Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+        Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
 
-        The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+        The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+        range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
         """
-        return FunctionExpression("zoomSymlog", args)
+        return FunctionExpression("zoomSymlog", (domain, anchor, scaleFactor, constant))
 
     @classmethod
-    def geoArea(cls, *args) -> FunctionExpression:
+    def geoArea(
+        cls,
+        projection: IntoExpression,
+        feature: IntoExpression,
+        group: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the projected planar area (typically in square pixels) of a GeoJSON *feature* according to the named *projection*.
+        Returns the projected planar area (typically in square pixels) of a GeoJSON ``feature`` according to the named ``projection``.
+
+        If the ``projection`` argument is ``null``, computes the spherical area in steradians using
+        unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
+        scenegraph group mark item to indicate the specific scope in which to look up the
+        projection. Uses d3-geo's `geoArea`_ and `path.area`_ methods.
 
-        If the *projection* argument is `null`, computes the spherical area in steradians using unprojected longitude, latitude coordinates.
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection.
-        Uses d3-geo's `geoArea <https://d3js.org/d3-geo#geoArea>`__ and `path.area <https://d3js.org/d3-geo#path_area>`__ methods.
+        .. _geoArea:
+            https://github.com/d3/d3-geo#geoArea
+        .. _path.area:
+            https://github.com/d3/d3-geo#path_area
         """
-        return FunctionExpression("geoArea", args)
+        return FunctionExpression("geoArea", (projection, feature, group))
 
     @classmethod
-    def geoBounds(cls, *args) -> FunctionExpression:
+    def geoBounds(
+        cls,
+        projection: IntoExpression,
+        feature: IntoExpression,
+        group: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*.
+        Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
 
-        The bounding box is represented by a two-dimensional array: `[[x0, y0], [x1, y1]]`,
-        where *x0* is the minimum x-coordinate, *y0* is the minimum y-coordinate,
-        *x1* is the maximum x-coordinate, and *y1* is the maximum y-coordinate.
+        The bounding box is represented by a two-dimensional array: [[*x₀*, *y₀*], [*x₁*, *y₁*]],
+        where *x₀* is the minimum x-coordinate, *y₀* is the minimum y-coordinate, *x₁* is the
+        maximum x-coordinate, and *y₁* is the maximum y-coordinate. If the ``projection`` argument
+        is ``null``, computes the spherical bounding box using unprojected longitude, latitude
+        coordinates. The optional ``group`` argument takes a scenegraph group mark item to indicate
+        the specific scope in which to look up the projection. Uses d3-geo's `geoBounds`_ and
+        `path.bounds`_ methods.
 
-        If the *projection* argument is `null`, computes the spherical bounding box using unprojected longitude, latitude coordinates.
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection.
-        Uses d3-geo's `geoBounds <https://d3js.org/d3-geo#geoBounds>`__ and `path.bounds <https://d3js.org/d3-geo#path_bounds>`__ methods.
+        .. _geoBounds:
+            https://github.com/d3/d3-geo#geoBounds
+        .. _path.bounds:
+            https://github.com/d3/d3-geo#path_bounds
         """
-        return FunctionExpression("geoBounds", args)
+        return FunctionExpression("geoBounds", (projection, feature, group))
 
     @classmethod
-    def geoCentroid(cls, *args) -> FunctionExpression:
+    def geoCentroid(
+        cls,
+        projection: IntoExpression,
+        feature: IntoExpression,
+        group: IntoExpression = None,
+        /,
+    ) -> Expression:
         """
-        Returns the projected planar centroid (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*.
+        Returns the projected planar centroid (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
+
+        If the ``projection`` argument is ``null``, computes the spherical centroid using
+        unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
+        scenegraph group mark item to indicate the specific scope in which to look up the
+        projection. Uses d3-geo's `geoCentroid`_ and `path.centroid`_ methods.
 
-        If the *projection* argument is `null`, computes the spherical centroid using unprojected longitude, latitude coordinates.
-        The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection.
-        Uses d3-geo's `geoCentroid <https://d3js.org/d3-geo#geoCentroid>`__ and `path.centroid <https://d3js.org/d3-geo#path_centroid>`__ methods.
+        .. _geoCentroid:
+            https://github.com/d3/d3-geo#geoCentroid
+        .. _path.centroid:
+            https://github.com/d3/d3-geo#path_centroid
         """
-        return FunctionExpression("geoCentroid", args)
+        return FunctionExpression("geoCentroid", (projection, feature, group))
 
     @classmethod
-    def treePath(cls, *args) -> FunctionExpression:
+    def geoScale(
+        cls, projection: IntoExpression, group: IntoExpression = None, /
+    ) -> Expression:
         """
-        For the hierarchy data set with the given *name*, returns the shortest path through from the *source* node id to the *target* node id.
+        Returns the scale value for the named ``projection``.
 
-        The path starts at the *source* node, ascends to the least common ancestor of the *source* node and the *target* node, and then descends to the *target* node.
+        The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+        scope in which to look up the projection.
         """
-        return FunctionExpression("treePath", args)
+        return FunctionExpression("geoScale", (projection, group))
 
     @classmethod
-    def treeAncestors(cls, *args) -> FunctionExpression:
-        """For the hierarchy data set with the given *name*, returns the array of ancestors nodes, starting with the input *node*, then followed by each parent up to the root."""
-        return FunctionExpression("treeAncestors", args)
+    def treePath(
+        cls, name: IntoExpression, source: IntoExpression, target: IntoExpression, /
+    ) -> Expression:
+        """
+        For the hierarchy data set with the given ``name``, returns the shortest path through from the ``source`` node id to the ``target`` node id.
+
+        The path starts at the ``source`` node, ascends to the least common ancestor of the
+        ``source`` node and the ``target`` node, and then descends to the ``target`` node.
+        """
+        return FunctionExpression("treePath", (name, source, target))
 
     @classmethod
-    def containerSize(cls, *args) -> FunctionExpression:
+    def treeAncestors(cls, name: IntoExpression, node: IntoExpression, /) -> Expression:
+        """For the hierarchy data set with the given ``name``, returns the array of ancestors nodes, starting with the input ``node``, then followed by each parent up to the root."""
+        return FunctionExpression("treeAncestors", (name, node))
+
+    @classmethod
+    def containerSize(cls) -> Expression:
         """
-        Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view.
+        Returns the current CSS box size (``[el.clientWidth, el.clientHeight]``) of the parent DOM element that contains the Vega view.
 
-        If there is no container element, returns `[undefined, undefined]`.
+        If there is no container element, returns ``[undefined, undefined]``.
         """
-        return FunctionExpression("containerSize", args)
+        return FunctionExpression("containerSize", ())
 
     @classmethod
-    def screen(cls, *args) -> FunctionExpression:
-        """Returns the `window.screen <https://developer.mozilla.org/en-US/docs/Web/API/Window/screen>`__ object, or `{}` if Vega is not running in a browser environment."""
-        return FunctionExpression("screen", args)
+    def screen(cls) -> Expression:
+        """
+        Returns the `window.screen`_ object, or ``{}`` if Vega is not running in a browser environment.
+
+        .. _window.screen:
+            https://developer.mozilla.org/en-US/docs/Web/API/Window/screen
+        """
+        return FunctionExpression("screen", ())
 
     @classmethod
-    def windowSize(cls, *args) -> FunctionExpression:
-        """Returns the current window size (`[window.innerWidth, window.innerHeight]`) or `[undefined, undefined]` if Vega is not running in a browser environment."""
-        return FunctionExpression("windowSize", args)
+    def windowSize(cls) -> Expression:
+        """Returns the current window size (``[window.innerWidth, window.innerHeight]``) or ``[undefined, undefined]`` if Vega is not running in a browser environment."""
+        return FunctionExpression("windowSize", ())
 
     @classmethod
-    def warn(cls, *args) -> FunctionExpression:
+    def warn(
+        cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+    ) -> Expression:
         """
         Logs a warning message and returns the last argument.
 
-        For the message to appear in the console, the visualization view must have the appropriate logging level set.
+        For the message to appear in the console, the visualization view must have the appropriate
+        logging level set.
         """
-        return FunctionExpression("warn", args)
+        return FunctionExpression("warn", (value1, value2, *args))
 
     @classmethod
-    def info(cls, *args) -> FunctionExpression:
+    def info(
+        cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+    ) -> Expression:
         """
         Logs an informative message and returns the last argument.
 
-        For the message to appear in the console, the visualization view must have the appropriate logging level set.
+        For the message to appear in the console, the visualization view must have the appropriate
+        logging level set.
         """
-        return FunctionExpression("info", args)
+        return FunctionExpression("info", (value1, value2, *args))
 
     @classmethod
-    def debug(cls, *args) -> FunctionExpression:
+    def debug(
+        cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+    ) -> Expression:
         """
         Logs a debugging message and returns the last argument.
 
-        For the message to appear in the console, the visualization view must have the appropriate logging level set.
+        For the message to appear in the console, the visualization view must have the appropriate
+        logging level set.
         """
-        return FunctionExpression("debug", args)
+        return FunctionExpression("debug", (value1, value2, *args))
 
 
 _ExprType = expr
diff --git a/tools/__init__.py b/tools/__init__.py
index 052b8e9c0..46fc97553 100644
--- a/tools/__init__.py
+++ b/tools/__init__.py
@@ -1,8 +1,15 @@
-from tools import generate_api_docs, generate_schema_wrapper, schemapi, update_init_file
+from tools import (
+    generate_api_docs,
+    generate_schema_wrapper,
+    markup,
+    schemapi,
+    update_init_file,
+)
 
 __all__ = [
     "generate_api_docs",
     "generate_schema_wrapper",
+    "markup",
     "schemapi",
     "update_init_file",
 ]
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 85860e8f3..58a395fb3 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -20,14 +20,8 @@
 sys.path.insert(0, str(Path.cwd()))
 
 
-from tools.schemapi import (  # noqa: F401
-    CodeSnippet,
-    SchemaInfo,
-    arg_invalid_kwds,
-    arg_kwds,
-    arg_required_kwds,
-    codegen,
-)
+from tools.markup import rst_syntax_for_class
+from tools.schemapi import CodeSnippet, SchemaInfo, arg_kwds, arg_required_kwds, codegen
 from tools.schemapi.utils import (
     SchemaProperties,
     TypeAliasTracer,
@@ -37,16 +31,17 @@
     import_typing_extensions,
     indent_docstring,
     resolve_references,
-    rst_syntax_for_class,
     ruff_format_py,
     ruff_write_lint_format_str,
     spell_literal,
 )
+from tools.vega_expr import write_expr_module
 
 if TYPE_CHECKING:
     from tools.schemapi.codegen import ArgInfo, AttrGetter
     from vl_convert import VegaThemes
 
+
 SCHEMA_VERSION: Final = "v5.20.1"
 
 
@@ -60,8 +55,14 @@
 """
 
 SCHEMA_URL_TEMPLATE: Final = "https://vega.github.io/schema/{library}/{version}.json"
+VL_PACKAGE_TEMPLATE = (
+    "https://raw.githubusercontent.com/vega/vega-lite/refs/tags/{version}/package.json"
+)
 SCHEMA_FILE = "vega-lite-schema.json"
 THEMES_FILE = "vega-themes.json"
+EXPR_FILE: Path = (
+    Path(__file__).parent / ".." / "altair" / "expr" / "__init__.py"
+).resolve()
 
 CHANNEL_MYPY_IGNORE_STATEMENTS: Final = """\
 # These errors need to be ignored as they come from the overload methods
@@ -1207,6 +1208,11 @@ def main() -> None:
     args = parser.parse_args()
     copy_schemapi_util()
     vegalite_main(args.skip_download)
+    write_expr_module(
+        vlc.get_vega_version(),
+        output=EXPR_FILE,
+        header=HEADER_COMMENT,
+    )
 
     # The modules below are imported after the generation of the new schema files
     # as these modules import Altair. This allows them to use the new changes
diff --git a/tools/markup.py b/tools/markup.py
new file mode 100644
index 000000000..b17e7ad24
--- /dev/null
+++ b/tools/markup.py
@@ -0,0 +1,150 @@
+"""Tools for working with formats like ``.md``, ``.rst``."""
+
+from __future__ import annotations
+
+import re
+from html import unescape
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Iterable, Literal
+from urllib import request
+
+import mistune.util
+from mistune import InlineParser as _InlineParser
+from mistune import Markdown as _Markdown
+from mistune.renderers.rst import RSTRenderer as _RSTRenderer
+
+if TYPE_CHECKING:
+    import sys
+
+    if sys.version_info >= (3, 11):
+        from typing import TypeAlias
+    else:
+        from typing_extensions import TypeAlias
+    from re import Pattern
+
+    from mistune import BaseRenderer, BlockParser, BlockState, InlineState
+
+    Url: TypeAlias = str
+
+Token: TypeAlias = "dict[str, Any]"
+
+_RE_LINK: Pattern[str] = re.compile(r"(?<=\[)([^\]]+)(?=\]\([^\)]+\))", re.MULTILINE)
+_RE_SPECIAL: Pattern[str] = re.compile(r"[*_]{2,3}|`", re.MULTILINE)
+_RE_LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
+
+
+class RSTRenderer(_RSTRenderer):
+    def __init__(self) -> None:
+        super().__init__()
+
+    def inline_html(self, token: Token, state: BlockState) -> str:
+        html = token["raw"]
+        return rf"\ :raw-html:`{html}`\ "
+
+
+class RSTParse(_Markdown):
+    """
+    Minor extension to support partial `ast`_ conversion.
+
+    Only need to convert the docstring tokens to `.rst`.
+
+    .. _ast:
+        https://mistune.lepture.com/en/latest/guide.html#abstract-syntax-tree
+    """
+
+    def __init__(
+        self,
+        renderer: BaseRenderer | Literal["ast"] | None,
+        block: BlockParser | None = None,
+        inline: _InlineParser | None = None,
+        plugins=None,
+    ) -> None:
+        if renderer == "ast":
+            renderer = None
+        super().__init__(renderer, block, inline, plugins)
+
+    def __call__(self, s: str) -> str:
+        s = super().__call__(s)  # pyright: ignore[reportAssignmentType]
+        return unescape(s).replace(r"\ ,", ",").replace(r"\ ", " ")
+
+    def render_tokens(self, tokens: Iterable[Token], /) -> str:
+        """
+        Render ast tokens originating from another parser.
+
+        Parameters
+        ----------
+        tokens
+            All tokens will be rendered into a single `.rst` string
+        """
+        if self.renderer is None:
+            msg = "Unable to render tokens without a renderer."
+            raise TypeError(msg)
+        state = self.block.state_cls()
+        s = self.renderer(self._iter_render(tokens, state), state)
+        return mistune.util.unescape(s)
+
+
+class RSTParseVegaLite(RSTParse):
+    def __init__(
+        self,
+        renderer: RSTRenderer | None = None,
+        block: BlockParser | None = None,
+        inline: _InlineParser | None = None,
+        plugins=None,
+    ) -> None:
+        super().__init__(renderer or RSTRenderer(), block, inline, plugins)
+
+    def __call__(self, s: str) -> str:
+        # remove formatting from links
+        description = "".join(
+            _RE_SPECIAL.sub("", d) if i % 2 else d
+            for i, d in enumerate(_RE_LINK.split(s))
+        )
+
+        description = super().__call__(description)
+        # Some entries in the Vega-Lite schema miss the second occurence of '__'
+        description = description.replace("__Default value: ", "__Default value:__ ")
+        # Links to the vega-lite documentation cannot be relative but instead need to
+        # contain the full URL.
+        description = description.replace(
+            "types#datetime", "https://vega.github.io/vega-lite/docs/datetime.html"
+        )
+        # Fixing ambiguous unicode, RUF001 produces RUF002 in docs
+        description = description.replace("’", "'")  # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK]
+        description = description.replace("–", "-")  # noqa: RUF001 [EN DASH]
+        description = description.replace(" ", " ")  # noqa: RUF001 [NO-BREAK SPACE]
+        return description.strip()
+
+
+class InlineParser(_InlineParser):
+    def __init__(self, hard_wrap: bool = False) -> None:
+        super().__init__(hard_wrap)
+
+    def process_text(self, text: str, state: InlineState) -> None:
+        """
+        Removes `liquid`_ templating markup.
+
+        .. _liquid:
+        https://shopify.github.io/liquid/
+        """
+        state.append_token({"type": "text", "raw": _RE_LIQUID_INCLUDE.sub(r"", text)})
+
+
+def read_ast_tokens(source: Url | Path, /) -> list[Token]:
+    """
+    Read from ``source``, drop ``BlockState``.
+
+    Factored out to provide accurate typing.
+    """
+    markdown = _Markdown(renderer=None, inline=InlineParser())
+    if isinstance(source, Path):
+        tokens = markdown.read(source)
+    else:
+        with request.urlopen(source) as response:
+            s = response.read().decode("utf-8")
+        tokens = markdown.parse(s, markdown.block.state_cls())
+    return tokens[0]
+
+
+def rst_syntax_for_class(class_name: str) -> str:
+    return f":class:`{class_name}`"
diff --git a/tools/schemapi/__init__.py b/tools/schemapi/__init__.py
index 55c5f4148..b3ea70704 100644
--- a/tools/schemapi/__init__.py
+++ b/tools/schemapi/__init__.py
@@ -9,6 +9,7 @@
 )
 from tools.schemapi.schemapi import SchemaBase, Undefined
 from tools.schemapi.utils import OneOrSeq, SchemaInfo
+from tools.vega_expr import write_expr_module
 
 __all__ = [
     "CodeSnippet",
@@ -21,4 +22,5 @@
     "arg_required_kwds",
     "codegen",
     "utils",
+    "write_expr_module",
 ]
diff --git a/tools/schemapi/utils.py b/tools/schemapi/utils.py
index 5c4a84f9c..6bc7b1f4b 100644
--- a/tools/schemapi/utils.py
+++ b/tools/schemapi/utils.py
@@ -8,7 +8,6 @@
 import sys
 import textwrap
 import urllib.parse
-from html import unescape
 from itertools import chain
 from keyword import iskeyword
 from operator import itemgetter
@@ -27,9 +26,7 @@
     overload,
 )
 
-import mistune
-from mistune.renderers.rst import RSTRenderer as _RSTRenderer
-
+from tools.markup import RSTParseVegaLite, rst_syntax_for_class
 from tools.schemapi.schemapi import _resolve_references as resolve_references
 
 if TYPE_CHECKING:
@@ -37,7 +34,6 @@
     from pathlib import Path
     from re import Pattern
 
-    from mistune import BlockState
 
 if sys.version_info >= (3, 12):
     from typing import TypeAliasType
@@ -76,8 +72,7 @@
 }
 
 _VALID_IDENT: Pattern[str] = re.compile(r"^[^\d\W]\w*\Z", re.ASCII)
-_RE_LINK: Pattern[str] = re.compile(r"(?<=\[)([^\]]+)(?=\]\([^\)]+\))", re.MULTILINE)
-_RE_SPECIAL: Pattern[str] = re.compile(r"[*_]{2,3}|`", re.MULTILINE)
+
 _RE_LIST_MISSING_ASTERISK: Pattern[str] = re.compile(r"^-(?=[ `\"a-z])", re.MULTILINE)
 _RE_LIST_MISSING_WHITESPACE: Pattern[str] = re.compile(r"^\*(?=[`\"a-z])", re.MULTILINE)
 
@@ -1083,30 +1078,6 @@ def import_typing_extensions(
 """
 
 
-class RSTRenderer(_RSTRenderer):
-    def __init__(self) -> None:
-        super().__init__()
-
-    def inline_html(self, token: dict[str, Any], state: BlockState) -> str:
-        html = token["raw"]
-        return rf"\ :raw-html:`{html}`\ "
-
-
-class RSTParse(mistune.Markdown):
-    def __init__(
-        self,
-        renderer: mistune.BaseRenderer,
-        block: mistune.BlockParser | None = None,
-        inline: mistune.InlineParser | None = None,
-        plugins=None,
-    ) -> None:
-        super().__init__(renderer, block, inline, plugins)
-
-    def __call__(self, s: str) -> str:
-        s = super().__call__(s)  # pyright: ignore[reportAssignmentType]
-        return unescape(s).replace(r"\ ,", ",").replace(r"\ ", " ")
-
-
 def indent_docstring(  # noqa: C901
     lines: Iterable[str], indent_level: int, width: int = 100, lstrip=True
 ) -> str:
@@ -1192,31 +1163,10 @@ def fix_docstring_issues(docstring: str) -> str:
     )
 
 
-def rst_syntax_for_class(class_name: str) -> str:
-    return f":class:`{class_name}`"
-
-
-rst_parse: RSTParse = RSTParse(RSTRenderer())
+rst_parse: RSTParseVegaLite = RSTParseVegaLite()
 
 
 # TODO: Investigate `mistune.Markdown.(before|after)_render_hooks`.
 def process_description(description: str) -> str:
     """Parse a JSON encoded markdown description into an `RST` string."""
-    # remove formatting from links
-    description = "".join(
-        _RE_SPECIAL.sub("", d) if i % 2 else d
-        for i, d in enumerate(_RE_LINK.split(description))
-    )
-    description = rst_parse(description)
-    # Some entries in the Vega-Lite schema miss the second occurence of '__'
-    description = description.replace("__Default value: ", "__Default value:__ ")
-    # Links to the vega-lite documentation cannot be relative but instead need to
-    # contain the full URL.
-    description = description.replace(
-        "types#datetime", "https://vega.github.io/vega-lite/docs/datetime.html"
-    )
-    # Fixing ambiguous unicode, RUF001 produces RUF002 in docs
-    description = description.replace("’", "'")  # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK]
-    description = description.replace("–", "-")  # noqa: RUF001 [EN DASH]
-    description = description.replace(" ", " ")  # noqa: RUF001 [NO-BREAK SPACE]
-    return description.strip()
+    return rst_parse(description)
diff --git a/tools/vega_expr.py b/tools/vega_expr.py
new file mode 100644
index 000000000..ce87cb2fb
--- /dev/null
+++ b/tools/vega_expr.py
@@ -0,0 +1,980 @@
+"""
+Parsing `Vega Expressions`_ docs to write the ``alt.expr`` module.
+
+.. _Vega Expressions:
+   https://vega.github.io/vega/docs/expressions/
+"""
+
+from __future__ import annotations
+
+import dataclasses
+import enum
+import keyword
+import re
+from collections import deque
+from inspect import getmembers
+from itertools import chain
+from textwrap import TextWrapper as _TextWrapper
+from textwrap import indent
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    ClassVar,
+    Iterable,
+    Iterator,
+    Literal,
+    Mapping,
+    Sequence,
+    overload,
+)
+
+from tools.markup import RSTParse, Token, read_ast_tokens
+from tools.markup import RSTRenderer as _RSTRenderer
+from tools.schemapi.schemapi import SchemaBase as _SchemaBase
+from tools.schemapi.utils import (
+    ruff_write_lint_format_str as _ruff_write_lint_format_str,
+)
+
+if TYPE_CHECKING:
+    import sys
+    from pathlib import Path
+    from re import Match, Pattern
+
+    from mistune import BlockState
+
+    if sys.version_info >= (3, 11):
+        from typing import LiteralString, Self
+    else:
+        from typing_extensions import LiteralString, Self
+    from _typeshed import SupportsKeysAndGetItem
+
+    from tools.markup import Url
+
+__all__ = ["parse_expressions", "write_expr_module"]
+
+
+# NOTE: Urls/fragments
+VEGA_DOCS_URL: LiteralString = "https://vega.github.io/vega/docs/"
+EXPRESSIONS_DOCS_URL: LiteralString = f"{VEGA_DOCS_URL}expressions/"
+EXPRESSIONS_URL_TEMPLATE = "https://raw.githubusercontent.com/vega/vega/refs/tags/{version}/docs/docs/expressions.md"
+
+
+# NOTE: Regex patterns
+FUNCTION_DEF_LINE: Pattern[str] = re.compile(
+    r"<a name=\"(?P<name>.+)\" href=\"#(.+)\">"
+)
+SENTENCE_BREAK: Pattern[str] = re.compile(r"(?<!\.)\. ")
+
+# NOTE: `mistune` token keys/values
+TYPE: Literal[r"type"] = r"type"
+RAW: Literal["raw"] = "raw"
+SOFTBREAK: Literal["softbreak"] = "softbreak"
+TEXT: Literal["text"] = "text"
+CHILDREN: Literal["children"] = "children"
+
+# NOTE: Punctuation/markers
+ELLIPSIS: Literal["..."] = "..."
+OPEN_PAREN: Literal["("] = "("
+CLOSE_PAREN: Literal[")"] = ")"
+OPEN_BRACKET: Literal["["] = "["
+CLOSE_BRACKET: Literal["]"] = "]"
+INLINE_OVERLOAD: Literal[" |"] = " |"
+
+METHOD_INDENT: LiteralString = 8 * " "
+SECTION_BREAK: Literal["\n\n"] = "\n\n"
+
+# NOTE: `altair` types (for annotations)
+RETURN_WRAPPER: LiteralString = "FunctionExpression"
+RETURN_ANNOTATION: LiteralString = "Expression"
+"""
+The annotation is intentionally *less* specific than the real type.
+
+``Expression`` is shorter, while preserving all the user-facing functionality
+"""
+
+CONST_WRAPPER: LiteralString = "ConstExpression"
+CLS_META: LiteralString = "_ExprMeta"
+INPUT_ANNOTATION: LiteralString = "IntoExpression"
+
+# NOTE: `python`/`mypy` related literals
+NONE: Literal[r"None"] = r"None"
+STAR_ARGS: Literal["*args"] = "*args"
+DECORATOR: LiteralString = r"@classmethod"
+IGNORE_OVERRIDE: LiteralString = r"# type: ignore[override]"
+IGNORE_MISC: LiteralString = r"# type: ignore[misc]"
+
+MODULE_PRE = '''\
+{header}
+"""Tools for creating transform & filter expressions with a python syntax."""
+
+from __future__ import annotations
+
+import sys
+from typing import Any, TYPE_CHECKING
+
+from altair.expr.core import {const}, {func}
+from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
+
+if sys.version_info >= (3, 12):
+    from typing import override
+else:
+    from typing_extensions import override
+
+if TYPE_CHECKING:
+    from altair.expr.core import {return_ann}, {input_ann}
+
+
+class {metaclass}(type):
+    """
+    Metaclass for :class:`expr`.
+
+    Currently providing read-only class properties, representing JavaScript constants.
+    """
+
+    @property
+    def NaN(cls) -> {return_ann}:
+        """Not a number (same as JavaScript literal NaN)."""
+        return {const}("NaN")
+
+    @property
+    def LN10(cls) -> {return_ann}:
+        """The natural log of 10 (alias to Math.LN10)."""
+        return {const}("LN10")
+
+    @property
+    def E(cls) -> {return_ann}:
+        """The transcendental number e (alias to Math.E)."""
+        return {const}("E")
+
+    @property
+    def LOG10E(cls) -> {return_ann}:
+        """The base 10 logarithm e (alias to Math.LOG10E)."""
+        return {const}("LOG10E")
+
+    @property
+    def LOG2E(cls) -> {return_ann}:
+        """The base 2 logarithm of e (alias to Math.LOG2E)."""
+        return {const}("LOG2E")
+
+    @property
+    def SQRT1_2(cls) -> {return_ann}:
+        """The square root of 0.5 (alias to Math.SQRT1_2)."""
+        return {const}("SQRT1_2")
+
+    @property
+    def LN2(cls) -> {return_ann}:
+        """The natural log of 2 (alias to Math.LN2)."""
+        return {const}("LN2")
+
+    @property
+    def SQRT2(cls) -> {return_ann}:
+        """The square root of 2 (alias to Math.SQRT1_2)."""
+        return {const}("SQRT2")
+
+    @property
+    def PI(cls) -> {return_ann}:
+        """The transcendental number pi (alias to Math.PI)."""
+        return {const}("PI")
+'''
+
+MODULE_POST = """\
+_ExprType = expr
+# NOTE: Compatibility alias for previous type of `alt.expr`.
+# `_ExprType` was not referenced in any internal imports/tests.
+"""
+
+CLS_DOC = """
+    Utility providing *constants* and *classmethods* to construct expressions.
+
+    `Expressions`_ can be used to write basic formulas that enable custom interactions.
+
+    Alternatively, an `inline expression`_ may be defined via :class:`expr()`.
+
+    Parameters
+    ----------
+    expr: str
+        A `vega expression`_ string.
+
+    Returns
+    -------
+    ``ExprRef``
+
+    .. _Expressions:
+        https://altair-viz.github.io/user_guide/interactions.html#expressions
+    .. _inline expression:
+       https://altair-viz.github.io/user_guide/interactions.html#inline-expressions
+    .. _vega expression:
+       https://vega.github.io/vega/docs/expressions/
+
+    Examples
+    --------
+    >>> import altair as alt
+
+    >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value:  ")
+    >>> param_width = alt.param(bind=bind_range, name="param_width")
+    >>> param_color = alt.param(
+    ...     expr=alt.expr.if_(param_width < 200, "red", "black"),
+    ...     name="param_color",
+    ... )
+    >>> y = alt.Y("yval").axis(titleColor=param_color)
+
+    >>> y
+    Y({
+      axis: {'titleColor': Parameter('param_color', VariableParameter({
+        expr: if((param_width < 200),'red','black'),
+        name: 'param_color'
+      }))},
+      shorthand: 'yval'
+    })
+    """
+
+CLS_TEMPLATE = '''\
+class expr({base}, metaclass={metaclass}):
+    """{doc}"""
+
+    @override
+    def __new__(cls: type[{base}], expr: str) -> {base}:  {type_ignore}
+        return {base}(expr=expr)
+'''
+
+METHOD_SIGNATURE = (
+    """def {title}(cls{sep}{param_list}{marker}) -> {return_ann}:{type_ignore}"""
+)
+
+METHOD_TEMPLATE = '''\
+    {decorator}
+    {signature}
+        """
+        {doc}
+        """
+        return {return_wrapper}({name}, {body_params})
+'''
+
+
+def _override_predicate(obj: Any, /) -> bool:
+    return callable(obj) and not (name := obj.__name__).startswith("_")  # noqa: F841
+
+
+_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
+    nm for nm, _ in getmembers(_SchemaBase, _override_predicate)
+)
+
+
+class RSTRenderer(_RSTRenderer):
+    def __init__(self) -> None:
+        super().__init__()
+
+    def link(self, token: Token, state: BlockState) -> str:
+        """Store link url, for appending at the end of doc."""
+        attrs = token["attrs"]
+        url = expand_urls(attrs["url"])
+        text = self.render_children(token, state)
+        text = text.replace("`", "")
+        inline = f"`{text}`_"
+        state.env["ref_links"][text] = {"url": url}
+        return inline
+
+    def _with_links(self, s: str, links: dict[str, Any] | Any, /) -> str:
+        it = chain.from_iterable(
+            (f".. _{ref_name}:", f"    {attrs['url']}")
+            for ref_name, attrs in links.items()
+        )
+        return "\n".join(chain([s], it))
+
+    def __call__(self, tokens: Iterable[Token], state: BlockState) -> str:
+        result = super().__call__(tokens, state)
+        if links := state.env.get("ref_links", {}):
+            return self._with_links(result, links)
+        else:
+            return result
+
+
+parser: RSTParse = RSTParse(RSTRenderer())
+text_wrap = _TextWrapper(
+    width=100,
+    break_long_words=False,
+    break_on_hyphens=False,
+    initial_indent=METHOD_INDENT,
+    subsequent_indent=METHOD_INDENT,
+)
+
+
+class ReplaceMany:
+    """
+    Perform many ``1:1`` replacements on a given text.
+
+    Structured wrapper around a `dict`_ and `re.sub`_.
+
+    Parameters
+    ----------
+    mapping
+        Optional initial mapping.
+    fmt_match
+        **Combined** format string/regex pattern.
+        Receives the keys of the final ``self._mapping`` as a positional argument.
+
+        .. note::
+            Special characters must be escaped **first**, if present.
+
+    fmt_replace
+        Format string applied to a succesful match, after substition.
+        Receives ``self._mapping[key]`` as a positional argument.
+
+    .. _dict:
+        https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+    .. _re.sub:
+        https://docs.python.org/3/library/re.html#re.sub
+
+    Examples
+    --------
+    Providing a mapping during construction:
+
+        string = "The dog chased the cat, chasing the mouse. Poor mouse"
+        animal_replacer = ReplaceMany({"dog": "cat"})
+        >>> animal_replacer(string)
+        'The cat chased the cat, chasing the mouse. Poor mouse'
+
+    Updating with new replacements:
+
+        animal_replacer.update({"cat": "mouse", "mouse": "dog"}, duck="rabbit")
+        >>> animal_replacer(string, refresh=True)
+        'The cat chased the mouse, chasing the dog. Poor dog'
+
+    Further calls will continue using the most recent update:
+
+        >>> animal_replacer("duck")
+        'rabbit'
+    """
+
+    def __init__(
+        self,
+        mapping: Mapping[str, str] | None = None,
+        /,
+        fmt_match: str = "(?P<key>{0})",
+        fmt_replace: str = "{0}",
+    ) -> None:
+        self._mapping: dict[str, str] = dict(mapping) if mapping else {}
+        self._fmt_match: str = fmt_match
+        self._fmt_replace: str = fmt_replace
+        self.pattern: Pattern[str]
+        self.repl: Callable[[Match[str]], str]
+        self._is_prepared: bool = False
+
+    def update(
+        self,
+        m: SupportsKeysAndGetItem[str, str] | Iterable[tuple[str, str]],
+        /,
+        **kwds: str,
+    ) -> None:
+        """Update replacements mapping."""
+        self._mapping.update(m, **kwds)
+
+    def clear(self) -> None:
+        """Reset replacements mapping."""
+        self._mapping.clear()
+
+    def refresh(self) -> None:
+        """
+        Compile replacement pattern and generate substitution function.
+
+        Notes
+        -----
+        Should be called **after** all (old, new) pairs have been collected.
+        """
+        self.pattern = self._compile()
+        self.repl = self._replacer()
+        self._is_prepared = True
+
+    def __call__(self, s: str, count: int = 0, /, refresh: bool = False) -> str:
+        """
+        Replace the leftmost non-overlapping occurrences of ``self.pattern`` in ``s`` using ``self.repl``.
+
+        Wraps `re.sub`_
+
+        .. _re.sub:
+            https://docs.python.org/3/library/re.html#re.sub
+        """
+        if not self._is_prepared or refresh:
+            self.refresh()
+        return self.pattern.sub(self.repl, s, count)
+
+    def _compile(self) -> Pattern[str]:
+        if not self._mapping:
+            name = self._mapping.__qualname__  # type: ignore[attr-defined]
+            msg = (
+                f"Requires {name!r} to be populated, but got:\n"
+                f"{name}={self._mapping!r}"
+            )
+            raise TypeError(msg)
+        return re.compile(rf"{self._fmt_match.format('|'.join(self._mapping))}")
+
+    def _replacer(self) -> Callable[[Match[str]], str]:
+        def repl(m: Match[str], /) -> str:
+            return self._fmt_replace.format(self._mapping[m["key"]])
+
+        return repl
+
+    def __getitem__(self, key: str) -> str:
+        return self._mapping[key]
+
+    def __setitem__(self, key: str, value: str) -> None:
+        self._mapping[key] = value
+
+    def __repr__(self) -> str:
+        return f"{type(self).__name__}(\n    {self._mapping!r}\n)"
+
+
+class Special(enum.Enum):
+    """
+    Special-case identifiers.
+
+    Representing ``VegaExprDef`` states that may be otherwise ambiguous.
+    """
+
+    NO_PARAMETERS = enum.auto()
+
+
+class VegaExprDef:
+    """
+    ``SchemaInfo``-like, but operates on `expressions.md`_.
+
+    .. _expressions.md:
+        https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md
+    """
+
+    remap_title: ClassVar[ReplaceMany] = ReplaceMany(
+        fmt_match=r"(?P<key>{0})\(", fmt_replace="{0}("
+    )
+
+    def __init__(self, name: str, children: Sequence[Token], /) -> None:
+        self.name: str = name
+        self._children: Sequence[Token] = children
+        self.parameters: list[VegaExprParam] = []
+        self.doc: str = ""
+        self.signature: str = ""
+        self._special: set[Special] = set()
+
+    def with_doc(self) -> Self:
+        """
+        Parses docstring content in full.
+
+        Accessible via ``self.doc``
+        """
+        s: str = parser.render_tokens(self._doc_tokens())
+        s = italics_to_backticks(s, self.parameter_names(variadic=False))
+        s = type(self).remap_title(s)
+        self.doc = format_doc(s)
+        return self
+
+    def with_parameters(self) -> Self:
+        """
+        Parses signature content into an intermediate representation.
+
+        Accessible via  ``self.parameters``.
+        """
+        split: Iterator[str] = self._split_signature_tokens(exclude_name=True)
+        self.parameters = list(VegaExprParam.from_texts(split))
+        if not self.parameters:
+            self._special.add(Special.NO_PARAMETERS)
+        return self
+
+    def with_signature(self) -> Self:
+        """
+        Parses ``self.parameters`` into a full signature definition line.
+
+        Accessible via  ``self.signature``
+        """
+        param_list = (
+            VegaExprParam.star_args()
+            if self.is_overloaded()
+            else ", ".join(p.render() for p in self.parameters)
+        )
+        self.signature = METHOD_SIGNATURE.format(
+            title=self.title,
+            sep="" if self.is_no_parameters() else ",",
+            param_list=param_list,
+            marker="" if (self.is_variadic() or self.is_no_parameters()) else ", /",
+            return_ann=RETURN_ANNOTATION,
+            type_ignore=(
+                f"  {IGNORE_OVERRIDE}" if self.is_incompatible_override() else ""
+            ),
+        )
+        return self
+
+    def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
+        """Pass ``variadic=False`` to omit names like``*args``."""
+        if self.parameters:
+            it: Iterator[str] = (
+                (p.name for p in self.parameters)
+                if variadic
+                else (p.name for p in self.parameters if not p.variadic)
+            )
+            yield from it
+        elif self.is_no_parameters():
+            yield from ()
+        else:
+            msg = (
+                f"Cannot provide `parameter_names` until they have been initialized via:\n"
+                f"{type(self).__name__}.with_parameters()\n\n"
+                f"{self!r}"
+            )
+            raise TypeError(msg)
+
+    def render(self) -> str:
+        """Return fully parsed method definition."""
+        if self.is_overloaded():
+            body_params = STAR_ARGS[1:]
+        else:
+            body_params = (
+                f"({self.parameters[0].name},)"
+                if len(self.parameters) == 1
+                else f"({','.join(self.parameter_names())})"
+            )
+        return METHOD_TEMPLATE.format(
+            decorator=DECORATOR,
+            signature=self.signature,
+            doc=self.doc,
+            return_wrapper=RETURN_WRAPPER,
+            name=f"{self.name!r}",
+            body_params=body_params,
+        )
+
+    @property
+    def title(self) -> str:
+        """
+        Use for the method definition, but not when calling internally.
+
+        Updates ``remap_title`` class variable for documentation example substitutions.
+        """
+        title = f"{self.name}_" if self.is_keyword() else self.name
+        type(self).remap_title.update({self.name: f"alt.expr.{title}"})
+        return title
+
+    def _signature_tokens(self) -> Iterator[Token]:
+        """
+        Target for signature appears between 2 softbreak tokens.
+
+        - Proceeds to the first token **after** a softbreak
+        - Yield **only** text tokens
+        - Skips all inline html tags
+        - Stops at 2nd softbreak
+        """
+        it: Iterator[Token] = iter(self)
+        current = next(it)
+        while current[TYPE] != SOFTBREAK:
+            current = next(it)
+        next(it)
+        for target in it:
+            if target[TYPE] == TEXT:
+                yield target
+            elif target[TYPE] == SOFTBREAK:
+                break
+            else:
+                continue
+
+    def _split_signature_tokens(self, *, exclude_name: bool = False) -> Iterator[str]:
+        """
+        Normalize the text content of the signature.
+
+        Examples
+        --------
+        The following definition:
+
+            <a name="sequence" href="#sequence">#</a>
+            <b>sequence</b>([<i>start</i>, ]<i>stop</i>[, <i>step</i>])<br/>
+            Returns an array containing an arithmetic sequence of numbers.
+            ...
+
+        Will yield:
+
+            ['sequence', '(', '[', 'start', ']', 'stop', '[', 'step', ']', ')']
+
+        When called with ``exclude_name=True``:
+
+            ['(', '[', 'start', ']', 'stop', '[', 'step', ']', ')']
+        """
+        EXCLUDE_INNER: set[str] = {self.name} if exclude_name else set()
+        EXCLUDE: set[str] = {", "} | EXCLUDE_INNER
+        for token in self._signature_tokens():
+            raw: str = token[RAW]
+            if raw == OPEN_PAREN:
+                yield raw
+            elif raw.startswith(OPEN_PAREN):
+                yield raw[0]
+                for s in raw[1:].split(","):
+                    if (clean := s.strip(" -")) not in EXCLUDE_INNER:
+                        yield from VegaExprDef._split_markers(clean)
+            elif (clean := raw.strip(", -")) not in EXCLUDE:
+                yield from VegaExprDef._split_markers(clean)
+
+    @staticmethod
+    def _split_markers(s: str, /) -> Iterator[str]:
+        """
+        When ``s`` ends with one of these markers:
+
+            ")", "]", "...", " |"
+
+        - Split ``s`` into rest, match
+            - using the length of the match to index
+        - Append match to ``end``
+        - Recurse
+        """  # noqa: D400
+        if s.isalnum():
+            yield s
+        else:
+            end: list[str] = []
+            if s.endswith((CLOSE_PAREN, CLOSE_BRACKET)):
+                end.append(s[-1])
+                s = s[:-1]
+            elif s.endswith(ELLIPSIS):
+                end.append(s[-3:])
+                s = s[:-3]
+            elif s.endswith(INLINE_OVERLOAD):
+                end.append(s[-2:])
+                s = s[:-2]
+            if len(s) == 1:
+                yield s
+            elif len(s) > 1:
+                yield from VegaExprDef._split_markers(s)
+            yield from end
+
+    def _doc_tokens(self) -> Sequence[Token]:
+        """Return the slice of `self.children` that contains docstring content."""
+        for idx, item in enumerate(self):
+            if item[TYPE] == SOFTBREAK and self[idx + 1][TYPE] == TEXT:
+                return self[idx + 1 :]
+            else:
+                continue
+        msg = (
+            f"Expected to find a text node marking the start of docstring content.\n"
+            f"Failed for:\n\n{self!r}"
+        )
+        raise NotImplementedError(msg)
+
+    def is_callable(self) -> bool:
+        """
+        Rough filter for excluding `constants`_.
+
+        - Most of the parsing is to handle varying signatures.
+        - Constants can just be referenced by name, so can skip those
+
+        Notes
+        -----
+        - Overwriting the <a name> with the rendered text
+        - required for `clamprange` -> `clampRange`
+
+        .. _constants:
+            https://vega.github.io/vega/docs/expressions/#constants
+        """
+        if self.is_overloaded_string_array() or self.is_bound_variable_name():
+            return False
+        it: Iterator[Token] = iter(self)
+        current: str = next(it, {}).get(RAW, "")
+        name: str = self.name.casefold()
+        while current.casefold() != name:
+            if (el := next(it, None)) is not None:
+                current = el.get(RAW, "")
+            else:
+                return False
+        if current != self.name:
+            self.name = current
+        next(it)
+        return next(it).get(RAW, "").startswith(OPEN_PAREN)
+
+    def is_bound_variable_name(self) -> bool:
+        """
+        ``Vega`` `bound variables`_.
+
+        These do not provide signatures:
+
+            {"datum", "event", "signal"}
+
+        .. _bound variables:
+            https://vega.github.io/vega/docs/expressions/#bound-variables
+        """
+        RESERVED_NAMES: set[str] = {"datum", "event", "signal"}
+        return self.name in RESERVED_NAMES
+
+    def is_overloaded(self) -> bool:
+        """
+        Covers the `color functions`_.
+
+        These look like:
+
+            lab(l, a, b[, opacity]) | lab(specifier)
+
+        Looping of parameters is for signatures like `sequence`_:
+
+            sequence([start, ]stop[, step])
+
+        The optional first parameter, followed by a required one would need an
+        ``@overload`` in ``python``.
+
+        .. _color functions:
+            https://vega.github.io/vega/docs/expressions/#color-functions
+        .. _sequence:
+            https://vega.github.io/vega/docs/expressions/#sequence
+        """
+        for idx, item in enumerate(self):
+            if item[TYPE] == TEXT and item.get(RAW, "").endswith(INLINE_OVERLOAD):
+                return self[idx + 1][TYPE] == SOFTBREAK
+            else:
+                continue
+        for idx, p in enumerate(self.parameters):
+            if not p.required:
+                others = self.parameters[idx + 1 :]
+                if not others:
+                    return False
+                else:
+                    return any(sp.required for sp in others)
+
+        return False
+
+    def is_overloaded_string_array(self) -> bool:
+        """
+        HACK: There are string/array functions that overlap.
+
+        - the `.md` handles this by prefixing the `<a name=...` for the string version
+        - This is very different to the handled overload kinds
+        - Both definitions have full documentation and appear under different sections
+            - Unlike color functions, sequence
+            - These are inline
+        """
+        return self.name.startswith("string_")
+
+    def is_keyword(self) -> bool:
+        return keyword.iskeyword(self.name)
+
+    def is_incompatible_override(self) -> bool:
+        """
+        ``self.title`` shadows an unrelated ``SchemaBase`` method.
+
+        Requires an ignore comment for a type checker.
+        """
+        return self.title in _SCHEMA_BASE_MEMBERS
+
+    def is_variadic(self) -> bool:
+        """Position-only parameter separator `"/"` not allowed after `"*"` parameter."""
+        return self.is_overloaded() or any(p.variadic for p in self.parameters)
+
+    def is_no_parameters(self) -> bool:
+        """
+        Signature has been parsed for parameters, but none were present.
+
+        For example the definition for `now`_ would **only** return ``True``
+        after calling ``self.with_parameters()``.
+
+        .. _now:
+            https://vega.github.io/vega/docs/expressions/#now
+        """
+        return bool(self._special) and Special.NO_PARAMETERS in self._special
+
+    def __iter__(self) -> Iterator[Token]:
+        yield from self._children
+
+    @overload
+    def __getitem__(self, index: int) -> Token: ...
+    @overload
+    def __getitem__(self, index: slice) -> Sequence[Token]: ...
+    def __getitem__(self, index: int | slice) -> Token | Sequence[Token]:
+        return self._children.__getitem__(index)
+
+    def __repr__(self) -> str:
+        return (
+            f"{type(self).__name__}(\n    "
+            f"name={self.name!r},\n    "
+            f"parameters={self.parameters!r},\n    "
+            f"doc={self.doc!r}\n"
+            ")"
+        )
+
+    @classmethod
+    def from_tokens(cls, tokens: Iterable[Token], /) -> Iterator[Self]:
+        """
+        Lazy, filtered partial parser.
+
+        Applies a series of filters before rendering everything but the docs.
+
+        Parameters
+        ----------
+        tokens
+            `ast tokens`_ produced by ``mistune``
+
+        .. _ast tokens:
+            https://mistune.lepture.com/en/latest/guide.html#abstract-syntax-tree
+        """
+        for tok in tokens:
+            if (
+                (children := tok.get(CHILDREN)) is not None
+                and (child := next(iter(children)).get(RAW)) is not None
+                and (match := FUNCTION_DEF_LINE.match(child))
+                and (node := cls(match["name"], children)).is_callable()
+            ):
+                yield node.with_parameters().with_signature()
+
+
+@dataclasses.dataclass
+class VegaExprParam:
+    name: str
+    required: bool
+    variadic: bool = False
+
+    @staticmethod
+    def star_args() -> LiteralString:
+        return f"{STAR_ARGS}: Any"
+
+    def render(self) -> str:
+        """Return as an annotated parameter, with a default if needed."""
+        if self.required:
+            return f"{self.name}: {INPUT_ANNOTATION}"
+        elif not self.variadic:
+            return f"{self.name}: {INPUT_ANNOTATION} = {NONE}"
+        else:
+            return self.star_args()
+
+    @classmethod
+    def from_texts(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
+        """Yields an ordered parameter list."""
+        is_required: bool = True
+        for s in raw_texts:
+            if s not in {OPEN_PAREN, CLOSE_PAREN}:
+                if s == OPEN_BRACKET:
+                    is_required = False
+                    continue
+                elif s == CLOSE_BRACKET:
+                    is_required = True
+                    continue
+                elif s.isalnum():
+                    yield cls(s, required=is_required)
+                elif s == ELLIPSIS:
+                    yield cls(STAR_ARGS, required=False, variadic=True)
+                else:
+                    continue
+
+
+def expand_urls(url: str, /) -> str:
+    if url.startswith("#"):
+        url = f"{EXPRESSIONS_DOCS_URL}{url}"
+    else:
+        url = url.replace(r"../", VEGA_DOCS_URL)
+    return url
+
+
+def format_doc(doc: str, /) -> str:
+    """
+    Format rendered docstring content.
+
+    Primarily used to prevent wrapping on `summary line`_ and references.
+
+    Notes
+    -----
+    - Source is very different to `vega-lite`
+    - There are no real sections, so these are created here
+    - Single line docs are unchanged
+    - Multi-line have everything following the first line wrappped.
+        - With a double break inserted for a summary line
+    - Reference-like links section (if present) are also ommitted from wrapping
+
+    .. _summary line:
+        https://numpydoc.readthedocs.io/en/latest/format.html#short-summary
+    """
+    sentences: deque[str] = deque(SENTENCE_BREAK.split(doc))
+    if len(sentences) > 1:
+        references: str = ""
+        summary = f"{sentences.popleft()}.\n"
+        last_line = sentences.pop().strip()
+        sentences = deque(f"{s}. " for s in sentences)
+        if SECTION_BREAK in last_line:
+            last_line, references = last_line.split(SECTION_BREAK, maxsplit=1)
+        sentences.append(last_line)
+        sentences = deque(text_wrap.wrap("".join(sentences)))
+        sentences.appendleft(summary)
+        if references:
+            sentences.extend(("", indent(references, METHOD_INDENT)))
+        return "\n".join(sentences)
+    elif SECTION_BREAK in doc:
+        # NOTE: 2 cases have a single line with a reference
+        summary, references = doc.split(SECTION_BREAK, maxsplit=1)
+        return "\n".join((summary, "", indent(references, METHOD_INDENT)))
+    else:
+        return sentences.pop().strip()
+
+
+def italics_to_backticks(s: str, names: Iterable[str], /) -> str:
+    """
+    Perform a targeted replacement, considering links.
+
+    Parameters
+    ----------
+    s
+        String containing rendered `.rst`.
+    names
+        Group of names the replacement applies to.
+
+    Notes
+    -----
+    - Avoids adding backticks to parameter names that are also used in a link.
+    - All cases of these are for `unit|units`.
+
+    Examples
+    --------
+    >>> italics_to_backticks(
+    ...     "some text and *name* and more text but also *other* text",
+    ...     ("name", "other"),
+    ... )
+    "some text and ``name`` and more text but also ``other`` text"
+    """
+    pattern = rf"(?P<not_link_start>[^`_])\*(?P<name>{'|'.join(names)})\*(?P<not_link_end>[^`])"
+    return re.sub(pattern, r"\g<not_link_start>``\g<name>``\g<not_link_end>", s)
+
+
+def parse_expressions(source: Url | Path, /) -> Iterator[VegaExprDef]:
+    """
+    Download remote or read local `.md` resource and eagerly parse signatures of relevant definitions.
+
+    Yields with docs to ensure each can use all remapped names, regardless of the order they appear.
+    """
+    tokens = read_ast_tokens(source)
+    expr_defs = tuple(VegaExprDef.from_tokens(tokens))
+    VegaExprDef.remap_title.refresh()
+    for expr_def in expr_defs:
+        yield expr_def.with_doc()
+
+
+def write_expr_module(version: str, output: Path, *, header: str) -> None:
+    """
+    Parse an ``expressions.md`` into a ``.py`` module.
+
+    Parameters
+    ----------
+    version
+        Vega release version, e.g. ``"v5.30.0"``.
+    output
+        Target path to write to.
+    """
+    version = version if version.startswith("v") else f"v{version}"
+    url = EXPRESSIONS_URL_TEMPLATE.format(version=version)
+    content = (
+        MODULE_PRE.format(
+            header=header,
+            metaclass=CLS_META,
+            const=CONST_WRAPPER,
+            return_ann=RETURN_ANNOTATION,
+            input_ann=INPUT_ANNOTATION,
+            func=RETURN_WRAPPER,
+        ),
+        CLS_TEMPLATE.format(
+            base="_ExprRef",
+            metaclass=CLS_META,
+            doc=CLS_DOC,
+            type_ignore=IGNORE_MISC,
+        ),
+    )
+    contents = chain(
+        content,
+        (expr_def.render() for expr_def in parse_expressions(url)),
+        [MODULE_POST],
+    )
+    print(f"Generating\n {url!s}\n  ->{output!s}")
+    _ruff_write_lint_format_str(output, contents)


Test patch:
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index 8842e0ced..2170c1e36 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -2,14 +2,20 @@
 
 import operator
 import sys
-from inspect import classify_class_attrs, getmembers
-from typing import Any, Iterator
+from inspect import classify_class_attrs, getmembers, signature
+from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, TypeVar, cast
 
 import pytest
 from jsonschema.exceptions import ValidationError
 
 from altair import datum, expr, ExprRef
-from altair.expr import _ConstExpressionType
+from altair.expr import _ExprMeta
+from altair.expr.core import Expression, GetAttrExpression
+
+if TYPE_CHECKING:
+    from inspect import _IntrospectableCallable
+
+T = TypeVar("T")
 
 # This maps vega expression function names to the Python name
 VEGA_REMAP = {"if_": "if"}
@@ -19,20 +25,29 @@ def _is_property(obj: Any, /) -> bool:
     return isinstance(obj, property)
 
 
-def _get_classmethod_names(tp: type[Any], /) -> Iterator[str]:
-    for m in classify_class_attrs(tp):
-        if m.kind == "class method" and m.defining_class is tp:
-            yield m.name
+def _get_property_names(tp: type[Any], /) -> Iterator[str]:
+    for nm, _ in getmembers(tp, _is_property):
+        yield nm
 
 
-def _remap_classmethod_names(tp: type[Any], /) -> Iterator[tuple[str, str]]:
-    for name in _get_classmethod_names(tp):
-        yield VEGA_REMAP.get(name, name), name
+def signature_n_params(
+    obj: _IntrospectableCallable,
+    /,
+    *,
+    exclude: Iterable[str] = frozenset(("cls", "self")),
+) -> int:
+    sig = signature(obj)
+    return len(set(sig.parameters).difference(exclude))
 
 
-def _get_property_names(tp: type[Any], /) -> Iterator[str]:
-    for nm, _ in getmembers(tp, _is_property):
-        yield nm
+def _iter_classmethod_specs(
+    tp: type[T], /
+) -> Iterator[tuple[str, Callable[..., Expression], int]]:
+    for m in classify_class_attrs(tp):
+        if m.kind == "class method" and m.defining_class is tp:
+            name = m.name
+            fn = cast("classmethod[T, ..., Expression]", m.object).__func__
+            yield (VEGA_REMAP.get(name, name), fn.__get__(tp), signature_n_params(fn))
 
 
 def test_unary_operations():
@@ -86,15 +101,18 @@ def test_abs():
     assert repr(z) == "abs(datum.xxx)"
 
 
-@pytest.mark.parametrize(("veganame", "methodname"), _remap_classmethod_names(expr))
-def test_expr_funcs(veganame: str, methodname: str):
-    """Test all functions defined in expr.funcs."""
-    func = getattr(expr, methodname)
-    z = func(datum.xxx)
-    assert repr(z) == f"{veganame}(datum.xxx)"
+@pytest.mark.parametrize(("veganame", "fn", "n_params"), _iter_classmethod_specs(expr))
+def test_expr_methods(
+    veganame: str, fn: Callable[..., Expression], n_params: int
+) -> None:
+    datum_names = [f"col_{n}" for n in range(n_params)]
+    datum_args = ",".join(f"datum.{nm}" for nm in datum_names)
+
+    fn_call = fn(*(GetAttrExpression("datum", nm) for nm in datum_names))
+    assert repr(fn_call) == f"{veganame}({datum_args})"
 
 
-@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType))
+@pytest.mark.parametrize("constname", _get_property_names(_ExprMeta))
 def test_expr_consts(constname: str):
     """Test all constants defined in expr.consts."""
     const = getattr(expr, constname)
@@ -102,7 +120,7 @@ def test_expr_consts(constname: str):
     assert repr(z) == f"({constname} * datum.xxx)"
 
 
-@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType))
+@pytest.mark.parametrize("constname", _get_property_names(_ExprMeta))
 def test_expr_consts_immutable(constname: str):
     """Ensure e.g `alt.expr.PI = 2` is prevented."""
     if sys.version_info >= (3, 11):
diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py
index 95c91a12b..741003421 100644
--- a/tests/vegalite/v5/test_api.py
+++ b/tests/vegalite/v5/test_api.py
@@ -553,9 +553,13 @@ def test_when_labels_position_based_on_condition() -> None:
     # `mypy` will flag structural errors here
     cond = when["condition"][0]
     otherwise = when["value"]
-    param_color_py_when = alt.param(
-        expr=alt.expr.if_(cond["test"], cond["value"], otherwise)
-    )
+
+    # TODO: Open an issue on making `OperatorMixin` generic
+    # Something like this would be used as the return type for all `__dunder__` methods:
+    # R = TypeVar("R", Expression, SelectionPredicateComposition)
+    test = cond["test"]
+    assert not isinstance(test, alt.PredicateComposition)
+    param_color_py_when = alt.param(expr=alt.expr.if_(test, cond["value"], otherwise))
     lhs_param = param_color_py_expr.param
     rhs_param = param_color_py_when.param
     assert isinstance(lhs_param, alt.VariableParameter)
@@ -600,7 +604,9 @@ def test_when_expressions_inside_parameters() -> None:
     cond = when_then_otherwise["condition"][0]
     otherwise = when_then_otherwise["value"]
     expected = alt.expr.if_(alt.datum.b >= 0, 10, -20)
-    actual = alt.expr.if_(cond["test"], cond["value"], otherwise)
+    test = cond["test"]
+    assert not isinstance(test, alt.PredicateComposition)
+    actual = alt.expr.if_(test, cond["value"], otherwise)
     assert expected == actual
 
     text_conditioned = bar.mark_text(