2020-11-04 22:26:39 +00:00
|
|
|
# `@supports` `<general-enclosed>`: Draft 2.1
|
2020-02-24 21:01:02 +00:00
|
|
|
|
2020-11-04 00:34:19 +00:00
|
|
|
*([Issue](https://github.com/sass/sass/issues/2780), [Changelog](supports-general.changes.md))*
|
2020-02-24 21:01:02 +00:00
|
|
|
|
|
|
|
This proposal defines how Sass parses supports queries that use the
|
2024-06-18 00:49:23 +00:00
|
|
|
[`<general-enclosed>`] production.
|
2020-02-24 21:01:02 +00:00
|
|
|
|
|
|
|
[`<general-enclosed>`]: https://drafts.csswg.org/mediaqueries-4/#typedef-general-enclosed
|
|
|
|
|
|
|
|
## Table of Contents
|
|
|
|
|
|
|
|
* [Background](#background)
|
|
|
|
* [Summary](#summary)
|
|
|
|
* [Design Decisions](#design-decisions)
|
|
|
|
* [How Much To Support](#how-much-to-support)
|
|
|
|
* [SassScript Injection](#sassscript-injection)
|
|
|
|
* [Backwards Compatibility](#backwards-compatibility)
|
|
|
|
* [Syntax](#syntax)
|
|
|
|
* [`SupportsCondition`](#supportscondition)
|
|
|
|
* [`InterpolatedAnyValue`](#interpolatedanyvalue)
|
|
|
|
|
|
|
|
## Background
|
|
|
|
|
|
|
|
> This section is non-normative.
|
|
|
|
|
|
|
|
Historically, Sass has supported the `@supports` condition syntax as defined in
|
2024-06-18 00:49:23 +00:00
|
|
|
[the April 2013 CSS Conditional Rules Level 3 Candidate Recommendation], with
|
2020-02-24 21:01:02 +00:00
|
|
|
the addition of supporting raw SassScript expressions in the declaration syntax.
|
2024-06-18 00:49:23 +00:00
|
|
|
The [Editor's Draft] (as published October 2019) expands this syntax with a
|
2020-02-24 21:01:02 +00:00
|
|
|
`<general-enclosed>` production to ensure parser forwards-compatibility with
|
2024-06-18 00:49:23 +00:00
|
|
|
future CSS, which Sass does not yet support. The [Level 4 Editor's Draft] (as
|
2020-02-24 21:01:02 +00:00
|
|
|
of March 2019) further adds a `selector()` function syntax (which is covered by
|
2024-06-18 00:49:23 +00:00
|
|
|
`<general-enclosed>`), which is [supported by Firefox] as of version 69.
|
2020-02-24 21:01:02 +00:00
|
|
|
|
|
|
|
[the April 2013 CSS Conditional Rules Level 3 Candidate Recommendation]: https://www.w3.org/TR/2013/CR-css3-conditional-20130404/#at-supports
|
|
|
|
[Editor's Draft]: https://drafts.csswg.org/css-conditional-3/#at-supports
|
|
|
|
[Level 4 Editor's Draft]: https://drafts.csswg.org/css-conditional-4/#at-supports-ext
|
|
|
|
[supported by Firefox]: https://caniuse.com/#search=supports%20selector
|
|
|
|
|
|
|
|
Sass's current `@supports` rule syntax allows SassScript expressions in place of
|
|
|
|
either the declaration name or value in `<supports-decl>`. This syntax doesn't
|
|
|
|
need to be wrapped in interpolation, which means that many expressions that
|
|
|
|
would be parsed in CSS as `<general-enclosed>` are currently parsed by Sass as
|
|
|
|
`<supports-decl>`, and even more expressions need arbitrary look-ahead to
|
|
|
|
determine whether they have a `:` that would distinguish them between
|
|
|
|
`<supports-decl>` and `<general-enclosed>`.
|
|
|
|
|
|
|
|
## Summary
|
|
|
|
|
|
|
|
> This section is non-normative.
|
|
|
|
|
|
|
|
Sass will parse the `<general-enclosed>` production essentially as defined by
|
|
|
|
CSS, with the following exceptions:
|
|
|
|
|
|
|
|
* SassScript can be injected in the productions using interpolation.
|
|
|
|
|
|
|
|
* The `(<ident> <any-value>)` syntax for `<general-enclosed>` may not contain
|
|
|
|
top-level `":"` tokens. This preserves backwards-compatibility with existing
|
|
|
|
`@supports` rules that use non-interpolated SassScript expressions in
|
|
|
|
`<supports-decl>` and may help catch accidental syntax errors.
|
|
|
|
|
|
|
|
### Design Decisions
|
|
|
|
|
|
|
|
#### How Much To Support
|
|
|
|
|
|
|
|
Per Sass's CSS compatibility policy, it must add support for at least the
|
|
|
|
`selector()` function, since it's been shipped in a browser. Everything else
|
|
|
|
under `<general-enclosed>` is optional, though, since the spec says "Authors
|
|
|
|
must not use `<general-enclosed>` in their stylesheets." We could choose to
|
|
|
|
avoid implementing it entirely and just special-case `selector()`, or we could
|
|
|
|
choose to implement only the function syntax and ignore the `(<ident>
|
|
|
|
<any-value>)` syntax.
|
|
|
|
|
|
|
|
This proposal covers the full `<general-enclosed>` syntax for the same reason
|
|
|
|
CSS does: forwards compatibility. Sass has a general policy of loose coupling
|
|
|
|
with CSS, so that Sass needs as few updates as possible as CSS continues to
|
|
|
|
evolve. Supporting the full range of potential syntax for `@supports` conditions
|
|
|
|
now means that we'll need fewer proposals like this in the future as new
|
|
|
|
conditions are added in practice.
|
|
|
|
|
|
|
|
#### SassScript Injection
|
|
|
|
|
|
|
|
There are two possible ways to allow SassScript to be injected into the
|
|
|
|
`<general-enclosed>` production. One is to support it only via interpolation,
|
|
|
|
allowing the production to otherwise exactly match the CSS syntax; the other is
|
|
|
|
to allow raw Sass-script values to be used within the parentheses.
|
|
|
|
|
|
|
|
The latter option is appealing at first glance. It requires fewer characters,
|
|
|
|
and matches the parsing of `<supports-decl>`, which allows non-interpolated
|
|
|
|
expressions on either side of the `":"`. However, it's only CSS-compatible as
|
|
|
|
long as all the syntax CSS uses in those positions is also a valid SassScript
|
|
|
|
expression. If not, Sass must continually update its syntax to support new CSS
|
|
|
|
constructs.
|
|
|
|
|
|
|
|
We've been burned by this in the past. Both this proposal and the [media ranges
|
2024-06-18 00:49:23 +00:00
|
|
|
proposal] would have been unnecessary (or at least much simpler) if the rules
|
2020-02-24 21:01:02 +00:00
|
|
|
in questions had only allowed SassScript via interpolation in the first place.
|
|
|
|
And it's likely that this syntax *will* evolve in ways that aren't
|
2024-06-18 00:49:23 +00:00
|
|
|
SassScript-compatible, as with the `selector()` function [mentioned above].
|
2020-02-24 21:01:02 +00:00
|
|
|
|
|
|
|
[media ranges proposal]: ../accepted/media-ranges.md
|
|
|
|
[mentioned above]: #background
|
|
|
|
|
|
|
|
Given this, it's much safer to only allow SassScript within interpolation.
|
|
|
|
|
|
|
|
#### Backwards Compatibility
|
|
|
|
|
|
|
|
The `<general-enclosed>` syntax is very broad, so care is needed to ensure that
|
|
|
|
adding support for it doesn't break existing Sass stylesheets—particularly those
|
|
|
|
that are relying on SassScript in `<supports-decl>`. For example, strictly
|
|
|
|
speaking `(foo + bar: baz)` is valid CSS that parses as `<general-enclosed>`,
|
|
|
|
but Sass needs to continue to parse it as a declaration containing a SassScript
|
|
|
|
expression on the left-hand side.
|
|
|
|
|
|
|
|
The question then is how to balance backwards-compatibility with CSS
|
|
|
|
compatibility. In most cases, CSS takes clear precedence, and we could manage
|
|
|
|
that here by declaring that `<supports-decl>`s that begin with an
|
|
|
|
`InterpolatedIdentifier` must immediately follow it with a `":"` and allowing
|
|
|
|
all other syntax to be parsed as `<general-enclosed>`. However, this approach is
|
|
|
|
not without its downsides. It would require a deprecation period, and it would
|
|
|
|
parse `<general-enclosed>` in a number of cases (such as `(foo: )` or `(fo o:
|
|
|
|
bar)`) that are much more likely to be typos.
|
|
|
|
|
|
|
|
Fortunately, `<general-enclosed>` is explicitly specified for
|
|
|
|
forwards-compatibility rather than having CSS semantics in and of itself, so we
|
|
|
|
have more flexibility to limit compatibility with it in favor of Sass backwards
|
|
|
|
compatibility. This allows us to limit the syntax we parse as
|
|
|
|
`<general-enclosed>` to forbid a top-level `":"`, which ensures it's never
|
|
|
|
ambiguous with a declaration and thus with raw SassScript.
|
|
|
|
|
|
|
|
## Syntax
|
|
|
|
|
|
|
|
### `SupportsCondition`
|
|
|
|
|
|
|
|
<x><pre>
|
|
|
|
**SupportsCondition** ::= 'not' SupportsInParens
|
|
|
|
  | SupportsInParens ('and' SupportsInParens)*
|
|
|
|
  | SupportsInParens ('or' SupportsInParens)*
|
|
|
|
**SupportsInParens** ::= '(' (SupportsCondition | SupportsDeclaration | SupportsAnything) ')'
|
2020-11-04 00:47:51 +00:00
|
|
|
  | SupportsFunction | Interpolation
|
2020-02-24 21:01:02 +00:00
|
|
|
**SupportsDeclaration** ::= Expression¹ ':' Expression
|
2024-06-18 00:49:23 +00:00
|
|
|
**SupportsAnything** ::= [InterpolatedIdentifier]² [InterpolatedAnyValue]³?
|
|
|
|
**SupportsFunction** ::= [InterpolatedIdentifier]⁴ '(' [InterpolatedAnyValue]? ')'
|
2020-02-24 21:01:02 +00:00
|
|
|
</pre></x>
|
|
|
|
|
|
|
|
[InterpolatedIdentifier]: ../spec/syntax.md#interpolatedidentifier
|
|
|
|
[InterpolatedAnyValue]: #interpolatedanyvalue
|
|
|
|
|
|
|
|
1: This `Expression` may not begin with the identifier `"not"` or the token
|
|
|
|
`"("`.
|
|
|
|
|
|
|
|
2: This `InterpolatedIdentifier` may not be the identifier `"not"`.
|
|
|
|
|
2020-11-04 22:26:39 +00:00
|
|
|
3: This `InterpolatedAnyValue` may not contain a top-level `":"`, and it may not
|
|
|
|
begin with the identifier tokens `"and"` or `"or"`.
|
2020-02-24 21:01:02 +00:00
|
|
|
|
|
|
|
4: This `InterpolatedIdentifier` may not be the identifier `"not"`. No
|
|
|
|
whitespace is allowed between it and the following `"("`.
|
|
|
|
|
|
|
|
The identifiers `"not"`, `"and"`, and `"or"` are matched case-insensitively for
|
|
|
|
the purposes of this production.
|
|
|
|
|
|
|
|
> Implementations must perform some amount of lookahead to disambiguate between
|
|
|
|
> `SupportsDeclaration`, `SupportsAnything`, and `SupportsFunction`. When doing
|
|
|
|
> so, it may be helpful to note that the contents of a `SupportsInParens` must
|
|
|
|
> be parsed as a `SupportsDeclaration` if and only if it contains a top-level
|
|
|
|
> `":"`.
|
|
|
|
|
|
|
|
### `InterpolatedAnyValue`
|
|
|
|
|
2024-06-18 00:49:23 +00:00
|
|
|
The `InterpolatedAnyValue` production is identical to CSS's [`<any-value>`]
|
2020-02-24 21:01:02 +00:00
|
|
|
except that after it parses `"#{"`, it parses an `Expression` which must be
|
|
|
|
followed by `"}"`.
|
|
|
|
|
|
|
|
[`<any-value>`]: https://drafts.csswg.org/css-syntax-3/#typedef-any-value
|